@dreamboard-games/ui-sdk 0.0.42 → 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 +2 -2
- 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
|
@@ -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
|
});
|