@moku-labs/game 0.0.1 → 0.0.3
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/README.md +495 -40
- package/dist/assets.d.mts +213 -0
- package/dist/assets.mjs +6125 -0
- package/dist/commands-BOxqlv7w.d.mts +99 -0
- package/dist/component-DGg5DqKK.mjs +44 -0
- package/dist/control.d.mts +73 -0
- package/dist/control.mjs +4 -0
- package/dist/define-BMo0pHe_.d.mts +51 -0
- package/dist/dev-CcwFrwok.mjs +413 -0
- package/dist/index.d.mts +1396 -54
- package/dist/index.mjs +26433 -2013
- package/dist/inspect.d.mts +111 -0
- package/dist/inspect.mjs +3 -0
- package/dist/jsx-dev-runtime.d.mts +2 -0
- package/dist/jsx-dev-runtime.mjs +2 -0
- package/dist/jsx-runtime.d.mts +2 -0
- package/dist/jsx-runtime.mjs +2 -0
- package/dist/memory-CvgdnsQO.mjs +259 -0
- package/dist/{rolldown-runtime-D7D4PA-g.mjs → rolldown-runtime-8H4AJuhK.mjs} +1 -0
- package/dist/run-tlcn1w7y.mjs +944 -0
- package/dist/runtime-BstIkrhH.d.mts +77 -0
- package/dist/runtime-DRlwxkIv.mjs +182 -0
- package/dist/session-DmAxY6Ll.mjs +145 -0
- package/dist/sources-BooL2MsX.mjs +545 -0
- package/dist/testing.d.mts +302 -88
- package/dist/testing.mjs +2005 -228
- package/dist/types-BTycq8_Z.d.mts +1196 -0
- package/dist/types-C0SLQsx0.d.mts +2058 -0
- package/dist/types-Cjf3ywai.d.mts +5982 -0
- package/dist/types-D0uYTTC3.d.mts +392 -0
- package/dist/types-DgwbzDLP.d.mts +690 -0
- package/dist/types-KlE_GL1z.d.mts +1444 -0
- package/dist/visual-NPrDkvUk.mjs +740 -0
- package/package.json +42 -5
- package/dist/registry-DWV5C0Mf.mjs +0 -666
- package/dist/types-BfsmUzLC.d.mts +0 -1908
package/dist/testing.d.mts
CHANGED
|
@@ -1,161 +1,375 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { At as Json, Dt as Api$2, Ft as PlayerStateProvider, It as ProviderCall, Lt as SaveDoc, Ut as Api$3, Zt as FakeClock, h as Api$1, t as Api } from "./types-Cjf3ywai.mjs";
|
|
2
|
+
import { _ as stepFrames, a as InputOf, d as HeadlessApp, f as HeadlessGame, g as runRepro, h as createHeadless, m as ReproResult, p as Repro, r as ControlApp } from "./types-D0uYTTC3.mjs";
|
|
3
|
+
import { H as InputApi } from "./types-C0SLQsx0.mjs";
|
|
4
|
+
import { b as UiApi, t as AnimApi } from "./types-KlE_GL1z.mjs";
|
|
5
|
+
import { t as commands } from "./commands-BOxqlv7w.mjs";
|
|
2
6
|
|
|
3
7
|
//#region src/plugins/clock/fake.d.ts
|
|
4
8
|
/**
|
|
5
|
-
* Creates a fake source for tests. Timers fire synchronously inside `advance`, in due order.
|
|
9
|
+
* Creates a fake source for tests. Timers fire synchronously inside `advance`, in due order. A
|
|
10
|
+
* negative delay counts as zero and an unknown handle is ignored.
|
|
6
11
|
*
|
|
7
12
|
* @param start - Starting moment in epoch milliseconds. Defaults to 0.
|
|
8
13
|
* @returns A clock source with the test-only controls `advance` and `set`.
|
|
9
14
|
* @example
|
|
10
15
|
* ```ts
|
|
16
|
+
* // An energy point refills 60 s after it was spent. The test moves the clock, not the minute.
|
|
11
17
|
* const clock = fakeClock(1000);
|
|
12
|
-
* clock
|
|
18
|
+
* const app = createApp({ pluginConfigs: { clock: { source: clock } } });
|
|
19
|
+
* const seen: number[] = [];
|
|
20
|
+
*
|
|
21
|
+
* app.clock.onElapsed(({ now }) => seen.push(now));
|
|
22
|
+
* app.clock.scheduleAt(61_000);
|
|
23
|
+
* clock.advance(60_000); // the timer fires inside advance, nothing waits
|
|
24
|
+
* seen; // [61000]
|
|
13
25
|
* ```
|
|
14
26
|
*/
|
|
15
27
|
declare function fakeClock(start?: number): FakeClock;
|
|
16
28
|
//#endregion
|
|
17
|
-
//#region src/plugins/
|
|
29
|
+
//#region src/plugins/model/store/providers/memory.d.ts
|
|
18
30
|
/**
|
|
19
|
-
*
|
|
20
|
-
* default
|
|
31
|
+
* Creates an in-memory provider that keeps what it was committed and records every call.
|
|
32
|
+
* It is the default when a game configures no `playerProvider`, and the double every test uses:
|
|
33
|
+
* `provider.calls` is the whole persistence protocol of one run, in order. The document lives as
|
|
34
|
+
* long as the provider does, so a second app over the same instance reads what the first one
|
|
35
|
+
* wrote, and nothing survives the process.
|
|
21
36
|
*
|
|
37
|
+
* `load()` reads the fixture, or everything committed since; without either, the player is new.
|
|
38
|
+
* `commitDurable()` resolves at once: there is no disk behind it.
|
|
39
|
+
*
|
|
40
|
+
* @param fixture - The save `load()` starts from. Omitted: a new player.
|
|
41
|
+
* @param fixture.state - The saved document.
|
|
42
|
+
* @param fixture.version - Schema version of the saved document.
|
|
43
|
+
* @returns A provider that keeps its document and records its calls.
|
|
22
44
|
* @example
|
|
23
45
|
* ```ts
|
|
24
|
-
*
|
|
46
|
+
* // A new player is saved at once. One roll reaches the provider at the next rest node.
|
|
47
|
+
* const provider = memory();
|
|
48
|
+
* const game = await createHeadless(createGame(provider));
|
|
49
|
+
*
|
|
50
|
+
* await game.walk([{ at: "home", intent: "roll" }]);
|
|
51
|
+
* provider.calls.map(call => call.method); // ["load", "commit", "commit"]
|
|
25
52
|
* ```
|
|
26
53
|
*/
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
54
|
+
declare function memory(fixture?: {
|
|
55
|
+
state: SaveDoc;
|
|
56
|
+
version: number;
|
|
57
|
+
}): PlayerStateProvider & {
|
|
58
|
+
calls: ProviderCall[];
|
|
32
59
|
};
|
|
33
60
|
/**
|
|
34
|
-
*
|
|
61
|
+
* Builds a save document from a player tree, with no rng streams drawn yet.
|
|
62
|
+
* Short fixtures in tests read as `saveOf({ coins: 5 })` instead of spelling out the rng branch.
|
|
63
|
+
*
|
|
64
|
+
* @param player - Player tree of the save.
|
|
65
|
+
* @param seed - Rng seed of the save.
|
|
66
|
+
* @returns The save document.
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* // A returning player with 5 coins. The test starts from this save, not from `initialPlayer`.
|
|
70
|
+
* saveOf({ coins: 5 }, 42); // { player: { coins: 5 }, rng: { seed: 42, streams: {} } }
|
|
71
|
+
* const provider = memory({ state: saveOf({ coins: 5 }, 42), version: 1 });
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
declare function saveOf(player: Json, seed?: number): SaveDoc;
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/visual/types.d.ts
|
|
77
|
+
/** The base commands of the `/control` door, keyed by their short names. */
|
|
78
|
+
type Catalogue = typeof commands;
|
|
79
|
+
/**
|
|
80
|
+
* The commands a step may name: the catalogue of `/control` minus `bookmark` (it only reads),
|
|
81
|
+
* `capture` (the runner captures by itself) and `debug` (debug drawing would enter a baseline). A
|
|
82
|
+
* command the catalogue gains is a step with no change to the runner.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* const name: StepCommand = "tap"; // also "answer", "walk", "step", "reducedMotion", ...
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
type StepCommand = Exclude<keyof Catalogue, "bookmark" | "capture" | "debug">;
|
|
90
|
+
/**
|
|
91
|
+
* Where a visual test starts: what `game.restore` loads, a repro without its route. The player
|
|
92
|
+
* is required; the session, the rng and the checkpoint are optional, as in a repro.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts
|
|
96
|
+
* // A save with a plank ready for the first order, entered at Home.
|
|
97
|
+
* const start: VisualStart = { player: { coins: 40, orders: [] }, checkpoint: "home" };
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
type VisualStart = Omit<Repro, "route">;
|
|
101
|
+
/**
|
|
102
|
+
* One step of a visual test: a `/control` command by its short name with that command's input,
|
|
103
|
+
* typed from the catalogue, or a checkpoint by its name.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* // Tap Play, answer the order on the board, then compare the screen with its baseline.
|
|
108
|
+
* const steps: VisualStep[] = [
|
|
109
|
+
* { tap: { key: "play" } },
|
|
110
|
+
* { answer: { intent: "deliver", payload: { orderId: "o1" } } },
|
|
111
|
+
* { checkpoint: "open" }
|
|
112
|
+
* ];
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
type VisualStep = {
|
|
116
|
+
checkpoint: string;
|
|
117
|
+
} | { [K in StepCommand]: { [P in K]: InputOf<Catalogue[K]["input"]> } }[StepCommand];
|
|
118
|
+
/**
|
|
119
|
+
* A visual test as `defineVisualTest` answers it: its name, where it starts and its steps.
|
|
120
|
+
* Frozen data; nothing runs at definition.
|
|
35
121
|
*
|
|
36
122
|
* @example
|
|
37
123
|
* ```ts
|
|
38
|
-
* const
|
|
39
|
-
*
|
|
124
|
+
* const test: VisualTest = defineVisualTest("reward-popup", {
|
|
125
|
+
* start: { player: { coins: 40 }, checkpoint: "home" },
|
|
126
|
+
* steps: [{ tap: { key: "play" } }, { checkpoint: "board" }]
|
|
127
|
+
* });
|
|
128
|
+
* test.name; // "reward-popup"
|
|
129
|
+
* Object.isFrozen(test.steps); // true
|
|
40
130
|
* ```
|
|
41
131
|
*/
|
|
42
|
-
type
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
history(): readonly JournalEntry[];
|
|
47
|
-
stop(): Promise<void>;
|
|
132
|
+
type VisualTest = {
|
|
133
|
+
readonly name: string;
|
|
134
|
+
readonly start: Readonly<VisualStart>;
|
|
135
|
+
readonly steps: readonly VisualStep[];
|
|
48
136
|
};
|
|
49
137
|
/**
|
|
50
|
-
*
|
|
138
|
+
* What the runner needs of an app: the control app, the plugins the base commands act on and
|
|
139
|
+
* the plugins the readers read. Every app composed with the screen set fits.
|
|
51
140
|
*
|
|
52
141
|
* @example
|
|
53
142
|
* ```ts
|
|
54
|
-
* const
|
|
143
|
+
* const app: VisualApp = createApp({ plugins: [...screen, boardFeature], pluginConfigs });
|
|
55
144
|
* ```
|
|
56
145
|
*/
|
|
57
|
-
type
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
146
|
+
type VisualApp = ControlApp & {
|
|
147
|
+
readonly anim: AnimApi;
|
|
148
|
+
readonly ui: UiApi;
|
|
149
|
+
readonly renderer: Api;
|
|
150
|
+
readonly world: Api$1;
|
|
151
|
+
readonly model: Api$2;
|
|
152
|
+
readonly input: InputApi;
|
|
153
|
+
readonly lifecycle: Api$3;
|
|
63
154
|
};
|
|
64
155
|
/**
|
|
65
|
-
*
|
|
156
|
+
* The dev page the browser leg opens: its URL, the viewport width in CSS px (390 by default), the
|
|
157
|
+
* device scale (2 by default) and which Chrome to launch. The page height follows the inert frame
|
|
158
|
+
* of the renderer: 520 for a 390 wide portrait game at the default 4:3. Without `browser` the leg
|
|
159
|
+
* launches Playwright's own Chromium, and the system Chrome when that one is not installed.
|
|
66
160
|
*
|
|
67
161
|
* @example
|
|
68
162
|
* ```ts
|
|
69
|
-
*
|
|
163
|
+
* // The fixture's dev server, a 390 px wide phone at 2x, the system Chrome.
|
|
164
|
+
* const page: VisualPage = { url: "http://localhost:3000/", browser: { channel: "chrome" } };
|
|
70
165
|
* ```
|
|
71
166
|
*/
|
|
72
|
-
type
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
167
|
+
type VisualPage = {
|
|
168
|
+
url: string;
|
|
169
|
+
width?: number;
|
|
170
|
+
deviceScaleFactor?: number;
|
|
171
|
+
browser?: {
|
|
172
|
+
channel?: string;
|
|
173
|
+
executablePath?: string;
|
|
174
|
+
};
|
|
76
175
|
};
|
|
77
176
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* `game.state().path` therefore names the first rest node of the graph as soon as the call
|
|
81
|
-
* resolves. A fatal error of the loop rejects this call, and a later one is re-thrown by `walk`
|
|
82
|
-
* and by `stop`, so a headless test never loses it.
|
|
177
|
+
* How the runner builds a game: a fresh app per test, not started, with the screen set
|
|
178
|
+
* composed; the renderer stays inert in Bun. `page` is the dev page of the pixel leg.
|
|
83
179
|
*
|
|
84
|
-
* @param app - An app that is not started yet.
|
|
85
|
-
* @returns The game: walk it, answer it, read it, stop it.
|
|
86
|
-
* @throws {Error} When the loop failed fatally before it reached its first rest node.
|
|
87
180
|
* @example
|
|
88
181
|
* ```ts
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* await game.walk([{ at: "board/awaitIntent", intent: "merge", payload: { from: "c2", to: "c3" } }]);
|
|
92
|
-
* await game.stop();
|
|
182
|
+
* // tests/visual/run.ts of a game.
|
|
183
|
+
* const setup: VisualSetup = { app: () => createScreenGame().app, page: { url: "http://localhost:3000/" } };
|
|
93
184
|
* ```
|
|
94
185
|
*/
|
|
95
|
-
|
|
186
|
+
type VisualSetup = {
|
|
187
|
+
app: () => VisualApp;
|
|
188
|
+
page?: VisualPage;
|
|
189
|
+
};
|
|
96
190
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* result and stops it itself.
|
|
191
|
+
* How far a pixel may differ: `threshold` is the largest channel delta that still counts as the
|
|
192
|
+
* same pixel, `ratio` the share of differing pixels a checkpoint tolerates.
|
|
100
193
|
*
|
|
101
|
-
* @param app - An app that is not started yet.
|
|
102
|
-
* @param repro - Starting state, optional checkpoint and the route.
|
|
103
|
-
* @returns The path the run ended at, and the committed state.
|
|
104
194
|
* @example
|
|
105
195
|
* ```ts
|
|
106
|
-
* const
|
|
196
|
+
* const tolerance: VisualTolerance = { ratio: 0.001, threshold: 24 }; // the defaults
|
|
107
197
|
* ```
|
|
108
198
|
*/
|
|
109
|
-
|
|
199
|
+
type VisualTolerance = {
|
|
200
|
+
ratio: number;
|
|
201
|
+
threshold: number;
|
|
202
|
+
};
|
|
110
203
|
/**
|
|
111
|
-
*
|
|
112
|
-
*
|
|
204
|
+
* The options of a run. Each one left out comes from the command line (`argv`), then from its
|
|
205
|
+
* default: `dir` `"tests/visual"`, `update` false, `pixels` true with a `page` on a Mac,
|
|
206
|
+
* `settleFrames` 600, `tolerance` `{ ratio: 0.001, threshold: 24 }`, `argv`
|
|
207
|
+
* `process.argv.slice(2)`. An option given here wins over a flag.
|
|
113
208
|
*
|
|
114
|
-
* @param app - A started app.
|
|
115
|
-
* @param count - Number of frames.
|
|
116
|
-
* @param deltaMs - Milliseconds per frame.
|
|
117
209
|
* @example
|
|
118
210
|
* ```ts
|
|
119
|
-
*
|
|
211
|
+
* // The same as `bun tests/visual/run.ts --only reward-popup --no-pixels`.
|
|
212
|
+
* const options: VisualOptions = { only: ["reward-popup"], pixels: false };
|
|
120
213
|
* ```
|
|
121
214
|
*/
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
215
|
+
type VisualOptions = {
|
|
216
|
+
dir?: string;
|
|
217
|
+
update?: boolean;
|
|
218
|
+
pixels?: boolean;
|
|
219
|
+
only?: readonly string[];
|
|
220
|
+
settleFrames?: number;
|
|
221
|
+
tolerance?: VisualTolerance;
|
|
222
|
+
argv?: readonly string[];
|
|
223
|
+
};
|
|
125
224
|
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* `provider.calls` is the whole persistence protocol of one run, in order. The document lives as
|
|
129
|
-
* long as the provider does, so a second app over the same instance reads what the first one
|
|
130
|
-
* wrote, and nothing survives the process.
|
|
225
|
+
* What a comparison with a baseline file found: the same content, a different one, or no
|
|
226
|
+
* baseline (or `--update`), so the file was written.
|
|
131
227
|
*
|
|
132
|
-
* @param fixture - The save `load()` starts from. Omitted: a new player.
|
|
133
|
-
* @param fixture.state - The saved document.
|
|
134
|
-
* @param fixture.version - Schema version of the saved document.
|
|
135
|
-
* @returns A provider that keeps its document and records its calls.
|
|
136
228
|
* @example
|
|
137
229
|
* ```ts
|
|
138
|
-
* const
|
|
230
|
+
* const outcome: Outcome = "written"; // the first run of a new checkpoint
|
|
139
231
|
* ```
|
|
140
232
|
*/
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
233
|
+
type Outcome = "same" | "different" | "written";
|
|
234
|
+
/**
|
|
235
|
+
* The outcome of one checkpoint. `first` is the JSON path of the first difference, the state's
|
|
236
|
+
* before the describe's. `pixels` is `"skipped"` when the pixel leg did not run; `pixelRatio`
|
|
237
|
+
* and `verdict` come with a pixel difference.
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```ts
|
|
241
|
+
* // The player's coins differ from the baseline; the layout is the same.
|
|
242
|
+
* const result: CheckpointResult = {
|
|
243
|
+
* name: "claimed", state: "different", describe: "same", pixels: "skipped", first: "player.coins"
|
|
244
|
+
* };
|
|
245
|
+
* ```
|
|
246
|
+
*/
|
|
247
|
+
type CheckpointResult = {
|
|
248
|
+
name: string;
|
|
249
|
+
state: Outcome;
|
|
250
|
+
describe: Outcome;
|
|
251
|
+
pixels: Outcome | "skipped";
|
|
252
|
+
pixelRatio?: number;
|
|
253
|
+
first?: string;
|
|
254
|
+
verdict?: "rendering" | "behaviour";
|
|
146
255
|
};
|
|
147
256
|
/**
|
|
148
|
-
*
|
|
149
|
-
* Short fixtures in tests read as `saveOf({ coins: 5 })` instead of spelling out the rng branch.
|
|
257
|
+
* The outcome of one visual test: every checkpoint it reached, and the error that ended it early.
|
|
150
258
|
*
|
|
151
|
-
* @param player - Player tree of the save.
|
|
152
|
-
* @param seed - Rng seed of the save.
|
|
153
|
-
* @returns The save document.
|
|
154
259
|
* @example
|
|
155
260
|
* ```ts
|
|
156
|
-
* const
|
|
261
|
+
* const result: VisualTestResult = {
|
|
262
|
+
* name: "reward-popup",
|
|
263
|
+
* checkpoints: [{ name: "open", state: "same", describe: "same", pixels: "skipped" }]
|
|
264
|
+
* };
|
|
157
265
|
* ```
|
|
158
266
|
*/
|
|
159
|
-
|
|
267
|
+
type VisualTestResult = {
|
|
268
|
+
name: string;
|
|
269
|
+
checkpoints: readonly CheckpointResult[];
|
|
270
|
+
error?: string;
|
|
271
|
+
};
|
|
272
|
+
/**
|
|
273
|
+
* What `runVisualTests` answers: `ok` is true when no test failed and no outcome is
|
|
274
|
+
* `"different"`.
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```ts
|
|
278
|
+
* const report: VisualReport = await runVisualTests(setup, [rewardPopup], { argv: [] });
|
|
279
|
+
* process.exitCode = report.ok ? 0 : 1;
|
|
280
|
+
* ```
|
|
281
|
+
*/
|
|
282
|
+
type VisualReport = {
|
|
283
|
+
ok: boolean;
|
|
284
|
+
tests: readonly VisualTestResult[];
|
|
285
|
+
};
|
|
286
|
+
/** What the command line asked for: the flags it carried, nothing else. */
|
|
287
|
+
type VisualFlags = {
|
|
288
|
+
dir?: string;
|
|
289
|
+
update?: boolean;
|
|
290
|
+
pixels?: boolean;
|
|
291
|
+
only?: readonly string[];
|
|
292
|
+
};
|
|
293
|
+
//#endregion
|
|
294
|
+
//#region src/visual/define.d.ts
|
|
295
|
+
/**
|
|
296
|
+
* Defines a visual test: where it starts, what `game.restore` loads, and its steps. A step is a
|
|
297
|
+
* `/control` command by its short name with that command's input, or `{ checkpoint: name }`,
|
|
298
|
+
* where the runner saves `state.json` and `describe.json` next to the test. `walk` runs fast: a
|
|
299
|
+
* popup its last node opens never mounts, so the step before a checkpoint that shows a node's
|
|
300
|
+
* result is `answer` or `tap`. The answer is a frozen copy; nothing runs here.
|
|
301
|
+
*
|
|
302
|
+
* @param name - The test name, also its folder: lowercase letters, digits and dashes.
|
|
303
|
+
* @param test - Where it starts and its steps.
|
|
304
|
+
* @param test.start - The player, and optionally the session, the rng and the checkpoint.
|
|
305
|
+
* @param test.steps - The commands and the checkpoints, in order.
|
|
306
|
+
* @returns The test, frozen.
|
|
307
|
+
* @throws {Error} When the name or a checkpoint name is not a file name, a checkpoint name is
|
|
308
|
+
* used twice, or a step is not one step command.
|
|
309
|
+
* @example
|
|
310
|
+
* ```ts
|
|
311
|
+
* // tests/visual/reward-popup.visual.ts: Play, deliver the first order, claim the reward.
|
|
312
|
+
* export const rewardPopup = defineVisualTest("reward-popup", {
|
|
313
|
+
* start: { player: fixtures.ready, checkpoint: "home" },
|
|
314
|
+
* steps: [{ tap: { key: "play" } }, { answer: { intent: "deliver", payload: { orderId: "o1" } } },
|
|
315
|
+
* { checkpoint: "open" }, { tap: { key: "claim" } }, { checkpoint: "claimed" }]
|
|
316
|
+
* });
|
|
317
|
+
* ```
|
|
318
|
+
*/
|
|
319
|
+
declare function defineVisualTest(name: string, test: {
|
|
320
|
+
start: VisualStart;
|
|
321
|
+
steps: readonly VisualStep[];
|
|
322
|
+
}): VisualTest;
|
|
323
|
+
//#endregion
|
|
324
|
+
//#region src/visual/run.d.ts
|
|
325
|
+
/**
|
|
326
|
+
* Reads the flags of the visual tests from a command line: `--update`, `--no-pixels`,
|
|
327
|
+
* `--only <name>` (repeatable) and `--dir <path>`. An argument it does not know is left to the
|
|
328
|
+
* script that got it, so a test runner's own arguments pass through.
|
|
329
|
+
*
|
|
330
|
+
* @param argv - The arguments after the script name.
|
|
331
|
+
* @returns The flags that were given, and nothing else.
|
|
332
|
+
* @throws {Error} When `--only` or `--dir` has no value.
|
|
333
|
+
* @example
|
|
334
|
+
* ```ts
|
|
335
|
+
* // `bun tests/visual/run.ts --update --only reward-popup`
|
|
336
|
+
* parseVisualArgv(["--update", "--only", "reward-popup"]); // { update: true, only: ["reward-popup"] }
|
|
337
|
+
* parseVisualArgv(["--no-pixels", "--dir", "shots"]); // { pixels: false, dir: "shots" }
|
|
338
|
+
* ```
|
|
339
|
+
*/
|
|
340
|
+
declare function parseVisualArgv(argv: readonly string[]): VisualFlags;
|
|
341
|
+
/**
|
|
342
|
+
* Runs visual tests: the headless leg over every selected test, then the browser leg over the
|
|
343
|
+
* same tests when `pixels` is on (by default only with a `page` on a Mac). A checkpoint saves
|
|
344
|
+
* `state.json` and `describe.json` in `<dir>/<test>/<checkpoint>/`, and the browser leg
|
|
345
|
+
* `screen.webp`, lossless: a missing file is written, `--update` rewrites every file of the tests
|
|
346
|
+
* run, a JSON file is compared exactly. The browser leg opens the dev page in Chrome with WebGPU
|
|
347
|
+
* through `playwright-core`, pauses its game, plays each test there on stepped frames of 1000/60
|
|
348
|
+
* ms, so two runs see the same game time, checks the page's state against `state.json` and
|
|
349
|
+
* compares the picture in the page: a pixel differs above `tolerance.threshold`, and the screen
|
|
350
|
+
* differs when more than `tolerance.ratio` of its pixels do; `screen.actual.webp` and
|
|
351
|
+
* `screen.diff.webp` are written beside the baseline then. A pixel difference over the same state
|
|
352
|
+
* and describe is a rendering regression (`verdict: "rendering"`). One line per checkpoint goes
|
|
353
|
+
* to the branded console; the script sets the exit code from `ok`.
|
|
354
|
+
*
|
|
355
|
+
* @param setup - How to build a game: a fresh, unstarted app per test, and the dev page.
|
|
356
|
+
* @param tests - Every visual test of the game.
|
|
357
|
+
* @param options - Overrides of the flags of `argv` and of the defaults.
|
|
358
|
+
* @returns The report: `ok` when no test failed and nothing is `"different"`.
|
|
359
|
+
* @throws {Error} When a test is not well formed, two tests share a name, `only` names no test,
|
|
360
|
+
* a flag misses its value, or the browser leg fails as a whole: no `page`, no
|
|
361
|
+
* `playwright-core`, no Chrome, or a page that does not open, expose `game` and `doors`, start,
|
|
362
|
+
* draw with WebGPU or stop loading.
|
|
363
|
+
* @example
|
|
364
|
+
* ```ts
|
|
365
|
+
* // tests/visual/run.ts of a game, `bun tests/visual/run.ts --update` on a Mac
|
|
366
|
+
* const report = await runVisualTests(
|
|
367
|
+
* { app: () => createScreenGame().app, page: { url: "http://localhost:3000/" } },
|
|
368
|
+
* [rewardPopup]
|
|
369
|
+
* );
|
|
370
|
+
* report.tests[0]?.checkpoints[0]; // { name: "open", state: "written", describe: "written", pixels: "written" }
|
|
371
|
+
* ```
|
|
372
|
+
*/
|
|
373
|
+
declare function runVisualTests(setup: VisualSetup, tests: readonly VisualTest[], options?: VisualOptions): Promise<VisualReport>;
|
|
160
374
|
//#endregion
|
|
161
|
-
export { type HeadlessApp, type HeadlessGame, type Repro, type ReproResult, createHeadless, fakeClock, memory, runRepro, saveOf, stepFrames };
|
|
375
|
+
export { type CheckpointResult, type HeadlessApp, type HeadlessGame, type Repro, type ReproResult, type VisualApp, type VisualOptions, type VisualPage, type VisualReport, type VisualSetup, type VisualStart, type VisualStep, type VisualTest, type VisualTestResult, type VisualTolerance, createHeadless, defineVisualTest, fakeClock, memory, parseVisualArgv, runRepro, runVisualTests, saveOf, stepFrames };
|