@agent-native/toolkit 0.9.1 → 0.10.1
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/agent-native.eject.json +71 -0
- package/dist/chat-history/ChatHistoryRail.d.ts +15 -1
- package/dist/chat-history/ChatHistoryRail.d.ts.map +1 -1
- package/dist/chat-history/ChatHistoryRail.js +26 -13
- package/dist/chat-history/ChatHistoryRail.js.map +1 -1
- package/dist/chat-history/ChatHistoryRail.spec.js +77 -1
- package/dist/chat-history/ChatHistoryRail.spec.js.map +1 -1
- package/dist/chat-history/index.d.ts +2 -1
- package/dist/chat-history/index.d.ts.map +1 -1
- package/dist/chat-history/index.js +2 -1
- package/dist/chat-history/index.js.map +1 -1
- package/dist/chat-history/useChatHistoryRailController.d.ts +26 -0
- package/dist/chat-history/useChatHistoryRailController.d.ts.map +1 -0
- package/dist/chat-history/useChatHistoryRailController.js +36 -0
- package/dist/chat-history/useChatHistoryRailController.js.map +1 -0
- package/dist/chat-history/useChatHistoryRailController.spec.d.ts +2 -0
- package/dist/chat-history/useChatHistoryRailController.spec.d.ts.map +1 -0
- package/dist/chat-history/useChatHistoryRailController.spec.js +101 -0
- package/dist/chat-history/useChatHistoryRailController.spec.js.map +1 -0
- package/dist/chat-history.css +2 -0
- package/dist/composer/ComposerPlusMenu.js +1 -1
- package/dist/composer/ComposerPlusMenu.js.map +1 -1
- package/dist/composer/PromptComposer.d.ts +1 -0
- package/dist/composer/PromptComposer.d.ts.map +1 -1
- package/dist/composer/PromptComposer.js +26 -5
- package/dist/composer/PromptComposer.js.map +1 -1
- package/dist/composer/PromptComposer.spec.js +8 -1
- package/dist/composer/PromptComposer.spec.js.map +1 -1
- package/dist/composer/RealtimeVoiceMode.d.ts.map +1 -1
- package/dist/composer/RealtimeVoiceMode.js +56 -11
- package/dist/composer/RealtimeVoiceMode.js.map +1 -1
- package/dist/composer/RealtimeVoiceMode.spec.js +59 -7
- package/dist/composer/RealtimeVoiceMode.spec.js.map +1 -1
- package/dist/composer/useRealtimeVoiceMode.d.ts +7 -0
- package/dist/composer/useRealtimeVoiceMode.d.ts.map +1 -1
- package/dist/composer/useRealtimeVoiceMode.js +110 -6
- package/dist/composer/useRealtimeVoiceMode.js.map +1 -1
- package/dist/composer/useRealtimeVoiceMode.spec.js +74 -2
- package/dist/composer/useRealtimeVoiceMode.spec.js.map +1 -1
- package/dist/conformance/__fixtures__/css-in-js-adapter.d.ts +7 -0
- package/dist/conformance/__fixtures__/css-in-js-adapter.d.ts.map +1 -0
- package/dist/conformance/__fixtures__/css-in-js-adapter.js +84 -0
- package/dist/conformance/__fixtures__/css-in-js-adapter.js.map +1 -0
- package/dist/conformance/conformance.spec.d.ts +2 -0
- package/dist/conformance/conformance.spec.d.ts.map +1 -0
- package/dist/conformance/conformance.spec.js +87 -0
- package/dist/conformance/conformance.spec.js.map +1 -0
- package/dist/conformance/index.d.ts +4 -0
- package/dist/conformance/index.d.ts.map +1 -0
- package/dist/conformance/index.js +4 -0
- package/dist/conformance/index.js.map +1 -0
- package/dist/conformance/runner.d.ts +12 -0
- package/dist/conformance/runner.d.ts.map +1 -0
- package/dist/conformance/runner.js +515 -0
- package/dist/conformance/runner.js.map +1 -0
- package/dist/conformance/types.d.ts +26 -0
- package/dist/conformance/types.d.ts.map +1 -0
- package/dist/conformance/types.js +13 -0
- package/dist/conformance/types.js.map +1 -0
- package/dist/conformance/version.d.ts +6 -0
- package/dist/conformance/version.d.ts.map +1 -0
- package/dist/conformance/version.js +14 -0
- package/dist/conformance/version.js.map +1 -0
- package/dist/design-system/components.d.ts +64 -0
- package/dist/design-system/components.d.ts.map +1 -0
- package/dist/design-system/components.js +50 -0
- package/dist/design-system/components.js.map +1 -0
- package/dist/design-system/context.d.ts +11 -0
- package/dist/design-system/context.d.ts.map +1 -0
- package/dist/design-system/context.js +17 -0
- package/dist/design-system/context.js.map +1 -0
- package/dist/design-system/default-adapter.d.ts +3 -0
- package/dist/design-system/default-adapter.d.ts.map +1 -0
- package/dist/design-system/default-adapter.js +166 -0
- package/dist/design-system/default-adapter.js.map +1 -0
- package/dist/design-system/default-adapter.spec.d.ts +2 -0
- package/dist/design-system/default-adapter.spec.d.ts.map +1 -0
- package/dist/design-system/default-adapter.spec.js +96 -0
- package/dist/design-system/default-adapter.spec.js.map +1 -0
- package/dist/design-system/definition.d.ts +9 -0
- package/dist/design-system/definition.d.ts.map +1 -0
- package/dist/design-system/definition.js +4 -0
- package/dist/design-system/definition.js.map +1 -0
- package/dist/design-system/design-system.spec.d.ts +2 -0
- package/dist/design-system/design-system.spec.d.ts.map +1 -0
- package/dist/design-system/design-system.spec.js +121 -0
- package/dist/design-system/design-system.spec.js.map +1 -0
- package/dist/design-system/error-boundary.d.ts +18 -0
- package/dist/design-system/error-boundary.d.ts.map +1 -0
- package/dist/design-system/error-boundary.js +16 -0
- package/dist/design-system/error-boundary.js.map +1 -0
- package/dist/design-system/index.d.ts +8 -0
- package/dist/design-system/index.d.ts.map +1 -0
- package/dist/design-system/index.js +8 -0
- package/dist/design-system/index.js.map +1 -0
- package/dist/design-system/theme.d.ts +30 -0
- package/dist/design-system/theme.d.ts.map +1 -0
- package/dist/design-system/theme.js +95 -0
- package/dist/design-system/theme.js.map +1 -0
- package/dist/design-system/theme.spec.d.ts +2 -0
- package/dist/design-system/theme.spec.d.ts.map +1 -0
- package/dist/design-system/theme.spec.js +43 -0
- package/dist/design-system/theme.spec.js.map +1 -0
- package/dist/design-system/types.d.ts +288 -0
- package/dist/design-system/types.d.ts.map +1 -0
- package/dist/design-system/types.js +2 -0
- package/dist/design-system/types.js.map +1 -0
- package/dist/editor/SharedRichEditor.d.ts.map +1 -1
- package/dist/editor/SharedRichEditor.js +3 -2
- package/dist/editor/SharedRichEditor.js.map +1 -1
- package/dist/editor/SlashCommandMenu.d.ts +8 -0
- package/dist/editor/SlashCommandMenu.d.ts.map +1 -1
- package/dist/editor/SlashCommandMenu.js +6 -0
- package/dist/editor/SlashCommandMenu.js.map +1 -1
- package/dist/editor/SlashCommandMenu.spec.d.ts +2 -0
- package/dist/editor/SlashCommandMenu.spec.d.ts.map +1 -0
- package/dist/editor/SlashCommandMenu.spec.js +20 -0
- package/dist/editor/SlashCommandMenu.spec.js.map +1 -0
- package/dist/editor/index.d.ts +1 -1
- package/dist/editor/index.d.ts.map +1 -1
- package/dist/editor/index.js +1 -1
- package/dist/editor/index.js.map +1 -1
- package/dist/provider.d.ts +4 -3
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +46 -4
- package/dist/provider.js.map +1 -1
- package/dist/styles.css +52 -23
- package/dist/ui/button.d.ts +5 -0
- package/dist/ui/button.d.ts.map +1 -1
- package/dist/ui/button.js +25 -4
- package/dist/ui/button.js.map +1 -1
- package/dist/ui/dialog.d.ts +4 -0
- package/dist/ui/dialog.d.ts.map +1 -1
- package/dist/ui/dialog.js +2 -2
- package/dist/ui/dialog.js.map +1 -1
- package/dist/ui/popover.d.ts +1 -0
- package/dist/ui/popover.d.ts.map +1 -1
- package/dist/ui/popover.js +2 -2
- package/dist/ui/popover.js.map +1 -1
- package/dist/ui/select.d.ts +3 -1
- package/dist/ui/select.d.ts.map +1 -1
- package/dist/ui/select.js +1 -1
- package/dist/ui/select.js.map +1 -1
- package/dist/ui/sidebar.d.ts +6 -2
- package/dist/ui/sidebar.d.ts.map +1 -1
- package/dist/ui/sidebar.js +12 -15
- package/dist/ui/sidebar.js.map +1 -1
- package/dist/ui/tooltip.d.ts +3 -1
- package/dist/ui/tooltip.d.ts.map +1 -1
- package/dist/ui/tooltip.js +2 -2
- package/dist/ui/tooltip.js.map +1 -1
- package/package.json +31 -1
- package/src/chat-history/ChatHistoryRail.spec.tsx +174 -0
- package/src/chat-history/ChatHistoryRail.tsx +100 -39
- package/src/chat-history/index.ts +9 -0
- package/src/chat-history/useChatHistoryRailController.spec.tsx +131 -0
- package/src/chat-history/useChatHistoryRailController.ts +76 -0
- package/src/chat-history.css +2 -0
- package/src/composer/ComposerPlusMenu.tsx +11 -8
- package/src/composer/PromptComposer.spec.ts +12 -1
- package/src/composer/PromptComposer.tsx +32 -7
- package/src/composer/RealtimeVoiceMode.spec.tsx +117 -9
- package/src/composer/RealtimeVoiceMode.tsx +119 -31
- package/src/composer/useRealtimeVoiceMode.spec.ts +101 -1
- package/src/composer/useRealtimeVoiceMode.tsx +124 -9
- package/src/conformance/__fixtures__/css-in-js-adapter.tsx +401 -0
- package/src/conformance/conformance.spec.tsx +143 -0
- package/src/conformance/index.ts +3 -0
- package/src/conformance/runner.tsx +862 -0
- package/src/conformance/types.ts +45 -0
- package/src/conformance/version.ts +22 -0
- package/src/design-system/components.tsx +146 -0
- package/src/design-system/context.tsx +31 -0
- package/src/design-system/default-adapter.spec.tsx +143 -0
- package/src/design-system/default-adapter.tsx +915 -0
- package/src/design-system/definition.ts +14 -0
- package/src/design-system/design-system.spec.tsx +183 -0
- package/src/design-system/error-boundary.tsx +37 -0
- package/src/design-system/index.ts +7 -0
- package/src/design-system/theme.spec.ts +61 -0
- package/src/design-system/theme.ts +145 -0
- package/src/design-system/types.ts +363 -0
- package/src/editor/SharedRichEditor.tsx +13 -2
- package/src/editor/SlashCommandMenu.spec.ts +33 -0
- package/src/editor/SlashCommandMenu.tsx +20 -0
- package/src/editor/index.ts +2 -0
- package/src/provider.tsx +98 -5
- package/src/styles.css +52 -23
- package/src/ui/button.tsx +95 -6
- package/src/ui/dialog.tsx +48 -23
- package/src/ui/popover.tsx +16 -2
- package/src/ui/select.tsx +7 -3
- package/src/ui/sidebar.tsx +47 -48
- package/src/ui/tooltip.tsx +7 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conformance.spec.d.ts","sourceRoot":"","sources":["../../src/conformance/conformance.spec.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// @vitest-environment happy-dom
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { defaultDesignSystemComponents } from "../design-system/default-adapter.js";
|
|
5
|
+
import { DESIGN_SYSTEM_CONTRACT_VERSION, } from "../design-system/types.js";
|
|
6
|
+
import { cssInJsFixtureAdapter } from "./__fixtures__/css-in-js-adapter.js";
|
|
7
|
+
import { assertDesignSystemConformance, DESIGN_SYSTEM_CONFORMANCE_CHECKS, runDesignSystemConformance, } from "./runner.js";
|
|
8
|
+
import { assertDesignSystemContractVersion, DESIGN_SYSTEM_CONTRACT_EVOLUTION_POLICY, } from "./version.js";
|
|
9
|
+
class ResizeObserverStub {
|
|
10
|
+
observe() { }
|
|
11
|
+
unobserve() { }
|
|
12
|
+
disconnect() { }
|
|
13
|
+
}
|
|
14
|
+
describe("design-system conformance kit", () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", false);
|
|
17
|
+
vi.stubGlobal("ResizeObserver", ResizeObserverStub);
|
|
18
|
+
HTMLElement.prototype.scrollIntoView = vi.fn();
|
|
19
|
+
HTMLElement.prototype.hasPointerCapture = vi.fn(() => false);
|
|
20
|
+
HTMLElement.prototype.setPointerCapture = vi.fn();
|
|
21
|
+
HTMLElement.prototype.releasePointerCapture = vi.fn();
|
|
22
|
+
});
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
document.body.replaceChildren();
|
|
25
|
+
vi.restoreAllMocks();
|
|
26
|
+
vi.unstubAllGlobals();
|
|
27
|
+
});
|
|
28
|
+
it("publishes named coverage for all seventeen components and overlay concerns", () => {
|
|
29
|
+
const covered = new Set(DESIGN_SYSTEM_CONFORMANCE_CHECKS.flatMap((check) => check.components));
|
|
30
|
+
expect(covered.size).toBe(17);
|
|
31
|
+
expect(DESIGN_SYSTEM_CONFORMANCE_CHECKS.map((check) => check.id)).toEqual(expect.arrayContaining([
|
|
32
|
+
"overlay.portal-and-z-index-stacking",
|
|
33
|
+
"overlay.focus-interoperability",
|
|
34
|
+
]));
|
|
35
|
+
});
|
|
36
|
+
it("enforces the published contract evolution policy", () => {
|
|
37
|
+
expect(DESIGN_SYSTEM_CONTRACT_EVOLUTION_POLICY.minor).toContain("optional props");
|
|
38
|
+
expect(DESIGN_SYSTEM_CONTRACT_EVOLUTION_POLICY.major).toContain("behavioral");
|
|
39
|
+
expect(() => assertDesignSystemContractVersion(DESIGN_SYSTEM_CONTRACT_VERSION)).not.toThrow();
|
|
40
|
+
expect(() => assertDesignSystemContractVersion(2)).toThrow(/incompatible/);
|
|
41
|
+
});
|
|
42
|
+
it("passes the styling-independent CSS-in-JS fixture", async () => {
|
|
43
|
+
const report = await assertDesignSystemConformance({
|
|
44
|
+
adapterName: "CSS-in-JS fixture",
|
|
45
|
+
components: cssInJsFixtureAdapter,
|
|
46
|
+
contractVersion: DESIGN_SYSTEM_CONTRACT_VERSION,
|
|
47
|
+
});
|
|
48
|
+
expect(report.passed).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
it("passes the default shadcn and Radix adapter", async () => {
|
|
51
|
+
const report = await runDesignSystemConformance({
|
|
52
|
+
adapterName: "Toolkit default",
|
|
53
|
+
components: defaultDesignSystemComponents,
|
|
54
|
+
contractVersion: DESIGN_SYSTEM_CONTRACT_VERSION,
|
|
55
|
+
});
|
|
56
|
+
expect(report.results.filter((result) => !result.passed)).toEqual([]);
|
|
57
|
+
});
|
|
58
|
+
it("rejects tabs that stack an icon above its label", async () => {
|
|
59
|
+
const stackedTabsAdapter = {
|
|
60
|
+
...cssInJsFixtureAdapter,
|
|
61
|
+
Tabs: ({ items, value, onChange, orientation }) => (_jsxs("div", { children: [_jsx("div", { role: "tablist", "aria-orientation": orientation, children: items.map((item) => (_jsxs("button", { role: "tab", "aria-selected": item.value === value, style: { display: "flex", flexDirection: "column" }, onClick: () => onChange(item.value), children: [item.icon, item.label] }, String(item.value)))) }), items.find((item) => item.value === value)?.content] })),
|
|
62
|
+
};
|
|
63
|
+
const report = await runDesignSystemConformance({
|
|
64
|
+
adapterName: "stacked-tabs fixture",
|
|
65
|
+
components: stackedTabsAdapter,
|
|
66
|
+
contractVersion: DESIGN_SYSTEM_CONTRACT_VERSION,
|
|
67
|
+
});
|
|
68
|
+
const tabsResult = report.results.find((result) => result.id === "behavior.tabs");
|
|
69
|
+
expect(tabsResult?.passed).toBe(false);
|
|
70
|
+
expect(tabsResult?.message).toContain("same row");
|
|
71
|
+
});
|
|
72
|
+
it("rejects dialogs that render footer controls outside the dialog", async () => {
|
|
73
|
+
const detachedFooterAdapter = {
|
|
74
|
+
...cssInJsFixtureAdapter,
|
|
75
|
+
Dialog: ({ footer, ...props }) => (_jsxs(_Fragment, { children: [_jsx(cssInJsFixtureAdapter.Dialog, { ...props }), footer] })),
|
|
76
|
+
};
|
|
77
|
+
const report = await runDesignSystemConformance({
|
|
78
|
+
adapterName: "detached-footer fixture",
|
|
79
|
+
components: detachedFooterAdapter,
|
|
80
|
+
contractVersion: DESIGN_SYSTEM_CONTRACT_VERSION,
|
|
81
|
+
});
|
|
82
|
+
const dialogResult = report.results.find((result) => result.id === "behavior.dialog");
|
|
83
|
+
expect(dialogResult?.passed).toBe(false);
|
|
84
|
+
expect(dialogResult?.message).toContain("footer");
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
//# sourceMappingURL=conformance.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conformance.spec.js","sourceRoot":"","sources":["../../src/conformance/conformance.spec.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/conformance/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/conformance/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { DesignSystemComponents } from "../design-system/types.js";
|
|
3
|
+
import type { DesignSystemConformanceCategory, DesignSystemConformanceReport, RunDesignSystemConformanceOptions } from "./types.js";
|
|
4
|
+
export declare const DESIGN_SYSTEM_CONFORMANCE_CHECKS: {
|
|
5
|
+
id: string;
|
|
6
|
+
category: DesignSystemConformanceCategory;
|
|
7
|
+
components: readonly (keyof DesignSystemComponents)[];
|
|
8
|
+
}[];
|
|
9
|
+
export declare function runDesignSystemConformance({ adapterName, components, contractVersion, document: suppliedDocument, }: RunDesignSystemConformanceOptions): Promise<DesignSystemConformanceReport>;
|
|
10
|
+
export declare function assertDesignSystemConformance(options: RunDesignSystemConformanceOptions): Promise<DesignSystemConformanceReport>;
|
|
11
|
+
export declare function renderDesignSystemConformanceFixture(components: DesignSystemComponents): ReactNode;
|
|
12
|
+
//# sourceMappingURL=runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/conformance/runner.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAKf,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,KAAK,EACV,+BAA+B,EAE/B,6BAA6B,EAC7B,iCAAiC,EAClC,MAAM,YAAY,CAAC;AAssBpB,eAAO,MAAM,gCAAgC;;;;GAE5C,CAAC;AAEF,wBAAsB,0BAA0B,CAAC,EAC/C,WAA8B,EAC9B,UAAU,EACV,eAAe,EACf,QAAQ,EAAE,gBAAgB,GAC3B,EAAE,iCAAiC,GAAG,OAAO,CAAC,6BAA6B,CAAC,CAyG5E;AAED,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,iCAAiC,GACzC,OAAO,CAAC,6BAA6B,CAAC,CAOxC;AAED,wBAAgB,oCAAoC,CAClD,UAAU,EAAE,sBAAsB,GACjC,SAAS,CAKX"}
|
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { createElement, useRef, useState, } from "react";
|
|
3
|
+
import { flushSync } from "react-dom";
|
|
4
|
+
import { createRoot } from "react-dom/client";
|
|
5
|
+
import { DESIGN_SYSTEM_CONTRACT_VERSION } from "../design-system/types.js";
|
|
6
|
+
import { assertDesignSystemContractVersion } from "./version.js";
|
|
7
|
+
const contractComponentNames = [
|
|
8
|
+
"ActionButton",
|
|
9
|
+
"IconButton",
|
|
10
|
+
"TextField",
|
|
11
|
+
"TextArea",
|
|
12
|
+
"Spinner",
|
|
13
|
+
"Skeleton",
|
|
14
|
+
"Status",
|
|
15
|
+
"Surface",
|
|
16
|
+
"Avatar",
|
|
17
|
+
"Tooltip",
|
|
18
|
+
"Menu",
|
|
19
|
+
"Popover",
|
|
20
|
+
"Dialog",
|
|
21
|
+
"Picker",
|
|
22
|
+
"Checkbox",
|
|
23
|
+
"Switch",
|
|
24
|
+
"Tabs",
|
|
25
|
+
];
|
|
26
|
+
function invariant(value, message) {
|
|
27
|
+
if (!value)
|
|
28
|
+
throw new Error(message);
|
|
29
|
+
}
|
|
30
|
+
function dispatch(element, event) {
|
|
31
|
+
flushSync(() => element.dispatchEvent(event));
|
|
32
|
+
}
|
|
33
|
+
function click(element, document) {
|
|
34
|
+
for (const type of ["pointerdown", "mousedown"]) {
|
|
35
|
+
dispatch(element, new document.defaultView.MouseEvent(type, {
|
|
36
|
+
bubbles: true,
|
|
37
|
+
cancelable: true,
|
|
38
|
+
button: 0,
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
dispatch(element, new document.defaultView.MouseEvent("click", {
|
|
42
|
+
bubbles: true,
|
|
43
|
+
cancelable: true,
|
|
44
|
+
button: 0,
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
function keydown(element, key, document) {
|
|
48
|
+
dispatch(element, new document.defaultView.KeyboardEvent("keydown", {
|
|
49
|
+
key,
|
|
50
|
+
bubbles: true,
|
|
51
|
+
cancelable: true,
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
function input(element, value) {
|
|
55
|
+
const prototype = element instanceof element.ownerDocument.defaultView.HTMLTextAreaElement
|
|
56
|
+
? element.ownerDocument.defaultView.HTMLTextAreaElement.prototype
|
|
57
|
+
: element.ownerDocument.defaultView.HTMLInputElement.prototype;
|
|
58
|
+
Object.getOwnPropertyDescriptor(prototype, "value")?.set?.call(element, value);
|
|
59
|
+
dispatch(element, new element.ownerDocument.defaultView.Event("input", {
|
|
60
|
+
bubbles: true,
|
|
61
|
+
cancelable: true,
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
function elementWithText(document, selector, text) {
|
|
65
|
+
return [...document.querySelectorAll(selector)].find((element) => element.textContent?.includes(text));
|
|
66
|
+
}
|
|
67
|
+
function assertInlineLayout(container, iconSelector, labelSelector, message) {
|
|
68
|
+
const icon = container.querySelector(iconSelector);
|
|
69
|
+
const label = container.querySelector(labelSelector);
|
|
70
|
+
invariant(icon && label, message);
|
|
71
|
+
const iconRect = icon.getBoundingClientRect();
|
|
72
|
+
const labelRect = label.getBoundingClientRect();
|
|
73
|
+
const hasGeometry = iconRect.width > 0 ||
|
|
74
|
+
iconRect.height > 0 ||
|
|
75
|
+
labelRect.width > 0 ||
|
|
76
|
+
labelRect.height > 0;
|
|
77
|
+
if (hasGeometry) {
|
|
78
|
+
const verticalOverlap = Math.min(iconRect.bottom, labelRect.bottom) -
|
|
79
|
+
Math.max(iconRect.top, labelRect.top);
|
|
80
|
+
invariant(verticalOverlap > 0 || Math.abs(iconRect.top - labelRect.top) <= 2, message);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// happy-dom does not calculate layout. Still reject adapters that explicitly
|
|
84
|
+
// stack the tab contents, while browser-backed runs use the geometry check.
|
|
85
|
+
const commonAncestor = (() => {
|
|
86
|
+
let candidate = icon;
|
|
87
|
+
while (candidate && !candidate.contains(label)) {
|
|
88
|
+
candidate = candidate.parentElement;
|
|
89
|
+
}
|
|
90
|
+
return candidate;
|
|
91
|
+
})();
|
|
92
|
+
const styles = [
|
|
93
|
+
container,
|
|
94
|
+
commonAncestor,
|
|
95
|
+
icon.parentElement,
|
|
96
|
+
label.parentElement,
|
|
97
|
+
]
|
|
98
|
+
.filter((element) => Boolean(element))
|
|
99
|
+
.map((element) => element.ownerDocument.defaultView?.getComputedStyle(element));
|
|
100
|
+
invariant(!styles.some((style) => style?.display === "flex" && style.flexDirection === "column"), message);
|
|
101
|
+
}
|
|
102
|
+
function assertContainedFooter(dialog, footerSelector) {
|
|
103
|
+
const footer = dialog.querySelector(footerSelector);
|
|
104
|
+
invariant(footer, "Dialog footer controls must be rendered inside the dialog surface.");
|
|
105
|
+
const dialogRect = dialog.getBoundingClientRect();
|
|
106
|
+
const footerRect = footer.getBoundingClientRect();
|
|
107
|
+
const hasGeometry = dialogRect.width > 0 ||
|
|
108
|
+
dialogRect.height > 0 ||
|
|
109
|
+
footerRect.width > 0 ||
|
|
110
|
+
footerRect.height > 0;
|
|
111
|
+
if (!hasGeometry)
|
|
112
|
+
return;
|
|
113
|
+
const epsilon = 2;
|
|
114
|
+
invariant(footerRect.top >= dialogRect.top - epsilon &&
|
|
115
|
+
footerRect.bottom <= dialogRect.bottom + epsilon &&
|
|
116
|
+
footerRect.left >= dialogRect.left - epsilon &&
|
|
117
|
+
footerRect.right <= dialogRect.right + epsilon, "Dialog footer controls must remain within the dialog surface bounds.");
|
|
118
|
+
}
|
|
119
|
+
const checks = [
|
|
120
|
+
{
|
|
121
|
+
id: "contract.components",
|
|
122
|
+
category: "contract",
|
|
123
|
+
components: contractComponentNames,
|
|
124
|
+
run: ({ components }) => {
|
|
125
|
+
for (const name of contractComponentNames) {
|
|
126
|
+
invariant(typeof components[name] === "function", `${name} must be supplied as a React component.`);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: "leaf.action-button",
|
|
132
|
+
category: "leaf",
|
|
133
|
+
components: ["ActionButton"],
|
|
134
|
+
run: ({ components, document, mount, unmount }) => {
|
|
135
|
+
let presses = 0;
|
|
136
|
+
const probe = mount(_jsx(components.ActionButton, { intent: "danger", emphasis: "outline", size: "compact", onPress: () => presses++, children: "Remove" }));
|
|
137
|
+
const button = probe.container.querySelector("button");
|
|
138
|
+
invariant(button, "ActionButton must render an operable button.");
|
|
139
|
+
click(button, document);
|
|
140
|
+
invariant(presses === 1, "ActionButton must call onPress once.");
|
|
141
|
+
unmount(probe);
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: "leaf.icon-button",
|
|
146
|
+
category: "leaf",
|
|
147
|
+
components: ["IconButton"],
|
|
148
|
+
run: ({ components, document, mount, unmount }) => {
|
|
149
|
+
let presses = 0;
|
|
150
|
+
const probe = mount(_jsx(components.IconButton, { label: "Close panel", icon: _jsx("span", { "aria-hidden": "true", children: "x" }), onPress: () => presses++ }));
|
|
151
|
+
const button = probe.container.querySelector('button[aria-label="Close panel"]');
|
|
152
|
+
invariant(button, "IconButton must expose its label as an accessible name.");
|
|
153
|
+
click(button, document);
|
|
154
|
+
invariant(presses === 1, "IconButton must call onPress once.");
|
|
155
|
+
unmount(probe);
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
id: "leaf.text-field",
|
|
160
|
+
category: "leaf",
|
|
161
|
+
components: ["TextField"],
|
|
162
|
+
run: ({ components, mount, unmount }) => {
|
|
163
|
+
let value = "";
|
|
164
|
+
const probe = mount(_jsx(components.TextField, { "aria-label": "Project name", value: value, onChange: (next) => (value = next) }));
|
|
165
|
+
const field = probe.container.querySelector("input");
|
|
166
|
+
invariant(field, "TextField must render an input.");
|
|
167
|
+
input(field, "Atlas");
|
|
168
|
+
invariant(value === "Atlas", "TextField must report string values.");
|
|
169
|
+
unmount(probe);
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: "leaf.text-area",
|
|
174
|
+
category: "leaf",
|
|
175
|
+
components: ["TextArea"],
|
|
176
|
+
run: ({ components, mount, unmount }) => {
|
|
177
|
+
let value = "";
|
|
178
|
+
const probe = mount(_jsx(components.TextArea, { "aria-label": "Notes", value: value, onChange: (next) => (value = next) }));
|
|
179
|
+
const field = probe.container.querySelector("textarea");
|
|
180
|
+
invariant(field, "TextArea must render a textarea.");
|
|
181
|
+
input(field, "Conformant");
|
|
182
|
+
invariant(value === "Conformant", "TextArea must report string values.");
|
|
183
|
+
unmount(probe);
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: "leaf.progress-and-placeholder",
|
|
188
|
+
category: "leaf",
|
|
189
|
+
components: ["Spinner", "Skeleton"],
|
|
190
|
+
run: ({ components, mount, unmount }) => {
|
|
191
|
+
const probe = mount(_jsxs(_Fragment, { children: [_jsx(components.Spinner, { label: "Loading records", size: "compact" }), _jsx(components.Skeleton, { width: 48, height: 12, shape: "line" })] }));
|
|
192
|
+
invariant(probe.container.querySelector('[role="status"]'), "A labeled Spinner must expose status semantics.");
|
|
193
|
+
invariant(probe.container.querySelector('[aria-hidden="true"]'), "Skeleton must be hidden from assistive technology.");
|
|
194
|
+
unmount(probe);
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
id: "leaf.status-surface-avatar",
|
|
199
|
+
category: "leaf",
|
|
200
|
+
components: ["Status", "Surface", "Avatar"],
|
|
201
|
+
run: ({ components, document, mount, unmount }) => {
|
|
202
|
+
let presses = 0;
|
|
203
|
+
const probe = mount(_jsxs(_Fragment, { children: [_jsx(components.Status, { tone: "success", children: "Connected" }), _jsx(components.Surface, { interactive: true, onPress: () => presses++, children: "Open workspace" }), _jsx(components.Avatar, { name: "Ada Lovelace", fallback: "AL" })] }));
|
|
204
|
+
invariant(probe.container.textContent?.includes("Connected"), "Status must render its content.");
|
|
205
|
+
const surface = elementWithText(document, '[role="button"]', "Open workspace");
|
|
206
|
+
invariant(surface, "An interactive Surface must expose button semantics.");
|
|
207
|
+
keydown(surface, "Enter", document);
|
|
208
|
+
invariant(presses === 1, "An interactive Surface must support keyboard press.");
|
|
209
|
+
invariant(probe.container.textContent?.includes("AL"), "Avatar must render a fallback when no image is supplied.");
|
|
210
|
+
unmount(probe);
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: "behavior.tooltip",
|
|
215
|
+
category: "behavior",
|
|
216
|
+
components: ["Tooltip"],
|
|
217
|
+
run: async ({ components, document, mount, settle, unmount }) => {
|
|
218
|
+
const portal = document.createElement("div");
|
|
219
|
+
portal.dataset.conformancePortal = "true";
|
|
220
|
+
document.body.appendChild(portal);
|
|
221
|
+
const probe = mount(_jsx(components.Tooltip, { trigger: _jsx("button", { children: "Help" }), content: "Helpful context", open: true, portalContainer: portal }));
|
|
222
|
+
await settle();
|
|
223
|
+
invariant(portal.textContent?.includes("Helpful context"), "Tooltip must honor controlled open and portalContainer.");
|
|
224
|
+
unmount(probe);
|
|
225
|
+
const defaultOpenProbe = mount(_jsx(components.Tooltip, { trigger: _jsx("button", { children: "Help by default" }), content: "Default tooltip", defaultOpen: true, portalContainer: portal }));
|
|
226
|
+
await settle();
|
|
227
|
+
invariant(portal.textContent?.includes("Default tooltip"), "Tooltip must honor defaultOpen and portalContainer.");
|
|
228
|
+
unmount(defaultOpenProbe);
|
|
229
|
+
portal.remove();
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
id: "behavior.menu",
|
|
234
|
+
category: "behavior",
|
|
235
|
+
components: ["Menu"],
|
|
236
|
+
run: async ({ components, document, mount, settle, unmount }) => {
|
|
237
|
+
let selected;
|
|
238
|
+
const probe = mount(_jsx(components.Menu, { trigger: _jsx("button", { children: "Actions" }), items: [{ id: "archive", label: "Archive" }], open: true, onAction: (id) => (selected = id) }));
|
|
239
|
+
await settle();
|
|
240
|
+
const item = elementWithText(document, '[role="menuitem"]', "Archive");
|
|
241
|
+
invariant(item, "Menu must render open items with menuitem semantics.");
|
|
242
|
+
click(item, document);
|
|
243
|
+
invariant(selected === "archive", "Menu must report selected item ids.");
|
|
244
|
+
unmount(probe);
|
|
245
|
+
const defaultOpenProbe = mount(_jsx(components.Menu, { trigger: _jsx("button", { children: "Default actions" }), items: [{ id: "restore", label: "Restore" }], defaultOpen: true, onAction: () => { } }));
|
|
246
|
+
await settle();
|
|
247
|
+
invariant(elementWithText(document, '[role="menuitem"]', "Restore"), "Menu must honor defaultOpen.");
|
|
248
|
+
unmount(defaultOpenProbe);
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
id: "behavior.popover",
|
|
253
|
+
category: "behavior",
|
|
254
|
+
components: ["Popover"],
|
|
255
|
+
run: async ({ components, document, mount, settle, unmount }) => {
|
|
256
|
+
const portal = document.createElement("div");
|
|
257
|
+
portal.dataset.conformancePortal = "true";
|
|
258
|
+
document.body.appendChild(portal);
|
|
259
|
+
const probe = mount(_jsx(components.Popover, { trigger: _jsx("button", { children: "Details" }), open: true, portalContainer: portal, children: "Popover details" }));
|
|
260
|
+
await settle();
|
|
261
|
+
invariant(portal.textContent?.includes("Popover details"), "Popover must honor controlled open and portalContainer.");
|
|
262
|
+
unmount(probe);
|
|
263
|
+
portal.remove();
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
id: "behavior.dialog",
|
|
268
|
+
category: "behavior",
|
|
269
|
+
components: ["Dialog"],
|
|
270
|
+
run: async ({ components, document, mount, settle, unmount }) => {
|
|
271
|
+
const portal = document.createElement("div");
|
|
272
|
+
portal.dataset.conformancePortal = "true";
|
|
273
|
+
document.body.appendChild(portal);
|
|
274
|
+
const probe = mount(_jsx(components.Dialog, { open: true, onOpenChange: () => { }, title: "Edit project", dismissible: false, portalContainer: portal, children: _jsx("input", { "aria-label": "Project title" }) }));
|
|
275
|
+
await settle();
|
|
276
|
+
invariant(portal.querySelector('[role="dialog"]'), "Dialog must expose dialog semantics.");
|
|
277
|
+
invariant(portal.textContent?.includes("Edit project"), "Dialog must expose its title and honor portalContainer.");
|
|
278
|
+
invariant(portal.querySelector('[role="dialog"]')?.querySelectorAll("button")
|
|
279
|
+
.length === 0, "Dialog must not invent actions when footer is omitted.");
|
|
280
|
+
unmount(probe);
|
|
281
|
+
const footerProbe = mount(_jsx(components.Dialog, { open: true, onOpenChange: () => { }, title: "Footer probe", portalContainer: portal, footer: _jsx("button", { "data-conformance-dialog-footer-action": "true", type: "button", children: "Save" }), children: "Footer content" }));
|
|
282
|
+
await settle();
|
|
283
|
+
const footerDialog = portal.querySelector('[role="dialog"]');
|
|
284
|
+
invariant(footerDialog, "Dialog footer probe did not render a dialog.");
|
|
285
|
+
assertContainedFooter(footerDialog, '[data-conformance-dialog-footer-action="true"]');
|
|
286
|
+
unmount(footerProbe);
|
|
287
|
+
portal.remove();
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
id: "behavior.picker",
|
|
292
|
+
category: "behavior",
|
|
293
|
+
components: ["Picker"],
|
|
294
|
+
run: async ({ components, document, mount, settle, unmount }) => {
|
|
295
|
+
let value = null;
|
|
296
|
+
const probe = mount(_jsx(components.Picker, { mode: "select", "aria-label": "Owner", options: [{ value: "ada", label: "Ada" }], value: value, onChange: (next) => (value = next), open: true }));
|
|
297
|
+
await settle();
|
|
298
|
+
const option = elementWithText(document, '[role="option"]', "Ada");
|
|
299
|
+
invariant(option, "An open Picker must expose options.");
|
|
300
|
+
click(option, document);
|
|
301
|
+
invariant(value === "ada", "Picker must report option values.");
|
|
302
|
+
unmount(probe);
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
id: "behavior.checkbox-switch",
|
|
307
|
+
category: "behavior",
|
|
308
|
+
components: ["Checkbox", "Switch"],
|
|
309
|
+
run: ({ components, document, mount, unmount }) => {
|
|
310
|
+
let checked = false;
|
|
311
|
+
let switched = false;
|
|
312
|
+
const probe = mount(_jsxs(_Fragment, { children: [_jsx(components.Checkbox, { "aria-label": "Include archived", checked: checked, onChange: (next) => (checked = next) }), _jsx(components.Switch, { "aria-label": "Notifications", checked: switched, onChange: (next) => (switched = next) })] }));
|
|
313
|
+
const checkbox = probe.container.querySelector('[role="checkbox"]');
|
|
314
|
+
const toggle = probe.container.querySelector('[role="switch"]');
|
|
315
|
+
invariant(checkbox, "Checkbox must expose checkbox semantics.");
|
|
316
|
+
invariant(toggle, "Switch must expose switch semantics.");
|
|
317
|
+
click(checkbox, document);
|
|
318
|
+
click(toggle, document);
|
|
319
|
+
invariant(checked && switched, "Checkbox and Switch must report state changes.");
|
|
320
|
+
unmount(probe);
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
id: "behavior.tabs",
|
|
325
|
+
category: "behavior",
|
|
326
|
+
components: ["Tabs"],
|
|
327
|
+
run: ({ components, document, mount, unmount }) => {
|
|
328
|
+
let value = "first";
|
|
329
|
+
const probe = mount(_jsx(components.Tabs, { value: value, orientation: "vertical", onChange: (next) => (value = next), items: [
|
|
330
|
+
{
|
|
331
|
+
value: "first",
|
|
332
|
+
label: _jsx("span", { "data-conformance-tab-label": "true", children: "First" }),
|
|
333
|
+
icon: (_jsx("span", { "data-conformance-tab-icon": "true", "aria-hidden": "true", children: "\u25CF" })),
|
|
334
|
+
content: "One",
|
|
335
|
+
},
|
|
336
|
+
{ value: "second", label: "Second", content: "Two" },
|
|
337
|
+
] }));
|
|
338
|
+
const tabList = probe.container.querySelector('[role="tablist"]');
|
|
339
|
+
invariant(tabList, "Tabs must expose a tablist.");
|
|
340
|
+
invariant(tabList.getAttribute("aria-orientation") === "vertical", "Tabs must honor vertical orientation in their tablist semantics.");
|
|
341
|
+
const first = elementWithText(document, '[role="tab"]', "First");
|
|
342
|
+
invariant(first, "Tabs must expose the icon-bearing tab.");
|
|
343
|
+
assertInlineLayout(first, '[data-conformance-tab-icon="true"]', '[data-conformance-tab-label="true"]', "Tabs must lay out an icon and its label on the same row.");
|
|
344
|
+
const second = elementWithText(document, '[role="tab"]', "Second");
|
|
345
|
+
invariant(second, "Tabs must expose tab semantics.");
|
|
346
|
+
click(second, document);
|
|
347
|
+
invariant(value === "second", "Tabs must report selected values.");
|
|
348
|
+
unmount(probe);
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
id: "overlay.portal-and-z-index-stacking",
|
|
353
|
+
category: "overlay-interoperability",
|
|
354
|
+
components: ["Dialog"],
|
|
355
|
+
run: async ({ components, document, mount, settle, unmount }) => {
|
|
356
|
+
const radixHost = document.createElement("div");
|
|
357
|
+
radixHost.dataset.hostedOverlay = "radix";
|
|
358
|
+
radixHost.dataset.conformancePortal = "true";
|
|
359
|
+
radixHost.style.zIndex = "40";
|
|
360
|
+
document.body.appendChild(radixHost);
|
|
361
|
+
const customerOverlayLayer = document.createElement("div");
|
|
362
|
+
customerOverlayLayer.dataset.designSystemOverlayLayer = "true";
|
|
363
|
+
customerOverlayLayer.dataset.conformancePortal = "true";
|
|
364
|
+
customerOverlayLayer.style.position = "relative";
|
|
365
|
+
customerOverlayLayer.style.zIndex = "50";
|
|
366
|
+
document.body.appendChild(customerOverlayLayer);
|
|
367
|
+
const probe = mount(_jsx(components.Dialog, { open: true, onOpenChange: () => { }, title: "Stacking probe", portalContainer: customerOverlayLayer, children: "Overlay content" }));
|
|
368
|
+
await settle();
|
|
369
|
+
invariant(customerOverlayLayer.querySelector('[role="dialog"]'), "Behavior adapters must portal into the supplied stacking layer so they can interoperate with Radix-hosted surfaces.");
|
|
370
|
+
invariant(Number(customerOverlayLayer.style.zIndex) >
|
|
371
|
+
Number(radixHost.style.zIndex), "The supplied design-system portal layer must be able to stack above an existing hosted overlay.");
|
|
372
|
+
unmount(probe);
|
|
373
|
+
customerOverlayLayer.remove();
|
|
374
|
+
radixHost.remove();
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
id: "overlay.focus-interoperability",
|
|
379
|
+
category: "overlay-interoperability",
|
|
380
|
+
components: ["Dialog"],
|
|
381
|
+
run: async ({ components, document, mount, settle, unmount }) => {
|
|
382
|
+
function FocusProbe() {
|
|
383
|
+
const [open, setOpen] = useState(true);
|
|
384
|
+
const initialFocusRef = useRef(null);
|
|
385
|
+
const restoreFocusRef = useRef(null);
|
|
386
|
+
return (_jsxs(_Fragment, { children: [_jsx("button", { ref: restoreFocusRef, children: "Hosted trigger" }), _jsxs(components.Dialog, { open: open, onOpenChange: setOpen, title: "Focus probe", initialFocusRef: initialFocusRef, restoreFocusRef: restoreFocusRef, children: [_jsx("input", { ref: initialFocusRef, "aria-label": "Initial focus" }), _jsx("button", { onClick: () => setOpen(false), children: "Close probe" })] })] }));
|
|
387
|
+
}
|
|
388
|
+
const probe = mount(_jsx(FocusProbe, {}));
|
|
389
|
+
await settle();
|
|
390
|
+
const initial = document.querySelector('input[aria-label="Initial focus"]');
|
|
391
|
+
invariant(initial, "Dialog must render its initial focus target.");
|
|
392
|
+
invariant(document.activeElement === initial, "Dialog must honor initialFocusRef across adapter overlay implementations.");
|
|
393
|
+
const close = elementWithText(document, "button", "Close probe");
|
|
394
|
+
invariant(close, "Focus probe close control did not render.");
|
|
395
|
+
click(close, document);
|
|
396
|
+
await settle();
|
|
397
|
+
invariant(document.activeElement?.textContent === "Hosted trigger", "Dialog must restore focus to a hosted trigger after its native overlay closes.");
|
|
398
|
+
unmount(probe);
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
];
|
|
402
|
+
export const DESIGN_SYSTEM_CONFORMANCE_CHECKS = checks.map(({ id, category, components }) => ({ id, category, components }));
|
|
403
|
+
export async function runDesignSystemConformance({ adapterName = "custom adapter", components, contractVersion, document: suppliedDocument, }) {
|
|
404
|
+
const document = suppliedDocument ?? globalThis.document;
|
|
405
|
+
if (!document?.body || !document.defaultView) {
|
|
406
|
+
throw new Error("runDesignSystemConformance requires a browser-like document, such as Playwright, jsdom, or happy-dom.");
|
|
407
|
+
}
|
|
408
|
+
const results = [];
|
|
409
|
+
try {
|
|
410
|
+
assertDesignSystemContractVersion(contractVersion);
|
|
411
|
+
results.push({
|
|
412
|
+
id: "contract.version-policy",
|
|
413
|
+
category: "contract",
|
|
414
|
+
components: [],
|
|
415
|
+
passed: true,
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
results.push({
|
|
420
|
+
id: "contract.version-policy",
|
|
421
|
+
category: "contract",
|
|
422
|
+
components: [],
|
|
423
|
+
passed: false,
|
|
424
|
+
message: error instanceof Error ? error.message : String(error),
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
const mounted = new Set();
|
|
428
|
+
const exercisedComponents = new Set();
|
|
429
|
+
const trackedComponents = new Proxy(components, {
|
|
430
|
+
get(target, property, receiver) {
|
|
431
|
+
if (typeof property === "string" && property in target) {
|
|
432
|
+
exercisedComponents.add(property);
|
|
433
|
+
}
|
|
434
|
+
return Reflect.get(target, property, receiver);
|
|
435
|
+
},
|
|
436
|
+
});
|
|
437
|
+
const context = {
|
|
438
|
+
components: trackedComponents,
|
|
439
|
+
document,
|
|
440
|
+
mount(element) {
|
|
441
|
+
const container = document.createElement("div");
|
|
442
|
+
container.dataset.conformanceRoot = "true";
|
|
443
|
+
document.body.appendChild(container);
|
|
444
|
+
const root = createRoot(container);
|
|
445
|
+
const probe = { container, root };
|
|
446
|
+
mounted.add(probe);
|
|
447
|
+
flushSync(() => root.render(element));
|
|
448
|
+
return probe;
|
|
449
|
+
},
|
|
450
|
+
unmount(probe) {
|
|
451
|
+
if (!mounted.delete(probe))
|
|
452
|
+
return;
|
|
453
|
+
flushSync(() => probe.root.unmount());
|
|
454
|
+
probe.container.remove();
|
|
455
|
+
},
|
|
456
|
+
async settle() {
|
|
457
|
+
await new Promise((resolve) => document.defaultView.setTimeout(resolve, 0));
|
|
458
|
+
},
|
|
459
|
+
};
|
|
460
|
+
for (const check of checks) {
|
|
461
|
+
try {
|
|
462
|
+
exercisedComponents.clear();
|
|
463
|
+
await check.run(context);
|
|
464
|
+
const missingComponents = check.components.filter((component) => !exercisedComponents.has(component));
|
|
465
|
+
invariant(missingComponents.length === 0, `Conformance check ${check.id} declared ${missingComponents.join(", ")} but did not exercise them.`);
|
|
466
|
+
results.push({
|
|
467
|
+
id: check.id,
|
|
468
|
+
category: check.category,
|
|
469
|
+
components: check.components,
|
|
470
|
+
passed: true,
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
catch (error) {
|
|
474
|
+
results.push({
|
|
475
|
+
id: check.id,
|
|
476
|
+
category: check.category,
|
|
477
|
+
components: check.components,
|
|
478
|
+
passed: false,
|
|
479
|
+
message: error instanceof Error ? error.message : String(error),
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
finally {
|
|
483
|
+
for (const probe of [...mounted])
|
|
484
|
+
context.unmount(probe);
|
|
485
|
+
document
|
|
486
|
+
.querySelectorAll("[data-radix-popper-content-wrapper]")
|
|
487
|
+
.forEach((element) => element.remove());
|
|
488
|
+
document
|
|
489
|
+
.querySelectorAll('[data-conformance-portal="true"]')
|
|
490
|
+
.forEach((element) => element.remove());
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
const report = {
|
|
494
|
+
adapterName,
|
|
495
|
+
contractVersion: DESIGN_SYSTEM_CONTRACT_VERSION,
|
|
496
|
+
passed: results.every((result) => result.passed),
|
|
497
|
+
results,
|
|
498
|
+
};
|
|
499
|
+
return report;
|
|
500
|
+
}
|
|
501
|
+
export async function assertDesignSystemConformance(options) {
|
|
502
|
+
const report = await runDesignSystemConformance(options);
|
|
503
|
+
if (!report.passed) {
|
|
504
|
+
const { DesignSystemConformanceError } = await import("./types.js");
|
|
505
|
+
throw new DesignSystemConformanceError(report);
|
|
506
|
+
}
|
|
507
|
+
return report;
|
|
508
|
+
}
|
|
509
|
+
export function renderDesignSystemConformanceFixture(components) {
|
|
510
|
+
return createElement(components.Status, {
|
|
511
|
+
tone: "info",
|
|
512
|
+
children: "Adapter loaded",
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
//# sourceMappingURL=runner.js.map
|