@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.
Files changed (166) hide show
  1. package/dist/components/ActionButton.d.ts.map +1 -1
  2. package/dist/components/ActionButton.js +2 -1
  3. package/dist/components/Card.d.ts +1 -1
  4. package/dist/components/Card.d.ts.map +1 -1
  5. package/dist/components/DiceRoller.d.ts +3 -2
  6. package/dist/components/DiceRoller.d.ts.map +1 -1
  7. package/dist/components/DiceRoller.js +4 -13
  8. package/dist/components/ErrorBoundary.d.ts.map +1 -1
  9. package/dist/components/ErrorBoundary.js +94 -2
  10. package/dist/components/InteractionForm.d.ts +1 -1
  11. package/dist/components/InteractionForm.d.ts.map +1 -1
  12. package/dist/components/InteractionForm.js +29 -15
  13. package/dist/components/PrimaryActionButton.d.ts.map +1 -1
  14. package/dist/components/PrimaryActionButton.js +7 -6
  15. package/dist/components/ResourceCounter.d.ts +59 -25
  16. package/dist/components/ResourceCounter.d.ts.map +1 -1
  17. package/dist/components/ResourceCounter.js +106 -115
  18. package/dist/components/Toast.d.ts +13 -6
  19. package/dist/components/Toast.d.ts.map +1 -1
  20. package/dist/components/Toast.js +10 -5
  21. package/dist/components/board/HexGrid.js +6 -6
  22. package/dist/components/board/target-layer.d.ts +18 -2
  23. package/dist/components/board/target-layer.d.ts.map +1 -1
  24. package/dist/components/board/target-layer.js +20 -3
  25. package/dist/components/index.d.ts +3 -4
  26. package/dist/components/index.d.ts.map +1 -1
  27. package/dist/components/index.js +3 -4
  28. package/dist/components/surfaces/InboxSurface.d.ts.map +1 -1
  29. package/dist/components/surfaces/InboxSurface.js +2 -6
  30. package/dist/components/surfaces/PlayerCardsSurface.js +2 -2
  31. package/dist/components/surfaces/internal/CardZoneRoutedForm.d.ts +7 -0
  32. package/dist/components/surfaces/internal/CardZoneRoutedForm.d.ts.map +1 -0
  33. package/dist/components/surfaces/internal/CardZoneRoutedForm.js +9 -0
  34. package/dist/components/surfaces/internal/DefaultInteractionButton.d.ts.map +1 -1
  35. package/dist/components/surfaces/internal/DefaultInteractionButton.js +5 -8
  36. package/dist/components/surfaces/internal/useCardZoneInteractions.d.ts +2 -2
  37. package/dist/components/surfaces/internal/useCardZoneInteractions.d.ts.map +1 -1
  38. package/dist/components/surfaces/internal/useCardZoneInteractions.js +19 -43
  39. package/dist/context/InteractionDraftContext.d.ts +11 -2
  40. package/dist/context/InteractionDraftContext.d.ts.map +1 -1
  41. package/dist/context/InteractionDraftContext.js +41 -4
  42. package/dist/defaults/components.d.ts +0 -5
  43. package/dist/defaults/components.d.ts.map +1 -1
  44. package/dist/defaults/components.js +7 -11
  45. package/dist/hooks/useBoardInteractions.d.ts +35 -12
  46. package/dist/hooks/useBoardInteractions.d.ts.map +1 -1
  47. package/dist/hooks/useBoardInteractions.js +186 -82
  48. package/dist/hooks/useInteractionHandle.d.ts +1 -1
  49. package/dist/hooks/useInteractionHandle.d.ts.map +1 -1
  50. package/dist/hooks/useInteractionHandle.js +12 -27
  51. package/dist/index.d.ts +11 -17
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +5 -14
  54. package/dist/primitives/board.d.ts +53 -3
  55. package/dist/primitives/board.d.ts.map +1 -1
  56. package/dist/primitives/board.js +65 -41
  57. package/dist/primitives/dialog-lifecycle.d.ts +17 -0
  58. package/dist/primitives/dialog-lifecycle.d.ts.map +1 -0
  59. package/dist/primitives/dialog-lifecycle.js +24 -0
  60. package/dist/primitives/dice.d.ts +31 -0
  61. package/dist/primitives/dice.d.ts.map +1 -0
  62. package/dist/primitives/dice.js +33 -0
  63. package/dist/primitives/game.d.ts +55 -0
  64. package/dist/primitives/game.d.ts.map +1 -0
  65. package/dist/primitives/game.js +101 -0
  66. package/dist/primitives/index.d.ts +7 -4
  67. package/dist/primitives/index.d.ts.map +1 -1
  68. package/dist/primitives/index.js +7 -4
  69. package/dist/primitives/interaction-form-binding.d.ts +12 -0
  70. package/dist/primitives/interaction-form-binding.d.ts.map +1 -0
  71. package/dist/primitives/interaction-form-binding.js +14 -0
  72. package/dist/primitives/interaction-submit.d.ts +23 -0
  73. package/dist/primitives/interaction-submit.d.ts.map +1 -0
  74. package/dist/primitives/interaction-submit.js +41 -0
  75. package/dist/primitives/interaction.d.ts +76 -6
  76. package/dist/primitives/interaction.d.ts.map +1 -1
  77. package/dist/primitives/interaction.js +210 -26
  78. package/dist/primitives/player-roster.d.ts +2 -1
  79. package/dist/primitives/player-roster.d.ts.map +1 -1
  80. package/dist/primitives/prompt.d.ts +36 -11
  81. package/dist/primitives/prompt.d.ts.map +1 -1
  82. package/dist/primitives/prompt.js +29 -17
  83. package/dist/primitives/ui.d.ts +9 -0
  84. package/dist/primitives/ui.d.ts.map +1 -0
  85. package/dist/primitives/ui.js +7 -0
  86. package/dist/primitives/zone.d.ts +111 -5
  87. package/dist/primitives/zone.d.ts.map +1 -1
  88. package/dist/primitives/zone.js +349 -9
  89. package/dist/reducer.d.ts +2 -14
  90. package/dist/reducer.d.ts.map +1 -1
  91. package/dist/reducer.js +1 -14
  92. package/dist/runtime/createPluginRuntimeAPI.js +1 -1
  93. package/dist/types/hex-color.d.ts +7 -0
  94. package/dist/types/hex-color.d.ts.map +1 -0
  95. package/dist/types/hex-color.js +13 -0
  96. package/dist/types/player-state.d.ts +28 -14
  97. package/dist/types/player-state.d.ts.map +1 -1
  98. package/dist/types/plugin-state.d.ts +9 -3
  99. package/dist/types/plugin-state.d.ts.map +1 -1
  100. package/dist/ui-contract.d.ts +119 -14
  101. package/dist/ui-contract.d.ts.map +1 -1
  102. package/dist/ui-contract.js +4 -3
  103. package/dist/ui-sdk.d.ts +1637 -1245
  104. package/dist/utils/interaction-inputs.d.ts +8 -5
  105. package/dist/utils/interaction-inputs.d.ts.map +1 -1
  106. package/dist/utils/interaction-inputs.js +82 -14
  107. package/dist/utils/interaction-router.d.ts +31 -0
  108. package/dist/utils/interaction-router.d.ts.map +1 -0
  109. package/dist/utils/interaction-router.js +114 -0
  110. package/package.json +1 -1
  111. package/src/components/ActionButton.tsx +2 -1
  112. package/src/components/Card.tsx +1 -1
  113. package/src/components/DiceRoller.tsx +13 -22
  114. package/src/components/ErrorBoundary.test.tsx +19 -0
  115. package/src/components/ErrorBoundary.tsx +113 -24
  116. package/src/components/InteractionForm.test.tsx +24 -0
  117. package/src/components/InteractionForm.tsx +48 -23
  118. package/src/components/PrimaryActionButton.tsx +19 -5
  119. package/src/components/ResourceCounter.test.tsx +13 -13
  120. package/src/components/ResourceCounter.tsx +238 -244
  121. package/src/components/Toast.tsx +23 -10
  122. package/src/components/__fixtures__/ResourceCounter.fixture.tsx +70 -169
  123. package/src/components/board/HexGrid.tsx +6 -6
  124. package/src/components/board/target-layer.ts +44 -5
  125. package/src/components/index.ts +17 -10
  126. package/src/components/surfaces/InboxSurface.tsx +7 -5
  127. package/src/components/surfaces/PlayerCardsSurface.tsx +6 -6
  128. package/src/components/surfaces/internal/CardZoneRoutedForm.tsx +35 -0
  129. package/src/components/surfaces/internal/DefaultInteractionButton.tsx +17 -7
  130. package/src/components/surfaces/internal/useCardZoneInteractions.ts +25 -67
  131. package/src/context/InteractionDraftContext.tsx +51 -5
  132. package/src/defaults/components.tsx +12 -50
  133. package/src/defaults/defaults.test.tsx +1 -50
  134. package/src/hooks/useBoardInteractions.test.tsx +240 -17
  135. package/src/hooks/useBoardInteractions.ts +330 -105
  136. package/src/hooks/useInteractionHandle.ts +23 -28
  137. package/src/index.test.ts +60 -40
  138. package/src/index.ts +30 -36
  139. package/src/primitives/board.test.tsx +73 -0
  140. package/src/primitives/board.tsx +191 -40
  141. package/src/primitives/dialog-lifecycle.ts +58 -0
  142. package/src/primitives/dice.test.tsx +47 -0
  143. package/src/primitives/dice.tsx +79 -0
  144. package/src/primitives/game.test.tsx +98 -0
  145. package/src/primitives/game.tsx +213 -0
  146. package/src/primitives/index.ts +84 -0
  147. package/src/primitives/interaction-form-binding.tsx +56 -0
  148. package/src/primitives/interaction-submit.ts +90 -0
  149. package/src/primitives/interaction.test.tsx +396 -0
  150. package/src/primitives/interaction.tsx +451 -31
  151. package/src/primitives/player-roster.tsx +2 -1
  152. package/src/primitives/prompt.test.tsx +94 -3
  153. package/src/primitives/prompt.tsx +87 -48
  154. package/src/primitives/ui.test.tsx +131 -0
  155. package/src/primitives/ui.tsx +13 -0
  156. package/src/primitives/zone.test.tsx +305 -0
  157. package/src/primitives/zone.tsx +660 -12
  158. package/src/reducer.ts +7 -20
  159. package/src/runtime/createPluginRuntimeAPI.ts +1 -1
  160. package/src/types/hex-color.ts +20 -0
  161. package/src/types/player-state.ts +36 -18
  162. package/src/types/plugin-state.ts +10 -3
  163. package/src/ui-contract.ts +253 -21
  164. package/src/utils/interaction-inputs.test.ts +400 -0
  165. package/src/utils/interaction-inputs.ts +113 -11
  166. package/src/utils/interaction-router.ts +200 -0
