@assistant-ui/react-generative-ui 0.0.23 → 0.1.0

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 (338) hide show
  1. package/README.md +2 -0
  2. package/dist/a2ui.d.ts +1 -4
  3. package/dist/a2ui.d.ts.map +1 -1
  4. package/dist/a2ui.js +1 -4
  5. package/dist/index.d.ts +1 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -10
  8. package/dist/index.server.d.ts +1 -2
  9. package/dist/index.server.d.ts.map +1 -1
  10. package/dist/index.server.js +1 -10
  11. package/dist/ir.d.ts +1 -111
  12. package/dist/ir.d.ts.map +1 -1
  13. package/dist/ir.js +1 -175
  14. package/dist/slack.d.ts +1 -4
  15. package/dist/slack.d.ts.map +1 -1
  16. package/dist/slack.js +1 -4
  17. package/dist/teams.d.ts +1 -4
  18. package/dist/teams.d.ts.map +1 -1
  19. package/dist/teams.js +1 -4
  20. package/package.json +5 -5
  21. package/src/a2ui.ts +1 -22
  22. package/src/index.server.ts +1 -2
  23. package/src/index.test.ts +75 -0
  24. package/src/index.ts +1 -2
  25. package/src/ir.ts +1 -279
  26. package/src/slack.ts +1 -39
  27. package/src/teams.ts +1 -35
  28. package/dist/JSONGenerativeUI.client.d.ts +0 -28
  29. package/dist/JSONGenerativeUI.client.d.ts.map +0 -1
  30. package/dist/JSONGenerativeUI.client.js +0 -101
  31. package/dist/JSONGenerativeUI.client.js.map +0 -1
  32. package/dist/JSONGenerativeUI.server.d.ts +0 -21
  33. package/dist/JSONGenerativeUI.server.d.ts.map +0 -1
  34. package/dist/JSONGenerativeUI.server.js +0 -32
  35. package/dist/JSONGenerativeUI.server.js.map +0 -1
  36. package/dist/JSONGenerativeUI.shared.d.ts +0 -63
  37. package/dist/JSONGenerativeUI.shared.d.ts.map +0 -1
  38. package/dist/JSONGenerativeUI.shared.js +0 -31
  39. package/dist/JSONGenerativeUI.shared.js.map +0 -1
  40. package/dist/RadioGroupScope.d.ts +0 -6
  41. package/dist/RadioGroupScope.d.ts.map +0 -1
  42. package/dist/RadioGroupScope.js +0 -25
  43. package/dist/RadioGroupScope.js.map +0 -1
  44. package/dist/a2ui/BindingContext.d.ts +0 -11
  45. package/dist/a2ui/BindingContext.d.ts.map +0 -1
  46. package/dist/a2ui/BindingContext.js +0 -12
  47. package/dist/a2ui/BindingContext.js.map +0 -1
  48. package/dist/a2ui/PresentRenderer.d.ts +0 -11
  49. package/dist/a2ui/PresentRenderer.d.ts.map +0 -1
  50. package/dist/a2ui/PresentRenderer.js +0 -39
  51. package/dist/a2ui/PresentRenderer.js.map +0 -1
  52. package/dist/a2ui/convert.d.ts +0 -15
  53. package/dist/a2ui/convert.d.ts.map +0 -1
  54. package/dist/a2ui/convert.js +0 -728
  55. package/dist/a2ui/convert.js.map +0 -1
  56. package/dist/a2ui/dataModel.d.ts +0 -9
  57. package/dist/a2ui/dataModel.d.ts.map +0 -1
  58. package/dist/a2ui/dataModel.js +0 -44
  59. package/dist/a2ui/dataModel.js.map +0 -1
  60. package/dist/a2ui/reducer.d.ts +0 -8
  61. package/dist/a2ui/reducer.d.ts.map +0 -1
  62. package/dist/a2ui/reducer.js +0 -242
  63. package/dist/a2ui/reducer.js.map +0 -1
  64. package/dist/a2ui/snapshot.d.ts +0 -3
  65. package/dist/a2ui/snapshot.d.ts.map +0 -1
  66. package/dist/a2ui/snapshot.js +0 -33
  67. package/dist/a2ui/snapshot.js.map +0 -1
  68. package/dist/a2ui/types.d.ts +0 -88
  69. package/dist/a2ui/types.d.ts.map +0 -1
  70. package/dist/a2ui/types.js +0 -6
  71. package/dist/a2ui/types.js.map +0 -1
  72. package/dist/a2ui/valueFunctions.d.ts +0 -16
  73. package/dist/a2ui/valueFunctions.d.ts.map +0 -1
  74. package/dist/a2ui/valueFunctions.js +0 -332
  75. package/dist/a2ui/valueFunctions.js.map +0 -1
  76. package/dist/actionRegistry.d.ts +0 -29
  77. package/dist/actionRegistry.d.ts.map +0 -1
  78. package/dist/actionRegistry.js +0 -41
  79. package/dist/actionRegistry.js.map +0 -1
  80. package/dist/answeredValues.d.ts +0 -16
  81. package/dist/answeredValues.d.ts.map +0 -1
  82. package/dist/answeredValues.js +0 -37
  83. package/dist/answeredValues.js.map +0 -1
  84. package/dist/buildPresentParameters.d.ts +0 -21
  85. package/dist/buildPresentParameters.d.ts.map +0 -1
  86. package/dist/buildPresentParameters.js +0 -95
  87. package/dist/buildPresentParameters.js.map +0 -1
  88. package/dist/constants.d.ts +0 -15
  89. package/dist/constants.d.ts.map +0 -1
  90. package/dist/constants.js +0 -19
  91. package/dist/constants.js.map +0 -1
  92. package/dist/convert/boundSpec.d.ts +0 -48
  93. package/dist/convert/boundSpec.d.ts.map +0 -1
  94. package/dist/convert/boundSpec.js +0 -108
  95. package/dist/convert/boundSpec.js.map +0 -1
  96. package/dist/convert/copyBounded.d.ts +0 -25
  97. package/dist/convert/copyBounded.d.ts.map +0 -1
  98. package/dist/convert/copyBounded.js +0 -38
  99. package/dist/convert/copyBounded.js.map +0 -1
  100. package/dist/convert/isElement.d.ts +0 -3
  101. package/dist/convert/isElement.d.ts.map +0 -1
  102. package/dist/convert/isElement.js +0 -6
  103. package/dist/convert/isElement.js.map +0 -1
  104. package/dist/convert/takeRun.d.ts +0 -6
  105. package/dist/convert/takeRun.d.ts.map +0 -1
  106. package/dist/convert/takeRun.js +0 -20
  107. package/dist/convert/takeRun.js.map +0 -1
  108. package/dist/defineGenerativeComponents.d.ts +0 -29
  109. package/dist/defineGenerativeComponents.d.ts.map +0 -1
  110. package/dist/defineGenerativeComponents.js +0 -34
  111. package/dist/defineGenerativeComponents.js.map +0 -1
  112. package/dist/fieldReferences.d.ts +0 -9
  113. package/dist/fieldReferences.d.ts.map +0 -1
  114. package/dist/fieldReferences.js +0 -53
  115. package/dist/fieldReferences.js.map +0 -1
  116. package/dist/generativeUIToJSX.d.ts +0 -18
  117. package/dist/generativeUIToJSX.d.ts.map +0 -1
  118. package/dist/generativeUIToJSX.js +0 -76
  119. package/dist/generativeUIToJSX.js.map +0 -1
  120. package/dist/index.shared.d.ts +0 -15
  121. package/dist/index.shared.d.ts.map +0 -1
  122. package/dist/index.shared.js +0 -9
  123. package/dist/ir.js.map +0 -1
  124. package/dist/renderGenerativeUI.d.ts +0 -14
  125. package/dist/renderGenerativeUI.d.ts.map +0 -1
  126. package/dist/renderGenerativeUI.js +0 -83
  127. package/dist/renderGenerativeUI.js.map +0 -1
  128. package/dist/scopeSchema.d.ts +0 -6
  129. package/dist/scopeSchema.d.ts.map +0 -1
  130. package/dist/scopeSchema.js +0 -56
  131. package/dist/scopeSchema.js.map +0 -1
  132. package/dist/slack/constants.d.ts +0 -93
  133. package/dist/slack/constants.d.ts.map +0 -1
  134. package/dist/slack/constants.js +0 -146
  135. package/dist/slack/constants.js.map +0 -1
  136. package/dist/slack/decodeBlockAction.d.ts +0 -10
  137. package/dist/slack/decodeBlockAction.d.ts.map +0 -1
  138. package/dist/slack/decodeBlockAction.js +0 -129
  139. package/dist/slack/decodeBlockAction.js.map +0 -1
  140. package/dist/slack/fromSlackBlocks.d.ts +0 -27
  141. package/dist/slack/fromSlackBlocks.d.ts.map +0 -1
  142. package/dist/slack/fromSlackBlocks.js +0 -483
  143. package/dist/slack/fromSlackBlocks.js.map +0 -1
  144. package/dist/slack/toSlackBlocks.d.ts +0 -4
  145. package/dist/slack/toSlackBlocks.d.ts.map +0 -1
  146. package/dist/slack/toSlackBlocks.js +0 -901
  147. package/dist/slack/toSlackBlocks.js.map +0 -1
  148. package/dist/slack/types.d.ts +0 -204
  149. package/dist/slack/types.d.ts.map +0 -1
  150. package/dist/slack/types.js +0 -0
  151. package/dist/teams/constants.d.ts +0 -34
  152. package/dist/teams/constants.d.ts.map +0 -1
  153. package/dist/teams/constants.js +0 -68
  154. package/dist/teams/constants.js.map +0 -1
  155. package/dist/teams/decodeSubmitData.d.ts +0 -26
  156. package/dist/teams/decodeSubmitData.d.ts.map +0 -1
  157. package/dist/teams/decodeSubmitData.js +0 -70
  158. package/dist/teams/decodeSubmitData.js.map +0 -1
  159. package/dist/teams/temporalId.d.ts +0 -5
  160. package/dist/teams/temporalId.d.ts.map +0 -1
  161. package/dist/teams/temporalId.js +0 -28
  162. package/dist/teams/temporalId.js.map +0 -1
  163. package/dist/teams/toAdaptiveCard.d.ts +0 -45
  164. package/dist/teams/toAdaptiveCard.d.ts.map +0 -1
  165. package/dist/teams/toAdaptiveCard.js +0 -642
  166. package/dist/teams/toAdaptiveCard.js.map +0 -1
  167. package/dist/teams/toTeamsAttachments.d.ts +0 -10
  168. package/dist/teams/toTeamsAttachments.d.ts.map +0 -1
  169. package/dist/teams/toTeamsAttachments.js +0 -99
  170. package/dist/teams/toTeamsAttachments.js.map +0 -1
  171. package/dist/teams/types.d.ts +0 -224
  172. package/dist/teams/types.d.ts.map +0 -1
  173. package/dist/teams/types.js +0 -0
  174. package/dist/temporal.d.ts +0 -33
  175. package/dist/temporal.d.ts.map +0 -1
  176. package/dist/temporal.js +0 -152
  177. package/dist/temporal.js.map +0 -1
  178. package/dist/types.d.ts +0 -99
  179. package/dist/types.d.ts.map +0 -1
  180. package/dist/types.js +0 -0
  181. package/dist/vocabulary/alert.d.ts +0 -32
  182. package/dist/vocabulary/alert.d.ts.map +0 -1
  183. package/dist/vocabulary/alert.js +0 -160
  184. package/dist/vocabulary/alert.js.map +0 -1
  185. package/dist/vocabulary/collectFormValues.d.ts +0 -23
  186. package/dist/vocabulary/collectFormValues.d.ts.map +0 -1
  187. package/dist/vocabulary/collectFormValues.js +0 -44
  188. package/dist/vocabulary/collectFormValues.js.map +0 -1
  189. package/dist/vocabulary/data.d.ts +0 -66
  190. package/dist/vocabulary/data.d.ts.map +0 -1
  191. package/dist/vocabulary/data.js +0 -503
  192. package/dist/vocabulary/data.js.map +0 -1
  193. package/dist/vocabulary/dispatch.d.ts +0 -9
  194. package/dist/vocabulary/dispatch.d.ts.map +0 -1
  195. package/dist/vocabulary/dispatch.js +0 -50
  196. package/dist/vocabulary/dispatch.js.map +0 -1
  197. package/dist/vocabulary/fact.d.ts +0 -19
  198. package/dist/vocabulary/fact.d.ts.map +0 -1
  199. package/dist/vocabulary/fact.js +0 -51
  200. package/dist/vocabulary/fact.js.map +0 -1
  201. package/dist/vocabulary/form.d.ts +0 -11
  202. package/dist/vocabulary/form.d.ts.map +0 -1
  203. package/dist/vocabulary/form.js +0 -23
  204. package/dist/vocabulary/form.js.map +0 -1
  205. package/dist/vocabulary/formatValue.d.ts +0 -7
  206. package/dist/vocabulary/formatValue.d.ts.map +0 -1
  207. package/dist/vocabulary/formatValue.js +0 -76
  208. package/dist/vocabulary/formatValue.js.map +0 -1
  209. package/dist/vocabulary/icon.d.ts +0 -41
  210. package/dist/vocabulary/icon.d.ts.map +0 -1
  211. package/dist/vocabulary/icon.js +0 -272
  212. package/dist/vocabulary/icon.js.map +0 -1
  213. package/dist/vocabulary/index.d.ts +0 -12
  214. package/dist/vocabulary/index.d.ts.map +0 -1
  215. package/dist/vocabulary/index.js +0 -36
  216. package/dist/vocabulary/index.js.map +0 -1
  217. package/dist/vocabulary/interactive.d.ts +0 -230
  218. package/dist/vocabulary/interactive.d.ts.map +0 -1
  219. package/dist/vocabulary/interactive.js +0 -530
  220. package/dist/vocabulary/interactive.js.map +0 -1
  221. package/dist/vocabulary/layout.d.ts +0 -79
  222. package/dist/vocabulary/layout.d.ts.map +0 -1
  223. package/dist/vocabulary/layout.js +0 -139
  224. package/dist/vocabulary/layout.js.map +0 -1
  225. package/dist/vocabulary/list.d.ts +0 -14
  226. package/dist/vocabulary/list.d.ts.map +0 -1
  227. package/dist/vocabulary/list.js +0 -50
  228. package/dist/vocabulary/list.js.map +0 -1
  229. package/dist/vocabulary/media.d.ts +0 -25
  230. package/dist/vocabulary/media.d.ts.map +0 -1
  231. package/dist/vocabulary/media.js +0 -42
  232. package/dist/vocabulary/media.js.map +0 -1
  233. package/dist/vocabulary/text.d.ts +0 -57
  234. package/dist/vocabulary/text.d.ts.map +0 -1
  235. package/dist/vocabulary/text.js +0 -49
  236. package/dist/vocabulary/text.js.map +0 -1
  237. package/dist/vocabulary/toTextContent.d.ts +0 -2
  238. package/dist/vocabulary/toTextContent.d.ts.map +0 -1
  239. package/dist/vocabulary/toTextContent.js +0 -6
  240. package/dist/vocabulary/toTextContent.js.map +0 -1
  241. package/src/JSONGenerativeUI.client.tsx +0 -168
  242. package/src/JSONGenerativeUI.server.tsx +0 -39
  243. package/src/JSONGenerativeUI.shared.ts +0 -90
  244. package/src/JSONGenerativeUI.test.tsx +0 -1298
  245. package/src/RadioGroupScope.server.test.tsx +0 -24
  246. package/src/RadioGroupScope.tsx +0 -34
  247. package/src/a2ui/BindingContext.ts +0 -26
  248. package/src/a2ui/PresentRenderer.tsx +0 -83
  249. package/src/a2ui/convert.test.ts +0 -2923
  250. package/src/a2ui/convert.ts +0 -1207
  251. package/src/a2ui/dataModel.ts +0 -69
  252. package/src/a2ui/reducer.test.ts +0 -422
  253. package/src/a2ui/reducer.ts +0 -363
  254. package/src/a2ui/snapshot.test.ts +0 -52
  255. package/src/a2ui/snapshot.ts +0 -52
  256. package/src/a2ui/types.ts +0 -114
  257. package/src/a2ui/valueFunctions.test.ts +0 -271
  258. package/src/a2ui/valueFunctions.ts +0 -523
  259. package/src/actionRegistry.test.ts +0 -90
  260. package/src/actionRegistry.ts +0 -88
  261. package/src/answeredValues.test.ts +0 -33
  262. package/src/answeredValues.tsx +0 -70
  263. package/src/buildPresentParameters.test.ts +0 -246
  264. package/src/buildPresentParameters.ts +0 -130
  265. package/src/constants.ts +0 -17
  266. package/src/convert/boundSpec.test.ts +0 -192
  267. package/src/convert/boundSpec.ts +0 -134
  268. package/src/convert/copyBounded.test.ts +0 -131
  269. package/src/convert/copyBounded.ts +0 -38
  270. package/src/convert/isElement.ts +0 -6
  271. package/src/convert/takeRun.test.ts +0 -64
  272. package/src/convert/takeRun.ts +0 -19
  273. package/src/defineGenerativeComponents.ts +0 -38
  274. package/src/fieldReferences.test.ts +0 -66
  275. package/src/fieldReferences.ts +0 -83
  276. package/src/generativeUIToJSX.test.ts +0 -268
  277. package/src/generativeUIToJSX.ts +0 -152
  278. package/src/index.server.test.ts +0 -106
  279. package/src/index.shared.ts +0 -61
  280. package/src/ir.test.ts +0 -203
  281. package/src/renderGenerativeUI.test.tsx +0 -373
  282. package/src/renderGenerativeUI.tsx +0 -165
  283. package/src/scopeSchema.test.ts +0 -72
  284. package/src/scopeSchema.ts +0 -71
  285. package/src/slack/a2uiSlack.test.ts +0 -198
  286. package/src/slack/constants.ts +0 -194
  287. package/src/slack/datePicker.test.ts +0 -612
  288. package/src/slack/decodeBlockAction.test.ts +0 -233
  289. package/src/slack/decodeBlockAction.ts +0 -232
  290. package/src/slack/fromSlackBlocks.test.ts +0 -1465
  291. package/src/slack/fromSlackBlocks.ts +0 -789
  292. package/src/slack/toSlackBlocks.test.ts +0 -3065
  293. package/src/slack/toSlackBlocks.ts +0 -1818
  294. package/src/slack/types.ts +0 -264
  295. package/src/teams/constants.ts +0 -93
  296. package/src/teams/decodeSubmitData.test.ts +0 -397
  297. package/src/teams/decodeSubmitData.ts +0 -110
  298. package/src/teams/temporal.test.ts +0 -766
  299. package/src/teams/temporalId.ts +0 -56
  300. package/src/teams/toAdaptiveCard.test.ts +0 -1897
  301. package/src/teams/toAdaptiveCard.ts +0 -1057
  302. package/src/teams/toTeamsAttachments.test.ts +0 -74
  303. package/src/teams/toTeamsAttachments.ts +0 -133
  304. package/src/teams/types.ts +0 -266
  305. package/src/temporal.test.ts +0 -319
  306. package/src/temporal.ts +0 -259
  307. package/src/types.ts +0 -119
  308. package/src/vocabulary/alert.test.tsx +0 -427
  309. package/src/vocabulary/alert.tsx +0 -217
  310. package/src/vocabulary/collectFormValues.test.ts +0 -389
  311. package/src/vocabulary/collectFormValues.ts +0 -116
  312. package/src/vocabulary/data.test.tsx +0 -858
  313. package/src/vocabulary/data.tsx +0 -819
  314. package/src/vocabulary/datepicker.dom.test.tsx +0 -1148
  315. package/src/vocabulary/dispatch.ts +0 -83
  316. package/src/vocabulary/fact.test.tsx +0 -94
  317. package/src/vocabulary/fact.tsx +0 -54
  318. package/src/vocabulary/form.test.tsx +0 -113
  319. package/src/vocabulary/form.tsx +0 -39
  320. package/src/vocabulary/formatValue.test.ts +0 -72
  321. package/src/vocabulary/formatValue.ts +0 -127
  322. package/src/vocabulary/icon.test.tsx +0 -64
  323. package/src/vocabulary/icon.tsx +0 -174
  324. package/src/vocabulary/index.test.ts +0 -65
  325. package/src/vocabulary/index.ts +0 -33
  326. package/src/vocabulary/interactive.dispatch.test.tsx +0 -454
  327. package/src/vocabulary/interactive.dom.test.tsx +0 -1747
  328. package/src/vocabulary/interactive.test.tsx +0 -377
  329. package/src/vocabulary/interactive.tsx +0 -1048
  330. package/src/vocabulary/layout.test.tsx +0 -323
  331. package/src/vocabulary/layout.tsx +0 -247
  332. package/src/vocabulary/list.test.tsx +0 -222
  333. package/src/vocabulary/list.tsx +0 -50
  334. package/src/vocabulary/media.test.tsx +0 -85
  335. package/src/vocabulary/media.tsx +0 -56
  336. package/src/vocabulary/text.test.tsx +0 -102
  337. package/src/vocabulary/text.tsx +0 -60
  338. package/src/vocabulary/toTextContent.ts +0 -2
