Testing

Unit tests

npm test

Game-specific tests live in test/games/. Good unit tests will save future you (and future maintainers) a lot of headaches. No need to test basic stuff tested elsewhere (like graph/grid functions), but it's wise to test end-of-game resolution and any edge cases. See existing tests for patterns.

CLI example

bin/example.ts — run moves from the command line:

npx ts-node bin/example.ts <uid> [moves...]

Browser playground

The standalone gameslib playground is not part of the docs site:

gameslib.dev.abstractplay.com

Local setup

  1. Build — from the gameslib repo root:

    npm run playground
    

    This builds APGames.min.js, copies locales/, and copies playground.* into dist/.

  2. Serve over HTTP — required (do not open playground.html via file://). Use WAMP/LAMP/nginx, or:

    npm run playground:serve
    

    Point your server at dist/ as the document root (or copy the contents of dist/ into your vhost).

  3. Open — http://localhost:<port>/ or http://localhost:<port>/playground.html

  4. Renderer — playground.html loads APRender.min.js from the dev CDN by default. Only build or copy a local renderer bundle if you are working on renderer.

Translations

Game descriptions, variant names, and other strings are loaded at runtime from ./locales/{lang}/{ns}.json beside playground.html. npm run playground copies these into dist/locales/.

If descriptions show raw keys like apgames:descriptions.complica:

The browser console will warn if locale bundles failed to load.

Troubleshooting (WAMP / subfolder hosting)

If you serve from a subdirectory (e.g. http://localhost/myproject/playground.html), locale files must still sit beside playground.html in that folder (myproject/locales/...). The relative load path resolves from the page URL, not the server document root — so copying the full dist/ contents into your vhost subfolder is the simplest approach.

Hidden information (God view vs Live view)

When the loaded game’s serialize({ strip: true, player }) can differ by viewer (e.g. Thricewise, agofmars), the Game Panel shows Hidden information (view):

Dump State always shows the full committed snapshot (getCommittedStateString()), not the Live view clone.

Simultaneous games (God mode vs Seat mode)

For games with the simultaneous flag, the playground Game Panel also shows Simultaneous play controls (separate from view mode above):

Optional Strip hidden info when saving state uses serialize({ strip: true, player }) after each committed round (useful for Entropy-style hidden information). The playground keeps a full copy in playgroundStateFull when that option is on so you can still change perspective. In Seat mode, seats with isEliminated() are dimmed in the picker; move/pass/random/board input cannot submit for that seat (God mode full-row entry is unchanged).

The playground does not simulate clocks, WebSocket updates, email, or per-player API responses that hide opponents’ in-progress choices. For engine contracts and partial moves in unit tests, use inline fixtures and move(..., { partial: true }) under test/ — not bin/state.json.

Renderer output

Prototype board JSON at renderer.dev.abstractplay.com before wiring render() in your game.

Chat log

When you override collectChatLogLine or chatLogEntries, verify output with assertChatLogParity(game, playerNames) from test/fixtures/chat/helpers.ts. CI runs test/games/chatLogParity.test.ts for registry games with fixtures.

After collector changes, refresh local goldens (gitignored fixtures): npm run refresh-chat-golden-entries and/or npm run refresh-chat-golden.

See Structured move log for simultaneous indexing, aggregated frames, and anti-patterns.

Render labels

When a game emits player-named area or board titles, assert render() returns a structured object (textKey, actor.seat) — not a resolved username string. Optionally verify wording with resolveRenderLabel(label, names, mockT). See Structured render labels.

Example games