@dreamboard-games/ui-sdk 0.0.43 → 0.0.45
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/dist/components/ActionButton.d.ts.map +1 -1
- package/dist/components/ActionButton.js +2 -1
- package/dist/components/Card.d.ts +1 -1
- package/dist/components/Card.d.ts.map +1 -1
- package/dist/components/DiceRoller.d.ts +3 -2
- package/dist/components/DiceRoller.d.ts.map +1 -1
- package/dist/components/DiceRoller.js +4 -13
- package/dist/components/ErrorBoundary.d.ts.map +1 -1
- package/dist/components/ErrorBoundary.js +94 -2
- package/dist/components/InteractionForm.d.ts +1 -1
- package/dist/components/InteractionForm.d.ts.map +1 -1
- package/dist/components/InteractionForm.js +29 -15
- package/dist/components/PrimaryActionButton.d.ts.map +1 -1
- package/dist/components/PrimaryActionButton.js +7 -6
- package/dist/components/ResourceCounter.d.ts +59 -25
- package/dist/components/ResourceCounter.d.ts.map +1 -1
- package/dist/components/ResourceCounter.js +106 -115
- package/dist/components/Toast.d.ts +13 -6
- package/dist/components/Toast.d.ts.map +1 -1
- package/dist/components/Toast.js +10 -5
- package/dist/components/board/HexGrid.js +6 -6
- package/dist/components/board/target-layer.d.ts +18 -2
- package/dist/components/board/target-layer.d.ts.map +1 -1
- package/dist/components/board/target-layer.js +20 -3
- package/dist/components/index.d.ts +3 -4
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +3 -4
- package/dist/components/surfaces/InboxSurface.d.ts.map +1 -1
- package/dist/components/surfaces/InboxSurface.js +2 -6
- package/dist/components/surfaces/PlayerCardsSurface.js +2 -2
- package/dist/components/surfaces/internal/CardZoneRoutedForm.d.ts +7 -0
- package/dist/components/surfaces/internal/CardZoneRoutedForm.d.ts.map +1 -0
- package/dist/components/surfaces/internal/CardZoneRoutedForm.js +9 -0
- package/dist/components/surfaces/internal/DefaultInteractionButton.d.ts.map +1 -1
- package/dist/components/surfaces/internal/DefaultInteractionButton.js +5 -8
- package/dist/components/surfaces/internal/useCardZoneInteractions.d.ts +2 -2
- package/dist/components/surfaces/internal/useCardZoneInteractions.d.ts.map +1 -1
- package/dist/components/surfaces/internal/useCardZoneInteractions.js +19 -43
- package/dist/context/InteractionDraftContext.d.ts +11 -2
- package/dist/context/InteractionDraftContext.d.ts.map +1 -1
- package/dist/context/InteractionDraftContext.js +41 -4
- package/dist/defaults/components.d.ts +0 -5
- package/dist/defaults/components.d.ts.map +1 -1
- package/dist/defaults/components.js +7 -11
- package/dist/hooks/useBoardInteractions.d.ts +35 -12
- package/dist/hooks/useBoardInteractions.d.ts.map +1 -1
- package/dist/hooks/useBoardInteractions.js +186 -82
- package/dist/hooks/useInteractionHandle.d.ts +1 -1
- package/dist/hooks/useInteractionHandle.d.ts.map +1 -1
- package/dist/hooks/useInteractionHandle.js +12 -27
- package/dist/index.d.ts +11 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -14
- package/dist/primitives/board.d.ts +53 -3
- package/dist/primitives/board.d.ts.map +1 -1
- package/dist/primitives/board.js +65 -41
- package/dist/primitives/dialog-lifecycle.d.ts +17 -0
- package/dist/primitives/dialog-lifecycle.d.ts.map +1 -0
- package/dist/primitives/dialog-lifecycle.js +24 -0
- package/dist/primitives/dice.d.ts +31 -0
- package/dist/primitives/dice.d.ts.map +1 -0
- package/dist/primitives/dice.js +33 -0
- package/dist/primitives/game.d.ts +55 -0
- package/dist/primitives/game.d.ts.map +1 -0
- package/dist/primitives/game.js +101 -0
- package/dist/primitives/index.d.ts +7 -4
- package/dist/primitives/index.d.ts.map +1 -1
- package/dist/primitives/index.js +7 -4
- package/dist/primitives/interaction-form-binding.d.ts +12 -0
- package/dist/primitives/interaction-form-binding.d.ts.map +1 -0
- package/dist/primitives/interaction-form-binding.js +14 -0
- package/dist/primitives/interaction-submit.d.ts +23 -0
- package/dist/primitives/interaction-submit.d.ts.map +1 -0
- package/dist/primitives/interaction-submit.js +41 -0
- package/dist/primitives/interaction.d.ts +76 -6
- package/dist/primitives/interaction.d.ts.map +1 -1
- package/dist/primitives/interaction.js +210 -26
- package/dist/primitives/player-roster.d.ts +2 -1
- package/dist/primitives/player-roster.d.ts.map +1 -1
- package/dist/primitives/prompt.d.ts +36 -11
- package/dist/primitives/prompt.d.ts.map +1 -1
- package/dist/primitives/prompt.js +29 -17
- package/dist/primitives/ui.d.ts +9 -0
- package/dist/primitives/ui.d.ts.map +1 -0
- package/dist/primitives/ui.js +7 -0
- package/dist/primitives/zone.d.ts +111 -5
- package/dist/primitives/zone.d.ts.map +1 -1
- package/dist/primitives/zone.js +349 -9
- package/dist/reducer.d.ts +2 -14
- package/dist/reducer.d.ts.map +1 -1
- package/dist/reducer.js +1 -14
- package/dist/runtime/createPluginRuntimeAPI.js +1 -1
- package/dist/types/hex-color.d.ts +7 -0
- package/dist/types/hex-color.d.ts.map +1 -0
- package/dist/types/hex-color.js +13 -0
- package/dist/types/player-state.d.ts +28 -14
- package/dist/types/player-state.d.ts.map +1 -1
- package/dist/types/plugin-state.d.ts +9 -3
- package/dist/types/plugin-state.d.ts.map +1 -1
- package/dist/ui-contract.d.ts +119 -14
- package/dist/ui-contract.d.ts.map +1 -1
- package/dist/ui-contract.js +4 -3
- package/dist/ui-sdk.d.ts +1637 -1245
- package/dist/utils/interaction-inputs.d.ts +8 -5
- package/dist/utils/interaction-inputs.d.ts.map +1 -1
- package/dist/utils/interaction-inputs.js +82 -14
- package/dist/utils/interaction-router.d.ts +31 -0
- package/dist/utils/interaction-router.d.ts.map +1 -0
- package/dist/utils/interaction-router.js +114 -0
- package/package.json +1 -1
- package/src/components/ActionButton.tsx +2 -1
- package/src/components/Card.tsx +1 -1
- package/src/components/DiceRoller.tsx +13 -22
- package/src/components/ErrorBoundary.test.tsx +19 -0
- package/src/components/ErrorBoundary.tsx +113 -24
- package/src/components/InteractionForm.test.tsx +24 -0
- package/src/components/InteractionForm.tsx +48 -23
- package/src/components/PrimaryActionButton.tsx +19 -5
- package/src/components/ResourceCounter.test.tsx +13 -13
- package/src/components/ResourceCounter.tsx +238 -244
- package/src/components/Toast.tsx +23 -10
- package/src/components/__fixtures__/ResourceCounter.fixture.tsx +70 -169
- package/src/components/board/HexGrid.tsx +6 -6
- package/src/components/board/target-layer.ts +44 -5
- package/src/components/index.ts +17 -10
- package/src/components/surfaces/InboxSurface.tsx +7 -5
- package/src/components/surfaces/PlayerCardsSurface.tsx +6 -6
- package/src/components/surfaces/internal/CardZoneRoutedForm.tsx +35 -0
- package/src/components/surfaces/internal/DefaultInteractionButton.tsx +17 -7
- package/src/components/surfaces/internal/useCardZoneInteractions.ts +25 -67
- package/src/context/InteractionDraftContext.tsx +51 -5
- package/src/defaults/components.tsx +12 -50
- package/src/defaults/defaults.test.tsx +1 -50
- package/src/hooks/useBoardInteractions.test.tsx +240 -17
- package/src/hooks/useBoardInteractions.ts +330 -105
- package/src/hooks/useInteractionHandle.ts +23 -28
- package/src/index.test.ts +60 -40
- package/src/index.ts +30 -36
- package/src/primitives/board.test.tsx +73 -0
- package/src/primitives/board.tsx +191 -40
- package/src/primitives/dialog-lifecycle.ts +58 -0
- package/src/primitives/dice.test.tsx +47 -0
- package/src/primitives/dice.tsx +79 -0
- package/src/primitives/game.test.tsx +98 -0
- package/src/primitives/game.tsx +213 -0
- package/src/primitives/index.ts +84 -0
- package/src/primitives/interaction-form-binding.tsx +56 -0
- package/src/primitives/interaction-submit.ts +90 -0
- package/src/primitives/interaction.test.tsx +396 -0
- package/src/primitives/interaction.tsx +451 -31
- package/src/primitives/player-roster.tsx +2 -1
- package/src/primitives/prompt.test.tsx +94 -3
- package/src/primitives/prompt.tsx +87 -48
- package/src/primitives/ui.test.tsx +131 -0
- package/src/primitives/ui.tsx +13 -0
- package/src/primitives/zone.test.tsx +305 -0
- package/src/primitives/zone.tsx +660 -12
- package/src/reducer.ts +7 -20
- package/src/runtime/createPluginRuntimeAPI.ts +1 -1
- package/src/types/hex-color.ts +20 -0
- package/src/types/player-state.ts +36 -18
- package/src/types/plugin-state.ts +10 -3
- package/src/ui-contract.ts +253 -21
- package/src/utils/interaction-inputs.test.ts +400 -0
- package/src/utils/interaction-inputs.ts +113 -11
- package/src/utils/interaction-router.ts +200 -0
|
@@ -64,7 +64,10 @@ function createDescriptor(
|
|
|
64
64
|
}
|
|
65
65
|
: {
|
|
66
66
|
type: "choice" as const,
|
|
67
|
-
choices: [
|
|
67
|
+
choices: [
|
|
68
|
+
{ value: "fallback", label: "Fallback" },
|
|
69
|
+
{ value: "player-2", label: "Player 2" },
|
|
70
|
+
],
|
|
68
71
|
},
|
|
69
72
|
};
|
|
70
73
|
});
|
|
@@ -147,9 +150,11 @@ function render<T>(
|
|
|
147
150
|
recorder?: RecordedSubmit[],
|
|
148
151
|
setupDraft?: (store: InteractionUiStoreApi) => void,
|
|
149
152
|
submitOverride?: RuntimeAPI["submitInteraction"],
|
|
153
|
+
storeRef?: { current?: InteractionUiStoreApi },
|
|
150
154
|
): T {
|
|
151
155
|
const runtime = createRuntime(snapshot, recorder, submitOverride);
|
|
152
156
|
const draftStore = createInteractionUiStore();
|
|
157
|
+
if (storeRef) storeRef.current = draftStore;
|
|
153
158
|
setupDraft?.(draftStore);
|
|
154
159
|
let captured!: T;
|
|
155
160
|
function Harness() {
|
|
@@ -229,6 +234,73 @@ test("useBoardInteractions skips unavailable handles when merging eligibility",
|
|
|
229
234
|
expect(Array.from(board.eligible.edge)).toEqual(["e-2"]);
|
|
230
235
|
});
|
|
231
236
|
|
|
237
|
+
test("useBoardInteractions resolves dependent target eligibility from the draft", () => {
|
|
238
|
+
const session = createSessionState();
|
|
239
|
+
const snapshot = createSnapshot(session, [
|
|
240
|
+
createDescriptor({
|
|
241
|
+
interactionId: "craftAtWorkshop",
|
|
242
|
+
surface: "board-space",
|
|
243
|
+
inputs: [
|
|
244
|
+
{
|
|
245
|
+
key: "itemId",
|
|
246
|
+
kind: "form",
|
|
247
|
+
domain: {
|
|
248
|
+
type: "choice",
|
|
249
|
+
choices: [
|
|
250
|
+
{ value: "anvil", label: "Anvil" },
|
|
251
|
+
{ value: "loom", label: "Loom" },
|
|
252
|
+
],
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
key: "cell",
|
|
257
|
+
kind: "board-space",
|
|
258
|
+
domain: {
|
|
259
|
+
type: "target",
|
|
260
|
+
targetKind: "space",
|
|
261
|
+
eligibleTargets: [],
|
|
262
|
+
dependsOn: ["itemId"],
|
|
263
|
+
dependentCases: [
|
|
264
|
+
{
|
|
265
|
+
when: { itemId: "anvil" },
|
|
266
|
+
domain: {
|
|
267
|
+
type: "target",
|
|
268
|
+
targetKind: "space",
|
|
269
|
+
eligibleTargets: ["workshop-a"],
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
when: { itemId: "loom" },
|
|
274
|
+
domain: {
|
|
275
|
+
type: "target",
|
|
276
|
+
targetKind: "space",
|
|
277
|
+
eligibleTargets: ["workshop-b"],
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
}),
|
|
285
|
+
]);
|
|
286
|
+
|
|
287
|
+
const blocked = render(snapshot, () => useBoardInteractions());
|
|
288
|
+
expect(Array.from(blocked.eligible.space)).toEqual([]);
|
|
289
|
+
expect(blocked.isEligible("workshop-a", "space")).toBe(false);
|
|
290
|
+
|
|
291
|
+
const ready = render(
|
|
292
|
+
snapshot,
|
|
293
|
+
() => useBoardInteractions(),
|
|
294
|
+
undefined,
|
|
295
|
+
(store) => {
|
|
296
|
+
store.setInput("play.craftAtWorkshop", "itemId", "anvil");
|
|
297
|
+
},
|
|
298
|
+
);
|
|
299
|
+
expect(Array.from(ready.eligible.space)).toEqual(["workshop-a"]);
|
|
300
|
+
expect(ready.isEligible("workshop-a", "space")).toBe(true);
|
|
301
|
+
expect(ready.isEligible("workshop-b", "space")).toBe(false);
|
|
302
|
+
});
|
|
303
|
+
|
|
232
304
|
test("useBoardInteractions unarmed matching interactions require an explicit route", async () => {
|
|
233
305
|
const session = createSessionState();
|
|
234
306
|
const snapshot = createSnapshot(session, [
|
|
@@ -288,7 +360,7 @@ test("useBoardInteractions equal-rank unarmed matches throw BoardInteractionConf
|
|
|
288
360
|
}
|
|
289
361
|
});
|
|
290
362
|
|
|
291
|
-
test("useBoardInteractions target-kind select skips unavailable handles and returns
|
|
363
|
+
test("useBoardInteractions target-kind select skips unavailable handles and returns none when nothing matches", async () => {
|
|
292
364
|
const session = createSessionState();
|
|
293
365
|
const snapshot = createSnapshot(session, [
|
|
294
366
|
createDescriptor({
|
|
@@ -304,11 +376,11 @@ test("useBoardInteractions target-kind select skips unavailable handles and retu
|
|
|
304
376
|
const board = render(snapshot, () => useBoardInteractions(), recorder);
|
|
305
377
|
|
|
306
378
|
const fired = await board.select.vertex("v-1");
|
|
307
|
-
expect(fired).
|
|
379
|
+
expect(fired).toEqual({ status: "none" });
|
|
308
380
|
expect(recorder).toEqual([]);
|
|
309
381
|
});
|
|
310
382
|
|
|
311
|
-
test("useBoardInteractions target-kind select
|
|
383
|
+
test("useBoardInteractions target-kind select exposes pending input instead of backfilling null for missing required inputs", async () => {
|
|
312
384
|
const session = createSessionState();
|
|
313
385
|
const snapshot = createSnapshot(session, [
|
|
314
386
|
createDescriptor({
|
|
@@ -323,19 +395,20 @@ test("useBoardInteractions target-kind select merges extraInputs and backfills n
|
|
|
323
395
|
const recorder: RecordedSubmit[] = [];
|
|
324
396
|
const board = render(snapshot, () => useBoardInteractions(), recorder);
|
|
325
397
|
|
|
326
|
-
await board.select.space("space-7");
|
|
327
|
-
expect(
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
]);
|
|
398
|
+
const pending = await board.select.space("space-7");
|
|
399
|
+
expect(pending.status).toBe("pending");
|
|
400
|
+
if (pending.status !== "pending") {
|
|
401
|
+
throw new Error("Expected pending board selection result");
|
|
402
|
+
}
|
|
403
|
+
expect(pending.interactionKey).toBe("play.moveRobber");
|
|
404
|
+
expect(pending.missingInputs).toEqual(["stealFromPlayerId"]);
|
|
405
|
+
expect(recorder).toEqual([]);
|
|
334
406
|
|
|
335
407
|
recorder.length = 0;
|
|
336
|
-
await board.select.space("space-7", {
|
|
408
|
+
const submitted = await board.select.space("space-7", {
|
|
337
409
|
stealFromPlayerId: "player-2",
|
|
338
410
|
});
|
|
411
|
+
expect(submitted.status).toBe("submitted");
|
|
339
412
|
expect(recorder).toEqual([
|
|
340
413
|
{
|
|
341
414
|
playerId: "player-1",
|
|
@@ -345,6 +418,103 @@ test("useBoardInteractions target-kind select merges extraInputs and backfills n
|
|
|
345
418
|
]);
|
|
346
419
|
});
|
|
347
420
|
|
|
421
|
+
test("useBoardInteractions pending input arms the descriptor and stores the target draft", async () => {
|
|
422
|
+
const session = createSessionState();
|
|
423
|
+
const snapshot = createSnapshot(session, [
|
|
424
|
+
createDescriptor({
|
|
425
|
+
interactionId: "moveRobber",
|
|
426
|
+
surface: "board-tile",
|
|
427
|
+
commit: { mode: "autoWhenReady" },
|
|
428
|
+
targets: { spaceId: ["space-7"] },
|
|
429
|
+
keys: ["spaceId", "stealFromPlayerId"],
|
|
430
|
+
}),
|
|
431
|
+
]);
|
|
432
|
+
|
|
433
|
+
const storeRef: { current?: InteractionUiStoreApi } = {};
|
|
434
|
+
const board = render(
|
|
435
|
+
snapshot,
|
|
436
|
+
() => useBoardInteractions(),
|
|
437
|
+
undefined,
|
|
438
|
+
(store) => {
|
|
439
|
+
store.setInput("play.moveRobber", "spaceId", "space-7");
|
|
440
|
+
store.arm("board:space", "play.moveRobber");
|
|
441
|
+
},
|
|
442
|
+
undefined,
|
|
443
|
+
storeRef,
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
const pending = await board.select.space("space-7");
|
|
447
|
+
expect(pending.status).toBe("pending");
|
|
448
|
+
if (pending.status !== "pending") {
|
|
449
|
+
throw new Error("Expected pending board selection result");
|
|
450
|
+
}
|
|
451
|
+
expect(pending.interactionKey).toBe("play.moveRobber");
|
|
452
|
+
expect(pending.missingInputs).toEqual(["stealFromPlayerId"]);
|
|
453
|
+
expect(storeRef.current?.getDraft("play.moveRobber")).toEqual({
|
|
454
|
+
spaceId: "space-7",
|
|
455
|
+
});
|
|
456
|
+
expect(storeRef.current?.getArmed("board:space")).toBe("play.moveRobber");
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
test("useBoardInteractions explicit target selection follows the same no-null pending behavior", async () => {
|
|
460
|
+
const session = createSessionState();
|
|
461
|
+
const descriptor = createDescriptor({
|
|
462
|
+
interactionId: "moveRobber",
|
|
463
|
+
surface: "board-tile",
|
|
464
|
+
commit: { mode: "autoWhenReady" },
|
|
465
|
+
targets: { spaceId: ["space-7"] },
|
|
466
|
+
keys: ["spaceId", "stealFromPlayerId"],
|
|
467
|
+
});
|
|
468
|
+
const snapshot = createSnapshot(session, [descriptor]);
|
|
469
|
+
|
|
470
|
+
const recorder: RecordedSubmit[] = [];
|
|
471
|
+
const board = render(snapshot, () => useBoardInteractions(), recorder);
|
|
472
|
+
|
|
473
|
+
const result = await board.selectTarget(
|
|
474
|
+
descriptor,
|
|
475
|
+
"space",
|
|
476
|
+
"space-7",
|
|
477
|
+
"spaceId",
|
|
478
|
+
);
|
|
479
|
+
expect(result.status).toBe("pending");
|
|
480
|
+
if (result.status !== "pending") {
|
|
481
|
+
throw new Error("Expected pending board selection result");
|
|
482
|
+
}
|
|
483
|
+
expect(result.missingInputs).toEqual(["stealFromPlayerId"]);
|
|
484
|
+
expect(recorder).toEqual([]);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
test("useBoardInteractions submitted board target clears draft and arm state", async () => {
|
|
488
|
+
const session = createSessionState();
|
|
489
|
+
const snapshot = createSnapshot(session, [
|
|
490
|
+
createDescriptor({
|
|
491
|
+
interactionId: "moveRobber",
|
|
492
|
+
surface: "board-tile",
|
|
493
|
+
commit: { mode: "autoWhenReady" },
|
|
494
|
+
targets: { spaceId: ["space-7"] },
|
|
495
|
+
keys: ["spaceId", "stealFromPlayerId"],
|
|
496
|
+
}),
|
|
497
|
+
]);
|
|
498
|
+
|
|
499
|
+
const storeRef: { current?: InteractionUiStoreApi } = {};
|
|
500
|
+
const board = render(
|
|
501
|
+
snapshot,
|
|
502
|
+
() => useBoardInteractions(),
|
|
503
|
+
[],
|
|
504
|
+
(store) => {
|
|
505
|
+
store.setInput("play.moveRobber", "stealFromPlayerId", "player-2");
|
|
506
|
+
store.arm("board:space", "play.moveRobber");
|
|
507
|
+
},
|
|
508
|
+
undefined,
|
|
509
|
+
storeRef,
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
const result = await board.select.space("space-7");
|
|
513
|
+
expect(result.status).toBe("submitted");
|
|
514
|
+
expect(storeRef.current?.getDraft("play.moveRobber")).toEqual({});
|
|
515
|
+
expect(storeRef.current?.getArmed("board:space")).toBeNull();
|
|
516
|
+
});
|
|
517
|
+
|
|
348
518
|
test("useBoardInteractions targetLayers expose reducer eligibility sets", () => {
|
|
349
519
|
const session = createSessionState();
|
|
350
520
|
const snapshot = createSnapshot(session, [
|
|
@@ -423,7 +593,7 @@ test("useBoardInteractions targetLayers apply extraInputs and onBeforeSelect", a
|
|
|
423
593
|
let beforeCount = 0;
|
|
424
594
|
const board = render(snapshot, () => useBoardInteractions(), recorder);
|
|
425
595
|
|
|
426
|
-
await board.targetLayers
|
|
596
|
+
const result = await board.targetLayers
|
|
427
597
|
.space({
|
|
428
598
|
extraInputs: (targetId) => ({
|
|
429
599
|
stealFromPlayerId: targetId === "space-7" ? "player-2" : null,
|
|
@@ -435,6 +605,7 @@ test("useBoardInteractions targetLayers apply extraInputs and onBeforeSelect", a
|
|
|
435
605
|
.selectTargetId?.("space-7");
|
|
436
606
|
|
|
437
607
|
expect(beforeCount).toBe(1);
|
|
608
|
+
expect(result?.status).toBe("submitted");
|
|
438
609
|
expect(recorder).toEqual([
|
|
439
610
|
{
|
|
440
611
|
playerId: "player-1",
|
|
@@ -444,6 +615,50 @@ test("useBoardInteractions targetLayers apply extraInputs and onBeforeSelect", a
|
|
|
444
615
|
]);
|
|
445
616
|
});
|
|
446
617
|
|
|
618
|
+
test("useBoardInteractions many-target board input submits only when selection minimum is satisfied", async () => {
|
|
619
|
+
const session = createSessionState();
|
|
620
|
+
const snapshot = createSnapshot(session, [
|
|
621
|
+
createDescriptor({
|
|
622
|
+
interactionId: "buildJumpGate",
|
|
623
|
+
surface: "board-edge",
|
|
624
|
+
commit: { mode: "autoWhenReady" },
|
|
625
|
+
inputs: [
|
|
626
|
+
{
|
|
627
|
+
key: "edgeIds",
|
|
628
|
+
kind: "board-edge",
|
|
629
|
+
domain: {
|
|
630
|
+
type: "target",
|
|
631
|
+
targetKind: "edge",
|
|
632
|
+
eligibleTargets: ["e-1", "e-2"],
|
|
633
|
+
selection: { mode: "many", min: 2, max: 2, distinct: true },
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
],
|
|
637
|
+
}),
|
|
638
|
+
]);
|
|
639
|
+
|
|
640
|
+
const recorder: RecordedSubmit[] = [];
|
|
641
|
+
const board = render(snapshot, () => useBoardInteractions(), recorder);
|
|
642
|
+
|
|
643
|
+
const first = await board.select.edge("e-1");
|
|
644
|
+
expect(first.status).toBe("pending");
|
|
645
|
+
if (first.status !== "pending") {
|
|
646
|
+
throw new Error("Expected pending board selection result");
|
|
647
|
+
}
|
|
648
|
+
expect(first.missingInputs).toEqual(["edgeIds"]);
|
|
649
|
+
expect(recorder).toEqual([]);
|
|
650
|
+
|
|
651
|
+
const second = await board.select.edge("e-2");
|
|
652
|
+
expect(second.status).toBe("submitted");
|
|
653
|
+
expect(recorder).toEqual([
|
|
654
|
+
{
|
|
655
|
+
playerId: "player-1",
|
|
656
|
+
interactionId: "buildJumpGate",
|
|
657
|
+
params: { edgeIds: ["e-1", "e-2"] },
|
|
658
|
+
},
|
|
659
|
+
]);
|
|
660
|
+
});
|
|
661
|
+
|
|
447
662
|
test("useBoardInteractions targetLayers route submit failures to onError", async () => {
|
|
448
663
|
const session = createSessionState();
|
|
449
664
|
const snapshot = createSnapshot(session, [
|
|
@@ -570,7 +785,11 @@ test("useBoardInteractions only exposes hand-card board targets after the card a
|
|
|
570
785
|
expect(Array.from(armed.eligible.space)).toEqual(["space-7"]);
|
|
571
786
|
|
|
572
787
|
const fired = await armed.select.space("space-7");
|
|
573
|
-
expect(fired).toBe("
|
|
788
|
+
expect(fired.status).toBe("submitted");
|
|
789
|
+
if (fired.status !== "submitted") {
|
|
790
|
+
throw new Error("Expected submitted board selection result");
|
|
791
|
+
}
|
|
792
|
+
expect(fired.interactionKey).toBe("play.playKnight");
|
|
574
793
|
expect(recorder).toEqual([
|
|
575
794
|
{
|
|
576
795
|
playerId: "player-1",
|
|
@@ -580,7 +799,7 @@ test("useBoardInteractions only exposes hand-card board targets after the card a
|
|
|
580
799
|
]);
|
|
581
800
|
});
|
|
582
801
|
|
|
583
|
-
test("useBoardInteractions armed
|
|
802
|
+
test("useBoardInteractions armed pending input beats unarmed ambient board action", async () => {
|
|
584
803
|
const session = createSessionState();
|
|
585
804
|
const snapshot = createSnapshot(session, [
|
|
586
805
|
createDescriptor({
|
|
@@ -611,7 +830,11 @@ test("useBoardInteractions armed follow-up beats unarmed ambient board action",
|
|
|
611
830
|
);
|
|
612
831
|
|
|
613
832
|
const fired = await board.select.edge("e-shared");
|
|
614
|
-
expect(fired).toBe("
|
|
833
|
+
expect(fired.status).toBe("submitted");
|
|
834
|
+
if (fired.status !== "submitted") {
|
|
835
|
+
throw new Error("Expected submitted board selection result");
|
|
836
|
+
}
|
|
837
|
+
expect(fired.interactionKey).toBe("play.playRoadBuilding");
|
|
615
838
|
expect(recorder).toEqual([
|
|
616
839
|
{
|
|
617
840
|
playerId: "player-1",
|