@seriousgaming/sdk-tools 0.0.0-stage → 1.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +93 -0
- package/README.md +36 -2
- package/dist/bundling/logic-exports.d.ts +34 -0
- package/dist/bundling/node-builtins.d.ts +21 -0
- package/dist/bundling/transpiler.d.ts +7 -0
- package/dist/cli.js +27368 -0
- package/dist/client/dev-server.d.ts +59 -0
- package/dist/client/index.d.ts +28 -0
- package/dist/client/stub-services.d.ts +32 -0
- package/dist/client/test-session.d.ts +195 -0
- package/dist/client.js +16884 -0
- package/dist/core/asset-reference-translator.d.ts +46 -0
- package/dist/core/clock.d.ts +57 -0
- package/dist/core/faults.d.ts +65 -0
- package/dist/core/game-settings.d.ts +65 -0
- package/dist/core/history.d.ts +89 -0
- package/dist/core/listen-with-fallback.d.ts +30 -0
- package/dist/core/mutation-queue.d.ts +61 -0
- package/dist/core/package-scenario.d.ts +89 -0
- package/dist/core/participant-registry.d.ts +109 -0
- package/dist/core/pending-changes.d.ts +14 -0
- package/dist/core/protocol.d.ts +83 -0
- package/dist/core/scenario-drafts.d.ts +32 -0
- package/dist/core/session-endpoints.d.ts +108 -0
- package/dist/core/session-gates.d.ts +75 -0
- package/dist/core/session-host.d.ts +94 -0
- package/dist/core/session-refusals.d.ts +56 -0
- package/dist/core/session-rewind.d.ts +32 -0
- package/dist/core/session-roster.d.ts +128 -0
- package/dist/core/session-surfaces.d.ts +32 -0
- package/dist/core/session-ticks.d.ts +18 -0
- package/dist/core/session-timers.d.ts +84 -0
- package/dist/core/session-writes.d.ts +40 -0
- package/dist/core/session.d.ts +385 -0
- package/dist/core/settings-locks.d.ts +22 -0
- package/dist/core/team-names.d.ts +52 -0
- package/dist/core/team-state-store.d.ts +209 -0
- package/dist/core/tick-timer.d.ts +75 -0
- package/dist/dev/blob-links.d.ts +10 -0
- package/dist/dev/console/assets/index-DgFglsnA.js +204 -0
- package/dist/dev/console/index.html +12 -0
- package/dist/dev/console-path.d.ts +19 -0
- package/dist/dev/lockfile.d.ts +124 -0
- package/dist/dev/package-watcher.d.ts +21 -0
- package/dist/dev/routes.d.ts +67 -0
- package/dist/dev/shell.d.ts +260 -0
- package/dist/dev/surfaces.d.ts +41 -0
- package/dist/dev/tools-identity.d.ts +23 -0
- package/dist/dev/view/batch.d.ts +172 -0
- package/dist/dev/view/commands.d.ts +75 -0
- package/dist/dev/view/route.d.ts +58 -0
- package/dist/dev/view/view.d.ts +78 -0
- package/dist/dev/view/viewer.d.ts +32 -0
- package/dist/discovery/config.d.ts +9 -0
- package/dist/discovery/manifest-paths.d.ts +21 -0
- package/dist/discovery/removed-fields.d.ts +90 -0
- package/dist/docs/api/deck.md +386 -0
- package/dist/docs/api/index.md +67 -0
- package/dist/docs/api/sdk-commands.md +286 -0
- package/dist/docs/api/sdk-e2e.md +433 -0
- package/dist/docs/api/sdk-projection.md +207 -0
- package/dist/docs/api/sdk-testing.md +214 -0
- package/dist/docs/api/sdk-tools-client.md +560 -0
- package/dist/docs/api/sdk-ui-testing.md +231 -0
- package/dist/docs/api/sdk-ui.md +1269 -0
- package/dist/docs/api/sdk.md +2140 -0
- package/dist/docs/changelog-0.x.md +1156 -0
- package/dist/docs/changelog.md +236 -0
- package/dist/docs/cli-and-dev-server.md +217 -0
- package/dist/docs/cli-error-codes.md +154 -0
- package/dist/docs/cli-reference.md +2488 -0
- package/dist/docs/cli-simulation-testing.md +222 -0
- package/dist/docs/concepts/index.md +46 -0
- package/dist/docs/concepts/terminology.md +322 -0
- package/dist/docs/environment-variables.md +102 -0
- package/dist/docs/getting-started.md +145 -0
- package/dist/docs/guides/ai-facilitation.md +220 -0
- package/dist/docs/guides/capabilities.md +460 -0
- package/dist/docs/guides/endpoints.md +335 -0
- package/dist/docs/guides/engine-testing.md +477 -0
- package/dist/docs/guides/engine.md +527 -0
- package/dist/docs/guides/file-handling.md +405 -0
- package/dist/docs/guides/games.md +176 -0
- package/dist/docs/guides/global-settings.md +94 -0
- package/dist/docs/guides/guide.md +123 -0
- package/dist/docs/guides/hooks.md +183 -0
- package/dist/docs/guides/index.md +51 -0
- package/dist/docs/guides/internationalization.md +288 -0
- package/dist/docs/guides/llm-integration.md +635 -0
- package/dist/docs/guides/multi-team-games.md +586 -0
- package/dist/docs/guides/patterns.md +391 -0
- package/dist/docs/guides/presentation.md +196 -0
- package/dist/docs/guides/production-constraints.md +130 -0
- package/dist/docs/guides/scenario-editor.md +143 -0
- package/dist/docs/guides/scenarios.md +515 -0
- package/dist/docs/guides/session.md +972 -0
- package/dist/docs/guides/settings.md +458 -0
- package/dist/docs/guides/setup.md +193 -0
- package/dist/docs/guides/speech-generation.md +233 -0
- package/dist/docs/guides/starter-game.md +164 -0
- package/dist/docs/guides/testing.md +575 -0
- package/dist/docs/guides/tick-controller.md +569 -0
- package/dist/docs/guides/type-safety.md +516 -0
- package/dist/docs/guides/ui.md +1389 -0
- package/dist/docs/guides/upgrading.md +636 -0
- package/dist/docs/guides/validators.md +217 -0
- package/dist/docs/guides/waiting-room.md +244 -0
- package/dist/docs/index.md +41 -0
- package/dist/docs/recipes/act-on-the-session-from-an-endpoint.md +95 -0
- package/dist/docs/recipes/adapt-a-game-for-walkthroughs.md +133 -0
- package/dist/docs/recipes/adapt-ui-for-observers.md +74 -0
- package/dist/docs/recipes/call-endpoint-from-ui.md +109 -0
- package/dist/docs/recipes/check-a-package-from-a-script.md +117 -0
- package/dist/docs/recipes/create-capability.md +66 -0
- package/dist/docs/recipes/create-endpoint.md +127 -0
- package/dist/docs/recipes/create-engine.md +111 -0
- package/dist/docs/recipes/create-game.md +70 -0
- package/dist/docs/recipes/create-guide.md +75 -0
- package/dist/docs/recipes/create-hook.md +80 -0
- package/dist/docs/recipes/create-presentation.md +142 -0
- package/dist/docs/recipes/create-scenario-editor.md +102 -0
- package/dist/docs/recipes/create-scenario.md +98 -0
- package/dist/docs/recipes/create-session-ui.md +116 -0
- package/dist/docs/recipes/create-settings.md +88 -0
- package/dist/docs/recipes/create-setup.md +115 -0
- package/dist/docs/recipes/create-tick-controller.md +65 -0
- package/dist/docs/recipes/create-user-interface.md +96 -0
- package/dist/docs/recipes/create-validator.md +67 -0
- package/dist/docs/recipes/create-waiting-room-ui.md +109 -0
- package/dist/docs/recipes/declare-api-uses.md +67 -0
- package/dist/docs/recipes/design-state-for-concurrent-writes.md +96 -0
- package/dist/docs/recipes/distribute-package.md +80 -0
- package/dist/docs/recipes/e2e-testing.md +116 -0
- package/dist/docs/recipes/end-game-on-condition.md +154 -0
- package/dist/docs/recipes/fix-duplicate-packages.md +101 -0
- package/dist/docs/recipes/index.md +86 -0
- package/dist/docs/recipes/make-an-endpoint-external.md +101 -0
- package/dist/docs/recipes/negotiate-across-teams.md +164 -0
- package/dist/docs/recipes/observe-other-teams-state.md +119 -0
- package/dist/docs/recipes/publish-a-package.md +139 -0
- package/dist/docs/recipes/remember-a-viewers-choice.md +55 -0
- package/dist/docs/recipes/review-a-past-day.md +73 -0
- package/dist/docs/recipes/share-assets-across-scenarios.md +120 -0
- package/dist/docs/recipes/share-logic-between-engine-and-ui.md +130 -0
- package/dist/docs/recipes/show-all-teams-participants.md +109 -0
- package/dist/docs/recipes/start-from-an-empty-package.md +169 -0
- package/dist/docs/recipes/take-a-file-in-an-endpoint.md +82 -0
- package/dist/docs/recipes/test-a-multi-role-game.md +51 -0
- package/dist/docs/recipes/test-user-interface.md +95 -0
- package/dist/docs/recipes/test-with-engine.md +115 -0
- package/dist/docs/recipes/upload-and-display-files.md +148 -0
- package/dist/docs/recipes/use-request-play.md +65 -0
- package/dist/docs/recipes/use-request-tick.md +75 -0
- package/dist/docs/troubleshooting.md +816 -0
- package/dist/execution/blob-inputs.d.ts +57 -0
- package/dist/execution/bundle-reload.d.ts +42 -0
- package/dist/execution/config-resolver.d.ts +127 -0
- package/dist/execution/configuration-builder.d.ts +66 -0
- package/dist/execution/context-composition.d.ts +161 -0
- package/dist/execution/endpoint-actions.d.ts +9 -0
- package/dist/execution/endpoint-payloads.d.ts +32 -0
- package/dist/execution/endpoint-runner.d.ts +129 -0
- package/dist/execution/engine-state.d.ts +40 -0
- package/dist/execution/executor-callbacks.d.ts +56 -0
- package/dist/execution/executor.d.ts +436 -0
- package/dist/execution/gates.d.ts +45 -0
- package/dist/execution/hook-chain.d.ts +25 -0
- package/dist/execution/logic-calls.d.ts +190 -0
- package/dist/execution/logic-failure.d.ts +84 -0
- package/dist/execution/logic-loader.d.ts +54 -0
- package/dist/execution/logic-result.d.ts +76 -0
- package/dist/execution/tick-controller-call.d.ts +32 -0
- package/dist/execution/types.d.ts +48 -0
- package/dist/generation/ts-type.d.ts +39 -0
- package/dist/guide/locale.d.ts +12 -0
- package/dist/guide/video-registry.d.ts +42 -0
- package/dist/help-examples.d.ts +125 -0
- package/dist/host/adapter.d.ts +67 -0
- package/dist/host/host-channel.d.ts +222 -0
- package/dist/host/index.d.ts +20 -0
- package/dist/host/protocol.d.ts +783 -0
- package/dist/host/surfaces.d.ts +42 -0
- package/dist/host/theme.d.ts +55 -0
- package/dist/host.js +583 -0
- package/dist/prompts/index.d.ts +7 -0
- package/dist/prompts/schema-builder.d.ts +29 -0
- package/dist/prompts/types.d.ts +108 -0
- package/dist/prompts/validators.d.ts +32 -0
- package/dist/runtime/capability-scope.d.ts +6 -0
- package/dist/runtime/context-merge.d.ts +29 -0
- package/dist/runtime/context-projection.d.ts +41 -0
- package/dist/runtime/guarded-callbacks.d.ts +15 -0
- package/dist/runtime/index.d.ts +27 -0
- package/dist/runtime/manifest-version.d.ts +55 -0
- package/dist/runtime/tick-validation.d.ts +25 -0
- package/dist/runtime.js +346 -0
- package/dist/schema.json +1546 -0
- package/dist/server/vite-plugin.d.ts +46 -0
- package/dist/services/tts/client.d.ts +51 -0
- package/dist/services/tts/elevenlabs.d.ts +24 -0
- package/dist/services/tts/types.d.ts +44 -0
- package/dist/storage/asset-resolver.d.ts +49 -0
- package/dist/storage/blob-store.d.ts +41 -0
- package/dist/templates/endpoint/logic.test.ts.liquid +61 -0
- package/dist/templates/endpoint/logic.ts.liquid +25 -0
- package/dist/templates/engine/init.json.liquid +7 -0
- package/dist/templates/engine/logic-aggregate.test.ts.liquid +90 -0
- package/dist/templates/engine/logic-aggregate.ts.liquid +46 -0
- package/dist/templates/engine/logic.test.ts.liquid +61 -0
- package/dist/templates/engine/logic.ts.liquid +32 -0
- package/dist/templates/engine/run.json.liquid +7 -0
- package/dist/templates/guide/01-at-a-glance.md.liquid +19 -0
- package/dist/templates/guide/02-preparation.md.liquid +18 -0
- package/dist/templates/guide/03-running-the-session.md.liquid +37 -0
- package/dist/templates/hook/logic.test.ts.liquid +46 -0
- package/dist/templates/hook/logic.ts.liquid +12 -0
- package/dist/templates/package/CLAUDE.md.template +338 -0
- package/dist/templates/package/DESIGN.md.liquid +144 -0
- package/dist/templates/package/README.md.liquid +143 -0
- package/dist/templates/package/claude/rules/workflow-create-capability.md +26 -0
- package/dist/templates/package/claude/rules/workflow-create-endpoint.md +35 -0
- package/dist/templates/package/claude/rules/workflow-create-engine.md +35 -0
- package/dist/templates/package/claude/rules/workflow-create-guide.md +40 -0
- package/dist/templates/package/claude/rules/workflow-create-presentation.md +37 -0
- package/dist/templates/package/claude/rules/workflow-create-user-interface.md +31 -0
- package/dist/templates/package/claude/rules/workflow-publish-package.md +54 -0
- package/dist/templates/package/claude/rules/workflow-test-engine-or-ui.md +30 -0
- package/dist/templates/package/claude/rules/workflow-update-sdk-version.md +43 -0
- package/dist/templates/package/claude/settings.json.template +16 -0
- package/dist/templates/package/gitignore.template +15 -0
- package/dist/templates/package/global-setup.ts.template +21 -0
- package/dist/templates/package/globals.d.ts.template +2 -0
- package/dist/templates/package/oxfmtrc.json.template +14 -0
- package/dist/templates/package/oxlintrc.json.template +238 -0
- package/dist/templates/package/package.json.liquid +65 -0
- package/dist/templates/package/playwright.config.ts.liquid +19 -0
- package/dist/templates/package/pnpm-workspace.yaml.template +27 -0
- package/dist/templates/package/test-setup.ts.liquid +7 -0
- package/dist/templates/package/tsconfig.json.liquid +15 -0
- package/dist/templates/package/vite.config.ts.liquid +19 -0
- package/dist/templates/package/vitest.config.ts.liquid +25 -0
- package/dist/templates/package/vitest.engine.config.ts.liquid +12 -0
- package/dist/templates/presentation/index.html.liquid +20 -0
- package/dist/templates/presentation/src/App.tsx.liquid +49 -0
- package/dist/templates/presentation/src/Slides.tsx.liquid +81 -0
- package/dist/templates/presentation/src/i18n.ts.liquid +54 -0
- package/dist/templates/presentation/src/main.tsx.liquid +14 -0
- package/dist/templates/scenario_editor/src/App.test.tsx.liquid +30 -0
- package/dist/templates/scenario_editor/src/App.tsx.liquid +95 -0
- package/dist/templates/scenario_editor/src/i18n.ts.liquid +44 -0
- package/dist/templates/session/src/App.test.tsx.liquid +60 -0
- package/dist/templates/session/src/App.tsx.liquid +128 -0
- package/dist/templates/session/src/i18n.ts.liquid +53 -0
- package/dist/templates/session/src/main.tsx.liquid +48 -0
- package/dist/templates/session/src/theme.ts.liquid +46 -0
- package/dist/templates/settings-editor/index.html.liquid +12 -0
- package/dist/templates/settings-editor/src/main.tsx.liquid +48 -0
- package/dist/templates/settings-editor/src/theme.ts.liquid +24 -0
- package/dist/templates/setup/src/App.test.tsx.liquid +39 -0
- package/dist/templates/setup/src/App.tsx.liquid +102 -0
- package/dist/templates/setup/src/i18n.ts.liquid +47 -0
- package/dist/templates/starter-game/capabilities/briefing/capability.sdk.json +18 -0
- package/dist/templates/starter-game/capabilities/plan/capability.sdk.json +17 -0
- package/dist/templates/starter-game/capabilities/results/capability.sdk.json +61 -0
- package/dist/templates/starter-game/capabilities/roles/capability.sdk.json +17 -0
- package/dist/templates/starter-game/capabilities/weather/capability.sdk.json +23 -0
- package/dist/templates/starter-game/engine/defaults.ts +81 -0
- package/dist/templates/starter-game/engine/engine.sdk.json +12 -0
- package/dist/templates/starter-game/engine/logic.test.ts +221 -0
- package/dist/templates/starter-game/engine/logic.ts +149 -0
- package/dist/templates/starter-game/engine/test-helpers.ts +74 -0
- package/dist/templates/starter-game/engine/types.ts +139 -0
- package/dist/templates/starter-game/guide/content/en/01-at-a-glance.md +51 -0
- package/dist/templates/starter-game/guide/content/en/02-preparation.md +35 -0
- package/dist/templates/starter-game/guide/content/en/03-running-the-day.md +58 -0
- package/dist/templates/starter-game/guide/guide.sdk.json +10 -0
- package/dist/templates/starter-game/guide/guide.test.ts +108 -0
- package/dist/templates/starter-game/hooks/assignproducts/defaults.ts +37 -0
- package/dist/templates/starter-game/hooks/assignproducts/hook.sdk.json +13 -0
- package/dist/templates/starter-game/hooks/assignproducts/logic/logic.test.ts +77 -0
- package/dist/templates/starter-game/hooks/assignproducts/logic/logic.ts +47 -0
- package/dist/templates/starter-game/hooks/assignproducts/types.ts +55 -0
- package/dist/templates/starter-game/package.json +68 -0
- package/dist/templates/starter-game/playwright.config.ts +19 -0
- package/dist/templates/starter-game/scenarios/heatwave/scenario.sdk.json +20 -0
- package/dist/templates/starter-game/scenarios/ordinary_week/scenario.sdk.json +12 -0
- package/dist/templates/starter-game/seriousgaming.sdk.json +23 -0
- package/dist/templates/starter-game/session/defaults.ts +80 -0
- package/dist/templates/starter-game/session/index.html +12 -0
- package/dist/templates/starter-game/session/session.sdk.json +11 -0
- package/dist/templates/starter-game/session/src/App.test.tsx +307 -0
- package/dist/templates/starter-game/session/src/App.tsx +309 -0
- package/dist/templates/starter-game/session/src/Section.tsx +37 -0
- package/dist/templates/starter-game/session/src/TeamDoor.tsx +96 -0
- package/dist/templates/starter-game/session/src/format.ts +8 -0
- package/dist/templates/starter-game/session/src/i18n.ts +51 -0
- package/dist/templates/starter-game/session/src/main.tsx +48 -0
- package/dist/templates/starter-game/session/src/theme.ts +45 -0
- package/dist/templates/starter-game/session/types.ts +135 -0
- package/dist/templates/starter-game/settings/engine/settings.sdk.json +50 -0
- package/dist/templates/starter-game/settings/menu/settings.sdk.json +78 -0
- package/dist/templates/starter-game/settings/session/settings.sdk.json +23 -0
- package/dist/templates/starter-game/settings/ui/settings.sdk.json +25 -0
- package/dist/templates/starter-game/test-setup.ts +7 -0
- package/dist/templates/starter-game/testing/e2e/game.spec.ts +139 -0
- package/dist/templates/starter-game/testing/e2e/global-setup.ts +21 -0
- package/dist/templates/starter-game/testing/engine/game.engine.test.ts +171 -0
- package/dist/templates/starter-game/tsconfig.json +19 -0
- package/dist/templates/starter-game/ui/defaults.ts +60 -0
- package/dist/templates/starter-game/ui/index.html +12 -0
- package/dist/templates/starter-game/ui/src/App.test.tsx +138 -0
- package/dist/templates/starter-game/ui/src/App.tsx +175 -0
- package/dist/templates/starter-game/ui/src/DayBar.test.tsx +78 -0
- package/dist/templates/starter-game/ui/src/DayBar.tsx +72 -0
- package/dist/templates/starter-game/ui/src/HistoryPanel.test.tsx +74 -0
- package/dist/templates/starter-game/ui/src/HistoryPanel.tsx +120 -0
- package/dist/templates/starter-game/ui/src/MakeRow.tsx +129 -0
- package/dist/templates/starter-game/ui/src/Section.tsx +37 -0
- package/dist/templates/starter-game/ui/src/ThemeToggle.tsx +49 -0
- package/dist/templates/starter-game/ui/src/TodayPanel.test.tsx +179 -0
- package/dist/templates/starter-game/ui/src/TodayPanel.tsx +92 -0
- package/dist/templates/starter-game/ui/src/YesterdayPanel.tsx +142 -0
- package/dist/templates/starter-game/ui/src/format.ts +12 -0
- package/dist/templates/starter-game/ui/src/i18n.ts +95 -0
- package/dist/templates/starter-game/ui/src/main.tsx +48 -0
- package/dist/templates/starter-game/ui/src/menu.ts +18 -0
- package/dist/templates/starter-game/ui/src/test-helpers.ts +58 -0
- package/dist/templates/starter-game/ui/src/theme.ts +45 -0
- package/dist/templates/starter-game/ui/src/writes.ts +43 -0
- package/dist/templates/starter-game/ui/types.ts +137 -0
- package/dist/templates/starter-game/ui/ui.sdk.json +11 -0
- package/dist/templates/starter-game/validators/capacity/defaults.ts +37 -0
- package/dist/templates/starter-game/validators/capacity/logic/logic.test.ts +96 -0
- package/dist/templates/starter-game/validators/capacity/logic/logic.ts +45 -0
- package/dist/templates/starter-game/validators/capacity/types.ts +55 -0
- package/dist/templates/starter-game/validators/capacity/validator.sdk.json +11 -0
- package/dist/templates/starter-game/vite.config.ts +19 -0
- package/dist/templates/starter-game/vitest.config.ts +15 -0
- package/dist/templates/starter-game/vitest.engine.config.ts +19 -0
- package/dist/templates/starter-game/waiting_room/defaults.ts +40 -0
- package/dist/templates/starter-game/waiting_room/index.html +12 -0
- package/dist/templates/starter-game/waiting_room/src/App.test.tsx +102 -0
- package/dist/templates/starter-game/waiting_room/src/App.tsx +194 -0
- package/dist/templates/starter-game/waiting_room/src/i18n.ts +33 -0
- package/dist/templates/starter-game/waiting_room/src/main.tsx +48 -0
- package/dist/templates/starter-game/waiting_room/src/theme.ts +45 -0
- package/dist/templates/starter-game/waiting_room/types.ts +59 -0
- package/dist/templates/starter-game/waiting_room/waiting_room.sdk.json +10 -0
- package/dist/templates/tick-controller/logic.ts.liquid +8 -0
- package/dist/templates/ui/index.html.liquid +12 -0
- package/dist/templates/ui/src/App.test.tsx.liquid +41 -0
- package/dist/templates/ui/src/App.tsx.liquid +103 -0
- package/dist/templates/ui/src/i18n.ts.liquid +47 -0
- package/dist/templates/ui/src/main.tsx.liquid +48 -0
- package/dist/templates/ui/src/theme.ts.liquid +46 -0
- package/dist/templates/validator/logic.test.ts.liquid +42 -0
- package/dist/templates/validator/logic.ts.liquid +21 -0
- package/dist/templates/waiting-room/index.html.liquid +12 -0
- package/dist/templates/waiting-room/src/App.test.tsx.liquid +57 -0
- package/dist/templates/waiting-room/src/App.tsx.liquid +118 -0
- package/dist/templates/waiting-room/src/i18n.ts.liquid +44 -0
- package/dist/templates/waiting-room/src/main.tsx.liquid +48 -0
- package/dist/templates/waiting-room/src/theme.ts.liquid +46 -0
- package/dist/templating/resource-metadata.d.ts +131 -0
- package/dist/types.d.ts +14 -0
- package/dist/utils/atomic-write.d.ts +40 -0
- package/dist/utils/cli-error.d.ts +64 -0
- package/dist/utils/document-parser-client.d.ts +20 -0
- package/dist/utils/env-loader.d.ts +40 -0
- package/dist/utils/error-codes.d.ts +335 -0
- package/dist/utils/errors.d.ts +19 -0
- package/dist/utils/global-settings.d.ts +65 -0
- package/dist/utils/llm-client.d.ts +34 -0
- package/dist/utils/logger.d.ts +36 -0
- package/dist/utils/output.d.ts +109 -0
- package/dist/utils/platform.d.ts +14 -0
- package/dist/utils/prompt.d.ts +36 -0
- package/dist/utils/real-path.d.ts +6 -0
- package/dist/utils/schema-properties.d.ts +35 -0
- package/dist/validation/ajv.d.ts +17 -0
- package/dist/validation/facilitator-guide-validator.d.ts +27 -0
- package/dist/validation/findings.d.ts +40 -0
- package/dist/validation/game-constraints.d.ts +10 -0
- package/dist/validation/game-validator.d.ts +34 -0
- package/dist/validation/package-validation.d.ts +41 -0
- package/dist/validation/package-validator.d.ts +19 -0
- package/dist/validation/resource-validator.d.ts +79 -0
- package/dist/validation/schema-defaults-validator.d.ts +29 -0
- package/dist/validation/schema-validation.d.ts +115 -0
- package/dist/validation/semver.d.ts +9 -0
- package/package.json +122 -3
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
# Engine testing
|
|
2
|
+
|
|
3
|
+
Test your whole game in-process: no dev server, no browser, and no LLM API key required, because service callbacks are stubbed by default.
|
|
4
|
+
|
|
5
|
+
## What you'll learn
|
|
6
|
+
|
|
7
|
+
- What `createTestSession` is and when to use it
|
|
8
|
+
- How engine tests fit alongside unit tests and Playwright tests
|
|
9
|
+
- How to write engine tests: ticking, asserting context, injecting decisions
|
|
10
|
+
- How to test hooks, validators, endpoints, undo and timers
|
|
11
|
+
- How service callbacks are stubbed, and how to override them or reach the platform
|
|
12
|
+
- How to move a test from `GameRunner`, which has been removed
|
|
13
|
+
|
|
14
|
+
## What is createTestSession?
|
|
15
|
+
|
|
16
|
+
`createTestSession` starts the session the dev server runs, inside your test process. Everything a game does is there: `Init` and `Run`, hooks when somebody joins, validators on a participant's write, endpoints, undo, independent teams ticking on their own, the configuration step, and automatic ticks. You drive it with the same commands the console, the CLI and your surfaces send, and read it with the same state `sdk view --full` prints.
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { fileURLToPath } from 'url';
|
|
20
|
+
|
|
21
|
+
import { createTestSession } from '@seriousgaming/sdk-tools/client';
|
|
22
|
+
|
|
23
|
+
const session = await createTestSession({
|
|
24
|
+
packagePath: fileURLToPath(new URL('../..', import.meta.url)),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
await session.send({ type: 'tick' });
|
|
28
|
+
const team = session.state().teams[0];
|
|
29
|
+
|
|
30
|
+
await session.close();
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Which runtime version your tests execute
|
|
34
|
+
|
|
35
|
+
`createTestSession` runs a copy of the SDK runtime that is compiled into
|
|
36
|
+
`@seriousgaming/sdk-tools` when that package is built, not
|
|
37
|
+
the `@seriousgaming/sdk` you have installed. Upgrading one
|
|
38
|
+
without the other therefore changes nothing about how your engine executes, so
|
|
39
|
+
keep the two on the same version and treat the tools version as the one that
|
|
40
|
+
decides behaviour. It also means that if you are narrowing down a defect by
|
|
41
|
+
changing versions, only the tools version is a variable.
|
|
42
|
+
|
|
43
|
+
## The three testing layers
|
|
44
|
+
|
|
45
|
+
Engine tests sit between unit tests and Playwright tests:
|
|
46
|
+
|
|
47
|
+
| Layer | Tool | Tests | Speed | Needs |
|
|
48
|
+
| ---------- | ----------------- | ------------------------------------------------------------------- | ------- | ----------- |
|
|
49
|
+
| Unit | Vitest (jsdom) | Individual Init/Run functions, React components | ~1s | Nothing |
|
|
50
|
+
| **Engine** | **Vitest (node)** | **The whole game: ticks, decisions, hooks, validators, completion** | **~1s** | **Nothing** |
|
|
51
|
+
| Browser | Playwright | UI rendering, user input, iframe communication | ~30s+ | Dev server |
|
|
52
|
+
|
|
53
|
+
**When a Playwright test fails**, run engine tests first to isolate the problem:
|
|
54
|
+
|
|
55
|
+
- Engine test fails → bug is in your game logic
|
|
56
|
+
- Engine test passes → bug is in your UI component or iframe communication
|
|
57
|
+
|
|
58
|
+
## Setup
|
|
59
|
+
|
|
60
|
+
When you create a package with `sdk new package`, the engine test scaffold is included automatically:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
testing/engine/game.engine.test.ts ← starter engine test
|
|
64
|
+
vitest.engine.config.ts ← node environment config
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
And your `package.json` includes:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
"test:engine": "vitest run --config vitest.engine.config.ts"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
For an existing project, see the [Engine Testing recipe](../recipes/test-with-engine.md).
|
|
74
|
+
|
|
75
|
+
## Writing engine tests
|
|
76
|
+
|
|
77
|
+
### Basic structure
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// testing/engine/game.engine.test.ts
|
|
81
|
+
import { fileURLToPath } from 'url';
|
|
82
|
+
|
|
83
|
+
import { createTestSession, type TestSession } from '@seriousgaming/sdk-tools/client';
|
|
84
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
85
|
+
|
|
86
|
+
import type { Context, SharedContext } from '../../engine/types';
|
|
87
|
+
|
|
88
|
+
const PACKAGE_PATH = fileURLToPath(new URL('../..', import.meta.url));
|
|
89
|
+
|
|
90
|
+
describe('mygame', () => {
|
|
91
|
+
let session: TestSession<Context, SharedContext>;
|
|
92
|
+
|
|
93
|
+
beforeEach(async () => {
|
|
94
|
+
session = await createTestSession<Context, SharedContext>({ packagePath: PACKAGE_PATH });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
afterEach(async () => {
|
|
98
|
+
await session.close();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('initialises with expected defaults', () => {
|
|
102
|
+
expect(session.team(0).context.main).toEqual({ Delta: 0, Total: 0 });
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The two type parameters are the engine's generated `Context` and `SharedContext`. They type what `state()`, `team()` and `shared` return and what `setData` and `setSharedData` accept, so a test reads `session.team(0).context.main.Total` with no cast. Nothing checks them against the package, as nothing checks a cast; leave them out and the contexts are untyped records. A game whose hooks keep capabilities the engine does not declare can intersect their `Context` types too.
|
|
108
|
+
|
|
109
|
+
`beforeEach` starts a fresh session for every test, and `afterEach` stops its timers. Nothing is read from or written to `.seriousgaming-sdk/`, so there are no files to clean up, and a test does not depend on what you last ran `sdk configure` with.
|
|
110
|
+
|
|
111
|
+
### Ticking and asserting
|
|
112
|
+
|
|
113
|
+
`tick()` ticks as the facilitator does, and answers with how it settled. A tick that succeeds is `{ ok: true }`; one the session refuses, such as a tick past the game's limit, is `{ ok: false, outcome }`. A refusal is an answer, not a throw, so a test can assert on it:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
it('adds Delta to Total on each tick', async () => {
|
|
117
|
+
await session.setData(0, { main: { Delta: 5 } });
|
|
118
|
+
|
|
119
|
+
expect(await session.tick()).toEqual({ ok: true, committedAtTick: 1 });
|
|
120
|
+
|
|
121
|
+
expect(session.state().session.completedTicks).toBe(1);
|
|
122
|
+
expect(session.team(0).context.main.Total).toBe(5);
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Every helper is a command underneath, and `send` takes any command directly: `session.tick()` is `session.send({ type: 'tick' })`.
|
|
127
|
+
|
|
128
|
+
`state()` is the whole session: `session.completedTicks`, `session.complete`, every team with its `context`, `completedTicks` and `participants`, the shared store, the timer and `settings`, every settings resource's resolved values keyed by identifier.
|
|
129
|
+
|
|
130
|
+
### A note on `instanceof` and engine-produced values
|
|
131
|
+
|
|
132
|
+
Engine logic is evaluated in its own JavaScript environment, matching the
|
|
133
|
+
production runtime. Values it builds therefore answer `instanceof` and
|
|
134
|
+
`toBeInstanceOf` against that environment's constructors, not your test file's,
|
|
135
|
+
so an assertion written this way can fail on a value that is perfectly correct:
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
// Fails on an array your engine built, even though it is a real array
|
|
139
|
+
expect(context.main.Segments).toBeInstanceOf(Array);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Assert on the shape instead, which holds wherever the value was built:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
expect(Array.isArray(context.main.Segments)).toBe(true);
|
|
146
|
+
expect(context.main.Segments).toEqual(['seg_a', 'seg_b']);
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The same applies to `instanceof Array` and `instanceof Object` inside your own
|
|
150
|
+
helpers. `Array.isArray`, `typeof`, and `toEqual` are all safe.
|
|
151
|
+
|
|
152
|
+
### Injecting decisions
|
|
153
|
+
|
|
154
|
+
`setData(team, patch)` merges a patch into one team's context, by the team's stable index, as a player's surface does: as the team's first participant, through the `ui` surface. So the write is held to that surface's `writes`, and your validators judge it:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
await session.setData(0, { pricing: { Price: 10 } });
|
|
158
|
+
await session.tick();
|
|
159
|
+
|
|
160
|
+
const result = await session.setData(0, { addons: { ExtraShift: true, MarketingPush: true } });
|
|
161
|
+
expect(result).toMatchObject({ ok: false, outcome: { kind: 'validation-rejected' } });
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Options choose another writer: `participantId` for a participant other than the first, `ui` for another player surface, and `deletes` for paths to remove after the patch merges. `as: 'system'` writes as the CLI does instead, trusted and ungated (see [what bypasses validation](./validators.md#what-bypasses-validation)), which suits arranging a board the test then plays on:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
await session.setData(0, { progress: { Score: 45 } }, { as: 'system' });
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`requestTick(team)` asks for the next tick as the team's first participant, so your validators' `RequestTick` gates judge it, where `tick()` is the facilitator's and passes them:
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
expect(await session.requestTick(0)).toMatchObject({
|
|
174
|
+
ok: false,
|
|
175
|
+
outcome: { kind: 'validation-rejected' },
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
A shared capability is written with `setSharedData`, and every team sees it. With no options it writes as the CLI does; pass `ui: 'session'` to write as the session surface does, held to its `writes`:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
await session.setSharedData({ noticeboard: { Message: 'Break at 3' } }, { ui: 'session' });
|
|
183
|
+
expect(session.shared.context.noticeboard.Message).toBe('Break at 3');
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`shared` holds the store under `context`, as a session surface's `sessionData.shared` does, so an assertion moves between a test and the surface unchanged. `state().shared` is the store itself.
|
|
187
|
+
|
|
188
|
+
### Testing game completion
|
|
189
|
+
|
|
190
|
+
A game with a `maxTicks` limit refuses the tick after it. A game whose tick controller returns `complete: true` reports it on `state().session.complete`, and refuses the next tick:
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
it('completes when the target is reached', async () => {
|
|
194
|
+
await session.setData(0, { progress: { Score: 45 } }, { as: 'system' });
|
|
195
|
+
await session.tick();
|
|
196
|
+
|
|
197
|
+
expect(session.state().session.complete).toBe(true);
|
|
198
|
+
expect(await session.tick()).toMatchObject({
|
|
199
|
+
ok: false,
|
|
200
|
+
outcome: { kind: 'tick-failed' },
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Teams and participants
|
|
206
|
+
|
|
207
|
+
Pass `teams` for a multi-team game; the default is the game's `teams.min`. In an independent game, `tick()` advances every team and `tick({ team: 1 })` advances one. In a shared-state game every team advances together.
|
|
208
|
+
|
|
209
|
+
Every team starts with one participant, or the game's `perTeam.minParticipants`. Pass `participants` to choose, as one count or one per team. Zero is allowed:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
const session = await createTestSession({
|
|
213
|
+
packagePath: PACKAGE_PATH,
|
|
214
|
+
teams: 2,
|
|
215
|
+
participants: [3, 0], // three on team 1, nobody on team 2
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Your participantJoin hooks ran for the three.
|
|
219
|
+
await session.send({ type: 'addParticipant', teamId: 'team-2' }); // and now for one more
|
|
220
|
+
await session.send({ type: 'moveParticipant', participantId, teamId: 'team-1' });
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Endpoints
|
|
224
|
+
|
|
225
|
+
Call one with `callEndpoint`. Leave out `uiIdentifier` and the call is system-trusted, as the CLI's is. Name a surface (`uiIdentifier: 'ui'`) to hold the call to that surface's `endpoints` list, as a player's would be. An identifier the package does not declare, the empty string included, is refused rather than trusted: the call answers with a `denied` failure.
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
const result = await session.send({
|
|
229
|
+
type: 'callEndpoint',
|
|
230
|
+
endpointIdentifier: 'openround',
|
|
231
|
+
request: { round: 4 },
|
|
232
|
+
});
|
|
233
|
+
expect(result).toEqual({ type: 'endpoint', response: { previous: 0, current: 4 } });
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
A failed call is an answer too, not a throw: `{ type: 'endpointFailure', failure }`, where `failure` is the same `EndpointFailure` a surface's `EndpointError` carries. Assert on its kind and on what had already landed:
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
const refused = await session.send({
|
|
240
|
+
type: 'callEndpoint',
|
|
241
|
+
endpointIdentifier: 'openround',
|
|
242
|
+
request: { round: 'four' },
|
|
243
|
+
});
|
|
244
|
+
expect(refused).toMatchObject({
|
|
245
|
+
type: 'endpointFailure',
|
|
246
|
+
failure: { outcome: { kind: 'invalid-request' }, applied: [] },
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Undo, reset and starting again
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
await session.send({ type: 'undo' }); // take back the last tick
|
|
254
|
+
await session.send({ type: 'reset' }); // back to tick 0, keeping teams and scenario
|
|
255
|
+
await session.send({ type: 'reinitialise' }); // start again from the package
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Automatic ticks
|
|
259
|
+
|
|
260
|
+
Timers run on the clock you pass. Pass a `FakeClock` and move it with `advanceClock`, which runs every tick that falls due on the way, so a test says how much time passes rather than waiting for it:
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
import { FakeClock } from '@seriousgaming/sdk-tools/client';
|
|
264
|
+
|
|
265
|
+
const session = await createTestSession({ packagePath: PACKAGE_PATH, clock: new FakeClock(0) });
|
|
266
|
+
await session.send({ type: 'advanceClock', byMs: 180_000 }); // three one-minute ticks
|
|
267
|
+
expect(session.state().session.completedTicks).toBe(3);
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
`pauseTimer`, `resumeTimer` and `extendTick` work as they do from a session UI.
|
|
271
|
+
|
|
272
|
+
The same clock is what logic reads as `meta.now`, so a `FakeClock` also pins the timestamps a game records: a session started on `new FakeClock(Date.UTC(2026, 0, 1))` hands its first `Init` exactly that instant.
|
|
273
|
+
|
|
274
|
+
### Pinning a scenario
|
|
275
|
+
|
|
276
|
+
By default the session resolves the game's default scenario when one is declared, exactly as the dev server does. Your assertions then silently track whatever scenario is currently the default: if someone later marks a different one as default, your expected values change without any test edit. Pin it:
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
// A specific scenario (it must be listed in the game's `scenarios`)
|
|
280
|
+
await createTestSession({ packagePath: PACKAGE_PATH, scenarioId: 'easy_mode' });
|
|
281
|
+
|
|
282
|
+
// Or opt out of the default scenario and test the settings defaults
|
|
283
|
+
await createTestSession({ packagePath: PACKAGE_PATH, scenarioId: null });
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### The configuration step
|
|
287
|
+
|
|
288
|
+
A game with a `setup` slot starts at its configuration step. `createTestSession` presses Play for you, so a tick has a game to run. Pass `play: false` to test the step itself, then send `updateSettings` (keyed by settings identifier, for example `{ type: 'updateSettings', payload: { engine: { BaseDemand: 200 } } }`) and `play` as the setup screen would. Settings are fixed from Play onwards, so `state().settings` after `play` is what every tick reads.
|
|
289
|
+
|
|
290
|
+
## Service callbacks are stubbed
|
|
291
|
+
|
|
292
|
+
Tests are offline by default, whether they run a whole session or call one logic function. A test session, and the arguments `logicArgs` builds for a unit test (see [Testing the Run function](./testing.md#testing-the-run-function)), answer the service callbacks from the same deterministic stubs, so a test runs without a network or a platform key and gets the same answer every run:
|
|
293
|
+
|
|
294
|
+
- **`generateText`** answers `[stub <hash>] <excerpt>`: an eight-digit hex hash of the whole request (prompt, system prompt, tier and the rest), then the first 60 characters of the prompt, or of the last `user` message's text, with whitespace collapsed and `...` appended when cut. A request with a `jsonSchema` is answered with the smallest JSON value that satisfies the schema: each property's `const`, first `enum` member or `default` where it has one, `'[stub]'` for a string, the `minimum` or `0` for a number, `false`, and `minItems` entries for an array
|
|
295
|
+
- **`generateSpeech`** stores 100ms of silence as a WAV blob and returns `{ reference: 'blob:<id>', mimeType: 'audio/wav', durationMs: 100, charactersBilled: 0 }`, so `readAsset` resolves it as it would real audio
|
|
296
|
+
- **`extractText`** answers `{ content: '[stub] text of <reference>', mimeType: 'text/plain' }`, without reading the file
|
|
297
|
+
|
|
298
|
+
With no session behind them, the arguments `logicArgs` builds stub the rest as well: `writeBlob` stores in memory and returns `blob:stub-<n>`, `readAsset` reads back only a blob written in the same test (and rejects anything else, `asset:` references included), and `getPastState` answers `[]`.
|
|
299
|
+
|
|
300
|
+
| Export | From | Use it to |
|
|
301
|
+
| --------------------------------- | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
302
|
+
| `stubGenerateText(request)` | `@seriousgaming/sdk/testing`, `@seriousgaming/sdk-tools/client` | Compute the stub's answer, so a test can assert on it exactly |
|
|
303
|
+
| `createStubCallbacks()` | `@seriousgaming/sdk/testing` | Get a fresh set of every stubbed callback, with its own blob store, as `logicArgs` fills them |
|
|
304
|
+
| `createStubServices(packagePath)` | `@seriousgaming/sdk-tools/client` | Get the three service stubs a test session uses, which store speech in the package's blob store |
|
|
305
|
+
|
|
306
|
+
### Overriding a callback
|
|
307
|
+
|
|
308
|
+
When a test needs a particular answer, override that callback alone; the others stay stubbed. In a test session, pass `callbacks`:
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
const session = await createTestSession({
|
|
312
|
+
packagePath: PACKAGE_PATH,
|
|
313
|
+
callbacks: {
|
|
314
|
+
generateText: async () => ({
|
|
315
|
+
content: [{ type: 'text' as const, text: 'mock response' }],
|
|
316
|
+
stopReason: 'endTurn' as const,
|
|
317
|
+
}),
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
In a unit test, pass it to `logicArgs` the same way, which makes a spy easy to assert on:
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
import { logicArgs } from '@seriousgaming/sdk/testing';
|
|
326
|
+
import { expect, it, vi } from 'vitest';
|
|
327
|
+
|
|
328
|
+
it('includes the LLM report in context', async () => {
|
|
329
|
+
const generateText = vi.fn().mockResolvedValue({
|
|
330
|
+
content: [{ type: 'text' as const, text: 'Mocked report.' }],
|
|
331
|
+
stopReason: 'endTurn' as const,
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
const result = await Run(
|
|
335
|
+
logicArgs.run({ context, settings: defaultSettings, callbacks: { generateText } })
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
expect(generateText).toHaveBeenCalledOnce();
|
|
339
|
+
expect(result.capabilities.report.Content).toBe('Mocked report.');
|
|
340
|
+
});
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
For logic that expects structured JSON output, inspect the options to return the right format:
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
import type { LLMGenerateOptions } from '@seriousgaming/sdk';
|
|
347
|
+
|
|
348
|
+
const mockGenerateText = async (options: LLMGenerateOptions | string) => {
|
|
349
|
+
if (typeof options === 'object' && options.jsonSchema) {
|
|
350
|
+
return {
|
|
351
|
+
content: [
|
|
352
|
+
{ type: 'text' as const, text: JSON.stringify({ score: 75, status: 'Good' }) },
|
|
353
|
+
],
|
|
354
|
+
stopReason: 'endTurn' as const,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
content: [{ type: 'text' as const, text: 'Tick summary: performance is on track.' }],
|
|
359
|
+
stopReason: 'endTurn' as const,
|
|
360
|
+
};
|
|
361
|
+
};
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
`callbacks` is typed as `Partial<SignatureCallbacks>`, so you only override what your logic uses. In a test session, anything you don't override keeps its default (a stub, or the platform's with `services: 'platform'`), and the override reaches hooks, validators and endpoints as well as the engine. A callback the resource did not declare in `uses` still throws there, override or not.
|
|
365
|
+
|
|
366
|
+
## Calling the platform
|
|
367
|
+
|
|
368
|
+
To send the service calls to the Serious Gaming platform, pass `services: 'platform'`:
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
const session = await createTestSession({ packagePath: PACKAGE_PATH, services: 'platform' });
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
The session then reads the platform credentials from the same sources as the CLI: the `SERIOUSGAMING_API_KEY` and `SERIOUSGAMING_API_HOST` environment variables, a `.env` file in the package root, and the global settings file. You can also pass them explicitly with `platform: { apiKey, apiHost }`. Keep such tests out of CI unless it has a key.
|
|
375
|
+
|
|
376
|
+
## Running engine tests
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
# Run all engine tests once
|
|
380
|
+
pnpm test:engine
|
|
381
|
+
|
|
382
|
+
# Watch mode (re-runs on file changes)
|
|
383
|
+
pnpm exec vitest --config vitest.engine.config.ts
|
|
384
|
+
|
|
385
|
+
# Run a specific file
|
|
386
|
+
pnpm exec vitest --config vitest.engine.config.ts testing/engine/game.engine.test.ts
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
Engine tests run in milliseconds per test because there are no subprocesses, no HTTP, and no browser startup.
|
|
390
|
+
|
|
391
|
+
## CI integration
|
|
392
|
+
|
|
393
|
+
Engine tests require nothing beyond Node.js: no dev server, no browser, and no platform API key, because the service callbacks are stubbed unless a test asks for the platform. Add them to your CI pipeline alongside unit tests:
|
|
394
|
+
|
|
395
|
+
```yaml
|
|
396
|
+
# Example GitHub Actions step
|
|
397
|
+
- name: Run tests
|
|
398
|
+
run: |
|
|
399
|
+
pnpm test:run # unit and component tests
|
|
400
|
+
pnpm test:engine # engine tests
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Run Playwright tests in a separate job or stage, since they need a browser and the dev server.
|
|
404
|
+
|
|
405
|
+
## API reference
|
|
406
|
+
|
|
407
|
+
### `createTestSession(options)`
|
|
408
|
+
|
|
409
|
+
| Option | Type | Description |
|
|
410
|
+
| -------------- | ----------------------------- | ------------------------------------------------------------------------------------ |
|
|
411
|
+
| `packagePath` | `string` | Absolute path to the project root (where `seriousgaming.sdk.json` lives) |
|
|
412
|
+
| `teams` | `number` | Teams to start with. Defaults to the game's `teams.min` |
|
|
413
|
+
| `participants` | `number \| number[]` | Participants per team, one count or one per team. Zero allowed. Defaults to one each |
|
|
414
|
+
| `scenarioId` | `string \| null` | Scenario to start on. Omit for the default; `null` for the settings defaults |
|
|
415
|
+
| `locale` | `string` | Session locale |
|
|
416
|
+
| `callbacks` | `Partial<SignatureCallbacks>` | Replace callbacks your logic receives, such as `generateText` |
|
|
417
|
+
| `services` | `'stub' \| 'platform'` | Where `generateText`, `generateSpeech` and `extractText` go. Default `'stub'` |
|
|
418
|
+
| `clock` | `Clock` | The clock timers run on. Pass a `FakeClock` to move time by hand |
|
|
419
|
+
| `play` | `boolean` | Press Play on a game with a configuration step. Default `true` |
|
|
420
|
+
| `platform` | `{ apiKey?, apiHost? }` | Platform credentials, used with `services: 'platform'` |
|
|
421
|
+
|
|
422
|
+
`createTestSession<Context, Shared>(options)` takes two optional type parameters, the engine's generated `Context` and `SharedContext`, and returns a `TestSession<Context, Shared>`.
|
|
423
|
+
|
|
424
|
+
### `TestSession`
|
|
425
|
+
|
|
426
|
+
| Member | Returns | Description |
|
|
427
|
+
| -------------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
428
|
+
| `send(command)` | `Promise<MutationResult>` or `Promise<QueryResult>` | Run a command as the dev server would. A refusal is an answer, not a throw |
|
|
429
|
+
| `state()` | `TestSessionState<Context, Shared>` | The whole session now, each team's context typed |
|
|
430
|
+
| `team(index)` | `TestSessionTeam<Context>` | One team by stable index: `context`, `participants`, `completedTicks` and the rest. Throws for an unknown one |
|
|
431
|
+
| `shared` | `{ context: Shared }` | The shared store now, under `context` as a session surface's `sessionData.shared` holds it |
|
|
432
|
+
| `tick(options?)` | `Promise<MutationResult>` | A facilitator's tick. `{ team }` ticks one team in independent mode |
|
|
433
|
+
| `requestTick(team, options?)` | `Promise<MutationResult>` | A participant's request, which `RequestTick` gates judge. `{ participantId }` to choose who asks |
|
|
434
|
+
| `setData(team, patch, options?)` | `Promise<MutationResult>` | A participant's write through `ui`, or `{ as: 'system' }` for a trusted one. Also `deletes`, `participantId`, `ui` |
|
|
435
|
+
| `setSharedData(patch, options?)` | `Promise<MutationResult>` | A write to the shared store. `{ ui }` holds it to that session surface's `writes`; also `deletes` |
|
|
436
|
+
| `settled()` | `Promise<void>` | Resolves once everything queued has run, including a timer's tick |
|
|
437
|
+
| `close()` | `Promise<void>` | Stop the session's timers. Call it in `afterEach` |
|
|
438
|
+
|
|
439
|
+
`Command`, `MutationCommand`, `QueryCommand`, `QueryResult`, `MutationResult`, `StateProjection`, `TestSessionState`, `TestSessionTeam`, `ContextPatch`, `SetDataOptions`, `SetSharedDataOptions`, `FakeClock`, `ParticipantCounts`, `TestServices`, `createStubServices` and `stubGenerateText` are exported from the same module.
|
|
440
|
+
|
|
441
|
+
To call one logic function directly instead, with no session, build its arguments with `logicArgs` from `@seriousgaming/sdk/testing` (see [Testing](./testing.md#testing-the-run-function)).
|
|
442
|
+
|
|
443
|
+
## Moving from GameRunner
|
|
444
|
+
|
|
445
|
+
`GameRunner` and `MultiTeamGameRunner` were deprecated in 0.6.0 and have been removed. They drove the engine alone, so a test written with them never saw a hook, a validator, an endpoint, a participant, undo, a per-team tick or a timer. Each call has a `createTestSession` equivalent:
|
|
446
|
+
|
|
447
|
+
| `GameRunner` | `createTestSession` |
|
|
448
|
+
| --------------------------------------------------------- | ---------------------------------------------------------------- |
|
|
449
|
+
| `GameRunner.create(options)` then `init()` | `createTestSession(options)`: it is ready at tick 0 |
|
|
450
|
+
| `MultiTeamGameRunner.create({ teamCount })` | `createTestSession({ teams })`, in either interaction mode |
|
|
451
|
+
| `runner.tick()` | `session.tick()`, then read `session.team(0)` |
|
|
452
|
+
| `runner.update([{ op: 'replace', path: '/a/B', value }])` | `session.setData(0, { a: { B: value } }, { as: 'system' })` |
|
|
453
|
+
| `runner.update(patches, { teamId: 'team-2' })` | the same, with team `1` |
|
|
454
|
+
| `runner.updateShared(patches)` | `session.setSharedData(payload)` |
|
|
455
|
+
| `runner.getContext()` / `getContext('team-2')` | `session.team(0).context` / `session.team(1).context` |
|
|
456
|
+
| `runner.getTick()` | `session.state().session.completedTicks` |
|
|
457
|
+
| `runner.complete()`, and `tick()` throwing | `session.state().session.complete`, and a refused tick |
|
|
458
|
+
| `runner.init()` to start again | `session.send({ type: 'reinitialise' })`, or `undo` for one tick |
|
|
459
|
+
| `persist: true` | Not offered: a test session never touches `.seriousgaming-sdk/` |
|
|
460
|
+
|
|
461
|
+
Two behaviours differ, both towards what the dev server does. A tick past the limit comes back as `{ ok: false }` rather than throwing, and a multi-team game runs in its declared interaction mode, where `MultiTeamGameRunner` always ran shared-state.
|
|
462
|
+
|
|
463
|
+
## Best practices
|
|
464
|
+
|
|
465
|
+
1. **Leave the services stubbed unless a test is about the model**: the stubs keep tests fast, deterministic, and runnable in CI without an API key. Override `generateText` when a test needs a particular answer
|
|
466
|
+
2. **Start a fresh session in `beforeEach`, and close it in `afterEach`**: each test starts clean, and no timer outlives it
|
|
467
|
+
3. **Assert on computed values, not just the value you set**: the point of an engine test is that the game processes inputs correctly
|
|
468
|
+
4. **Write a decision as a participant when a validator should judge it**: `setData` does by default; `as: 'system'` is the facilitator's and skips validators
|
|
469
|
+
5. **Pin `scenarioId` when the game declares scenarios**: explicit pinning (or `null` for the settings defaults) keeps assertions stable when the default scenario changes
|
|
470
|
+
6. **Run engine tests before Playwright**: if an engine test fails, there is no point running browser tests for the same scenario
|
|
471
|
+
|
|
472
|
+
## Related
|
|
473
|
+
|
|
474
|
+
- [Engine testing recipe](../recipes/test-with-engine.md) - step-by-step setup for an existing project
|
|
475
|
+
- [Testing guide](./testing.md) - unit tests for the engine and React components
|
|
476
|
+
- [E2E testing with Playwright](../recipes/e2e-testing.md) - browser-based UI tests
|
|
477
|
+
- [Drive a game from the CLI](../cli-simulation-testing.md) - Test through `sdk tick` and `sdk view` against a live session
|