@assistant-ui/react-generative-ui 0.0.20 → 0.0.22

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 (189) hide show
  1. package/LICENSE +1 -1
  2. package/dist/JSONGenerativeUI.client.d.ts.map +1 -1
  3. package/dist/JSONGenerativeUI.client.js +30 -13
  4. package/dist/JSONGenerativeUI.client.js.map +1 -1
  5. package/dist/RadioGroupScope.d.ts +6 -0
  6. package/dist/RadioGroupScope.d.ts.map +1 -0
  7. package/dist/RadioGroupScope.js +25 -0
  8. package/dist/RadioGroupScope.js.map +1 -0
  9. package/dist/a2ui/BindingContext.d.ts +11 -0
  10. package/dist/a2ui/BindingContext.d.ts.map +1 -0
  11. package/dist/a2ui/BindingContext.js +12 -0
  12. package/dist/a2ui/BindingContext.js.map +1 -0
  13. package/dist/a2ui/PresentRenderer.d.ts +11 -0
  14. package/dist/a2ui/PresentRenderer.d.ts.map +1 -0
  15. package/dist/a2ui/PresentRenderer.js +39 -0
  16. package/dist/a2ui/PresentRenderer.js.map +1 -0
  17. package/dist/a2ui/convert.d.ts +6 -0
  18. package/dist/a2ui/convert.d.ts.map +1 -1
  19. package/dist/a2ui/convert.js +369 -87
  20. package/dist/a2ui/convert.js.map +1 -1
  21. package/dist/a2ui/dataModel.d.ts +9 -0
  22. package/dist/a2ui/dataModel.d.ts.map +1 -0
  23. package/dist/a2ui/dataModel.js +44 -0
  24. package/dist/a2ui/dataModel.js.map +1 -0
  25. package/dist/a2ui/reducer.d.ts +5 -0
  26. package/dist/a2ui/reducer.d.ts.map +1 -1
  27. package/dist/a2ui/reducer.js +3 -3
  28. package/dist/a2ui/reducer.js.map +1 -1
  29. package/dist/a2ui/types.d.ts +8 -2
  30. package/dist/a2ui/types.d.ts.map +1 -1
  31. package/dist/a2ui/types.js.map +1 -1
  32. package/dist/a2ui/valueFunctions.d.ts +16 -0
  33. package/dist/a2ui/valueFunctions.d.ts.map +1 -0
  34. package/dist/a2ui/valueFunctions.js +332 -0
  35. package/dist/a2ui/valueFunctions.js.map +1 -0
  36. package/dist/actionRegistry.d.ts +1 -1
  37. package/dist/actionRegistry.js.map +1 -1
  38. package/dist/answeredValues.d.ts +16 -0
  39. package/dist/answeredValues.d.ts.map +1 -0
  40. package/dist/answeredValues.js +37 -0
  41. package/dist/answeredValues.js.map +1 -0
  42. package/dist/constants.d.ts +2 -0
  43. package/dist/constants.d.ts.map +1 -1
  44. package/dist/constants.js +3 -1
  45. package/dist/constants.js.map +1 -1
  46. package/dist/fieldReferences.d.ts +9 -0
  47. package/dist/fieldReferences.d.ts.map +1 -0
  48. package/dist/fieldReferences.js +53 -0
  49. package/dist/fieldReferences.js.map +1 -0
  50. package/dist/generativeUIToJSX.js +3 -3
  51. package/dist/generativeUIToJSX.js.map +1 -1
  52. package/dist/index.js +2 -2
  53. package/dist/index.server.js +2 -2
  54. package/dist/index.shared.js +2 -2
  55. package/dist/ir.d.ts +1 -1
  56. package/dist/ir.js.map +1 -1
  57. package/dist/renderGenerativeUI.d.ts.map +1 -1
  58. package/dist/renderGenerativeUI.js +12 -1
  59. package/dist/renderGenerativeUI.js.map +1 -1
  60. package/dist/slack/constants.d.ts +2 -0
  61. package/dist/slack/constants.d.ts.map +1 -1
  62. package/dist/slack/constants.js +3 -1
  63. package/dist/slack/constants.js.map +1 -1
  64. package/dist/slack/decodeBlockAction.d.ts +3 -1
  65. package/dist/slack/decodeBlockAction.d.ts.map +1 -1
  66. package/dist/slack/decodeBlockAction.js +58 -7
  67. package/dist/slack/decodeBlockAction.js.map +1 -1
  68. package/dist/slack/fromSlackBlocks.d.ts.map +1 -1
  69. package/dist/slack/fromSlackBlocks.js +20 -3
  70. package/dist/slack/fromSlackBlocks.js.map +1 -1
  71. package/dist/slack/toSlackBlocks.d.ts.map +1 -1
  72. package/dist/slack/toSlackBlocks.js +187 -19
  73. package/dist/slack/toSlackBlocks.js.map +1 -1
  74. package/dist/slack/types.d.ts +4 -0
  75. package/dist/slack/types.d.ts.map +1 -1
  76. package/dist/teams/constants.d.ts +1 -1
  77. package/dist/teams/constants.d.ts.map +1 -1
  78. package/dist/teams/constants.js +1 -1
  79. package/dist/teams/constants.js.map +1 -1
  80. package/dist/teams/decodeSubmitData.d.ts +4 -0
  81. package/dist/teams/decodeSubmitData.d.ts.map +1 -1
  82. package/dist/teams/decodeSubmitData.js +6 -1
  83. package/dist/teams/decodeSubmitData.js.map +1 -1
  84. package/dist/teams/toAdaptiveCard.d.ts +5 -4
  85. package/dist/teams/toAdaptiveCard.d.ts.map +1 -1
  86. package/dist/teams/toAdaptiveCard.js +56 -17
  87. package/dist/teams/toAdaptiveCard.js.map +1 -1
  88. package/dist/teams/types.d.ts +4 -1
  89. package/dist/teams/types.d.ts.map +1 -1
  90. package/dist/vocabulary/alert.d.ts +9 -2
  91. package/dist/vocabulary/alert.d.ts.map +1 -1
  92. package/dist/vocabulary/alert.js +116 -19
  93. package/dist/vocabulary/alert.js.map +1 -1
  94. package/dist/vocabulary/collectFormValues.d.ts +2 -1
  95. package/dist/vocabulary/collectFormValues.d.ts.map +1 -1
  96. package/dist/vocabulary/collectFormValues.js +10 -3
  97. package/dist/vocabulary/collectFormValues.js.map +1 -1
  98. package/dist/vocabulary/data.d.ts +22 -5
  99. package/dist/vocabulary/data.d.ts.map +1 -1
  100. package/dist/vocabulary/data.js +278 -83
  101. package/dist/vocabulary/data.js.map +1 -1
  102. package/dist/vocabulary/dispatch.d.ts +3 -2
  103. package/dist/vocabulary/dispatch.d.ts.map +1 -1
  104. package/dist/vocabulary/dispatch.js +33 -4
  105. package/dist/vocabulary/dispatch.js.map +1 -1
  106. package/dist/vocabulary/fact.d.ts +8 -1
  107. package/dist/vocabulary/fact.d.ts.map +1 -1
  108. package/dist/vocabulary/fact.js +37 -11
  109. package/dist/vocabulary/fact.js.map +1 -1
  110. package/dist/vocabulary/form.d.ts.map +1 -1
  111. package/dist/vocabulary/form.js +3 -3
  112. package/dist/vocabulary/form.js.map +1 -1
  113. package/dist/vocabulary/formatValue.d.ts +7 -0
  114. package/dist/vocabulary/formatValue.d.ts.map +1 -0
  115. package/dist/vocabulary/formatValue.js +76 -0
  116. package/dist/vocabulary/formatValue.js.map +1 -0
  117. package/dist/vocabulary/icon.d.ts +1 -1
  118. package/dist/vocabulary/interactive.d.ts +137 -8
  119. package/dist/vocabulary/interactive.d.ts.map +1 -1
  120. package/dist/vocabulary/interactive.js +404 -103
  121. package/dist/vocabulary/interactive.js.map +1 -1
  122. package/dist/vocabulary/layout.d.ts.map +1 -1
  123. package/dist/vocabulary/layout.js +4 -4
  124. package/dist/vocabulary/layout.js.map +1 -1
  125. package/dist/vocabulary/list.js +2 -2
  126. package/dist/vocabulary/list.js.map +1 -1
  127. package/dist/vocabulary/text.d.ts +1 -1
  128. package/package.json +6 -3
  129. package/src/JSONGenerativeUI.client.tsx +84 -40
  130. package/src/JSONGenerativeUI.test.tsx +964 -68
  131. package/src/RadioGroupScope.server.test.tsx +24 -0
  132. package/src/RadioGroupScope.tsx +34 -0
  133. package/src/a2ui/BindingContext.ts +26 -0
  134. package/src/a2ui/PresentRenderer.tsx +83 -0
  135. package/src/a2ui/convert.test.ts +1982 -145
  136. package/src/a2ui/convert.ts +677 -137
  137. package/src/a2ui/dataModel.ts +69 -0
  138. package/src/a2ui/reducer.test.ts +35 -0
  139. package/src/a2ui/reducer.ts +5 -4
  140. package/src/a2ui/types.ts +9 -2
  141. package/src/a2ui/valueFunctions.test.ts +271 -0
  142. package/src/a2ui/valueFunctions.ts +523 -0
  143. package/src/actionRegistry.ts +1 -1
  144. package/src/answeredValues.test.ts +33 -0
  145. package/src/answeredValues.tsx +70 -0
  146. package/src/constants.ts +4 -0
  147. package/src/fieldReferences.test.ts +66 -0
  148. package/src/fieldReferences.ts +83 -0
  149. package/src/generativeUIToJSX.test.ts +18 -0
  150. package/src/generativeUIToJSX.ts +4 -6
  151. package/src/index.server.test.ts +106 -0
  152. package/src/ir.ts +1 -1
  153. package/src/renderGenerativeUI.test.tsx +14 -5
  154. package/src/renderGenerativeUI.tsx +30 -1
  155. package/src/slack/a2uiSlack.test.ts +198 -0
  156. package/src/slack/constants.ts +3 -0
  157. package/src/slack/decodeBlockAction.test.ts +98 -0
  158. package/src/slack/decodeBlockAction.ts +110 -17
  159. package/src/slack/fromSlackBlocks.test.ts +222 -3
  160. package/src/slack/fromSlackBlocks.ts +53 -2
  161. package/src/slack/toSlackBlocks.test.ts +682 -3
  162. package/src/slack/toSlackBlocks.ts +374 -23
  163. package/src/slack/types.ts +4 -0
  164. package/src/teams/constants.ts +1 -1
  165. package/src/teams/decodeSubmitData.test.ts +33 -0
  166. package/src/teams/decodeSubmitData.ts +9 -1
  167. package/src/teams/toAdaptiveCard.test.ts +219 -0
  168. package/src/teams/toAdaptiveCard.ts +93 -19
  169. package/src/teams/types.ts +4 -1
  170. package/src/vocabulary/alert.test.tsx +288 -7
  171. package/src/vocabulary/alert.tsx +168 -26
  172. package/src/vocabulary/collectFormValues.test.ts +28 -1
  173. package/src/vocabulary/collectFormValues.ts +25 -5
  174. package/src/vocabulary/data.test.tsx +248 -6
  175. package/src/vocabulary/data.tsx +398 -100
  176. package/src/vocabulary/dispatch.ts +63 -2
  177. package/src/vocabulary/fact.test.tsx +67 -0
  178. package/src/vocabulary/fact.tsx +40 -9
  179. package/src/vocabulary/form.tsx +7 -2
  180. package/src/vocabulary/formatValue.test.ts +72 -0
  181. package/src/vocabulary/formatValue.ts +127 -0
  182. package/src/vocabulary/index.test.ts +2 -0
  183. package/src/vocabulary/interactive.dispatch.test.tsx +167 -79
  184. package/src/vocabulary/interactive.dom.test.tsx +1699 -0
  185. package/src/vocabulary/interactive.test.tsx +60 -4
  186. package/src/vocabulary/interactive.tsx +770 -151
  187. package/src/vocabulary/layout.test.tsx +6 -4
  188. package/src/vocabulary/layout.tsx +13 -2
  189. package/src/vocabulary/list.tsx +2 -2
