@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.
- package/README.md +2 -0
- package/dist/a2ui.d.ts +1 -4
- package/dist/a2ui.d.ts.map +1 -1
- package/dist/a2ui.js +1 -4
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -10
- package/dist/index.server.d.ts +1 -2
- package/dist/index.server.d.ts.map +1 -1
- package/dist/index.server.js +1 -10
- package/dist/ir.d.ts +1 -111
- package/dist/ir.d.ts.map +1 -1
- package/dist/ir.js +1 -175
- package/dist/slack.d.ts +1 -4
- package/dist/slack.d.ts.map +1 -1
- package/dist/slack.js +1 -4
- package/dist/teams.d.ts +1 -4
- package/dist/teams.d.ts.map +1 -1
- package/dist/teams.js +1 -4
- package/package.json +5 -5
- package/src/a2ui.ts +1 -22
- package/src/index.server.ts +1 -2
- package/src/index.test.ts +75 -0
- package/src/index.ts +1 -2
- package/src/ir.ts +1 -279
- package/src/slack.ts +1 -39
- package/src/teams.ts +1 -35
- package/dist/JSONGenerativeUI.client.d.ts +0 -28
- package/dist/JSONGenerativeUI.client.d.ts.map +0 -1
- package/dist/JSONGenerativeUI.client.js +0 -101
- package/dist/JSONGenerativeUI.client.js.map +0 -1
- package/dist/JSONGenerativeUI.server.d.ts +0 -21
- package/dist/JSONGenerativeUI.server.d.ts.map +0 -1
- package/dist/JSONGenerativeUI.server.js +0 -32
- package/dist/JSONGenerativeUI.server.js.map +0 -1
- package/dist/JSONGenerativeUI.shared.d.ts +0 -63
- package/dist/JSONGenerativeUI.shared.d.ts.map +0 -1
- package/dist/JSONGenerativeUI.shared.js +0 -31
- package/dist/JSONGenerativeUI.shared.js.map +0 -1
- package/dist/RadioGroupScope.d.ts +0 -6
- package/dist/RadioGroupScope.d.ts.map +0 -1
- package/dist/RadioGroupScope.js +0 -25
- package/dist/RadioGroupScope.js.map +0 -1
- package/dist/a2ui/BindingContext.d.ts +0 -11
- package/dist/a2ui/BindingContext.d.ts.map +0 -1
- package/dist/a2ui/BindingContext.js +0 -12
- package/dist/a2ui/BindingContext.js.map +0 -1
- package/dist/a2ui/PresentRenderer.d.ts +0 -11
- package/dist/a2ui/PresentRenderer.d.ts.map +0 -1
- package/dist/a2ui/PresentRenderer.js +0 -39
- package/dist/a2ui/PresentRenderer.js.map +0 -1
- package/dist/a2ui/convert.d.ts +0 -15
- package/dist/a2ui/convert.d.ts.map +0 -1
- package/dist/a2ui/convert.js +0 -728
- package/dist/a2ui/convert.js.map +0 -1
- package/dist/a2ui/dataModel.d.ts +0 -9
- package/dist/a2ui/dataModel.d.ts.map +0 -1
- package/dist/a2ui/dataModel.js +0 -44
- package/dist/a2ui/dataModel.js.map +0 -1
- package/dist/a2ui/reducer.d.ts +0 -8
- package/dist/a2ui/reducer.d.ts.map +0 -1
- package/dist/a2ui/reducer.js +0 -242
- package/dist/a2ui/reducer.js.map +0 -1
- package/dist/a2ui/snapshot.d.ts +0 -3
- package/dist/a2ui/snapshot.d.ts.map +0 -1
- package/dist/a2ui/snapshot.js +0 -33
- package/dist/a2ui/snapshot.js.map +0 -1
- package/dist/a2ui/types.d.ts +0 -88
- package/dist/a2ui/types.d.ts.map +0 -1
- package/dist/a2ui/types.js +0 -6
- package/dist/a2ui/types.js.map +0 -1
- package/dist/a2ui/valueFunctions.d.ts +0 -16
- package/dist/a2ui/valueFunctions.d.ts.map +0 -1
- package/dist/a2ui/valueFunctions.js +0 -332
- package/dist/a2ui/valueFunctions.js.map +0 -1
- package/dist/actionRegistry.d.ts +0 -29
- package/dist/actionRegistry.d.ts.map +0 -1
- package/dist/actionRegistry.js +0 -41
- package/dist/actionRegistry.js.map +0 -1
- package/dist/answeredValues.d.ts +0 -16
- package/dist/answeredValues.d.ts.map +0 -1
- package/dist/answeredValues.js +0 -37
- package/dist/answeredValues.js.map +0 -1
- package/dist/buildPresentParameters.d.ts +0 -21
- package/dist/buildPresentParameters.d.ts.map +0 -1
- package/dist/buildPresentParameters.js +0 -95
- package/dist/buildPresentParameters.js.map +0 -1
- package/dist/constants.d.ts +0 -15
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js +0 -19
- package/dist/constants.js.map +0 -1
- package/dist/convert/boundSpec.d.ts +0 -48
- package/dist/convert/boundSpec.d.ts.map +0 -1
- package/dist/convert/boundSpec.js +0 -108
- package/dist/convert/boundSpec.js.map +0 -1
- package/dist/convert/copyBounded.d.ts +0 -25
- package/dist/convert/copyBounded.d.ts.map +0 -1
- package/dist/convert/copyBounded.js +0 -38
- package/dist/convert/copyBounded.js.map +0 -1
- package/dist/convert/isElement.d.ts +0 -3
- package/dist/convert/isElement.d.ts.map +0 -1
- package/dist/convert/isElement.js +0 -6
- package/dist/convert/isElement.js.map +0 -1
- package/dist/convert/takeRun.d.ts +0 -6
- package/dist/convert/takeRun.d.ts.map +0 -1
- package/dist/convert/takeRun.js +0 -20
- package/dist/convert/takeRun.js.map +0 -1
- package/dist/defineGenerativeComponents.d.ts +0 -29
- package/dist/defineGenerativeComponents.d.ts.map +0 -1
- package/dist/defineGenerativeComponents.js +0 -34
- package/dist/defineGenerativeComponents.js.map +0 -1
- package/dist/fieldReferences.d.ts +0 -9
- package/dist/fieldReferences.d.ts.map +0 -1
- package/dist/fieldReferences.js +0 -53
- package/dist/fieldReferences.js.map +0 -1
- package/dist/generativeUIToJSX.d.ts +0 -18
- package/dist/generativeUIToJSX.d.ts.map +0 -1
- package/dist/generativeUIToJSX.js +0 -76
- package/dist/generativeUIToJSX.js.map +0 -1
- package/dist/index.shared.d.ts +0 -15
- package/dist/index.shared.d.ts.map +0 -1
- package/dist/index.shared.js +0 -9
- package/dist/ir.js.map +0 -1
- package/dist/renderGenerativeUI.d.ts +0 -14
- package/dist/renderGenerativeUI.d.ts.map +0 -1
- package/dist/renderGenerativeUI.js +0 -83
- package/dist/renderGenerativeUI.js.map +0 -1
- package/dist/scopeSchema.d.ts +0 -6
- package/dist/scopeSchema.d.ts.map +0 -1
- package/dist/scopeSchema.js +0 -56
- package/dist/scopeSchema.js.map +0 -1
- package/dist/slack/constants.d.ts +0 -93
- package/dist/slack/constants.d.ts.map +0 -1
- package/dist/slack/constants.js +0 -146
- package/dist/slack/constants.js.map +0 -1
- package/dist/slack/decodeBlockAction.d.ts +0 -10
- package/dist/slack/decodeBlockAction.d.ts.map +0 -1
- package/dist/slack/decodeBlockAction.js +0 -129
- package/dist/slack/decodeBlockAction.js.map +0 -1
- package/dist/slack/fromSlackBlocks.d.ts +0 -27
- package/dist/slack/fromSlackBlocks.d.ts.map +0 -1
- package/dist/slack/fromSlackBlocks.js +0 -483
- package/dist/slack/fromSlackBlocks.js.map +0 -1
- package/dist/slack/toSlackBlocks.d.ts +0 -4
- package/dist/slack/toSlackBlocks.d.ts.map +0 -1
- package/dist/slack/toSlackBlocks.js +0 -901
- package/dist/slack/toSlackBlocks.js.map +0 -1
- package/dist/slack/types.d.ts +0 -204
- package/dist/slack/types.d.ts.map +0 -1
- package/dist/slack/types.js +0 -0
- package/dist/teams/constants.d.ts +0 -34
- package/dist/teams/constants.d.ts.map +0 -1
- package/dist/teams/constants.js +0 -68
- package/dist/teams/constants.js.map +0 -1
- package/dist/teams/decodeSubmitData.d.ts +0 -26
- package/dist/teams/decodeSubmitData.d.ts.map +0 -1
- package/dist/teams/decodeSubmitData.js +0 -70
- package/dist/teams/decodeSubmitData.js.map +0 -1
- package/dist/teams/temporalId.d.ts +0 -5
- package/dist/teams/temporalId.d.ts.map +0 -1
- package/dist/teams/temporalId.js +0 -28
- package/dist/teams/temporalId.js.map +0 -1
- package/dist/teams/toAdaptiveCard.d.ts +0 -45
- package/dist/teams/toAdaptiveCard.d.ts.map +0 -1
- package/dist/teams/toAdaptiveCard.js +0 -642
- package/dist/teams/toAdaptiveCard.js.map +0 -1
- package/dist/teams/toTeamsAttachments.d.ts +0 -10
- package/dist/teams/toTeamsAttachments.d.ts.map +0 -1
- package/dist/teams/toTeamsAttachments.js +0 -99
- package/dist/teams/toTeamsAttachments.js.map +0 -1
- package/dist/teams/types.d.ts +0 -224
- package/dist/teams/types.d.ts.map +0 -1
- package/dist/teams/types.js +0 -0
- package/dist/temporal.d.ts +0 -33
- package/dist/temporal.d.ts.map +0 -1
- package/dist/temporal.js +0 -152
- package/dist/temporal.js.map +0 -1
- package/dist/types.d.ts +0 -99
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -0
- package/dist/vocabulary/alert.d.ts +0 -32
- package/dist/vocabulary/alert.d.ts.map +0 -1
- package/dist/vocabulary/alert.js +0 -160
- package/dist/vocabulary/alert.js.map +0 -1
- package/dist/vocabulary/collectFormValues.d.ts +0 -23
- package/dist/vocabulary/collectFormValues.d.ts.map +0 -1
- package/dist/vocabulary/collectFormValues.js +0 -44
- package/dist/vocabulary/collectFormValues.js.map +0 -1
- package/dist/vocabulary/data.d.ts +0 -66
- package/dist/vocabulary/data.d.ts.map +0 -1
- package/dist/vocabulary/data.js +0 -503
- package/dist/vocabulary/data.js.map +0 -1
- package/dist/vocabulary/dispatch.d.ts +0 -9
- package/dist/vocabulary/dispatch.d.ts.map +0 -1
- package/dist/vocabulary/dispatch.js +0 -50
- package/dist/vocabulary/dispatch.js.map +0 -1
- package/dist/vocabulary/fact.d.ts +0 -19
- package/dist/vocabulary/fact.d.ts.map +0 -1
- package/dist/vocabulary/fact.js +0 -51
- package/dist/vocabulary/fact.js.map +0 -1
- package/dist/vocabulary/form.d.ts +0 -11
- package/dist/vocabulary/form.d.ts.map +0 -1
- package/dist/vocabulary/form.js +0 -23
- package/dist/vocabulary/form.js.map +0 -1
- package/dist/vocabulary/formatValue.d.ts +0 -7
- package/dist/vocabulary/formatValue.d.ts.map +0 -1
- package/dist/vocabulary/formatValue.js +0 -76
- package/dist/vocabulary/formatValue.js.map +0 -1
- package/dist/vocabulary/icon.d.ts +0 -41
- package/dist/vocabulary/icon.d.ts.map +0 -1
- package/dist/vocabulary/icon.js +0 -272
- package/dist/vocabulary/icon.js.map +0 -1
- package/dist/vocabulary/index.d.ts +0 -12
- package/dist/vocabulary/index.d.ts.map +0 -1
- package/dist/vocabulary/index.js +0 -36
- package/dist/vocabulary/index.js.map +0 -1
- package/dist/vocabulary/interactive.d.ts +0 -230
- package/dist/vocabulary/interactive.d.ts.map +0 -1
- package/dist/vocabulary/interactive.js +0 -530
- package/dist/vocabulary/interactive.js.map +0 -1
- package/dist/vocabulary/layout.d.ts +0 -79
- package/dist/vocabulary/layout.d.ts.map +0 -1
- package/dist/vocabulary/layout.js +0 -139
- package/dist/vocabulary/layout.js.map +0 -1
- package/dist/vocabulary/list.d.ts +0 -14
- package/dist/vocabulary/list.d.ts.map +0 -1
- package/dist/vocabulary/list.js +0 -50
- package/dist/vocabulary/list.js.map +0 -1
- package/dist/vocabulary/media.d.ts +0 -25
- package/dist/vocabulary/media.d.ts.map +0 -1
- package/dist/vocabulary/media.js +0 -42
- package/dist/vocabulary/media.js.map +0 -1
- package/dist/vocabulary/text.d.ts +0 -57
- package/dist/vocabulary/text.d.ts.map +0 -1
- package/dist/vocabulary/text.js +0 -49
- package/dist/vocabulary/text.js.map +0 -1
- package/dist/vocabulary/toTextContent.d.ts +0 -2
- package/dist/vocabulary/toTextContent.d.ts.map +0 -1
- package/dist/vocabulary/toTextContent.js +0 -6
- package/dist/vocabulary/toTextContent.js.map +0 -1
- package/src/JSONGenerativeUI.client.tsx +0 -168
- package/src/JSONGenerativeUI.server.tsx +0 -39
- package/src/JSONGenerativeUI.shared.ts +0 -90
- package/src/JSONGenerativeUI.test.tsx +0 -1298
- package/src/RadioGroupScope.server.test.tsx +0 -24
- package/src/RadioGroupScope.tsx +0 -34
- package/src/a2ui/BindingContext.ts +0 -26
- package/src/a2ui/PresentRenderer.tsx +0 -83
- package/src/a2ui/convert.test.ts +0 -2923
- package/src/a2ui/convert.ts +0 -1207
- package/src/a2ui/dataModel.ts +0 -69
- package/src/a2ui/reducer.test.ts +0 -422
- package/src/a2ui/reducer.ts +0 -363
- package/src/a2ui/snapshot.test.ts +0 -52
- package/src/a2ui/snapshot.ts +0 -52
- package/src/a2ui/types.ts +0 -114
- package/src/a2ui/valueFunctions.test.ts +0 -271
- package/src/a2ui/valueFunctions.ts +0 -523
- package/src/actionRegistry.test.ts +0 -90
- package/src/actionRegistry.ts +0 -88
- package/src/answeredValues.test.ts +0 -33
- package/src/answeredValues.tsx +0 -70
- package/src/buildPresentParameters.test.ts +0 -246
- package/src/buildPresentParameters.ts +0 -130
- package/src/constants.ts +0 -17
- package/src/convert/boundSpec.test.ts +0 -192
- package/src/convert/boundSpec.ts +0 -134
- package/src/convert/copyBounded.test.ts +0 -131
- package/src/convert/copyBounded.ts +0 -38
- package/src/convert/isElement.ts +0 -6
- package/src/convert/takeRun.test.ts +0 -64
- package/src/convert/takeRun.ts +0 -19
- package/src/defineGenerativeComponents.ts +0 -38
- package/src/fieldReferences.test.ts +0 -66
- package/src/fieldReferences.ts +0 -83
- package/src/generativeUIToJSX.test.ts +0 -268
- package/src/generativeUIToJSX.ts +0 -152
- package/src/index.server.test.ts +0 -106
- package/src/index.shared.ts +0 -61
- package/src/ir.test.ts +0 -203
- package/src/renderGenerativeUI.test.tsx +0 -373
- package/src/renderGenerativeUI.tsx +0 -165
- package/src/scopeSchema.test.ts +0 -72
- package/src/scopeSchema.ts +0 -71
- package/src/slack/a2uiSlack.test.ts +0 -198
- package/src/slack/constants.ts +0 -194
- package/src/slack/datePicker.test.ts +0 -612
- package/src/slack/decodeBlockAction.test.ts +0 -233
- package/src/slack/decodeBlockAction.ts +0 -232
- package/src/slack/fromSlackBlocks.test.ts +0 -1465
- package/src/slack/fromSlackBlocks.ts +0 -789
- package/src/slack/toSlackBlocks.test.ts +0 -3065
- package/src/slack/toSlackBlocks.ts +0 -1818
- package/src/slack/types.ts +0 -264
- package/src/teams/constants.ts +0 -93
- package/src/teams/decodeSubmitData.test.ts +0 -397
- package/src/teams/decodeSubmitData.ts +0 -110
- package/src/teams/temporal.test.ts +0 -766
- package/src/teams/temporalId.ts +0 -56
- package/src/teams/toAdaptiveCard.test.ts +0 -1897
- package/src/teams/toAdaptiveCard.ts +0 -1057
- package/src/teams/toTeamsAttachments.test.ts +0 -74
- package/src/teams/toTeamsAttachments.ts +0 -133
- package/src/teams/types.ts +0 -266
- package/src/temporal.test.ts +0 -319
- package/src/temporal.ts +0 -259
- package/src/types.ts +0 -119
- package/src/vocabulary/alert.test.tsx +0 -427
- package/src/vocabulary/alert.tsx +0 -217
- package/src/vocabulary/collectFormValues.test.ts +0 -389
- package/src/vocabulary/collectFormValues.ts +0 -116
- package/src/vocabulary/data.test.tsx +0 -858
- package/src/vocabulary/data.tsx +0 -819
- package/src/vocabulary/datepicker.dom.test.tsx +0 -1148
- package/src/vocabulary/dispatch.ts +0 -83
- package/src/vocabulary/fact.test.tsx +0 -94
- package/src/vocabulary/fact.tsx +0 -54
- package/src/vocabulary/form.test.tsx +0 -113
- package/src/vocabulary/form.tsx +0 -39
- package/src/vocabulary/formatValue.test.ts +0 -72
- package/src/vocabulary/formatValue.ts +0 -127
- package/src/vocabulary/icon.test.tsx +0 -64
- package/src/vocabulary/icon.tsx +0 -174
- package/src/vocabulary/index.test.ts +0 -65
- package/src/vocabulary/index.ts +0 -33
- package/src/vocabulary/interactive.dispatch.test.tsx +0 -454
- package/src/vocabulary/interactive.dom.test.tsx +0 -1747
- package/src/vocabulary/interactive.test.tsx +0 -377
- package/src/vocabulary/interactive.tsx +0 -1048
- package/src/vocabulary/layout.test.tsx +0 -323
- package/src/vocabulary/layout.tsx +0 -247
- package/src/vocabulary/list.test.tsx +0 -222
- package/src/vocabulary/list.tsx +0 -50
- package/src/vocabulary/media.test.tsx +0 -85
- package/src/vocabulary/media.tsx +0 -56
- package/src/vocabulary/text.test.tsx +0 -102
- package/src/vocabulary/text.tsx +0 -60
- 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
|
-
});
|