@@ -1,766 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import type { TeamsInputTime } from "../teams";
3
- import type { TeamsTemporalField } from "./types";
4
- import { decodeSubmitData } from "./decodeSubmitData";
5
- import { toAdaptiveCard } from "./toAdaptiveCard";
6
- import { encodeTemporalInputId } from "./temporalId";
7
-
8
- const picker = (inputType: "date" | "time" | "datetime", value: string) =>
9
- toAdaptiveCard({
10
- $type: "DatePicker",
11
- name: "when",
12
- label: "When",
13
- inputType,
14
- value,
15
- $action: { type: "pick", selected: { $field: "when" } },
16
- });
17
-
18
- const submit = (
19
- inputType: "date" | "time" | "datetime",
20
- value: string,
21
- overrides: Record<string, string> = {},
22
- ) => {
23
- const { card, warnings } = picker(inputType, value);
24
- const inputs = card.body.filter(
25
- (element) => element.type === "Input.Date" || element.type === "Input.Time",
26
- );
27
- const actionSet = card.body.find((element) => element.type === "ActionSet");
28
- if (actionSet?.type !== "ActionSet") throw new Error("Missing submit action");
29
- const values = Object.fromEntries(
30
- inputs.map((input) => [
31
- input.id,
32
- overrides[
33
- inputType === "datetime"
34
- ? input.type === "Input.Time"
35
- ? "when_time"
36
- : "when"
37
- : input.id
38
- ] ??
39
- input.value ??
40
- "",
41
- ]),
42
- );
43
- const decoded = decodeSubmitData({
44
- ...actionSet.actions[0]!.data,
45
- ...values,
46
- });
47
- return { inputs, action: actionSet.actions[0]!, decoded, warnings };
48
- };
49
-
50
- describe("Teams temporal DatePicker", () => {
51
- it.each(["Input", "DatePicker"])(
52
- "round trips a %s named in the datetime namespace",
53
- ($type) => {
54
- const name = "aui:datetime:when";
55
- const { card, warnings } = toAdaptiveCard({
56
- $type,
57
- name,
58
- inputType: "datetime",
59
- value: "2025-12-15T17:00:30.123456Z",
60
- $action: { type: "pick", selected: { $field: name } },
61
- });
62
- const inputs = card.body.filter((element) => "id" in element);
63
- const action = card.body.find((element) => element.type === "ActionSet");
64
- if (action?.type !== "ActionSet") throw new Error("Missing action");
65
- const values = Object.fromEntries(
66
- inputs.map((input) => [
67
- input.id,
68
- input.type === "Input.Date"
69
- ? "2025-12-16"
70
- : input.type === "Input.Time"
71
- ? "18:20"
72
- : "ordinary",
73
- ]),
74
- );
75
- const selected =
76
- $type === "DatePicker" ? "2025-12-16T18:20:30.123456Z" : "ordinary";
77
- expect(
78
- decodeSubmitData({ ...action.actions[0]!.data, ...values }),
79
- ).toEqual({
80
- type: "pick",
81
- selected,
82
- $input: { [`_${name}`]: selected },
83
- });
84
- expect(warnings).toContainEqual(
85
- expect.objectContaining({ code: "fallback", component: $type }),
86
- );
87
- },
88
- );
89
-
90
- it("round trips distinct ordinary ids in the datetime namespace", () => {
91
- const names = ["aui:datetime:x", "_aui:datetime:x", "__aui:datetime:x"];
92
- const { card, warnings } = toAdaptiveCard([
93
- {
94
- $type: "Button",
95
- label: "Save",
96
- $action: {
97
- type: "save",
98
- selected: names.map((name) => ({ $field: name })),
99
- },
100
- },
101
- ...names.map((name) => ({ $type: "Input", name })),
102
- ]);
103
- const action = card.body.find((element) => element.type === "ActionSet");
104
- if (action?.type !== "ActionSet") throw new Error("Missing action");
105
- const inputs = card.body.filter((element) => element.type === "Input.Text");
106
- expect(inputs.map((input) => input.id)).toEqual(
107
- names.map((name) => `_${name}`),
108
- );
109
- const values = Object.fromEntries(
110
- inputs.map((input, index) => [input.id, String(index)]),
111
- );
112
- expect(decodeSubmitData({ ...action.actions[0]!.data, ...values })).toEqual(
113
- {
114
- type: "save",
115
- selected: ["0", "1", "2"],
116
- $input: values,
117
- },
118
- );
119
- expect(warnings).toEqual([
120
- expect.objectContaining({ code: "fallback", component: "Input" }),
121
- expect.objectContaining({ code: "fallback", component: "Input" }),
122
- expect.objectContaining({ code: "fallback", component: "Input" }),
123
- ]);
124
- });
125
-
126
- it("resolves adjacent escaped namespace ids in submission order", () => {
127
- const { card } = toAdaptiveCard([
128
- {
129
- $type: "Button",
130
- label: "Save",
131
- $action: {
132
- type: "save",
133
- underscored: { $field: "_aui:datetime:x" },
134
- plain: { $field: "aui:datetime:x" },
135
- },
136
- },
137
- { $type: "Input", name: "_aui:datetime:x" },
138
- { $type: "Input", name: "aui:datetime:x" },
139
- ]);
140
- const action = card.body[0];
141
- if (action?.type !== "ActionSet") throw new Error("Missing action");
142
- expect(
143
- decodeSubmitData({
144
- ...action.actions[0]!.data,
145
- "__aui:datetime:x": "underscored value",
146
- "_aui:datetime:x": "plain value",
147
- }),
148
- ).toEqual({
149
- type: "save",
150
- underscored: "underscored value",
151
- plain: "plain value",
152
- $input: {
153
- "__aui:datetime:x": "underscored value",
154
- "_aui:datetime:x": "plain value",
155
- },
156
- });
157
- });
158
-
159
- it.each(["aui:datetime:x", "_aui:datetime:x", "__aui:datetime:x"])(
160
- "round trips datetime field %s through the reserved namespace",
161
- (name) => {
162
- const { card } = toAdaptiveCard({
163
- $type: "DatePicker",
164
- name,
165
- inputType: "datetime",
166
- value: "2025-12-15T17:00:30.123456Z",
167
- $action: { type: "pick", selected: { $field: name } },
168
- });
169
- const inputs = card.body.filter(
170
- (element) =>
171
- element.type === "Input.Date" || element.type === "Input.Time",
172
- );
173
- const action = card.body.find((element) => element.type === "ActionSet");
174
- if (action?.type !== "ActionSet") throw new Error("Missing action");
175
- expect(inputs.map((input) => input.id)).toEqual([
176
- `aui:datetime:date:${encodeURIComponent(`_${name}`)}`,
177
- `aui:datetime:time:${encodeURIComponent(`_${name}`)}:2025-12-15T17%3A00%3A30.123456Z`,
178
- ]);
179
- expect(
180
- decodeSubmitData({
181
- ...action.actions[0]!.data,
182
- [inputs[0]!.id]: "2025-12-16",
183
- [inputs[1]!.id]: "18:20",
184
- }),
185
- ).toEqual({
186
- type: "pick",
187
- selected: "2025-12-16T18:20:30.123456Z",
188
- $input: { [`_${name}`]: "2025-12-16T18:20:30.123456Z" },
189
- });
190
- },
191
- );
192
-
193
- describe.each([
194
- "aui:datetime:",
195
- "aui:datetime:date:when",
196
- "aui:datetime:time:when:2025-12-15T17%3A00%3A00Z",
197
- ])("ordinary input named %j", (name) => {
198
- it.each([false, true])(
199
- "stays separate from a datetime picker (input first: %s)",
200
- (inputFirst) => {
201
- const input = { $type: "Input", name };
202
- const datetime = {
203
- $type: "DatePicker",
204
- name: "when",
205
- inputType: "datetime",
206
- value: "2025-12-15T17:00:00Z",
207
- };
208
- const { card } = toAdaptiveCard([
209
- {
210
- $type: "Button",
211
- label: "Save",
212
- $action: {
213
- type: "save",
214
- ordinary: { $field: name },
215
- datetime: { $field: "when" },
216
- },
217
- },
218
- ...(inputFirst ? [input, datetime] : [datetime, input]),
219
- ]);
220
- const action = card.body[0];
221
- if (action?.type !== "ActionSet") throw new Error("Missing action");
222
- const values = Object.fromEntries(
223
- card.body.flatMap((element) => {
224
- if (element.type === "Input.Text") {
225
- expect(element.id).toBe(`_${name}`);
226
- return [[element.id, "ordinary"]];
227
- }
228
- if (element.type === "Input.Date" || element.type === "Input.Time")
229
- return [[element.id, element.value]];
230
- return [];
231
- }),
232
- );
233
- expect(
234
- decodeSubmitData({ ...action.actions[0]!.data, ...values }),
235
- ).toEqual({
236
- type: "save",
237
- ordinary: "ordinary",
238
- datetime: "2025-12-15T17:00:00Z",
239
- $input: { [`_${name}`]: "ordinary", when: "2025-12-15T17:00:00Z" },
240
- });
241
- },
242
- );
243
- });
244
-
245
- it.each([
246
- ["20:45:30", true],
247
- ["20:45:00.000001", true],
248
- ["20:45:30.000001", true],
249
- ["20:45:00.000", false],
250
- ["20:45", false],
251
- ["2025-12-15T20:45:30Z", false],
252
- ])("reports lost time maximum precision for %s", (max, dropped) => {
253
- const { warnings } = toAdaptiveCard({
254
- $type: "DatePicker",
255
- inputType: "time",
256
- max,
257
- });
258
- expect(warnings).toEqual(
259
- dropped
260
- ? [
261
- {
262
- code: "dropped",
263
- component: "DatePicker",
264
- detail:
265
- max === "20:45:00.000001"
266
- ? "Nonzero fractional seconds were dropped from the time maximum."
267
- : max === "20:45:30.000001"
268
- ? "Nonzero seconds and fractional seconds were dropped from the time maximum."
269
- : "Nonzero seconds were dropped from the time maximum.",
270
- },
271
- ]
272
- : [],
273
- );
274
- });
275
-
276
- it.each(["datetime-local", "unknown", "", null, 42])(
277
- "warns when an unsupported inputType %j falls back to date",
278
- (inputType) => {
279
- const { card, warnings } = toAdaptiveCard({
280
- $type: "DatePicker",
281
- inputType,
282
- value: "2025-12-15",
283
- });
284
- expect(card.body).toEqual([
285
- { type: "Input.Date", id: "datepicker", value: "2025-12-15" },
286
- ]);
287
- expect(warnings).toEqual([
288
- {
289
- code: "dropped",
290
- component: "DatePicker",
291
- detail:
292
- "Unsupported inputType was dropped; the picker was rendered as a date input.",
293
- },
294
- ]);
295
- },
296
- );
297
-
298
- it.each([
299
- "__proto__",
300
- "constructor",
301
- "/dates/a:b % 🗓",
302
- "lone-\uD800",
303
- "lone-\uDC00",
304
- "literal-%uD800",
305
- ])("round trips the field name %j as an own property", (name) => {
306
- const previousValue = "2025-12-15T17:00:30.123456-00:00";
307
- const metadata: TeamsTemporalField = {
308
- fieldId: name,
309
- role: "time",
310
- previousValue,
311
- };
312
- const { card } = toAdaptiveCard({
313
- $type: "DatePicker",
314
- name,
315
- inputType: "datetime",
316
- value: previousValue,
317
- $action: { type: "pick", selected: { $field: name } },
318
- });
319
- const input = card.body[1] as TeamsInputTime;
320
- const action = card.body[2];
321
- if (action?.type !== "ActionSet") throw new Error("Missing action");
322
- expect(input.id).toBe(encodeTemporalInputId(metadata));
323
- const decoded = decodeSubmitData({
324
- ...action.actions[0]!.data,
325
- [encodeTemporalInputId({ fieldId: name, role: "date" })]: "2025-12-16",
326
- [input.id]: "18:20",
327
- });
328
- expect(decoded).toEqual({
329
- type: "pick",
330
- selected: "2025-12-16T18:20:30.123456-00:00",
331
- $input: { [name]: "2025-12-16T18:20:30.123456-00:00" },
332
- });
333
- expect(Object.hasOwn(decoded?.["$input"] as object, name)).toBe(true);
334
- expect(Object.getPrototypeOf(decoded?.["$input"])).toBe(Object.prototype);
335
- });
336
-
337
- it("keeps each field's precision and offset scoped to its own date", () => {
338
- const values = [
339
- "2025-12-15T17:00:30.123456+02:00",
340
- "2025-12-15T17:00:45-05:30",
341
- "2025-12-15T17:00",
342
- ];
343
- const { card } = toAdaptiveCard(
344
- values.map((value, index) => ({
345
- $type: "DatePicker",
346
- name: `when${index}`,
347
- value,
348
- inputType: "datetime",
349
- $action: { type: "pick" },
350
- })),
351
- );
352
- const inputs = Object.fromEntries(
353
- card.body.flatMap((element) =>
354
- element.type === "Input.Date"
355
- ? [[element.id, "2025-12-16"]]
356
- : element.type === "Input.Time"
357
- ? [[element.id, "18:20"]]
358
- : [],
359
- ),
360
- );
361
- for (const element of card.body) {
362
- if (element.type !== "ActionSet") continue;
363
- expect(
364
- decodeSubmitData({ ...element.actions[0]!.data, ...inputs }),
365
- ).toEqual({
366
- type: "pick",
367
- $input: {
368
- when0: "2025-12-16T18:20:30.123456+02:00",
369
- when1: "2025-12-16T18:20:45-05:30",
370
- when2: "2025-12-16T18:20",
371
- },
372
- });
373
- }
374
- });
375
-
376
- it("carries metadata once per field and grows linearly with companion actions", () => {
377
- const sizes = [20, 40, 80].map((count) => {
378
- const { card } = toAdaptiveCard(
379
- Array.from({ length: count }, (_, index) => ({
380
- $type: "DatePicker",
381
- name: `when${String(index).padStart(3, "0")}`,
382
- inputType: "datetime",
383
- value: "2025-12-15T17:00:30.123456+02:00",
384
- $action: { type: "pick" },
385
- })),
386
- );
387
- const serialized = JSON.stringify(card);
388
- expect(
389
- card.body.filter((item) => item.type === "ActionSet"),
390
- ).toHaveLength(count);
391
- expect(serialized.match(/aui:datetime:date:/g)).toHaveLength(count);
392
- expect(serialized.match(/aui:datetime:time:/g)).toHaveLength(count);
393
- expect(
394
- serialized.match(/2025-12-15T17%3A00%3A30.123456%2B02%3A00/g),
395
- ).toHaveLength(count);
396
- expect(serialized).not.toContain('"temporal"');
397
- return new TextEncoder().encode(serialized).length;
398
- });
399
- expect(sizes[2]! - sizes[1]!).toBe(2 * (sizes[1]! - sizes[0]!));
400
- });
401
-
402
- it.each(["", "2025-12-15T17:00", "2025-12-15T17:00:00+02:00"])(
403
- "accepts date-only bounds on datetime %j",
404
- (value) => {
405
- const { card } = toAdaptiveCard({
406
- $type: "DatePicker",
407
- inputType: "datetime",
408
- value,
409
- min: "2025-12-14",
410
- max: "2025-12-20",
411
- });
412
- expect(card.body[0]).toMatchObject({
413
- type: "Input.Date",
414
- min: "2025-12-14",
415
- max: "2025-12-20",
416
- });
417
- expect(card.body[1]).not.toHaveProperty("min");
418
- expect(card.body[1]).not.toHaveProperty("max");
419
- },
420
- );
421
-
422
- it.each([
423
- ["2025-12-15T17:00:30", true],
424
- ["2025-12-15T17:00:00.000001", true],
425
- ["2025-12-15T17:00:00.000", false],
426
- ["2025-12-15T17:00", false],
427
- ["2025-12-15T17:00:30.123Z", false],
428
- ])("reports lost wall-clock precision for %s", (value, dropped) => {
429
- const { warnings } = picker("datetime", value);
430
- expect(warnings.filter((warning) => warning.code === "dropped")).toEqual(
431
- dropped
432
- ? [
433
- {
434
- code: "dropped",
435
- component: "DatePicker",
436
- detail: "Nonzero seconds were dropped from the datetime value.",
437
- },
438
- ]
439
- : [],
440
- );
441
- });
442
-
443
- it("keeps date mode as one Input.Date with its submitted value", () => {
444
- const result = submit("date", "2025-12-15");
445
- expect(result.inputs).toEqual([
446
- { type: "Input.Date", id: "when", label: "When", value: "2025-12-15" },
447
- ]);
448
- expect(result.decoded).toEqual({
449
- type: "pick",
450
- selected: "2025-12-15",
451
- $input: { when: "2025-12-15" },
452
- });
453
- });
454
-
455
- it.each([
456
- ["17:00", false],
457
- ["17:00:30", true],
458
- ])("round trips time %s as an Input.Time minute", (value, dropped) => {
459
- const result = submit("time", value);
460
- expect(result.inputs).toEqual([
461
- { type: "Input.Time", id: "when", label: "When", value: "17:00" },
462
- ]);
463
- expect(result.action.data.aui).not.toHaveProperty("temporal");
464
- expect(result.decoded).toEqual({
465
- type: "pick",
466
- selected: "17:00",
467
- $input: { when: "17:00" },
468
- });
469
- expect(
470
- result.warnings.some(
471
- (warning) =>
472
- warning.code === "dropped" && warning.component === "DatePicker",
473
- ),
474
- ).toBe(dropped);
475
- });
476
-
477
- it("rounds a time minimum up and truncates a time maximum to HH:mm", () => {
478
- const { card } = toAdaptiveCard({
479
- $type: "DatePicker",
480
- inputType: "time",
481
- value: "17:00:30",
482
- min: "08:15:30",
483
- max: "20:45:30",
484
- });
485
- expect(card.body).toEqual([
486
- {
487
- type: "Input.Time",
488
- id: "datepicker",
489
- value: "17:00",
490
- min: "08:16",
491
- max: "20:45",
492
- },
493
- ]);
494
- });
495
-
496
- it("drops a time minimum that rounds past 23:59", () => {
497
- const { card, warnings } = toAdaptiveCard({
498
- $type: "DatePicker",
499
- inputType: "time",
500
- min: "23:59:30",
501
- });
502
- expect(card.body).toEqual([{ type: "Input.Time", id: "datepicker" }]);
503
- expect(warnings).toEqual([
504
- {
505
- code: "dropped",
506
- component: "DatePicker",
507
- detail: "The time minimum rounds past 23:59 and was dropped.",
508
- },
509
- ]);
510
- });
511
-
512
- it.each([
513
- ["2025-12-15T17:00:00Z", "When time (UTC)"],
514
- ["2025-12-15T17:00:00+02:00", "When time (UTC+02:00)"],
515
- ["2025-12-15T17:00", "When time"],
516
- ["", "When time"],
517
- ])(
518
- "round trips datetime %j through the paired inputs",
519
- (value, timeLabel) => {
520
- const result = submit("datetime", value);
521
- expect(result.inputs).toEqual([
522
- {
523
- type: "Input.Date",
524
- id: "aui:datetime:date:when",
525
- label: "When",
526
- ...(value ? { value: "2025-12-15" } : {}),
527
- },
528
- {
529
- type: "Input.Time",
530
- id:
531
- value.endsWith("Z") || value.endsWith("+02:00")
532
- ? encodeTemporalInputId({
533
- fieldId: "when",
534
- role: "time",
535
- previousValue: value,
536
- })
537
- : "aui:datetime:time:when:",
538
- label: timeLabel,
539
- ...(value ? { value: "17:00" } : {}),
540
- },
541
- ]);
542
- expect(result.action.data.aui).not.toHaveProperty("temporal");
543
- expect(result.decoded).toEqual({
544
- type: "pick",
545
- selected: value,
546
- $input: { when: value },
547
- });
548
- },
549
- );
550
-
551
- it.each([
552
- ["2025-12-15T17:00", "When time"],
553
- ["2025-12-15T17:00:00Z", "When time (UTC)"],
554
- ["2025-12-15T17:00:00+02:00", "When time (UTC+02:00)"],
555
- ])("labels both datetime inputs for %s", (value, timeLabel) => {
556
- const { card } = picker("datetime", value);
557
- expect(card.body.slice(0, 2)).toMatchObject([
558
- { type: "Input.Date", label: "When" },
559
- { type: "Input.Time", label: timeLabel },
560
- ]);
561
- });
562
-
563
- it("names only the offset of an unlabeled instant", () => {
564
- const { card } = toAdaptiveCard({
565
- $type: "DatePicker",
566
- name: "when",
567
- inputType: "datetime",
568
- value: "2025-12-15T17:00:00Z",
569
- });
570
- expect(card.body).toEqual([
571
- { type: "Input.Date", id: "aui:datetime:date:when", value: "2025-12-15" },
572
- {
573
- type: "Input.Time",
574
- id: "aui:datetime:time:when:2025-12-15T17%3A00%3A00Z",
575
- label: "Time (UTC)",
576
- value: "17:00",
577
- },
578
- ]);
579
- });
580
-
581
- it("leaves both datetime inputs unlabeled for an unlabeled local value", () => {
582
- const { card } = toAdaptiveCard({
583
- $type: "DatePicker",
584
- name: "when",
585
- inputType: "datetime",
586
- value: "2025-12-15T17:00",
587
- });
588
- expect(card.body).toEqual([
589
- { type: "Input.Date", id: "aui:datetime:date:when", value: "2025-12-15" },
590
- { type: "Input.Time", id: "aui:datetime:time:when:", value: "17:00" },
591
- ]);
592
- });
593
-
594
- it("submits a newly selected empty datetime as a local wall minute", () => {
595
- const result = submit("datetime", "", {
596
- when: "2025-12-15",
597
- when_time: "17:00",
598
- });
599
- expect(result.decoded).toEqual({
600
- type: "pick",
601
- selected: "2025-12-15T17:00",
602
- $input: { when: "2025-12-15T17:00" },
603
- });
604
- });
605
-
606
- describe.each(["2025-12-15T17:00:00Z", "2025-12-15T17:00", ""])(
607
- "clearing datetime %j",
608
- (value) => {
609
- it.each([
610
- { when: "", when_time: "17:00" },
611
- { when: "2025-12-15", when_time: "" },
612
- ])(
613
- "clears the field when either datetime half is empty (%j)",
614
- (overrides) => {
615
- expect(submit("datetime", value, overrides).decoded).toEqual({
616
- type: "pick",
617
- selected: "",
618
- $input: { when: "" },
619
- });
620
- },
621
- );
622
- },
623
- );
624
-
625
- it("keeps the instant's offset spelling and fractional precision after an edit", () => {
626
- const result = submit("datetime", "2025-12-15T17:00:30.123456-00:00", {
627
- when: "2025-12-16",
628
- when_time: "18:20",
629
- });
630
- expect(result.decoded?.["selected"]).toBe(
631
- "2025-12-16T18:20:30.123456-00:00",
632
- );
633
- });
634
-
635
- it("uses the instant's date parts for bounds and leaves its time unbounded", () => {
636
- const { card } = toAdaptiveCard({
637
- $type: "DatePicker",
638
- name: "when",
639
- label: "When",
640
- inputType: "datetime",
641
- value: "2025-12-15T17:00:00+02:00",
642
- min: "2025-12-14T23:30:00Z",
643
- max: "2025-12-20T23:30:00Z",
644
- });
645
- expect(card.body).toEqual([
646
- {
647
- type: "Input.Date",
648
- id: "aui:datetime:date:when",
649
- label: "When",
650
- value: "2025-12-15",
651
- min: "2025-12-15",
652
- max: "2025-12-21",
653
- },
654
- {
655
- type: "Input.Time",
656
- id: "aui:datetime:time:when:2025-12-15T17%3A00%3A00%2B02%3A00",
657
- label: "When time (UTC+02:00)",
658
- value: "17:00",
659
- },
660
- ]);
661
- });
662
-
663
- it("resolves $field references in an action before the paired inputs", () => {
664
- const { card } = toAdaptiveCard([
665
- {
666
- $type: "Button",
667
- label: "Save",
668
- $action: { type: "save", selected: { $field: "when" } },
669
- },
670
- {
671
- $type: "DatePicker",
672
- name: "when",
673
- inputType: "datetime",
674
- value: "2025-12-15T17:00:00Z",
675
- },
676
- ]);
677
- const button = card.body[0];
678
- if (button?.type !== "ActionSet") throw new Error("Missing button");
679
- expect(
680
- decodeSubmitData({
681
- ...button.actions[0]!.data,
682
- "aui:datetime:date:when": "2025-12-16",
683
- "aui:datetime:time:when:2025-12-15T17%3A00%3A00Z": "18:20",
684
- }),
685
- ).toEqual({
686
- type: "save",
687
- selected: "2025-12-16T18:20:00Z",
688
- $input: { when: "2025-12-16T18:20:00Z" },
689
- });
690
- });
691
-
692
- it("reserves both datetime ids when another input already uses the time id", () => {
693
- const { card } = toAdaptiveCard([
694
- { $type: "Input", name: "when_time" },
695
- {
696
- $type: "DatePicker",
697
- name: "when",
698
- inputType: "datetime",
699
- value: "2025-12-15T17:00",
700
- $action: { type: "pick", selected: { $field: "when" } },
701
- },
702
- ]);
703
- expect(
704
- card.body.map((element) => ("id" in element ? element.id : undefined)),
705
- ).toEqual([
706
- "when_time",
707
- "aui:datetime:date:when",
708
- "aui:datetime:time:when:",
709
- undefined,
710
- ]);
711
- const actionSet = card.body[3];
712
- if (actionSet?.type !== "ActionSet") throw new Error("Missing action");
713
- expect(
714
- decodeSubmitData({
715
- ...actionSet.actions[0]!.data,
716
- when_time: "other",
717
- "aui:datetime:date:when": "2025-12-15",
718
- "aui:datetime:time:when:": "17:00",
719
- }),
720
- ).toEqual({
721
- type: "pick",
722
- selected: "2025-12-15T17:00",
723
- $input: { when_time: "other", when: "2025-12-15T17:00" },
724
- });
725
- });
726
-
727
- it("keeps the reserved aui key for the action envelope", () => {
728
- const { card } = toAdaptiveCard({
729
- $type: "DatePicker",
730
- name: "aui",
731
- inputType: "datetime",
732
- value: "2025-12-15T17:00",
733
- $action: { type: "pick" },
734
- });
735
- expect(
736
- card.body.map((element) => ("id" in element ? element.id : undefined)),
737
- ).toEqual(["aui:datetime:date:aui_", "aui:datetime:time:aui_:", undefined]);
738
- const actionSet = card.body[2];
739
- if (actionSet?.type !== "ActionSet") throw new Error("Missing action");
740
- expect(
741
- decodeSubmitData({
742
- ...actionSet.actions[0]!.data,
743
- "aui:datetime:date:aui_": "2025-12-15",
744
- "aui:datetime:time:aui_:": "17:00",
745
- }),
746
- ).toEqual({ type: "pick", $input: { aui_: "2025-12-15T17:00" } });
747
- });
748
-
749
- it("uses a field fallback when neither datetime input was submitted", () => {
750
- const { card } = toAdaptiveCard({
751
- $type: "DatePicker",
752
- name: "when",
753
- inputType: "datetime",
754
- $action: {
755
- type: "pick",
756
- selected: { $field: "when", fallback: "missing" },
757
- },
758
- });
759
- const actionSet = card.body[2];
760
- if (actionSet?.type !== "ActionSet") throw new Error("Missing action");
761
- expect(decodeSubmitData(actionSet.actions[0]!.data)).toEqual({
762
- type: "pick",
763
- selected: "missing",
764
- });
765
- });
766
- });