@@ -45,53 +45,65 @@ describe("interactiveVocabulary $action dispatch", () => {
45
45
  it("Button render attaches an onClick that fires $dispatch with the $action payload", () => {
46
46
  const handler = vi.fn();
47
47
  const registry = createActionRegistry({ purchase: handler });
48
- const out = interactiveVocabulary.Button.render({
49
- label: "Buy",
50
- $status: "done",
51
- $action: { type: "purchase", itemId: "sku-1" },
52
- $dispatch: registry.dispatch,
53
- } as ButtonRenderProps) as ReactNode;
48
+ const out = renderWithHooks(() =>
49
+ interactiveVocabulary.Button.render({
50
+ label: "Buy",
51
+ $status: "done",
52
+ $action: { type: "purchase", itemId: "sku-1" },
53
+ $dispatch: registry.dispatch,
54
+ } as ButtonRenderProps),
55
+ );
54
56
  expect(isValidElement(out)).toBe(true);
55
- const onClick = (out as { props: { onClick: () => void } }).props.onClick;
57
+ const onClick = (out as { props: { onClick: (e: object) => void } }).props
58
+ .onClick;
56
59
  expect(typeof onClick).toBe("function");
57
- onClick();
60
+ onClick({ currentTarget: {} });
58
61
  expect(handler).toHaveBeenCalledWith({
59
62
  payload: { type: "purchase", itemId: "sku-1" },
60
63
  });
61
64
  });
62
65
 
63
66
  it("Button onClick is a no-op when no $dispatch is wired (read-only render)", () => {
64
- const out = interactiveVocabulary.Button.render({
65
- label: "Buy",
66
- $status: "done",
67
- $action: { type: "purchase", itemId: "sku-1" },
68
- } as ButtonRenderProps) as ReactNode;
69
- const onClick = (out as { props: { onClick: () => void } }).props.onClick;
67
+ const out = renderWithHooks(() =>
68
+ interactiveVocabulary.Button.render({
69
+ label: "Buy",
70
+ $status: "done",
71
+ $action: { type: "purchase", itemId: "sku-1" },
72
+ } as ButtonRenderProps),
73
+ );
74
+ const onClick = (out as { props: { onClick: (e: object) => void } }).props
75
+ .onClick;
70
76
  expect(typeof onClick).toBe("function");
71
- expect(() => onClick()).not.toThrow();
77
+ expect(() => onClick({ currentTarget: {} })).not.toThrow();
72
78
  });
73
79
 
74
80
  it("Button onClick is a no-op when $action is absent", () => {
75
81
  const handler = vi.fn();
76
82
  const registry = createActionRegistry({ purchase: handler });
77
- const out = interactiveVocabulary.Button.render({
78
- label: "Go",
79
- $status: "done",
80
- $dispatch: registry.dispatch,
81
- } as ButtonRenderProps) as ReactNode;
82
- (out as { props: { onClick: () => void } }).props.onClick();
83
+ const out = renderWithHooks(() =>
84
+ interactiveVocabulary.Button.render({
85
+ label: "Go",
86
+ $status: "done",
87
+ $dispatch: registry.dispatch,
88
+ } as ButtonRenderProps),
89
+ );
90
+ (out as { props: { onClick: (e: object) => void } }).props.onClick({
91
+ currentTarget: {},
92
+ });
83
93
  expect(handler).not.toHaveBeenCalled();
84
94
  });
85
95
 
86
96
  it("Select onChange merges the selected value into $input and preserves a model-supplied $action.value", () => {
87
97
  const handler = vi.fn();
88
98
  const registry = createActionRegistry({ pick: handler });
89
- const out = interactiveVocabulary.Select.render({
90
- options: [{ label: "A", value: "a" }],
91
- $status: "done",
92
- $action: { type: "pick", value: "model-supplied" },
93
- $dispatch: registry.dispatch,
94
- }) as ReactNode;
99
+ const out = renderWithHooks(() =>
100
+ interactiveVocabulary.Select.render({
101
+ options: [{ label: "A", value: "a" }],
102
+ $status: "done",
103
+ $action: { type: "pick", value: "model-supplied" },
104
+ $dispatch: registry.dispatch,
105
+ }),
106
+ ) as ReactNode;
95
107
  const onChange = (
96
108
  out as {
97
109
  props: { onChange: (e: { currentTarget: { value: string } }) => void };
@@ -110,11 +122,13 @@ describe("interactiveVocabulary $action dispatch", () => {
110
122
  it("Input onKeyDown (Enter) merges the entered value into $input", () => {
111
123
  const handler = vi.fn();
112
124
  const registry = createActionRegistry({ submit: handler });
113
- const out = interactiveVocabulary.Input.render({
114
- $status: "done",
115
- $action: { type: "submit" },
116
- $dispatch: registry.dispatch,
117
- }) as ReactNode;
125
+ const out = renderWithHooks(() =>
126
+ interactiveVocabulary.Input.render({
127
+ $status: "done",
128
+ $action: { type: "submit" },
129
+ $dispatch: registry.dispatch,
130
+ }),
131
+ ) as ReactNode;
118
132
  const onKeyDown = (
119
133
  out as {
120
134
  props: {
@@ -136,15 +150,75 @@ describe("interactiveVocabulary $action dispatch", () => {
136
150
  });
137
151
  });
138
152
 
153
+ it.each(["text", "password", "number"] as const)(
154
+ "Input renders its %s inputType",
155
+ (inputType) => {
156
+ expect(
157
+ interactiveVocabulary.Input.properties.parse({ inputType }),
158
+ ).toEqual({ inputType });
159
+ const out = renderWithHooks(() =>
160
+ interactiveVocabulary.Input.render({
161
+ inputType,
162
+ defaultValue: "12.5",
163
+ $status: "done",
164
+ }),
165
+ );
166
+
167
+ expect(out.type).toBe("input");
168
+ expect(out.props).toMatchObject({
169
+ type: inputType,
170
+ defaultValue: "12.5",
171
+ step: inputType === "number" ? "any" : undefined,
172
+ });
173
+ },
174
+ );
175
+
176
+ it.each(["12.5", ""])(
177
+ "Input with inputType number submits %j as a string",
178
+ (value) => {
179
+ const handler = vi.fn();
180
+ const registry = createActionRegistry({ submit: handler });
181
+ const out = renderWithHooks(() =>
182
+ interactiveVocabulary.Input.render({
183
+ inputType: "number",
184
+ $status: "done",
185
+ $action: { type: "submit" },
186
+ $dispatch: registry.dispatch,
187
+ }),
188
+ ) as ReactElement<{
189
+ type: string;
190
+ step: string;
191
+ onKeyDown: (event: {
192
+ key: string;
193
+ nativeEvent: { isComposing: boolean };
194
+ currentTarget: { value: string };
195
+ }) => void;
196
+ }>;
197
+
198
+ expect(out.props.type).toBe("number");
199
+ expect(out.props.step).toBe("any");
200
+ out.props.onKeyDown({
201
+ key: "Enter",
202
+ nativeEvent: { isComposing: false },
203
+ currentTarget: { value },
204
+ });
205
+ expect(handler).toHaveBeenCalledWith({
206
+ payload: { type: "submit", $input: value },
207
+ });
208
+ },
209
+ );
210
+
139
211
  it("Input onKeyDown (Enter) defers to an ancestor form instead of firing its own $action", () => {
140
212
  const handler = vi.fn();
141
213
  const registry = createActionRegistry({ submit: handler });
142
- const out = interactiveVocabulary.Input.render({
143
- name: "email",
144
- $status: "done",
145
- $action: { type: "submit" },
146
- $dispatch: registry.dispatch,
147
- }) as ReactNode;
214
+ const out = renderWithHooks(() =>
215
+ interactiveVocabulary.Input.render({
216
+ name: "email",
217
+ $status: "done",
218
+ $action: { type: "submit" },
219
+ $dispatch: registry.dispatch,
220
+ }),
221
+ ) as ReactNode;
148
222
  const onKeyDown = (
149
223
  out as {
150
224
  props: {
@@ -179,13 +253,15 @@ describe("interactiveVocabulary $action dispatch", () => {
179
253
  it("Input multiline (textarea) Ctrl+Enter inside a form calls HTMLFormElement.prototype.requestSubmit instead of firing its own $action", () => {
180
254
  const handler = vi.fn();
181
255
  const registry = createActionRegistry({ submit: handler });
182
- const out = interactiveVocabulary.Input.render({
183
- multiline: true,
184
- name: "notes",
185
- $status: "done",
186
- $action: { type: "submit" },
187
- $dispatch: registry.dispatch,
188
- }) as ReactNode;
256
+ const out = renderWithHooks(() =>
257
+ interactiveVocabulary.Input.render({
258
+ multiline: true,
259
+ name: "notes",
260
+ $status: "done",
261
+ $action: { type: "submit" },
262
+ $dispatch: registry.dispatch,
263
+ }),
264
+ ) as ReactNode;
189
265
  const onKeyDown = (
190
266
  out as { props: { onKeyDown: (e: TextareaKeyDownEvent) => void } }
191
267
  ).props.onKeyDown;
@@ -225,12 +301,14 @@ describe("interactiveVocabulary $action dispatch", () => {
225
301
  it("Input multiline (textarea) Ctrl+Enter without a form still fires its own $action", () => {
226
302
  const handler = vi.fn();
227
303
  const registry = createActionRegistry({ submit: handler });
228
- const out = interactiveVocabulary.Input.render({
229
- multiline: true,
230
- $status: "done",
231
- $action: { type: "submit" },
232
- $dispatch: registry.dispatch,
233
- }) as ReactNode;
304
+ const out = renderWithHooks(() =>
305
+ interactiveVocabulary.Input.render({
306
+ multiline: true,
307
+ $status: "done",
308
+ $action: { type: "submit" },
309
+ $dispatch: registry.dispatch,
310
+ }),
311
+ ) as ReactNode;
234
312
  const onKeyDown = (
235
313
  out as { props: { onKeyDown: (e: TextareaKeyDownEvent) => void } }
236
314
  ).props.onKeyDown;
@@ -252,13 +330,15 @@ describe("interactiveVocabulary $action dispatch", () => {
252
330
  it("Button with submit set renders no onClick handler, so it never fires $action on click", () => {
253
331
  const handler = vi.fn();
254
332
  const registry = createActionRegistry({ purchase: handler });
255
- const out = interactiveVocabulary.Button.render({
256
- label: "Buy",
257
- submit: true,
258
- $status: "done",
259
- $action: { type: "purchase" },
260
- $dispatch: registry.dispatch,
261
- } as ButtonRenderProps & { submit: boolean }) as ReactNode;
333
+ const out = renderWithHooks(() =>
334
+ interactiveVocabulary.Button.render({
335
+ label: "Buy",
336
+ submit: true,
337
+ $status: "done",
338
+ $action: { type: "purchase" },
339
+ $dispatch: registry.dispatch,
340
+ } as ButtonRenderProps & { submit: boolean }),
341
+ );
262
342
  expect(
263
343
  (out as { props: { onClick?: unknown } }).props.onClick,
264
344
  ).toBeUndefined();
@@ -268,26 +348,31 @@ describe("interactiveVocabulary $action dispatch", () => {
268
348
  it("Button without submit keeps firing $action on click", () => {
269
349
  const handler = vi.fn();
270
350
  const registry = createActionRegistry({ purchase: handler });
271
- const out = interactiveVocabulary.Button.render({
272
- label: "Buy",
273
- $status: "done",
274
- $action: { type: "purchase" },
275
- $dispatch: registry.dispatch,
276
- } as ButtonRenderProps) as ReactNode;
277
- const onClick = (out as { props: { onClick: () => void } }).props.onClick;
278
- onClick();
351
+ const out = renderWithHooks(() =>
352
+ interactiveVocabulary.Button.render({
353
+ label: "Buy",
354
+ $status: "done",
355
+ $action: { type: "purchase" },
356
+ $dispatch: registry.dispatch,
357
+ } as ButtonRenderProps),
358
+ );
359
+ const onClick = (out as { props: { onClick: (e: object) => void } }).props
360
+ .onClick;
361
+ onClick({ currentTarget: {} });
279
362
  expect(handler).toHaveBeenCalledWith({ payload: { type: "purchase" } });
280
363
  });
281
364
 
282
365
  it("Checkbox onChange fires $dispatch with the checked boolean as $input", () => {
283
366
  const handler = vi.fn();
284
367
  const registry = createActionRegistry({ toggle: handler });
285
- const out = interactiveVocabulary.Checkbox.render({
286
- label: "Accept",
287
- $status: "done",
288
- $action: { type: "toggle" },
289
- $dispatch: registry.dispatch,
290
- }) as ReactElement;
368
+ const out = renderWithHooks(() =>
369
+ interactiveVocabulary.Checkbox.render({
370
+ label: "Accept",
371
+ $status: "done",
372
+ $action: { type: "toggle" },
373
+ $dispatch: registry.dispatch,
374
+ }),
375
+ );
291
376
  const input = (out.props as { children: ReactElement[] }).children[0];
292
377
  const onChange = (
293
378
  input as {
@@ -303,11 +388,13 @@ describe("interactiveVocabulary $action dispatch", () => {
303
388
  });
304
389
 
305
390
  it("Checkbox onChange is a no-op when no $dispatch is wired", () => {
306
- const out = interactiveVocabulary.Checkbox.render({
307
- label: "Accept",
308
- $status: "done",
309
- $action: { type: "toggle" },
310
- }) as ReactElement;
391
+ const out = renderWithHooks(() =>
392
+ interactiveVocabulary.Checkbox.render({
393
+ label: "Accept",
394
+ $status: "done",
395
+ $action: { type: "toggle" },
396
+ }),
397
+ );
311
398
  const input = (out.props as { children: ReactElement[] }).children[0];
312
399
  const onChange = (
313
400
  input as {
@@ -336,8 +423,9 @@ describe("interactiveVocabulary $action dispatch", () => {
336
423
  const options = getRadioOptions(out);
337
424
  const secondRadio = (options[1]!.props as { children: ReactElement[] })
338
425
  .children[0] as ReactElement;
339
- const onChange = (secondRadio.props as { onChange: () => void }).onChange;
340
- onChange();
426
+ const onChange = (secondRadio.props as { onChange: (e: object) => void })
427
+ .onChange;
428
+ onChange({ currentTarget: {} });
341
429
  expect(handler).toHaveBeenCalledWith({
342
430
  payload: { type: "pick", $input: "lg" },
343
431
  });