game6 / client / test / frames.test.mjs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// Golden-vector tests: wire frames must serialize byte-identically to the TS
// implementation's (fixtures recorded off the real game brain driven through a
// fake NetLike, so they are exactly the bytes the deployed clients emit).
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import * as lib from "../test-dist/testlib.js";

const vectors = JSON.parse(readFileSync(new URL("./vectors/frames.json", import.meta.url), "utf8"));

// Every builder is called with the values the fixture recorded, so a key-order
// or dropped-field regression shows up as a byte diff. `f` is the recorded
// frame, so the arguments come from the fixture rather than from this file.
const BUILD = {
  ro: (f) => lib.mkRo(f.host, f.ps, f.age),
  tr: (f) => lib.mkTr(f.bpm, f.org),
  pg: (f) => lib.mkPg(f.c),
  po: (f) => lib.mkPo(f.c, f.h),
  hi: (f) => lib.mkHi(f.nick, f.inst),
  sn: (f) => lib.mkSn(f.bpm, f.org, f.loops),
  nt: (f) => lib.mkNt(f.i, f.n, f.v),
  nf: (f) => lib.mkNf(f.i, f.n),
  ts: () => lib.mkTs(),
  tc: (f) => lib.mkTc(f.f, f.g),
  te: () => lib.mkTe(),
  lp: (f) => lib.mkLp(f.id, f.i, f.len, f.ev),
  lx: (f) => lib.mkLx(f.id),
  ap: () => lib.mkAp(),
};

test("the fixtures cover every frame type the protocol defines", () => {
  const seen = new Set(vectors.map((v) => v.frame.t));
  assert.deepEqual([...seen].sort(), Object.keys(BUILD).sort());
});

for (const [i, v] of vectors.entries()) {
  test(`${v.name} #${i}: "${v.frame.t}" serializes byte-identically to TS`, () => {
    const build = BUILD[v.frame.t];
    assert.ok(build, `no builder for ${v.frame.t}`);
    assert.equal(JSON.stringify(build(v.frame)), v.json);
  });
}

test("no frame builder is wider than the eight-pair js-obj key-order limit", () => {
  // dev/docs/CLJS.md: at nine pairs `js-obj` silently emits in hash order.
  for (const v of vectors) {
    assert.ok(
      Object.keys(v.frame).length <= 8,
      `${v.frame.t} has ${Object.keys(v.frame).length} keys — build it with sequential unchecked-set`,
    );
  }
});

test("isGameFrame accepts frames, rejects junk", () => {
  for (const v of vectors) assert.equal(lib.isGameFrame(v.frame), true, v.frame.t);
  for (const junk of [null, undefined, 42, "hi", [], {}, { t: 7 }]) {
    assert.equal(Boolean(lib.isGameFrame(junk)), false, `junk: ${JSON.stringify(junk)}`);
  }
  // an array IS typeof "object" but carries no string `t`
  assert.equal(Boolean(lib.isGameFrame(["ro"])), false);
});

test("the compiled-in constants still match the TS ones", () => {
  const inst = JSON.parse(readFileSync(new URL("./vectors/instruments.json", import.meta.url), "utf8"));
  const c = lib.constants();
  assert.equal(c.appSalt, inst.appSalt);
  assert.equal(c.maxMusicians, 8);
  assert.equal(c.minBpm, inst.minBpm);
  assert.equal(c.maxBpm, inst.maxBpm);
  assert.equal(c.defaultBpm, inst.defaultBpm);
  assert.deepEqual([...c.loopLengths], inst.loopLengths);
  assert.deepEqual([...c.instruments], inst.instruments);
  assert.deepEqual([...c.instGlyphs], inst.instGlyphs);
  assert.deepEqual([...c.colors], inst.colors);
});

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/game6.git