@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,858 +0,0 @@
1
- // @vitest-environment jsdom
2
-
3
- import { act } from "react";
4
- import { createRoot, type Root } from "react-dom/client";
5
- import { describe, it, expect } from "vitest";
6
- import { renderToStaticMarkup } from "react-dom/server";
7
- import { renderGenerativeUI } from "../renderGenerativeUI";
8
- import { dataVocabulary } from "./data";
9
-
10
- const render = (node: unknown) =>
11
- renderToStaticMarkup(<>{renderGenerativeUI(node, dataVocabulary)}</>);
12
-
13
- (
14
- globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
15
- ).IS_REACT_ACT_ENVIRONMENT = true;
16
-
17
- describe("dataVocabulary", () => {
18
- it("Table renders columns and rows", () => {
19
- const html = render({
20
- $type: "Table",
21
- columns: [{ label: "Name" }, { label: "Age" }],
22
- rows: [
23
- ["Ada", 36],
24
- ["Bob", 24],
25
- ],
26
- });
27
- expect(html).toContain('data-aui="table"');
28
- expect(html).toContain('<th data-aui="table-col">Name</th>');
29
- expect(html).toContain('<th data-aui="table-col">Age</th>');
30
- expect(html).toContain("<td>Ada</td>");
31
- expect(html).toContain("<td>36</td>");
32
- expect(html).toContain("<td>Bob</td>");
33
- expect(html).toContain("<td>24</td>");
34
- });
35
-
36
- it("Table renders with only rows (no header)", () => {
37
- const html = render({ $type: "Table", rows: [["only"]] });
38
- expect(html).toContain('data-aui="table"');
39
- expect(html).not.toContain("<thead>");
40
- expect(html).toContain("<td>only</td>");
41
- });
42
-
43
- it("Table formats typed columns and end-aligns numeric formats", () => {
44
- const html = render({
45
- $type: "Table",
46
- columns: [
47
- { label: "Number", format: { kind: "number", decimals: 1 } },
48
- {
49
- label: "Revenue",
50
- format: { kind: "currency", currency: "USD", decimals: 2 },
51
- },
52
- { label: "Share", format: { kind: "percent", decimals: 0 } },
53
- { label: "Date", format: { kind: "date" } },
54
- ],
55
- rows: [[1234.5, 12.4, 0.08, "2024-01-02"]],
56
- });
57
- expect(html).toContain('data-aui-align="end">Number</th>');
58
- expect(html).toContain('data-aui-align="end">Revenue</th>');
59
- expect(html).toContain('data-aui-align="end">Share</th>');
60
- expect(html).toContain('<td data-aui-align="end">1,234.5</td>');
61
- expect(html).toContain('<td data-aui-align="end">$12.40</td>');
62
- expect(html).toContain('<td data-aui-align="end">8%</td>');
63
- expect(html).toContain("<td>Jan 2, 2024</td>");
64
- });
65
-
66
- it("Table keeps wrong-typed formatted cells as raw text", () => {
67
- const html = render({
68
- $type: "Table",
69
- columns: [
70
- { label: "Number", format: { kind: "number" } },
71
- { label: "Date", format: { kind: "date" } },
72
- {
73
- label: "Currency",
74
- format: { kind: "currency", currency: "not-a-currency" },
75
- },
76
- ],
77
- rows: [["not a number", "not a date", 12]],
78
- });
79
- expect(html).toContain('<td data-aui-align="end">not a number</td>');
80
- expect(html).toContain("<td>not a date</td>");
81
- expect(html).toContain('<td data-aui-align="end">12</td>');
82
- });
83
-
84
- it("Table sorts through ascending, descending, and original row order", async () => {
85
- const container = document.createElement("div");
86
- document.body.append(container);
87
- let root: Root | undefined;
88
- try {
89
- root = createRoot(container);
90
- await act(async () => {
91
- root!.render(
92
- renderGenerativeUI(
93
- {
94
- $type: "Table",
95
- sortable: true,
96
- columns: [
97
- { label: "Name" },
98
- { label: "Score", format: { kind: "number" } },
99
- ],
100
- rows: [
101
- ["Item 10", 2],
102
- ["Item 2", 10],
103
- ["Item 1", 1],
104
- ],
105
- },
106
- dataVocabulary,
107
- ),
108
- );
109
- });
110
-
111
- const headers = container.querySelectorAll("th");
112
- const nameButton = headers[0]?.querySelector("button");
113
- const scoreButton = headers[1]?.querySelector("button");
114
- const rowNames = () =>
115
- Array.from(container.querySelectorAll("tbody tr")).map(
116
- (row) => row.querySelector("td")?.textContent,
117
- );
118
- expect(headers[0]?.hasAttribute("aria-sort")).toBe(false);
119
- expect(headers[1]?.hasAttribute("aria-sort")).toBe(false);
120
-
121
- await act(async () => scoreButton?.click());
122
- expect(headers[0]?.hasAttribute("aria-sort")).toBe(false);
123
- expect(headers[1]?.getAttribute("aria-sort")).toBe("ascending");
124
- expect(scoreButton?.textContent).toBe("Score↑");
125
- expect(rowNames()).toEqual(["Item 1", "Item 10", "Item 2"]);
126
-
127
- await act(async () => scoreButton?.click());
128
- expect(headers[1]?.getAttribute("aria-sort")).toBe("descending");
129
- expect(scoreButton?.textContent).toBe("Score↓");
130
- expect(rowNames()).toEqual(["Item 2", "Item 10", "Item 1"]);
131
-
132
- await act(async () => scoreButton?.click());
133
- expect(headers[1]?.hasAttribute("aria-sort")).toBe(false);
134
- expect(scoreButton?.textContent).toBe("Score");
135
- expect(rowNames()).toEqual(["Item 10", "Item 2", "Item 1"]);
136
-
137
- await act(async () => nameButton?.click());
138
- expect(headers[0]?.getAttribute("aria-sort")).toBe("ascending");
139
- expect(rowNames()).toEqual(["Item 1", "Item 2", "Item 10"]);
140
- } finally {
141
- await act(async () => root?.unmount());
142
- container.remove();
143
- }
144
- });
145
-
146
- it("Table ignores malformed collections instead of throwing", () => {
147
- expect(
148
- render({
149
- $type: "Table",
150
- columns: "not-an-array",
151
- rows: [["kept", null, {}, false], null],
152
- }),
153
- ).toBe(
154
- '<table data-aui="table"><tbody><tr><td>kept</td><td></td><td></td><td>false</td></tr></tbody></table>',
155
- );
156
-
157
- expect(render({ $type: "Table", columns: [null], rows: [null] })).toBe(
158
- '<table data-aui="table"></table>',
159
- );
160
-
161
- expect(
162
- render({
163
- $type: "Table",
164
- columns: [null, { label: "Name" }],
165
- rows: "not-an-array",
166
- }),
167
- ).toBe(
168
- '<table data-aui="table"><thead><tr><th data-aui="table-col"></th><th data-aui="table-col">Name</th></tr></thead></table>',
169
- );
170
- });
171
-
172
- it("Markdown renders the value in a div", () => {
173
- expect(render({ $type: "Markdown", value: "# hi" })).toBe(
174
- '<div data-aui="markdown"># hi</div>',
175
- );
176
- });
177
-
178
- it("Markdown renders partial value while streaming", () => {
179
- expect(
180
- renderToStaticMarkup(
181
- <>
182
- {renderGenerativeUI(
183
- { $type: "Markdown", value: "partial" },
184
- dataVocabulary,
185
- { status: "streaming" },
186
- )}
187
- </>,
188
- ),
189
- ).toBe('<div data-aui="markdown">partial</div>');
190
- });
191
-
192
- it("Markdown ignores malformed streaming values", () => {
193
- expect(
194
- renderToStaticMarkup(
195
- <>
196
- {renderGenerativeUI(
197
- { $type: "Markdown", value: { unexpected: true } },
198
- dataVocabulary,
199
- { status: "streaming" },
200
- )}
201
- </>,
202
- ),
203
- ).toBe('<div data-aui="markdown"></div>');
204
- });
205
-
206
- it("Chart bar variant renders one rect per data point", () => {
207
- const html = render({
208
- $type: "Chart",
209
- variant: "bar",
210
- data: [{ value: 1 }, { value: 2 }, { value: 3 }],
211
- color: "#f00",
212
- });
213
- expect(html).toContain('data-aui="chart"');
214
- expect(html).toContain('data-aui-variant="bar"');
215
- expect(html).toContain('data-aui-color="#f00"');
216
- expect((html.match(/<rect/g) ?? []).length).toBe(3);
217
- expect(html).not.toContain("<polyline");
218
- expect(html).not.toContain("data-aui-data");
219
- });
220
-
221
- it("Chart line variant renders a single polyline through all points", () => {
222
- const html = render({
223
- $type: "Chart",
224
- variant: "line",
225
- data: [{ value: 0 }, { value: 20 }, { value: 40 }],
226
- });
227
- expect((html.match(/<polyline/g) ?? []).length).toBe(1);
228
- expect(html).not.toContain("<rect");
229
- expect(html).toContain('points="0,40 50,20 100,0"');
230
- });
231
-
232
- it("Chart line variant with a single data point renders a circle, not a polyline", () => {
233
- const html = render({
234
- $type: "Chart",
235
- variant: "line",
236
- data: [{ value: 20 }],
237
- });
238
- expect(html).not.toContain("<polyline");
239
- expect((html.match(/<circle/g) ?? []).length).toBe(2);
240
- expect(html).toContain('data-aui="chart-point"');
241
- expect(html).toContain('cx="50"');
242
- expect(html).toContain('cy="0"');
243
- expect(html).toContain('r="2"');
244
- });
245
-
246
- it("Chart sparkline variant is structurally identical to line, differentiated by the variant attribute", () => {
247
- const html = render({
248
- $type: "Chart",
249
- variant: "sparkline",
250
- data: [{ value: 0 }, { value: 20 }, { value: 40 }],
251
- });
252
- expect((html.match(/<polyline/g) ?? []).length).toBe(1);
253
- expect(html).toContain('data-aui-variant="sparkline"');
254
- expect(html).toContain('points="0,40 50,20 100,0"');
255
- });
256
-
257
- it("Chart renders an aria-label describing the variant and point count", () => {
258
- const html = render({
259
- $type: "Chart",
260
- variant: "bar",
261
- data: [{ value: 1 }, { value: 2 }],
262
- });
263
- expect(html).toContain('aria-label="bar chart with 2 data points"');
264
- });
265
-
266
- it("Chart with empty data renders an empty svg without throwing", () => {
267
- expect(() =>
268
- render({ $type: "Chart", variant: "bar", data: [] }),
269
- ).not.toThrow();
270
- const html = render({ $type: "Chart", variant: "line", data: [] });
271
- expect(html).toContain('data-aui="chart"');
272
- expect(html).not.toContain("<rect");
273
- expect(html).not.toContain("<polyline");
274
- expect(html).toContain('aria-label="line chart with 0 data points"');
275
- });
276
-
277
- it("Chart with missing data renders an empty svg without throwing", () => {
278
- expect(() => render({ $type: "Chart", variant: "bar" })).not.toThrow();
279
- const html = render({ $type: "Chart", variant: "bar" });
280
- expect(html).toContain('data-aui="chart"');
281
- expect(html).not.toContain("<rect");
282
- });
283
-
284
- it("Chart maps negative bars from the zero line and non-finite values to 0", () => {
285
- const html = render({
286
- $type: "Chart",
287
- variant: "bar",
288
- data: [
289
- { label: "Loss", value: -10 },
290
- { value: Number.NaN },
291
- { label: "Gain", value: 10 },
292
- ],
293
- });
294
- expect((html.match(/<rect/g) ?? []).length).toBe(3);
295
- expect(html).toContain('data-aui="chart-zero"');
296
- expect(html).toContain('y1="20" y2="20"');
297
- expect(html).toMatch(/<rect[^>]*y="20"[^>]*height="20"/);
298
- expect(html).toContain('height="0"');
299
- expect(html).toContain("<title>Loss: -10</title>");
300
- expect(html).toContain("<title>2: 0</title>");
301
- });
302
-
303
- it("Chart omits the zero baseline when values stay on one side of zero", () => {
304
- const positive = render({
305
- $type: "Chart",
306
- variant: "bar",
307
- data: [{ value: 1 }],
308
- });
309
- const negative = render({
310
- $type: "Chart",
311
- variant: "bar",
312
- data: [{ value: -1 }],
313
- });
314
- expect(positive).not.toContain('data-aui="chart-zero"');
315
- expect(negative).not.toContain('data-aui="chart-zero"');
316
- });
317
-
318
- it("Chart with all-zero data renders flat baseline marks", () => {
319
- const barHtml = render({
320
- $type: "Chart",
321
- variant: "bar",
322
- data: [{ value: 0 }, { value: 0 }],
323
- });
324
- expect((barHtml.match(/height="0"/g) ?? []).length).toBe(2);
325
-
326
- const lineHtml = render({
327
- $type: "Chart",
328
- variant: "line",
329
- data: [{ value: 0 }, { value: 0 }, { value: 0 }],
330
- });
331
- expect(lineHtml).toContain('points="0,40 50,40 100,40"');
332
- });
333
- });
334
-
335
- describe("dataVocabulary Chart multi-series", () => {
336
- it("wraps each series' marks in its own data-aui=chart-series group, indexed", () => {
337
- const html = render({
338
- $type: "Chart",
339
- variant: "line",
340
- series: [
341
- { label: "A", data: [{ value: 1 }, { value: 2 }] },
342
- { label: "B", data: [{ value: 3 }, { value: 4 }] },
343
- { label: "C", data: [{ value: 5 }, { value: 6 }] },
344
- ],
345
- });
346
- expect((html.match(/data-aui="chart-series"/g) ?? []).length).toBe(3);
347
- expect(html).toContain('data-aui-series="0"');
348
- expect(html).toContain('data-aui-series="1"');
349
- expect(html).toContain('data-aui-series="2"');
350
- expect((html.match(/<polyline/g) ?? []).length).toBe(3);
351
- });
352
-
353
- it("falls back to the data prop when series is an empty array", () => {
354
- const withEmptySeries = render({
355
- $type: "Chart",
356
- variant: "bar",
357
- series: [],
358
- data: [{ value: 1 }, { value: 2 }],
359
- });
360
- const withDataOnly = render({
361
- $type: "Chart",
362
- variant: "bar",
363
- data: [{ value: 1 }, { value: 2 }],
364
- });
365
- expect((withEmptySeries.match(/<rect/g) ?? []).length).toBe(2);
366
- expect(withDataOnly).toContain('data-aui-variant="bar"');
367
- });
368
-
369
- it("series takes precedence over data when both are present", () => {
370
- const html = render({
371
- $type: "Chart",
372
- variant: "bar",
373
- data: [{ value: 999 }],
374
- series: [{ data: [{ value: 1 }, { value: 2 }] }],
375
- });
376
- expect((html.match(/<rect/g) ?? []).length).toBe(2);
377
- });
378
-
379
- it("pads a shorter series with 0-valued points instead of throwing or misaligning", () => {
380
- expect(() =>
381
- render({
382
- $type: "Chart",
383
- variant: "bar",
384
- series: [
385
- { data: [{ value: 10 }, { value: 20 }, { value: 30 }] },
386
- { data: [{ value: 5 }] },
387
- ],
388
- }),
389
- ).not.toThrow();
390
- const html = render({
391
- $type: "Chart",
392
- variant: "bar",
393
- series: [
394
- { data: [{ value: 10 }, { value: 20 }, { value: 30 }] },
395
- { data: [{ value: 5 }] },
396
- ],
397
- });
398
- expect((html.match(/<rect/g) ?? []).length).toBe(6);
399
- expect(html).toContain('aria-label="bar chart with 3 data points"');
400
- });
401
-
402
- it("treats a missing series data array as empty rather than throwing", () => {
403
- expect(() =>
404
- render({
405
- $type: "Chart",
406
- variant: "bar",
407
- series: [{ label: "A" }, { data: [{ value: 1 }] }] as never,
408
- }),
409
- ).not.toThrow();
410
- });
411
- });
412
-
413
- describe("dataVocabulary Chart stacking", () => {
414
- it("stacked bars accumulate per x across series (known input)", () => {
415
- const html = render({
416
- $type: "Chart",
417
- variant: "bar",
418
- stacked: true,
419
- series: [
420
- { data: [{ value: 10 }] },
421
- { data: [{ value: 20 }] },
422
- { data: [{ value: 30 }] },
423
- ],
424
- });
425
- // total at the single x is 60 -> scaleMax (unstacked axis, plain max) = 60.
426
- // series 0: height (10/60)*40 ≈ 6.667 at y = 40 - 0 - 6.667 ≈ 33.333
427
- // series 1: height (20/60)*40 ≈ 13.333 at y = 40 - 6.667 - 13.333 ≈ 20
428
- // series 2: height (30/60)*40 = 20 at y = 40 - 20 - 20 = 0
429
- expect(html).toContain('y="33.333333333333336"');
430
- expect(html).toContain('height="6.666666666666666"');
431
- expect(html).toContain('y="20.000000000000004"');
432
- expect(html).toContain('height="13.333333333333332"');
433
- expect(html).toContain('y="0" width="80" height="20"');
434
- });
435
-
436
- it("unstacked (overlaid) bars all start from the baseline regardless of series order", () => {
437
- const html = render({
438
- $type: "Chart",
439
- variant: "bar",
440
- series: [{ data: [{ value: 10 }] }, { data: [{ value: 20 }] }],
441
- });
442
- expect((html.match(/y="0"/g) ?? []).length).toBe(1); // only the tallest bar touches y=0
443
- });
444
-
445
- it("stacks positive and negative bars independently from zero", () => {
446
- const html = render({
447
- $type: "Chart",
448
- variant: "bar",
449
- stacked: true,
450
- series: [
451
- { data: [{ value: -1 }, { value: 1 }] },
452
- { data: [{ value: -1 }, { value: 1 }] },
453
- ],
454
- });
455
- expect(html).toContain('x="5" y="20" width="40" height="10"');
456
- expect(html).toContain('x="5" y="30" width="40" height="10"');
457
- expect(html).toContain('x="55" y="10" width="40" height="10"');
458
- expect(html).toContain('x="55" y="0" width="40" height="10"');
459
- });
460
-
461
- it("stacked areas build cumulative top/bottom polygons per series", () => {
462
- const html = render({
463
- $type: "Chart",
464
- variant: "area",
465
- stacked: true,
466
- series: [
467
- { data: [{ value: 10 }, { value: 10 }] },
468
- { data: [{ value: 10 }, { value: 10 }] },
469
- ],
470
- });
471
- expect((html.match(/<polygon/g) ?? []).length).toBe(2);
472
- // series 0 (bottom band): top=10/20*40=20 -> y=20, bottom=0 -> y=40
473
- expect(html).toContain('points="0,20 100,20 100,40 0,40"');
474
- // series 1 (top band): top=20/20*40=40 -> y=0, bottom=10/20*40=20 -> y=20
475
- expect(html).toContain('points="0,0 100,0 100,20 0,20"');
476
- });
477
-
478
- it("stacks positive and negative areas independently from zero", () => {
479
- const html = render({
480
- $type: "Chart",
481
- variant: "area",
482
- stacked: true,
483
- series: [
484
- { data: [{ value: -1 }, { value: 1 }] },
485
- { data: [{ value: -1 }, { value: 1 }] },
486
- ],
487
- });
488
- expect(html).toContain('points="0,30 100,10 100,20 0,20"');
489
- expect(html).toContain('points="0,40 100,0 100,10 0,30"');
490
- });
491
-
492
- it("stacked bars with showAxis keep the bottom segment on the baseline, abut exactly, and share one denominator with the ticks (totals crossing a tick boundary)", () => {
493
- const html = render({
494
- $type: "Chart",
495
- variant: "bar",
496
- stacked: true,
497
- showAxis: true,
498
- series: [
499
- { data: [{ value: 50 }] },
500
- { data: [{ value: 30 }] },
501
- { data: [{ value: 25 }] },
502
- ],
503
- });
504
- // total 105 crosses the 50*10^n boundary, so niceMax must fall back to the
505
- // next power of ten (200) instead of resolving to an under-sized 100.
506
- expect(html).toContain("<div>200</div><div>150</div>");
507
- expect(html).toContain("<div>100</div><div>50</div><div>0</div>");
508
-
509
- // series 0 (bottom): height=(50/200)*40=10 at y=40-0-10=30; bottom edge
510
- // y+height=40, the chart baseline.
511
- expect(html).toContain('y="30" width="80" height="10"');
512
- // series 1: height=(30/200)*40=6 at y=40-10-6=24; abuts series 0's y=30
513
- // exactly (24+6=30, no gap or overlap).
514
- expect(html).toContain('y="24" width="80" height="6"');
515
- // series 2 (top): height=(25/200)*40=5 at y=40-16-5=19; abuts series 1's
516
- // y=24 exactly (19+5=24). The stack top (y=19) equals
517
- // 40-(105/200)*40=19, the same denominator (200) the ticks above use.
518
- expect(html).toContain('y="19" width="80" height="5"');
519
- });
520
-
521
- it("stacked areas keep the bottom band's baseline at the chart bottom regardless of scale", () => {
522
- const html = render({
523
- $type: "Chart",
524
- variant: "area",
525
- stacked: true,
526
- showAxis: true,
527
- series: [
528
- { data: [{ value: 60 }, { value: 60 }] },
529
- { data: [{ value: 60 }, { value: 60 }] },
530
- ],
531
- });
532
- // total 120 crosses the same tick boundary (niceMax -> 200); the bottom
533
- // band's own bottom edge must still sit at the chart baseline (y=40)
534
- // regardless of scale, since it is built from an all-zero `bottoms` array.
535
- expect(html).toContain("<div>200</div>");
536
- expect(html).toContain('points="0,28 100,28 100,40 0,40"');
537
- });
538
- });
539
-
540
- describe("dataVocabulary Chart area variant", () => {
541
- it("renders an unstroked filled polygon plus a top-edge polyline", () => {
542
- const html = render({
543
- $type: "Chart",
544
- variant: "area",
545
- data: [{ value: 0 }, { value: 40 }],
546
- });
547
- expect(html).toContain("<polygon");
548
- expect(html).toContain('fill="currentColor"');
549
- expect(html).toContain('fill-opacity="0.25"');
550
- expect(html).not.toMatch(/<polygon[^>]*stroke=/);
551
- // fill polygon: top line 0,40 -> 100,0 then closed back down to the baseline in reverse.
552
- expect(html).toContain('points="0,40 100,0 100,40 0,40"');
553
- // the visible edge is a separate polyline over the top points only.
554
- expect(html).toMatch(
555
- /<polyline[^>]*points="0,40 100,0"[^>]*stroke="currentColor"/,
556
- );
557
- expect(html).toMatch(/<polyline[^>]*vector-effect="non-scaling-stroke"/);
558
- });
559
-
560
- it("fills negative and positive values to the zero line", () => {
561
- const html = render({
562
- $type: "Chart",
563
- variant: "area",
564
- data: [
565
- { label: "Loss", value: -10 },
566
- { label: "Gain", value: 10 },
567
- ],
568
- });
569
- expect(html).toContain('data-aui="chart-zero"');
570
- expect(html).toContain('points="0,40 100,0 100,20 0,20"');
571
- expect((html.match(/data-aui="chart-point"/g) ?? []).length).toBe(2);
572
- expect(html).toContain("<title>Loss: -10</title>");
573
- });
574
-
575
- it("renders a circle instead of a degenerate polygon for a single point", () => {
576
- const html = render({
577
- $type: "Chart",
578
- variant: "area",
579
- data: [{ value: 20 }],
580
- });
581
- expect(html).not.toContain("<polygon");
582
- expect((html.match(/<circle/g) ?? []).length).toBe(2);
583
- });
584
-
585
- it("renders an empty series group without throwing when data is empty", () => {
586
- expect(() =>
587
- render({ $type: "Chart", variant: "area", data: [] }),
588
- ).not.toThrow();
589
- const html = render({ $type: "Chart", variant: "area", data: [] });
590
- expect(html).not.toContain("<polygon");
591
- expect(html).not.toContain("<circle");
592
- });
593
- });
594
-
595
- describe("dataVocabulary Chart showAxis niceMax ticks", () => {
596
- it("derives a round niceMax and renders 5 ticks from it down to 0", () => {
597
- const html = render({
598
- $type: "Chart",
599
- variant: "bar",
600
- showAxis: true,
601
- data: [{ value: 42 }],
602
- });
603
- // niceMax(42): exponent=1, candidates 10,20,50 -> 50 is the first >= 42.
604
- expect(html).toContain('data-aui="chart-frame"');
605
- expect(html).toContain('data-aui="chart-ticks"');
606
- const ticksMatch = html.match(
607
- /<div data-aui="chart-ticks">(.*?)<\/div><svg/,
608
- );
609
- expect(ticksMatch).not.toBeNull();
610
- const ticksHtml = ticksMatch![1]!;
611
- expect((ticksHtml.match(/<div>/g) ?? []).length).toBe(5);
612
- expect(ticksHtml).toContain("<div>50</div>");
613
- expect(ticksHtml).toContain("<div>37.5</div>");
614
- expect(ticksHtml).toContain("<div>25</div>");
615
- expect(ticksHtml).toContain("<div>12.5</div>");
616
- expect(ticksHtml).toContain("<div>0</div>");
617
- });
618
-
619
- it("scales marks against niceMax (not the raw max) when the axis is shown", () => {
620
- const html = render({
621
- $type: "Chart",
622
- variant: "bar",
623
- showAxis: true,
624
- data: [{ value: 42 }],
625
- });
626
- // niceMax = 50, so height = (42/50)*40 = 33.6, not (42/42)*40 = 40.
627
- expect(html).toContain('height="33.6"');
628
- });
629
-
630
- it("uses round ticks that include 0 for a domain crossing zero", () => {
631
- const html = render({
632
- $type: "Chart",
633
- variant: "line",
634
- showAxis: true,
635
- data: [{ value: -3 }, { value: 5 }],
636
- });
637
- expect(html).toContain("<div>10</div>");
638
- expect(html).toContain("<div>5</div>");
639
- expect(html).toContain("<div>0</div>");
640
- expect(html).toContain("<div>-5</div>");
641
- expect(html).toContain("<div>-10</div>");
642
- expect(html).toContain('data-aui="chart-zero"');
643
- });
644
-
645
- it("stays a bare svg (no frame) when neither showAxis nor showLegend is set, even with new props", () => {
646
- const html = render({
647
- $type: "Chart",
648
- variant: "bar",
649
- stacked: true,
650
- series: [{ data: [{ value: 1 }] }],
651
- });
652
- expect(html).not.toContain("chart-frame");
653
- expect(html.startsWith("<svg")).toBe(true);
654
- });
655
- });
656
-
657
- describe("dataVocabulary Chart legend and x-labels", () => {
658
- it("renders an x-label row from the first series' point labels", () => {
659
- const html = render({
660
- $type: "Chart",
661
- variant: "line",
662
- showAxis: true,
663
- data: [
664
- { label: "Mon", value: 1 },
665
- { label: "Tue", value: 2 },
666
- ],
667
- });
668
- expect(html).toContain('data-aui="chart-xlabels">');
669
- expect(html).toContain("<div>Mon</div>");
670
- expect(html).toContain("<div>Tue</div>");
671
- });
672
-
673
- it("omits the legend row when showLegend is not set", () => {
674
- const html = render({
675
- $type: "Chart",
676
- variant: "line",
677
- showAxis: true,
678
- series: [{ label: "A", data: [{ value: 1 }] }],
679
- });
680
- expect(html).not.toContain("chart-legend");
681
- });
682
-
683
- it("renders a legend item per series with an indexed swatch when showLegend is set", () => {
684
- const html = render({
685
- $type: "Chart",
686
- variant: "line",
687
- showLegend: true,
688
- series: [
689
- { label: "Revenue", data: [{ value: 1 }] },
690
- { label: "Cost", data: [{ value: 2 }] },
691
- ],
692
- });
693
- expect((html.match(/data-aui="chart-legend-item"/g) ?? []).length).toBe(2);
694
- expect(html).toContain(
695
- '<span data-aui="chart-legend-item" data-aui-series="0">',
696
- );
697
- expect(html).toContain(
698
- '<span data-aui="chart-legend-item" data-aui-series="1">',
699
- );
700
- expect(html).toContain("Revenue");
701
- expect(html).toContain("Cost");
702
- expect((html.match(/data-aui="chart-legend-swatch"/g) ?? []).length).toBe(
703
- 2,
704
- );
705
- });
706
-
707
- it("adds titles to line points with and without a series label", () => {
708
- const standalone = render({
709
- $type: "Chart",
710
- variant: "line",
711
- data: [{ label: "Jan", value: 1.5 }, { value: 2 }],
712
- });
713
- const named = render({
714
- $type: "Chart",
715
- variant: "line",
716
- series: [{ label: "Revenue", data: [{ label: "Jan", value: 1.5 }] }],
717
- });
718
- expect(standalone).toContain("<title>Jan: 1.5</title>");
719
- expect(standalone).toContain("<title>2: 2</title>");
720
- expect(named).toContain("<title>Revenue, Jan: 1.5</title>");
721
- });
722
-
723
- it("emits per-series colors on chart series and legend items", () => {
724
- const html = render({
725
- $type: "Chart",
726
- variant: "bar",
727
- showLegend: true,
728
- series: [
729
- { label: "Revenue", color: "white", data: [{ value: 1 }] },
730
- { label: "Cost", color: "alpha-70", data: [{ value: 2 }] },
731
- ],
732
- });
733
- expect(html).toContain(
734
- '<g data-aui="chart-series" data-aui-series="0" data-aui-color="white">',
735
- );
736
- expect(html).toContain(
737
- '<span data-aui="chart-legend-item" data-aui-series="1" data-aui-color="alpha-70">',
738
- );
739
- });
740
-
741
- it("uses the single-series color for its legend swatch", () => {
742
- const html = render({
743
- $type: "Chart",
744
- variant: "bar",
745
- color: "white",
746
- showLegend: true,
747
- data: [{ value: 1 }],
748
- });
749
- expect(html).toContain(
750
- '<span data-aui="chart-legend-item" data-aui-series="0" data-aui-color="white"><span data-aui="chart-legend-swatch"></span>',
751
- );
752
- });
753
-
754
- it("falls back to a positional label when a series has no label", () => {
755
- const html = render({
756
- $type: "Chart",
757
- variant: "line",
758
- showLegend: true,
759
- series: [{ data: [{ value: 1 }] }],
760
- });
761
- expect(html).toContain("Series 1");
762
- });
763
-
764
- it("area series overlay from the baseline when not stacked", () => {
765
- const html = render({
766
- $type: "Chart",
767
- variant: "area",
768
- series: [
769
- { data: [{ value: 10 }, { value: 20 }] },
770
- { data: [{ value: 30 }, { value: 40 }] },
771
- ],
772
- });
773
- const points = [...html.matchAll(/<polygon[^>]*points="([^"]+)"/g)].map(
774
- (m) => m[1]!,
775
- );
776
- expect(points).toHaveLength(2);
777
- for (const polygon of points) {
778
- expect(polygon).toContain("100,40");
779
- expect(polygon).not.toContain("-");
780
- }
781
- expect(points[1]).toContain("100,0");
782
- });
783
-
784
- it("area series accumulate when stacked", () => {
785
- const html = render({
786
- $type: "Chart",
787
- variant: "area",
788
- stacked: true,
789
- series: [
790
- { data: [{ value: 10 }, { value: 20 }] },
791
- { data: [{ value: 30 }, { value: 40 }] },
792
- ],
793
- });
794
- const points = [...html.matchAll(/<polygon[^>]*points="([^"]+)"/g)].map(
795
- (m) => m[1]!,
796
- );
797
- expect(points).toHaveLength(2);
798
- expect(points[1]).toContain("100,0");
799
- expect(points[1]).not.toContain("100,40");
800
- });
801
-
802
- it("line charts ignore stacked for the scale", () => {
803
- const html = render({
804
- $type: "Chart",
805
- variant: "line",
806
- stacked: true,
807
- series: [{ data: [{ value: 100 }] }, { data: [{ value: 100 }] }],
808
- });
809
- expect((html.match(/cy="0"/g) ?? []).length).toBe(4);
810
- });
811
-
812
- it("cycles series colors past the palette size", () => {
813
- const html = render({
814
- $type: "Chart",
815
- variant: "bar",
816
- showLegend: true,
817
- series: Array.from({ length: 6 }, (_, i) => ({
818
- label: `S${i + 1}`,
819
- data: [{ value: i + 1 }],
820
- })),
821
- });
822
- expect(html).toContain('<g data-aui="chart-series" data-aui-series="4">');
823
- expect(
824
- (html.match(/<g data-aui="chart-series" data-aui-series="0">/g) ?? [])
825
- .length,
826
- ).toBe(2);
827
- expect(
828
- (
829
- html.match(
830
- /<span data-aui="chart-legend-item" data-aui-series="0">/g,
831
- ) ?? []
832
- ).length,
833
- ).toBe(2);
834
- });
835
-
836
- it("legend without axis renders no ticks or x labels", () => {
837
- const html = render({
838
- $type: "Chart",
839
- variant: "bar",
840
- showLegend: true,
841
- series: [{ label: "A", data: [{ value: 1, label: "Jan" }] }],
842
- });
843
- expect(html).toContain('data-aui="chart-legend"');
844
- expect(html).not.toContain('data-aui="chart-ticks"');
845
- expect(html).not.toContain('data-aui="chart-xlabels"');
846
- });
847
-
848
- it("axis renders ticks and x labels", () => {
849
- const html = render({
850
- $type: "Chart",
851
- variant: "bar",
852
- showAxis: true,
853
- series: [{ label: "A", data: [{ value: 1, label: "Jan" }] }],
854
- });
855
- expect(html).toContain('data-aui="chart-ticks"');
856
- expect(html).toContain('data-aui="chart-xlabels"');
857
- });
858
- });