@@ -87,6 +87,272 @@ test("Interaction primitives render descriptor state through data attributes", (
87
87
  expect(html).toContain('data-ready="true"');
88
88
  });
89
89
 
90
+ test("Interaction.Form renders the bound descriptor without author handle plumbing", () => {
91
+ const runtime = createRuntime(
92
+ createSnapshot({
93
+ availableInteractions: [
94
+ {
95
+ phaseName: "setup",
96
+ interactionKey: "setup.namePlayer",
97
+ interactionId: "namePlayer",
98
+ kind: "action",
99
+ inputs: [
100
+ {
101
+ key: "age",
102
+ kind: "form",
103
+ domain: { type: "boundedNumber", min: 1, max: 120 },
104
+ },
105
+ ],
106
+ commit: { mode: "manual" },
107
+ available: true,
108
+ },
109
+ ],
110
+ }),
111
+ );
112
+
113
+ const html = renderToString(
114
+ <GameUIProvider runtime={runtime}>
115
+ <Interaction.Root interaction="setup.namePlayer">
116
+ <Interaction.Form
117
+ title="Name player"
118
+ submitLabel="Save name"
119
+ accordion={false}
120
+ />
121
+ </Interaction.Root>
122
+ </GameUIProvider>,
123
+ );
124
+
125
+ expect(html).toContain("Name player");
126
+ expect(html).toContain("Save name");
127
+ expect(html).toContain('type="number"');
128
+ expect(html).toContain("Increase age");
129
+ });
130
+
131
+ test("Interaction.Field renders a single bound form input from Root context", () => {
132
+ const runtime = createRuntime(
133
+ createSnapshot({
134
+ availableInteractions: [
135
+ {
136
+ phaseName: "setup",
137
+ interactionKey: "setup.namePlayer",
138
+ interactionId: "namePlayer",
139
+ kind: "action",
140
+ inputs: [
141
+ {
142
+ key: "age",
143
+ kind: "form",
144
+ domain: { type: "boundedNumber", min: 1, max: 120 },
145
+ },
146
+ ],
147
+ commit: { mode: "manual" },
148
+ available: true,
149
+ },
150
+ ],
151
+ }),
152
+ );
153
+
154
+ const html = renderToString(
155
+ <GameUIProvider runtime={runtime}>
156
+ <Interaction.Root interaction="setup.namePlayer">
157
+ <Interaction.Field input="age" />
158
+ </Interaction.Root>
159
+ </GameUIProvider>,
160
+ );
161
+
162
+ expect(html).toContain('type="number"');
163
+ expect(html).toContain("Increase age");
164
+ });
165
+
166
+ test("Interaction.Dialog owns headless lifecycle state", () => {
167
+ const runtime = createRuntime(createSnapshot());
168
+
169
+ const html = renderToString(
170
+ <GameUIProvider runtime={runtime}>
171
+ <Interaction.Root interaction="setup.ready">
172
+ <Interaction.Dialog>
173
+ {({ open, minimized, interaction }) => (
174
+ <div
175
+ data-interaction={interaction}
176
+ data-open={open}
177
+ data-minimized={minimized}
178
+ />
179
+ )}
180
+ </Interaction.Dialog>
181
+ </Interaction.Root>
182
+ </GameUIProvider>,
183
+ );
184
+
185
+ expect(html).toContain('data-interaction="setup.ready"');
186
+ expect(html).toContain('data-open="true"');
187
+ expect(html).toContain('data-minimized="false"');
188
+ });
189
+
190
+ test("Interaction.Switch routes the selected descriptor through Interaction.Root", () => {
191
+ const runtime = createRuntime(createSnapshot());
192
+
193
+ const html = renderToString(
194
+ <GameUIProvider runtime={runtime}>
195
+ <Interaction.Switch
196
+ interaction="setup.ready"
197
+ routes={{
198
+ "setup.ready": ({ interaction, descriptor }) => (
199
+ <Interaction.State unavailable={null}>
200
+ {({ isReady }) => (
201
+ <span>
202
+ {interaction}:{descriptor.interactionId}:{String(isReady)}
203
+ </span>
204
+ )}
205
+ </Interaction.State>
206
+ ),
207
+ }}
208
+ />
209
+ </GameUIProvider>,
210
+ ).replace(/<!-- -->/g, "");
211
+
212
+ expect(html).toContain("setup.ready:ready:true");
213
+ });
214
+
215
+ test("Interaction.Switch renders fallback when no route matches", () => {
216
+ const runtime = createRuntime(createSnapshot());
217
+
218
+ const html = renderToString(
219
+ <GameUIProvider runtime={runtime}>
220
+ <Interaction.Switch routes={{}} fallback={<span>No route</span>} />
221
+ </GameUIProvider>,
222
+ );
223
+
224
+ expect(html).toContain("No route");
225
+ });
226
+
227
+ test("Interaction.Switch without an explicit interaction routes the pending graph", () => {
228
+ const moveRaider: InteractionDescriptor = {
229
+ phaseName: "playing",
230
+ interactionKey: "playing.moveRaider",
231
+ interactionId: "moveRaider",
232
+ kind: "action",
233
+ inputs: [
234
+ {
235
+ key: "spaceId",
236
+ kind: "board-space",
237
+ domain: {
238
+ type: "target",
239
+ targetKind: "board-space",
240
+ eligibleTargets: ["space-1"],
241
+ },
242
+ },
243
+ {
244
+ key: "stealFromPlayerId",
245
+ kind: "form",
246
+ domain: {
247
+ type: "choice",
248
+ choices: [{ value: "player-2", label: "Player 2" }],
249
+ dependsOn: ["spaceId"],
250
+ },
251
+ },
252
+ ],
253
+ commit: { mode: "manual" },
254
+ available: true,
255
+ };
256
+ const runtime = createRuntime(
257
+ createSnapshot({
258
+ availableInteractions: [
259
+ createSnapshot().gameplay.availableInteractions[0]!,
260
+ moveRaider,
261
+ ],
262
+ }),
263
+ );
264
+ const interactionStore = createInteractionUiStore();
265
+ interactionStore.setInput("playing.moveRaider", "spaceId", "space-1");
266
+ interactionStore.setPendingInteraction("playing.moveRaider");
267
+
268
+ const html = renderToString(
269
+ <GameUIProvider runtime={runtime} interactionStore={interactionStore}>
270
+ <Interaction.Switch
271
+ routes={{
272
+ "setup.ready": () => <span>Ready route</span>,
273
+ "playing.moveRaider": ({ interaction }) => (
274
+ <Interaction.State unavailable={null}>
275
+ {({ readyFrontier }) => (
276
+ <span>
277
+ {interaction}:{readyFrontier.join(",")}
278
+ </span>
279
+ )}
280
+ </Interaction.State>
281
+ ),
282
+ }}
283
+ />
284
+ </GameUIProvider>,
285
+ );
286
+
287
+ expect(html.replace(/<!-- -->/g, "")).toContain(
288
+ "playing.moveRaider:stealFromPlayerId",
289
+ );
290
+ expect(html).not.toContain("Ready route");
291
+ });
292
+
293
+ test("Interaction.State exposes draft and status without hook plumbing", () => {
294
+ const passCards: InteractionDescriptor = {
295
+ phaseName: "passing",
296
+ interactionKey: "passing.passCards",
297
+ interactionId: "passCards",
298
+ kind: "action",
299
+ inputs: [
300
+ {
301
+ key: "cardIds",
302
+ kind: "card",
303
+ domain: {
304
+ type: "target",
305
+ targetKind: "card",
306
+ eligibleTargets: ["card-1", "card-2"],
307
+ selection: { mode: "many", min: 1, max: 3, distinct: true },
308
+ },
309
+ },
310
+ ],
311
+ commit: { mode: "manual" },
312
+ available: true,
313
+ };
314
+ const runtime = createRuntime(
315
+ createSnapshot({ availableInteractions: [passCards] }),
316
+ );
317
+ const interactionStore = createInteractionUiStore();
318
+ interactionStore.setInput("passing.passCards", "cardIds", ["card-1"]);
319
+
320
+ const html = renderToString(
321
+ <GameUIProvider runtime={runtime} interactionStore={interactionStore}>
322
+ <Interaction.Root interaction="passing.passCards">
323
+ <Interaction.State<{ cardIds: readonly string[] }> unavailable={null}>
324
+ {({
325
+ draft,
326
+ status,
327
+ isReady,
328
+ hasInputs,
329
+ inputKeys,
330
+ missingInputs,
331
+ readyFrontier,
332
+ blockedInputs,
333
+ }) => (
334
+ <span>
335
+ selected:{draft.cardIds?.join(",")} status:{status} ready:
336
+ {String(isReady)} hasInputs:{String(hasInputs)} inputs:
337
+ {inputKeys.join(",")} missing:{missingInputs.join(",")} frontier:
338
+ {readyFrontier.join(",")} blocked:{blockedInputs.join(",")}
339
+ </span>
340
+ )}
341
+ </Interaction.State>
342
+ </Interaction.Root>
343
+ </GameUIProvider>,
344
+ ).replace(/<!-- -->/g, "");
345
+
346
+ expect(html).toContain("selected:card-1");
347
+ expect(html).toContain("status:open");
348
+ expect(html).toContain("ready:true");
349
+ expect(html).toContain("hasInputs:true");
350
+ expect(html).toContain("inputs:cardIds");
351
+ expect(html).toContain("missing:");
352
+ expect(html).toContain("frontier:");
353
+ expect(html).toContain("blocked:");
354
+ });
355
+
90
356
  test("Interaction primitives surface unavailable descriptors without author wiring", () => {
91
357
  const runtime = createRuntime(
92
358
  createSnapshot({
@@ -276,6 +542,7 @@ test("Interaction.CardInput reads Zone.Item card context and many-selection stat
276
542
  expect(html).toContain('data-zone="hand"');
277
543
  expect(html).toContain('data-selected="true"');
278
544
  expect(html).toContain('data-eligible="true"');
545
+ expect(html).toContain('data-target-valid="true"');
279
546
  });
280
547
 
281
548
  test("Interaction.CardInput marks unselected cards once a many selection is full", () => {
@@ -345,6 +612,8 @@ test("Interaction.CardInput marks unselected cards once a many selection is full
345
612
  expect(html).toContain('data-selected="true"');
346
613
  expect(html).toContain('data-card-id="card-2"');
347
614
  expect(html).toContain('data-selectable="false"');
615
+ expect(html).toContain('data-disabled="true"');
616
+ expect(html).toContain("disabled=");
348
617
  });
349
618
 
350
619
  test("Interaction.CardInput marks card candidates unavailable in zone projection", () => {
@@ -417,4 +686,131 @@ test("Interaction.CardInput marks card candidates unavailable in zone projection
417
686
  expect(html).toContain('data-card-available="true"');
418
687
  expect(html).toContain('data-card-id="card-2"');
419
688
  expect(html).toContain('data-card-available="false"');
689
+ expect(html).toContain(
690
+ 'data-card-unavailable-reason="Lead with the two of clubs."',
691
+ );
692
+ expect(html).toContain('data-target-valid="true"');
693
+ });
694
+
695
+ test("Interaction.CardInput separates invalid target state from unavailable cards", () => {
696
+ const playCard: InteractionDescriptor = {
697
+ phaseName: "playing",
698
+ interactionKey: "playing.playCard",
699
+ interactionId: "playCard",
700
+ kind: "action",
701
+ inputs: [
702
+ {
703
+ key: "cardId",
704
+ kind: "card",
705
+ domain: {
706
+ type: "target",
707
+ targetKind: "card",
708
+ zoneIds: ["hand"],
709
+ },
710
+ },
711
+ ],
712
+ commit: { mode: "manual" },
713
+ available: true,
714
+ };
715
+ const runtime = createRuntime(
716
+ createSnapshot({
717
+ availableInteractions: [playCard],
718
+ zones: {
719
+ hand: {
720
+ cardIds: ["card-1", "card-2"],
721
+ cardsById: {
722
+ "card-1": '{"id":"card-1"}',
723
+ "card-2": '{"id":"card-2"}',
724
+ },
725
+ playableByCardId: {
726
+ "card-1": [playCard],
727
+ "card-2": [],
728
+ },
729
+ },
730
+ },
731
+ }),
732
+ );
733
+
734
+ const html = renderToString(
735
+ <GameUIProvider runtime={runtime}>
736
+ <Interaction.Root interaction="playing.playCard">
737
+ <Zone.Root zone="hand">
738
+ <Zone.Item card="card-2">
739
+ <Interaction.CardInput input="cardId">
740
+ Card two
741
+ </Interaction.CardInput>
742
+ </Zone.Item>
743
+ </Zone.Root>
744
+ </Interaction.Root>
745
+ </GameUIProvider>,
746
+ );
747
+
748
+ expect(html).toContain('data-card-id="card-2"');
749
+ expect(html).toContain('data-target-valid="false"');
750
+ expect(html).toContain('data-target-invalid-reason="not-top-card"');
751
+ expect(html).toContain('data-card-available="false"');
752
+ });
753
+
754
+ test("Interaction.CardInput throws dev errors for unsafe card mismatches", () => {
755
+ const playCard: InteractionDescriptor = {
756
+ phaseName: "playing",
757
+ interactionKey: "playing.playCard",
758
+ interactionId: "playCard",
759
+ kind: "action",
760
+ inputs: [
761
+ {
762
+ key: "cardId",
763
+ kind: "card",
764
+ domain: {
765
+ type: "target",
766
+ targetKind: "card",
767
+ zoneIds: ["hand"],
768
+ },
769
+ },
770
+ ],
771
+ commit: { mode: "manual" },
772
+ available: true,
773
+ };
774
+ const runtime = createRuntime(
775
+ createSnapshot({
776
+ availableInteractions: [playCard],
777
+ zones: {
778
+ hand: {
779
+ cardIds: ["card-1"],
780
+ cardsById: { "card-1": '{"id":"card-1"}' },
781
+ playableByCardId: { "card-1": [playCard] },
782
+ },
783
+ },
784
+ }),
785
+ );
786
+
787
+ expect(() =>
788
+ renderToString(
789
+ <GameUIProvider runtime={runtime}>
790
+ <Interaction.Root interaction="playing.playCard">
791
+ <Zone.Root zone="hand">
792
+ <Zone.Item card="card-1">
793
+ <Interaction.CardInput input="cardId" unsafeCardId="card-2">
794
+ Card
795
+ </Interaction.CardInput>
796
+ </Zone.Item>
797
+ </Zone.Root>
798
+ </Interaction.Root>
799
+ </GameUIProvider>,
800
+ ),
801
+ ).toThrow("does not match surrounding Zone.Item card");
802
+
803
+ expect(() =>
804
+ renderToString(
805
+ <GameUIProvider runtime={runtime}>
806
+ <Interaction.Root interaction="playing.playCard">
807
+ <Zone.Root zone="hand">
808
+ <Interaction.CardInput input="cardId" unsafeCardId="missing-card">
809
+ Card
810
+ </Interaction.CardInput>
811
+ </Zone.Root>
812
+ </Interaction.Root>
813
+ </GameUIProvider>,
814
+ ),
815
+ ).toThrow("is not present in surrounding zone");
420
816
  });