@luna_ui/luna 0.4.0 → 0.5.3
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/dist/cli.mjs +1 -1
- package/dist/{index-CDWzWF-h.d.ts → index-vO066aMd.d.ts} +17 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/jsx-dev-runtime.js +1 -1
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/jsx-runtime.js +1 -1
- package/dist/src-DviLMStS.js +1 -0
- package/package.json +1 -1
- package/src/index.ts +26 -8
- package/tests/__screenshots__/{preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-text-updates-match-1.png → apg.test.ts/APG-Components---Accessibility-Tests-Button-Pattern-disabled-button-has-aria-disabled-1.png} +0 -0
- package/tests/apg.test.ts +466 -0
- package/tests/debounced.test.ts +165 -0
- package/tests/dom.test.ts +3 -2
- package/tests/issue-11-show-null-to-truthy.test.ts +176 -0
- package/tests/solidjs-api.test.ts +5 -4
- package/dist/src-DEjrAhrg.js +0 -1
- package/tests/__screenshots__/context.test.ts/Context-API-context-with-reactive-effects-context-value-accessible-in-effect-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-For-component--SolidJS-style--For-updates-when-signal-changes-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-accepts-children-as-function-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-toggles-visibility-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-attribute-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-style-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-createElementNs--SVG-support--createElementNs-with-dynamic-attribute-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-effect-with-DOM-effect-tracks-signal-changes-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-clear-to-empty-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-empty-array-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-removes-items-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-renders-initial-list-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-updates-when-items-change-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-handles-empty-to-non-empty-transition-in-SVG-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-handles-reordering-in-SVG-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-updates-SVG-elements-when-signal-changes-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-with-nested-SVG-groups-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-ref-callback--JSX-style--ref-callback-with-nested-elements-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-creates-a-node-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-with-false-condition-creates-placeholder-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-text-nodes-textDyn-creates-reactive-text-node-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-forEach-renders-correctly-without-show--initial-items--1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-forEach-with-context-renders-correctly-without-show-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-nested-components-with-context--forEach--and-show-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-show-and-forEach-inherit-context-from-Owner--fixed--1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-show-and-forEach-work-together--context-uses-default--1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Context---ForEach-integration-forEach-items-can-access-context-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-renders-initial-list-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-updates-when-signal-changes-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-with-object-items-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-hides-when-condition-is-false-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-renders-when-condition-is-true-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-from-false-to-true-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-reactively-1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--event-listener-pattern--Solid-js-docs-example--1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--multiple-cleanups-in-component-body--LIFO-order--1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--onCleanup-in-component-body-runs-on-unmount-1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--onCleanup-works-with-For-loop-items--component-body-style--1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--timer-cleanup-pattern--Solid-js-style--1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Effects-effect-cleanup-runs-before-re-run-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-large-list-update-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-nested-batch-operations-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-rapid-sequential-updates-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-Show-component---visible-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-show-hide-element---visible-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-fragment-with-list-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-nested-fragments-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-simple-fragment-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-conditional-toggle-updates-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-addition-updates-match-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-removal-updates-match-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-className-updates-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-style-updates-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-multiple-dynamic-attributes-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-deeply-nested-conditionals-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-empty-to-populated-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-populated-to-empty-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-cleanup-order-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-with-inner-signal-change-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-is-called-when-effect-re-runs-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-with-resource-simulation-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-multiple-children--no-wrapper--1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-no-children-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-fragment-with-list-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-nested-Fragments-work-correctly-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-complex-reordering-with-additions-and-removals-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-insert-in-middle-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-remove-from-middle-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-reverse-list-order-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-shuffle-list-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-Show-component-renders-when-condition-is-true-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-renders-content-when-initially-true-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-toggles-visibility-dynamically-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Memo-Dependency-Chain-conditional-memo-dependencies-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-basic-signal-get-set-produces-same-values-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-batch-updates-produce-same-final-values-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-peek-reads-value-without-tracking-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-selective-tracking-with-untrack-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-untrack-prevents-dependency-tracking-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API--SolidJS-style--reactivity-accessor-is-reactive-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-empty-string-for-non-failure-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-undefined-for-non-failure-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsFailure-and-stateError-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsPending-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsSuccess-and-stateValue-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateValue-returns-undefined-for-non-success-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-reject-transitions-to-failure-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-resolve-transitions-to-success-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-returns-resource--resolve--and-reject-functions-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-starts-in-pending-state-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-async-resolve-works-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-starts-in-pending-state-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-failure-on-reject-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-success-on-resolve-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-can-wrap-fetch-like-async-operations-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-works-with-setTimeout-simulation-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourceGet-tracks-dependencies-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourcePeek-does-not-track-dependencies-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-resourceRefetch-refetch-resets-to-pending-and-re-runs-fetcher-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-children-to-body-by-default-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-to-selector-mount-target-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/SolidJS-API-compatibility-createEffect-tracks-dependencies-automatically-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-accessor-condition-in-Match-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-multiple-Match-components-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-single-Match-and-fallback-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-components-Switch-updates-DOM-when-signal-changes-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-multiple-dependencies-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-single-dependency-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-with-defer-option-skips-initial-run-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Arrays-array-updates-work-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Reactivity-only-triggers-when-accessed-property-changes-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Reactivity-parent-path-change-notifies-child-accessors-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-nested-property-access-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-property-access-in-effects-1.png +0 -0
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
createElement,
|
|
4
|
+
render,
|
|
5
|
+
text,
|
|
6
|
+
} from "../src/index";
|
|
7
|
+
import axe from "axe-core";
|
|
8
|
+
|
|
9
|
+
function attr(name: string, value: unknown) {
|
|
10
|
+
return { _0: name, _1: value };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const AttrValue = {
|
|
14
|
+
Static: (value: string) => ({ $tag: 0, _0: value }),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
async function checkA11y(container: HTMLElement): Promise<axe.Result[]> {
|
|
18
|
+
const results = await axe.run(container);
|
|
19
|
+
return results.violations;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe("APG Components - Accessibility Tests", () => {
|
|
23
|
+
let container: HTMLElement;
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
container = document.createElement("div");
|
|
27
|
+
document.body.appendChild(container);
|
|
28
|
+
return () => {
|
|
29
|
+
container.remove();
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("Link Pattern", () => {
|
|
34
|
+
test("native link has no a11y violations", async () => {
|
|
35
|
+
const node = createElement(
|
|
36
|
+
"a",
|
|
37
|
+
[
|
|
38
|
+
attr("href", AttrValue.Static("https://example.com")),
|
|
39
|
+
],
|
|
40
|
+
[text("Visit Example")]
|
|
41
|
+
);
|
|
42
|
+
render(container, node);
|
|
43
|
+
|
|
44
|
+
const violations = await checkA11y(container);
|
|
45
|
+
expect(violations).toHaveLength(0);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("link with aria-label for icon is accessible", async () => {
|
|
49
|
+
const node = createElement(
|
|
50
|
+
"a",
|
|
51
|
+
[
|
|
52
|
+
attr("href", AttrValue.Static("/home")),
|
|
53
|
+
attr("aria-label", AttrValue.Static("Go to home page")),
|
|
54
|
+
],
|
|
55
|
+
[text("🏠")]
|
|
56
|
+
);
|
|
57
|
+
render(container, node);
|
|
58
|
+
|
|
59
|
+
const violations = await checkA11y(container);
|
|
60
|
+
expect(violations).toHaveLength(0);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("link with target=_blank should have accessible text", async () => {
|
|
64
|
+
const node = createElement(
|
|
65
|
+
"a",
|
|
66
|
+
[
|
|
67
|
+
attr("href", AttrValue.Static("https://example.com")),
|
|
68
|
+
attr("target", AttrValue.Static("_blank")),
|
|
69
|
+
],
|
|
70
|
+
[text("External Link (opens in new tab)")]
|
|
71
|
+
);
|
|
72
|
+
render(container, node);
|
|
73
|
+
|
|
74
|
+
const violations = await checkA11y(container);
|
|
75
|
+
expect(violations).toHaveLength(0);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("link-button with role=link has proper attributes", async () => {
|
|
79
|
+
const node = createElement(
|
|
80
|
+
"span",
|
|
81
|
+
[
|
|
82
|
+
attr("role", AttrValue.Static("link")),
|
|
83
|
+
attr("tabindex", AttrValue.Static("0")),
|
|
84
|
+
],
|
|
85
|
+
[text("Click here")]
|
|
86
|
+
);
|
|
87
|
+
render(container, node);
|
|
88
|
+
|
|
89
|
+
const span = container.querySelector("span");
|
|
90
|
+
expect(span?.getAttribute("role")).toBe("link");
|
|
91
|
+
expect(span?.getAttribute("tabindex")).toBe("0");
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("Button Pattern", () => {
|
|
96
|
+
test("native button has no a11y violations", async () => {
|
|
97
|
+
const node = createElement(
|
|
98
|
+
"button",
|
|
99
|
+
[attr("type", AttrValue.Static("button"))],
|
|
100
|
+
[text("Click me")]
|
|
101
|
+
);
|
|
102
|
+
render(container, node);
|
|
103
|
+
|
|
104
|
+
const violations = await checkA11y(container);
|
|
105
|
+
expect(violations).toHaveLength(0);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("icon button with aria-label is accessible", async () => {
|
|
109
|
+
const node = createElement(
|
|
110
|
+
"button",
|
|
111
|
+
[
|
|
112
|
+
attr("type", AttrValue.Static("button")),
|
|
113
|
+
attr("aria-label", AttrValue.Static("Close dialog")),
|
|
114
|
+
],
|
|
115
|
+
[text("×")]
|
|
116
|
+
);
|
|
117
|
+
render(container, node);
|
|
118
|
+
|
|
119
|
+
const violations = await checkA11y(container);
|
|
120
|
+
expect(violations).toHaveLength(0);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("disabled button has aria-disabled", async () => {
|
|
124
|
+
const node = createElement(
|
|
125
|
+
"button",
|
|
126
|
+
[
|
|
127
|
+
attr("type", AttrValue.Static("button")),
|
|
128
|
+
attr("disabled", AttrValue.Static("true")),
|
|
129
|
+
attr("aria-disabled", AttrValue.Static("true")),
|
|
130
|
+
],
|
|
131
|
+
[text("Disabled")]
|
|
132
|
+
);
|
|
133
|
+
render(container, node);
|
|
134
|
+
|
|
135
|
+
const button = container.querySelector("button");
|
|
136
|
+
expect(button?.getAttribute("aria-disabled")).toBe("true");
|
|
137
|
+
|
|
138
|
+
const violations = await checkA11y(container);
|
|
139
|
+
expect(violations).toHaveLength(0);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("toggle button has aria-pressed", async () => {
|
|
143
|
+
const node = createElement(
|
|
144
|
+
"button",
|
|
145
|
+
[
|
|
146
|
+
attr("type", AttrValue.Static("button")),
|
|
147
|
+
attr("aria-pressed", AttrValue.Static("false")),
|
|
148
|
+
],
|
|
149
|
+
[text("Mute")]
|
|
150
|
+
);
|
|
151
|
+
render(container, node);
|
|
152
|
+
|
|
153
|
+
const button = container.querySelector("button");
|
|
154
|
+
expect(button?.getAttribute("aria-pressed")).toBe("false");
|
|
155
|
+
|
|
156
|
+
const violations = await checkA11y(container);
|
|
157
|
+
expect(violations).toHaveLength(0);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("toggle button pressed state", async () => {
|
|
161
|
+
const node = createElement(
|
|
162
|
+
"button",
|
|
163
|
+
[
|
|
164
|
+
attr("type", AttrValue.Static("button")),
|
|
165
|
+
attr("aria-pressed", AttrValue.Static("true")),
|
|
166
|
+
],
|
|
167
|
+
[text("Mute")]
|
|
168
|
+
);
|
|
169
|
+
render(container, node);
|
|
170
|
+
|
|
171
|
+
const button = container.querySelector("button");
|
|
172
|
+
expect(button?.getAttribute("aria-pressed")).toBe("true");
|
|
173
|
+
|
|
174
|
+
const violations = await checkA11y(container);
|
|
175
|
+
expect(violations).toHaveLength(0);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("menu button has aria-haspopup and aria-expanded", async () => {
|
|
179
|
+
const node = createElement(
|
|
180
|
+
"button",
|
|
181
|
+
[
|
|
182
|
+
attr("type", AttrValue.Static("button")),
|
|
183
|
+
attr("aria-haspopup", AttrValue.Static("menu")),
|
|
184
|
+
attr("aria-expanded", AttrValue.Static("false")),
|
|
185
|
+
],
|
|
186
|
+
[text("Options")]
|
|
187
|
+
);
|
|
188
|
+
render(container, node);
|
|
189
|
+
|
|
190
|
+
const button = container.querySelector("button");
|
|
191
|
+
expect(button?.getAttribute("aria-haspopup")).toBe("menu");
|
|
192
|
+
expect(button?.getAttribute("aria-expanded")).toBe("false");
|
|
193
|
+
|
|
194
|
+
const violations = await checkA11y(container);
|
|
195
|
+
expect(violations).toHaveLength(0);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("menu button expanded state", async () => {
|
|
199
|
+
const node = createElement(
|
|
200
|
+
"button",
|
|
201
|
+
[
|
|
202
|
+
attr("type", AttrValue.Static("button")),
|
|
203
|
+
attr("aria-haspopup", AttrValue.Static("menu")),
|
|
204
|
+
attr("aria-expanded", AttrValue.Static("true")),
|
|
205
|
+
attr("aria-controls", AttrValue.Static("menu-id")),
|
|
206
|
+
],
|
|
207
|
+
[text("Options")]
|
|
208
|
+
);
|
|
209
|
+
render(container, node);
|
|
210
|
+
|
|
211
|
+
const button = container.querySelector("button");
|
|
212
|
+
expect(button?.getAttribute("aria-haspopup")).toBe("menu");
|
|
213
|
+
expect(button?.getAttribute("aria-expanded")).toBe("true");
|
|
214
|
+
expect(button?.getAttribute("aria-controls")).toBe("menu-id");
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe("Meter Pattern", () => {
|
|
219
|
+
test("meter with role and aria attributes is accessible", async () => {
|
|
220
|
+
const node = createElement(
|
|
221
|
+
"div",
|
|
222
|
+
[
|
|
223
|
+
attr("role", AttrValue.Static("meter")),
|
|
224
|
+
attr("aria-valuenow", AttrValue.Static("75")),
|
|
225
|
+
attr("aria-valuemin", AttrValue.Static("0")),
|
|
226
|
+
attr("aria-valuemax", AttrValue.Static("100")),
|
|
227
|
+
attr("aria-label", AttrValue.Static("Battery level")),
|
|
228
|
+
],
|
|
229
|
+
[text("75%")]
|
|
230
|
+
);
|
|
231
|
+
render(container, node);
|
|
232
|
+
|
|
233
|
+
const meter = container.querySelector("[role='meter']");
|
|
234
|
+
expect(meter?.getAttribute("aria-valuenow")).toBe("75");
|
|
235
|
+
expect(meter?.getAttribute("aria-valuemin")).toBe("0");
|
|
236
|
+
expect(meter?.getAttribute("aria-valuemax")).toBe("100");
|
|
237
|
+
|
|
238
|
+
const violations = await checkA11y(container);
|
|
239
|
+
expect(violations).toHaveLength(0);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("meter with aria-valuetext for human-readable value", async () => {
|
|
243
|
+
const node = createElement(
|
|
244
|
+
"div",
|
|
245
|
+
[
|
|
246
|
+
attr("role", AttrValue.Static("meter")),
|
|
247
|
+
attr("aria-valuenow", AttrValue.Static("50")),
|
|
248
|
+
attr("aria-valuemin", AttrValue.Static("0")),
|
|
249
|
+
attr("aria-valuemax", AttrValue.Static("100")),
|
|
250
|
+
attr("aria-valuetext", AttrValue.Static("50% (6 hours) remaining")),
|
|
251
|
+
attr("aria-label", AttrValue.Static("Battery")),
|
|
252
|
+
],
|
|
253
|
+
[text("50%")]
|
|
254
|
+
);
|
|
255
|
+
render(container, node);
|
|
256
|
+
|
|
257
|
+
const meter = container.querySelector("[role='meter']");
|
|
258
|
+
expect(meter?.getAttribute("aria-valuetext")).toBe("50% (6 hours) remaining");
|
|
259
|
+
|
|
260
|
+
const violations = await checkA11y(container);
|
|
261
|
+
expect(violations).toHaveLength(0);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("native meter element is accessible", async () => {
|
|
265
|
+
const node = createElement(
|
|
266
|
+
"meter",
|
|
267
|
+
[
|
|
268
|
+
attr("value", AttrValue.Static("75")),
|
|
269
|
+
attr("min", AttrValue.Static("0")),
|
|
270
|
+
attr("max", AttrValue.Static("100")),
|
|
271
|
+
attr("aria-label", AttrValue.Static("Disk usage")),
|
|
272
|
+
],
|
|
273
|
+
[text("75%")]
|
|
274
|
+
);
|
|
275
|
+
render(container, node);
|
|
276
|
+
|
|
277
|
+
const meter = container.querySelector("meter");
|
|
278
|
+
expect(meter?.getAttribute("value")).toBe("75");
|
|
279
|
+
|
|
280
|
+
const violations = await checkA11y(container);
|
|
281
|
+
expect(violations).toHaveLength(0);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe("Landmarks Pattern", () => {
|
|
286
|
+
test("header element creates banner landmark", async () => {
|
|
287
|
+
const node = createElement("header", [], [text("Site Header")]);
|
|
288
|
+
render(container, node);
|
|
289
|
+
|
|
290
|
+
const header = container.querySelector("header");
|
|
291
|
+
expect(header).not.toBeNull();
|
|
292
|
+
|
|
293
|
+
const violations = await checkA11y(container);
|
|
294
|
+
expect(violations).toHaveLength(0);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test("nav element creates navigation landmark", async () => {
|
|
298
|
+
const node = createElement(
|
|
299
|
+
"nav",
|
|
300
|
+
[attr("aria-label", AttrValue.Static("Main navigation"))],
|
|
301
|
+
[
|
|
302
|
+
createElement("a", [attr("href", AttrValue.Static("/"))], [text("Home")]),
|
|
303
|
+
]
|
|
304
|
+
);
|
|
305
|
+
render(container, node);
|
|
306
|
+
|
|
307
|
+
const nav = container.querySelector("nav");
|
|
308
|
+
expect(nav?.getAttribute("aria-label")).toBe("Main navigation");
|
|
309
|
+
|
|
310
|
+
const violations = await checkA11y(container);
|
|
311
|
+
expect(violations).toHaveLength(0);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test("main element creates main landmark", async () => {
|
|
315
|
+
const node = createElement("main", [], [text("Main Content")]);
|
|
316
|
+
render(container, node);
|
|
317
|
+
|
|
318
|
+
const main = container.querySelector("main");
|
|
319
|
+
expect(main).not.toBeNull();
|
|
320
|
+
|
|
321
|
+
const violations = await checkA11y(container);
|
|
322
|
+
expect(violations).toHaveLength(0);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test("aside element creates complementary landmark", async () => {
|
|
326
|
+
const node = createElement(
|
|
327
|
+
"aside",
|
|
328
|
+
[attr("aria-label", AttrValue.Static("Related content"))],
|
|
329
|
+
[text("Sidebar")]
|
|
330
|
+
);
|
|
331
|
+
render(container, node);
|
|
332
|
+
|
|
333
|
+
const aside = container.querySelector("aside");
|
|
334
|
+
expect(aside?.getAttribute("aria-label")).toBe("Related content");
|
|
335
|
+
|
|
336
|
+
const violations = await checkA11y(container);
|
|
337
|
+
expect(violations).toHaveLength(0);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test("footer element creates contentinfo landmark", async () => {
|
|
341
|
+
const node = createElement("footer", [], [text("© 2024")]);
|
|
342
|
+
render(container, node);
|
|
343
|
+
|
|
344
|
+
const footer = container.querySelector("footer");
|
|
345
|
+
expect(footer).not.toBeNull();
|
|
346
|
+
|
|
347
|
+
const violations = await checkA11y(container);
|
|
348
|
+
expect(violations).toHaveLength(0);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test("search element creates search landmark", async () => {
|
|
352
|
+
const node = createElement(
|
|
353
|
+
"search",
|
|
354
|
+
[attr("aria-label", AttrValue.Static("Site search"))],
|
|
355
|
+
[
|
|
356
|
+
createElement(
|
|
357
|
+
"input",
|
|
358
|
+
[
|
|
359
|
+
attr("type", AttrValue.Static("search")),
|
|
360
|
+
attr("aria-label", AttrValue.Static("Search query")),
|
|
361
|
+
],
|
|
362
|
+
[]
|
|
363
|
+
),
|
|
364
|
+
]
|
|
365
|
+
);
|
|
366
|
+
render(container, node);
|
|
367
|
+
|
|
368
|
+
const search = container.querySelector("search");
|
|
369
|
+
expect(search).not.toBeNull();
|
|
370
|
+
|
|
371
|
+
const violations = await checkA11y(container);
|
|
372
|
+
expect(violations).toHaveLength(0);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test("form with role=search creates search landmark", async () => {
|
|
376
|
+
const node = createElement(
|
|
377
|
+
"form",
|
|
378
|
+
[
|
|
379
|
+
attr("role", AttrValue.Static("search")),
|
|
380
|
+
attr("aria-label", AttrValue.Static("Site search")),
|
|
381
|
+
],
|
|
382
|
+
[
|
|
383
|
+
createElement(
|
|
384
|
+
"input",
|
|
385
|
+
[
|
|
386
|
+
attr("type", AttrValue.Static("search")),
|
|
387
|
+
attr("aria-label", AttrValue.Static("Search query")),
|
|
388
|
+
],
|
|
389
|
+
[]
|
|
390
|
+
),
|
|
391
|
+
]
|
|
392
|
+
);
|
|
393
|
+
render(container, node);
|
|
394
|
+
|
|
395
|
+
const form = container.querySelector("form[role='search']");
|
|
396
|
+
expect(form).not.toBeNull();
|
|
397
|
+
|
|
398
|
+
const violations = await checkA11y(container);
|
|
399
|
+
expect(violations).toHaveLength(0);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test("section with aria-label creates region landmark", async () => {
|
|
403
|
+
const node = createElement(
|
|
404
|
+
"section",
|
|
405
|
+
[attr("aria-label", AttrValue.Static("Quick Stats"))],
|
|
406
|
+
[text("Statistics content")]
|
|
407
|
+
);
|
|
408
|
+
render(container, node);
|
|
409
|
+
|
|
410
|
+
const section = container.querySelector("section");
|
|
411
|
+
expect(section?.getAttribute("aria-label")).toBe("Quick Stats");
|
|
412
|
+
|
|
413
|
+
const violations = await checkA11y(container);
|
|
414
|
+
expect(violations).toHaveLength(0);
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
describe("Keyboard Interaction", () => {
|
|
419
|
+
test("button responds to Enter key", () => {
|
|
420
|
+
let clicked = false;
|
|
421
|
+
const node = createElement(
|
|
422
|
+
"button",
|
|
423
|
+
[
|
|
424
|
+
attr("type", AttrValue.Static("button")),
|
|
425
|
+
attr("click", { $tag: 2, _0: () => { clicked = true; } }),
|
|
426
|
+
],
|
|
427
|
+
[text("Click")]
|
|
428
|
+
);
|
|
429
|
+
render(container, node);
|
|
430
|
+
|
|
431
|
+
const button = container.querySelector("button");
|
|
432
|
+
button?.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter" }));
|
|
433
|
+
button?.click();
|
|
434
|
+
expect(clicked).toBe(true);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
test("button is focusable", () => {
|
|
438
|
+
const node = createElement(
|
|
439
|
+
"button",
|
|
440
|
+
[attr("type", AttrValue.Static("button"))],
|
|
441
|
+
[text("Focusable")]
|
|
442
|
+
);
|
|
443
|
+
render(container, node);
|
|
444
|
+
|
|
445
|
+
const button = container.querySelector("button");
|
|
446
|
+
button?.focus();
|
|
447
|
+
expect(document.activeElement).toBe(button);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
test("link-button with role=link is focusable via tabindex", () => {
|
|
451
|
+
const node = createElement(
|
|
452
|
+
"span",
|
|
453
|
+
[
|
|
454
|
+
attr("role", AttrValue.Static("link")),
|
|
455
|
+
attr("tabindex", AttrValue.Static("0")),
|
|
456
|
+
],
|
|
457
|
+
[text("Focusable link")]
|
|
458
|
+
);
|
|
459
|
+
render(container, node);
|
|
460
|
+
|
|
461
|
+
const span = container.querySelector("span");
|
|
462
|
+
span?.focus();
|
|
463
|
+
expect(document.activeElement).toBe(span);
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
});
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { createSignal, debounced, createRenderEffect } from "../src/index";
|
|
3
|
+
|
|
4
|
+
describe("debounced signal", () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
vi.useFakeTimers();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
vi.useRealTimers();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("debounced returns initial value immediately", () => {
|
|
14
|
+
const [value, setValue] = createSignal(42);
|
|
15
|
+
const [debouncedValue] = debounced([value, setValue], 100);
|
|
16
|
+
|
|
17
|
+
expect(debouncedValue()).toBe(42);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("debounced delays value updates", () => {
|
|
21
|
+
const [value, setValue] = createSignal("initial");
|
|
22
|
+
const [debouncedValue, setDebouncedValue] = debounced([value, setValue], 100);
|
|
23
|
+
|
|
24
|
+
// Update the value
|
|
25
|
+
setDebouncedValue("updated");
|
|
26
|
+
|
|
27
|
+
// Debounced value should still be initial (before timeout)
|
|
28
|
+
expect(debouncedValue()).toBe("initial");
|
|
29
|
+
|
|
30
|
+
// Advance time by less than delay
|
|
31
|
+
vi.advanceTimersByTime(50);
|
|
32
|
+
expect(debouncedValue()).toBe("initial");
|
|
33
|
+
|
|
34
|
+
// Advance time to complete the delay
|
|
35
|
+
vi.advanceTimersByTime(50);
|
|
36
|
+
expect(debouncedValue()).toBe("updated");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("debounced cancels previous timer on rapid updates", () => {
|
|
40
|
+
const [value, setValue] = createSignal(0);
|
|
41
|
+
const [debouncedValue, setDebouncedValue] = debounced([value, setValue], 100);
|
|
42
|
+
|
|
43
|
+
// Rapid updates
|
|
44
|
+
setDebouncedValue(1);
|
|
45
|
+
vi.advanceTimersByTime(50);
|
|
46
|
+
setDebouncedValue(2);
|
|
47
|
+
vi.advanceTimersByTime(50);
|
|
48
|
+
setDebouncedValue(3);
|
|
49
|
+
|
|
50
|
+
// Should still be initial value
|
|
51
|
+
expect(debouncedValue()).toBe(0);
|
|
52
|
+
|
|
53
|
+
// Advance to complete the delay from last update
|
|
54
|
+
vi.advanceTimersByTime(100);
|
|
55
|
+
expect(debouncedValue()).toBe(3);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("debounced with 0ms delay updates immediately after microtask", () => {
|
|
59
|
+
const [value, setValue] = createSignal("start");
|
|
60
|
+
const [debouncedValue, setDebouncedValue] = debounced([value, setValue], 0);
|
|
61
|
+
|
|
62
|
+
setDebouncedValue("end");
|
|
63
|
+
|
|
64
|
+
// Even 0ms delay needs timer to fire
|
|
65
|
+
vi.advanceTimersByTime(0);
|
|
66
|
+
expect(debouncedValue()).toBe("end");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("debounced triggers reactive effects after delay", () => {
|
|
70
|
+
const [value, setValue] = createSignal(0);
|
|
71
|
+
const [debouncedValue, setDebouncedValue] = debounced([value, setValue], 100);
|
|
72
|
+
|
|
73
|
+
const log: number[] = [];
|
|
74
|
+
createRenderEffect(() => {
|
|
75
|
+
log.push(debouncedValue());
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Initial effect run
|
|
79
|
+
expect(log).toEqual([0]);
|
|
80
|
+
|
|
81
|
+
// Update value
|
|
82
|
+
setDebouncedValue(10);
|
|
83
|
+
expect(log).toEqual([0]); // Not yet updated
|
|
84
|
+
|
|
85
|
+
// After delay, effect should run
|
|
86
|
+
vi.advanceTimersByTime(100);
|
|
87
|
+
expect(log).toEqual([0, 10]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("debounced handles multiple sequential updates correctly", () => {
|
|
91
|
+
const [value, setValue] = createSignal("a");
|
|
92
|
+
const [debouncedValue, setDebouncedValue] = debounced([value, setValue], 50);
|
|
93
|
+
|
|
94
|
+
// First update
|
|
95
|
+
setDebouncedValue("b");
|
|
96
|
+
vi.advanceTimersByTime(50);
|
|
97
|
+
expect(debouncedValue()).toBe("b");
|
|
98
|
+
|
|
99
|
+
// Second update (after first completed)
|
|
100
|
+
setDebouncedValue("c");
|
|
101
|
+
vi.advanceTimersByTime(50);
|
|
102
|
+
expect(debouncedValue()).toBe("c");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("debounced works with object values", () => {
|
|
106
|
+
const [value, setValue] = createSignal({ count: 0 });
|
|
107
|
+
const [debouncedValue, setDebouncedValue] = debounced([value, setValue], 100);
|
|
108
|
+
|
|
109
|
+
const newObj = { count: 5 };
|
|
110
|
+
setDebouncedValue(newObj);
|
|
111
|
+
|
|
112
|
+
expect(debouncedValue().count).toBe(0);
|
|
113
|
+
|
|
114
|
+
vi.advanceTimersByTime(100);
|
|
115
|
+
expect(debouncedValue().count).toBe(5);
|
|
116
|
+
expect(debouncedValue()).toBe(newObj); // Same reference
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("debounced works with array values", () => {
|
|
120
|
+
const [value, setValue] = createSignal<number[]>([1, 2, 3]);
|
|
121
|
+
const [debouncedValue, setDebouncedValue] = debounced([value, setValue], 100);
|
|
122
|
+
|
|
123
|
+
setDebouncedValue([4, 5, 6]);
|
|
124
|
+
|
|
125
|
+
expect(debouncedValue()).toEqual([1, 2, 3]);
|
|
126
|
+
|
|
127
|
+
vi.advanceTimersByTime(100);
|
|
128
|
+
expect(debouncedValue()).toEqual([4, 5, 6]);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("debounced with long delay", () => {
|
|
132
|
+
const [value, setValue] = createSignal("original");
|
|
133
|
+
const [debouncedValue, setDebouncedValue] = debounced([value, setValue], 1000);
|
|
134
|
+
|
|
135
|
+
setDebouncedValue("delayed");
|
|
136
|
+
|
|
137
|
+
// Check at various points
|
|
138
|
+
vi.advanceTimersByTime(500);
|
|
139
|
+
expect(debouncedValue()).toBe("original");
|
|
140
|
+
|
|
141
|
+
vi.advanceTimersByTime(499);
|
|
142
|
+
expect(debouncedValue()).toBe("original");
|
|
143
|
+
|
|
144
|
+
vi.advanceTimersByTime(1);
|
|
145
|
+
expect(debouncedValue()).toBe("delayed");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("debounced handles many rapid updates efficiently", () => {
|
|
149
|
+
const [value, setValue] = createSignal(0);
|
|
150
|
+
const [debouncedValue, setDebouncedValue] = debounced([value, setValue], 100);
|
|
151
|
+
|
|
152
|
+
// Simulate 100 rapid updates
|
|
153
|
+
for (let i = 1; i <= 100; i++) {
|
|
154
|
+
setDebouncedValue(i);
|
|
155
|
+
vi.advanceTimersByTime(10); // 10ms between each
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Should still be 0 because timer keeps getting reset
|
|
159
|
+
expect(debouncedValue()).toBe(0);
|
|
160
|
+
|
|
161
|
+
// Wait for final debounce to complete
|
|
162
|
+
vi.advanceTimersByTime(100);
|
|
163
|
+
expect(debouncedValue()).toBe(100);
|
|
164
|
+
});
|
|
165
|
+
});
|
package/tests/dom.test.ts
CHANGED
|
@@ -421,12 +421,13 @@ describe("DOM API", () => {
|
|
|
421
421
|
expect(container.querySelector("span")).toBeNull();
|
|
422
422
|
});
|
|
423
423
|
|
|
424
|
-
test("Show accepts children as function", () => {
|
|
424
|
+
test("Show accepts children as function with accessor (SolidJS-style)", () => {
|
|
425
425
|
const [value, setValue] = createSignal<string | null>(null);
|
|
426
426
|
|
|
427
427
|
const node = Show({
|
|
428
428
|
when: value,
|
|
429
|
-
|
|
429
|
+
// SolidJS-style: children receives accessor function, call it with ()
|
|
430
|
+
children: (v: () => string) => createElement("span", [], [text(v())]),
|
|
430
431
|
});
|
|
431
432
|
|
|
432
433
|
mount(container, node);
|