wren/val Public Public: anyone can read this; nobody can change it.

forked from benjivers/val

workbenchthis machine

Repair main: two green branches merged into a red one

Pulls #13 (the spectator view) and #15 (the renderer) were each green, and
git merged them without a conflict into a main whose tests fail — so the
deploy gate refused twice and production kept serving the build from before
the renderer work. Nobody was playing what we thought they were playing.

Textually clean, semantically not:

- client.test.ts. #13 added its spectator test in the middle of #15's seated
  test, so that test's tail — the retina case, the blit, the readout — landed
  on a client that has no seat, no world and nothing to draw. It asserted 40+
  fills against a frame that is one rectangle and six lines of text, which is
  exactly what the watching view should be. The seated test is whole again
  and the spectator test stands on its own.
- server.test.ts. The /join test needs a seat, and a playing seat is rationed
  per address deliberately; earlier tests in the file take that seat and never
  open a socket to close, so nothing frees it. It now releases the shared
  address first, which is the test's own problem and not the rule's.

Nothing in src/ changed: this was two test files describing each other's
world. 257 pass, three runs clean.
benjivers committed 54c3aae Browse files
2 files changed +40 −32
src/client.test.ts +32 −32
@@ -364,38 +364,6 @@ test("the bundled client connects, paints the HUD and draws a frame", async () =
364364 expect(seat.x).toBe(startedAt.x);
365365 expect(seat.y).toBe(startedAt.y);
366366 arena.startArena();
367 }, 15000);
368
369 test("a snapshot with no seat in it draws the watching view instead of throwing", async () => {
370 // The shape no other test covers: score, round, clock and feed, and no `me`
371 // and no players at all. The client used to reach straight into `snap.me`
372 // on the way in, so a second tab's first frame threw and the page stopped
373 // drawing. This is the server's own spectator snapshot, handed to the
374 // bundled client the way the socket hands it over.
375 const { spectatorSnapshot } = await import("./arena.ts");
376 const deliver = (why: "address" | "cookie") =>
377 openedSocket!.onmessage!({ data: spectatorSnapshot(why) } as MessageEvent);
378
379 drawn.length = 0;
380 deliver("address");
381 runFrame();
382
383 // It says what the match is doing, and why this connection is watching.
384 expect(String(nodes.get("phase")!.textContent)).toContain("round");
385 expect(String(nodes.get("hp")!.textContent)).toBe("watching");
386 expect(String(nodes.get("prompt")!.textContent)).toContain("address");
387 // Nothing on screen that would need a position to fill in.
388 expect((nodes.get("kit")!.children as unknown[]).length).toBe(0);
389 expect((nodes.get("roster")!.children as unknown[]).length).toBe(0);
390 expect((nodes.get("atkboard")!.children as unknown[]).length).toBe(0);
391 // And the canvas is words on a panel, not a map: one rectangle, no tiles.
392 expect(drawn.filter((op) => op.startsWith("fillRect")).length).toBe(1);
393 expect(drawn.filter((op) => op.startsWith("fillText")).length).toBeGreaterThan(3);
394
395 // The other reason reads differently, and the frame after it still draws.
396 deliver("cookie");
397 runFrame();
398 expect(String(nodes.get("prompt")!.textContent)).toContain("cookie");
399367
400368 // Back to the first-person view for the last of it, and a frame of it to
401369 // measure: the one before is the one that rebuilds anything cached.
@@ -442,4 +410,36 @@ test("a snapshot with no seat in it draws the watching view instead of throwing"
442410 // connection rather than left holding a seat for the rest of the suite.
443411 openedSocket?.close();
444412 await Bun.sleep(50);
413 }, 15000);
414
415 test("a snapshot with no seat in it draws the watching view instead of throwing", async () => {
416 // The shape no other test covers: score, round, clock and feed, and no `me`
417 // and no players at all. The client used to reach straight into `snap.me`
418 // on the way in, so a second tab's first frame threw and the page stopped
419 // drawing. This is the server's own spectator snapshot, handed to the
420 // bundled client the way the socket hands it over.
421 const { spectatorSnapshot } = await import("./arena.ts");
422 const deliver = (why: "address" | "cookie") =>
423 openedSocket!.onmessage!({ data: spectatorSnapshot(why) } as MessageEvent);
424
425 drawn.length = 0;
426 deliver("address");
427 runFrame();
428
429 // It says what the match is doing, and why this connection is watching.
430 expect(String(nodes.get("phase")!.textContent)).toContain("round");
431 expect(String(nodes.get("hp")!.textContent)).toBe("watching");
432 expect(String(nodes.get("prompt")!.textContent)).toContain("address");
433 // Nothing on screen that would need a position to fill in.
434 expect((nodes.get("kit")!.children as unknown[]).length).toBe(0);
435 expect((nodes.get("roster")!.children as unknown[]).length).toBe(0);
436 expect((nodes.get("atkboard")!.children as unknown[]).length).toBe(0);
437 // And the canvas is words on a panel, not a map: one rectangle, no tiles.
438 expect(drawn.filter((op) => op.startsWith("fillRect")).length).toBe(1);
439 expect(drawn.filter((op) => op.startsWith("fillText")).length).toBeGreaterThan(3);
440
441 // The other reason reads differently, and the frame after it still draws.
442 deliver("cookie");
443 runFrame();
444 expect(String(nodes.get("prompt")!.textContent)).toContain("cookie");
445445 });
src/server.test.ts +8
@@ -35,6 +35,14 @@ test("the page hands out a session cookie and the script is named by content", a
3535 });
3636
3737 test("a name is taken from a same-origin form and sanitised", async () => {
38 // Every test in this file reaches the server from the same address, and a
39 // playing seat is rationed per address on purpose. Earlier tests here take
40 // that seat and never open a socket to close, so nothing frees it: without
41 // this line the join below is correctly refused and the assertion measures
42 // the leftovers of another test rather than this one's behaviour.
43 releaseAddress("127.0.0.1");
44 releaseAddress("::1");
45 releaseAddress("::ffff:127.0.0.1");
3846 const form = new FormData();
3947 form.set("name", ' Wren<script>" ');
4048 const posted = await fetch(base + "/join", { method: "POST", body: form, redirect: "manual" });
↑↓ navigate ↵ open esc close

Keyboard shortcuts