@excom/quark 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/.rush/temp/chunked-rush-logs/quark.apply-exports.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/quark.build_package-metas.chunks.jsonl +1 -0
- package/.rush/temp/operation/apply-exports/all.log +1 -0
- package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/apply-exports/state.json +3 -0
- package/.rush/temp/operation/build_package-metas/all.log +1 -0
- package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/build_package-metas/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +3 -0
- package/config/rig.json +6 -0
- package/index.ts +4 -0
- package/language.ts +7 -0
- package/package.json +45 -0
- package/rush-logs/quark.apply-exports.cache.log +1 -0
- package/rush-logs/quark.apply-exports.log +1 -0
- package/rush-logs/quark.build_package-metas.cache.log +1 -0
- package/rush-logs/quark.build_package-metas.log +1 -0
- package/src/actions.ts +445 -0
- package/src/ast.ts +187 -0
- package/src/bindings.ts +115 -0
- package/src/builtin-modules.ts +401 -0
- package/src/constants.ts +47 -0
- package/src/cycle-check.ts +121 -0
- package/src/devtools-hook.ts +303 -0
- package/src/element-api.ts +109 -0
- package/src/evaluator.ts +317 -0
- package/src/form-controls.ts +69 -0
- package/src/language-tables.ts +137 -0
- package/src/language.ts +1225 -0
- package/src/observer.ts +110 -0
- package/src/paint.ts +305 -0
- package/src/properties.ts +818 -0
- package/src/props.ts +94 -0
- package/src/quark-element.ts +7 -0
- package/src/quark-internal.ts +365 -0
- package/src/quark.ts +817 -0
- package/src/resolvers.ts +867 -0
- package/src/rule.ts +800 -0
- package/src/scope-id.ts +47 -0
- package/src/selector-utils.ts +430 -0
- package/src/settle.ts +117 -0
- package/src/types.ts +155 -0
- package/src/utils.ts +79 -0
- package/src/variables.ts +359 -0
- package/support/demos/css-variables.html +18 -0
- package/support/demos/dispatch.html +20 -0
- package/support/demos/events.html +9 -0
- package/support/demos/iterate.html +13 -0
- package/support/demos/js-api.html +17 -0
- package/support/demos/provider-list.html +16 -0
- package/support/demos/toggle-content.html +14 -0
- package/support/demos/unset.html +12 -0
- package/support/demos/view-transition.html +36 -0
- package/support/docs/ATTRIBUTES.md +37 -0
- package/support/docs/AT_RULES.md +45 -0
- package/support/docs/BUILTINS.md +57 -0
- package/support/docs/CONTENT.md +40 -0
- package/support/docs/CSS_VARIABLES.md +16 -0
- package/support/docs/DECLARATIONS.md +26 -0
- package/support/docs/DELAY.md +37 -0
- package/support/docs/DIAGNOSTICS.md +32 -0
- package/support/docs/DISPATCH.md +81 -0
- package/support/docs/ELEMENT_PROPERTIES.md +34 -0
- package/support/docs/EXPRESSIONS.md +31 -0
- package/support/docs/INTERNAL.md +8 -0
- package/support/docs/JS_API.md +53 -0
- package/support/docs/JS_WRITES.md +36 -0
- package/support/docs/LIMITATIONS.md +26 -0
- package/support/docs/LOOP_GUARD.md +30 -0
- package/support/docs/METHODS.md +43 -0
- package/support/docs/MODULES.md +111 -0
- package/support/docs/NO_REVERSION.md +18 -0
- package/support/docs/ON.md +87 -0
- package/support/docs/REACTIVITY.md +24 -0
- package/support/docs/README.md +143 -0
- package/support/docs/SELECTORS.md +87 -0
- package/support/docs/SHEETS.md +67 -0
- package/support/docs/SYNTAX.md +36 -0
- package/support/docs/USE.md +24 -0
- package/support/docs/VALUES.md +27 -0
- package/support/docs/VARIABLES.md +47 -0
- package/support/docs/VIEW_TRANSITION.md +64 -0
- package/support/docs-sections.json +65 -0
- package/support/package-meta.json +208 -0
- package/support/scripts/build-language-docs.mjs +196 -0
- package/support/tests/__snapshots__/css-variables.view.test.ts.snap +23 -0
- package/support/tests/__snapshots__/dispatch.view.test.ts.snap +23 -0
- package/support/tests/__snapshots__/events.view.test.ts.snap +23 -0
- package/support/tests/__snapshots__/iterate.view.test.ts.snap +23 -0
- package/support/tests/__snapshots__/js-api.view.test.ts.snap +23 -0
- package/support/tests/__snapshots__/provider-list.view.test.ts.snap +23 -0
- package/support/tests/__snapshots__/quark-features.test.ts.snap +1079 -0
- package/support/tests/__snapshots__/quark-with-providers.test.ts.snap +89 -0
- package/support/tests/__snapshots__/quark.test.ts.snap +221 -0
- package/support/tests/__snapshots__/selectors.test.ts.snap +133 -0
- package/support/tests/__snapshots__/toggle-content.view.test.ts.snap +23 -0
- package/support/tests/__snapshots__/unset.view.test.ts.snap +23 -0
- package/support/tests/__snapshots__/view-transition.view.test.ts.snap +23 -0
- package/support/tests/actions.test.ts +437 -0
- package/support/tests/builtin-modules.test.ts +337 -0
- package/support/tests/builtins.test.ts +189 -0
- package/support/tests/css-variables.view.test.ts +35 -0
- package/support/tests/delay.test.ts +244 -0
- package/support/tests/devtools-hook.test.ts +496 -0
- package/support/tests/diagnostics.test.ts +224 -0
- package/support/tests/dispatch.view.test.ts +43 -0
- package/support/tests/element-api.test.ts +202 -0
- package/support/tests/evaluator.test.ts +237 -0
- package/support/tests/events.view.test.ts +40 -0
- package/support/tests/form-controls.test.ts +452 -0
- package/support/tests/helpers.ts +354 -0
- package/support/tests/internals.test.ts +568 -0
- package/support/tests/iterate.view.test.ts +40 -0
- package/support/tests/js-api.view.test.ts +45 -0
- package/support/tests/language-docs.test.ts +147 -0
- package/support/tests/lifecycle.test.ts +440 -0
- package/support/tests/loop-guard.test.ts +323 -0
- package/support/tests/provider-list.view.test.ts +47 -0
- package/support/tests/quark-features.test.ts +2887 -0
- package/support/tests/quark-with-providers.test.ts +169 -0
- package/support/tests/quark.test.ts +359 -0
- package/support/tests/resolvers.test.ts +346 -0
- package/support/tests/selector-analysis.test.ts +281 -0
- package/support/tests/selectors.test.ts +670 -0
- package/support/tests/toggle-content.view.test.ts +40 -0
- package/support/tests/unset.view.test.ts +42 -0
- package/support/tests/view-helpers.ts +81 -0
- package/support/tests/view-transition.test.ts +1069 -0
- package/support/tests/view-transition.view.test.ts +76 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterEach,
|
|
3
|
+
describe,
|
|
4
|
+
expect,
|
|
5
|
+
it,
|
|
6
|
+
spyFetch,
|
|
7
|
+
vi,
|
|
8
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
9
|
+
import "@excom/provider-fetch";
|
|
10
|
+
import "@excom/provider-storage";
|
|
11
|
+
import {
|
|
12
|
+
createSheet,
|
|
13
|
+
expectComplexity,
|
|
14
|
+
flush,
|
|
15
|
+
measureComplexity,
|
|
16
|
+
unregisterAll,
|
|
17
|
+
} from "./helpers";
|
|
18
|
+
|
|
19
|
+
const whenFetchSuccess = (el: Element) =>
|
|
20
|
+
new Promise<void>((resolve) => {
|
|
21
|
+
el.addEventListener("provider-fetch-success", () => resolve(), {
|
|
22
|
+
once: true,
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("Quark with providers", () => {
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
unregisterAll();
|
|
29
|
+
document.body.innerHTML = "";
|
|
30
|
+
localStorage.clear();
|
|
31
|
+
vi.restoreAllMocks();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("binds provider-fetch body fields via prop()", async () => {
|
|
35
|
+
spyFetch({
|
|
36
|
+
status: 200,
|
|
37
|
+
body: JSON.stringify({
|
|
38
|
+
id: 1,
|
|
39
|
+
title: "delectus aut autem",
|
|
40
|
+
completed: false,
|
|
41
|
+
}),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const { root, quark, register } = createSheet(
|
|
45
|
+
`<provider-fetch api-url="/api/todos/1">
|
|
46
|
+
<span bind-title></span>
|
|
47
|
+
</provider-fetch>`,
|
|
48
|
+
`provider-fetch[is-success] {
|
|
49
|
+
$todo: prop("provision").body;
|
|
50
|
+
[bind-title] { content: $todo.title; }
|
|
51
|
+
}`,
|
|
52
|
+
);
|
|
53
|
+
const meter = measureComplexity(quark);
|
|
54
|
+
register();
|
|
55
|
+
const provider = root.querySelector("provider-fetch")!;
|
|
56
|
+
await whenFetchSuccess(provider);
|
|
57
|
+
await flush();
|
|
58
|
+
const budget = meter.take();
|
|
59
|
+
meter.stop();
|
|
60
|
+
|
|
61
|
+
expect(root.querySelector("[bind-title]")?.textContent).toBe(
|
|
62
|
+
"delectus aut autem",
|
|
63
|
+
);
|
|
64
|
+
expectComplexity(budget);
|
|
65
|
+
quark.unregister();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("iterates provider-fetch body into a list", async () => {
|
|
69
|
+
spyFetch({
|
|
70
|
+
status: 200,
|
|
71
|
+
body: JSON.stringify([{ label: "one" }, { label: "two" }]),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const { root, quark, register } = createSheet(
|
|
75
|
+
`<provider-fetch api-url="/api/items">
|
|
76
|
+
<ul>
|
|
77
|
+
<template><li bind-label></li></template>
|
|
78
|
+
</ul>
|
|
79
|
+
</provider-fetch>`,
|
|
80
|
+
`provider-fetch[is-success] {
|
|
81
|
+
$items: prop("provision").body;
|
|
82
|
+
ul { content: iterate($items); }
|
|
83
|
+
[bind-label] { content: item.label; }
|
|
84
|
+
}`,
|
|
85
|
+
);
|
|
86
|
+
const meter = measureComplexity(quark);
|
|
87
|
+
register();
|
|
88
|
+
const provider = root.querySelector("provider-fetch")!;
|
|
89
|
+
await whenFetchSuccess(provider);
|
|
90
|
+
await flush();
|
|
91
|
+
|
|
92
|
+
const items = [...root.querySelectorAll("ul > li")];
|
|
93
|
+
expect(items).toHaveLength(2);
|
|
94
|
+
|
|
95
|
+
const budget = meter.take();
|
|
96
|
+
meter.stop();
|
|
97
|
+
|
|
98
|
+
expect(items.map((el) => el.textContent)).toEqual(["one", "two"]);
|
|
99
|
+
expectComplexity(budget);
|
|
100
|
+
quark.unregister();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("updates bindings when provider-fetch re-provisions", async () => {
|
|
104
|
+
let payload = { id: 1, title: "Hello", completed: false };
|
|
105
|
+
spyFetch(() => ({
|
|
106
|
+
status: 200,
|
|
107
|
+
body: JSON.stringify(payload),
|
|
108
|
+
}));
|
|
109
|
+
|
|
110
|
+
const { root, quark, register } = createSheet(
|
|
111
|
+
`<provider-fetch api-url="/api/todos/1">
|
|
112
|
+
<span bind-title></span>
|
|
113
|
+
</provider-fetch>`,
|
|
114
|
+
`provider-fetch[is-success] {
|
|
115
|
+
$todo: prop("provision").body;
|
|
116
|
+
[bind-title] { content: $todo.title; }
|
|
117
|
+
}`,
|
|
118
|
+
);
|
|
119
|
+
const meter = measureComplexity(quark);
|
|
120
|
+
register();
|
|
121
|
+
const provider = root.querySelector("provider-fetch")!;
|
|
122
|
+
await whenFetchSuccess(provider);
|
|
123
|
+
await flush();
|
|
124
|
+
expect(root.querySelector("[bind-title]")?.textContent).toBe("Hello");
|
|
125
|
+
|
|
126
|
+
payload = { id: 1, title: "Hello, world!", completed: false };
|
|
127
|
+
const nextSuccess = whenFetchSuccess(provider);
|
|
128
|
+
// `--fetch` command (provider-fetch no longer listens for a trigger event)
|
|
129
|
+
provider.dispatchEvent(
|
|
130
|
+
Object.defineProperties(new Event("command", { cancelable: true }), {
|
|
131
|
+
command: { value: "--fetch" },
|
|
132
|
+
source: { value: null },
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
await nextSuccess;
|
|
136
|
+
await flush();
|
|
137
|
+
const budget = meter.take();
|
|
138
|
+
meter.stop();
|
|
139
|
+
|
|
140
|
+
expect(root.querySelector("[bind-title]")?.textContent).toBe(
|
|
141
|
+
"Hello, world!",
|
|
142
|
+
);
|
|
143
|
+
expectComplexity(budget);
|
|
144
|
+
quark.unregister();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("binds provider-storage via prop()", async () => {
|
|
148
|
+
localStorage.setItem("quark-ps-test", JSON.stringify({ msg: "stored" }));
|
|
149
|
+
|
|
150
|
+
const { root, quark, register } = createSheet(
|
|
151
|
+
`<provider-storage key-name="quark-ps-test">
|
|
152
|
+
<span bind-msg></span>
|
|
153
|
+
</provider-storage>`,
|
|
154
|
+
`provider-storage[is-success] {
|
|
155
|
+
$val: prop("provision");
|
|
156
|
+
[bind-msg] { content: $val.msg; }
|
|
157
|
+
}`,
|
|
158
|
+
);
|
|
159
|
+
const meter = measureComplexity(quark);
|
|
160
|
+
register();
|
|
161
|
+
await flush();
|
|
162
|
+
const budget = meter.take();
|
|
163
|
+
meter.stop();
|
|
164
|
+
|
|
165
|
+
expect(root.querySelector("[bind-msg]")?.textContent).toBe("stored");
|
|
166
|
+
expectComplexity(budget);
|
|
167
|
+
quark.unregister();
|
|
168
|
+
});
|
|
169
|
+
});
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { Quark } from "../../index";
|
|
2
|
+
import { deref, minifyQuark, stringToHash } from "../../src/utils";
|
|
3
|
+
import {
|
|
4
|
+
afterEach,
|
|
5
|
+
describe,
|
|
6
|
+
expect,
|
|
7
|
+
fixture,
|
|
8
|
+
it,
|
|
9
|
+
vi,
|
|
10
|
+
wait,
|
|
11
|
+
} from "@excom/heft-rig/profiles/default/config/test-utils";
|
|
12
|
+
import {
|
|
13
|
+
createSheet,
|
|
14
|
+
expectComplexity,
|
|
15
|
+
flush,
|
|
16
|
+
measureComplexity,
|
|
17
|
+
mount,
|
|
18
|
+
} from "./helpers";
|
|
19
|
+
|
|
20
|
+
const SCALE = 50;
|
|
21
|
+
|
|
22
|
+
const repeatHtml = (count: number, render: (i: number) => string) =>
|
|
23
|
+
Array.from({ length: count }, (_, i) => render(i)).join("");
|
|
24
|
+
|
|
25
|
+
describe("quark utils", () => {
|
|
26
|
+
it("hashes strings deterministically", () => {
|
|
27
|
+
expect(stringToHash("abc")).toBe(stringToHash("abc"));
|
|
28
|
+
expect(stringToHash("abc")).not.toBe(stringToHash("abcd"));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("normalizes whitespace but leaves comments to the parser", () => {
|
|
32
|
+
const input =
|
|
33
|
+
"/* comment */\n\n .foo { color: red; } // line\n\n\n.bar { x: 1; }";
|
|
34
|
+
const result = minifyQuark(input);
|
|
35
|
+
expect(result).toBe(
|
|
36
|
+
"/* comment */\n.foo { color: red; } // line\n.bar { x: 1; }"
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("keeps // inside string literals intact", () => {
|
|
41
|
+
const input = '@use "https://cdn.example.com/x.js" as api;';
|
|
42
|
+
expect(minifyQuark(input)).toBe(input);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("dereferences values and WeakRefs", () => {
|
|
46
|
+
const el = document.createElement("div");
|
|
47
|
+
const ref = new WeakRef(el);
|
|
48
|
+
expect(deref(el)).toBe(el);
|
|
49
|
+
expect(deref(ref)).toBe(el);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("happy-dom querySelectorAll shim", () => {
|
|
54
|
+
afterEach(() => {
|
|
55
|
+
document.body.innerHTML = "";
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("matches ancestors outside the query root (browser semantics)", () => {
|
|
59
|
+
const root = fixture<HTMLElement>(`<div id="outer">
|
|
60
|
+
<div id="subject"><div id="inner"></div></div>
|
|
61
|
+
</div>`);
|
|
62
|
+
const subject = root.querySelector("#subject")!;
|
|
63
|
+
expect(subject.querySelectorAll("#outer #inner")).toHaveLength(1);
|
|
64
|
+
expect(subject.querySelector("#outer #inner")?.id).toBe("inner");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("never returns the query root itself (browser semantics)", () => {
|
|
68
|
+
const root = fixture<HTMLElement>(`<section><section></section></section>`);
|
|
69
|
+
expect(root.querySelectorAll("section")).toHaveLength(1);
|
|
70
|
+
expect(root.querySelector("section")).not.toBe(root);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("resolves :scope via a temporary marker on the query root", () => {
|
|
74
|
+
const root = fixture<HTMLElement>(
|
|
75
|
+
`<div><div id="subject"><span class="child"></span></div></div>`
|
|
76
|
+
);
|
|
77
|
+
const subject = root.querySelector("#subject")!;
|
|
78
|
+
expect(subject.querySelectorAll(":scope > .child")).toHaveLength(1);
|
|
79
|
+
expect(subject.querySelector(":scope > .child")?.className).toBe("child");
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("Quark", () => {
|
|
84
|
+
afterEach(() => {
|
|
85
|
+
document.body.innerHTML = "";
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("mutates matching bind targets on register", async () => {
|
|
89
|
+
const body = `<main>${repeatHtml(
|
|
90
|
+
SCALE,
|
|
91
|
+
(i) => `<span bind-label data-i="${i}"></span>`
|
|
92
|
+
)}</main>`;
|
|
93
|
+
const { root, quark, register } = createSheet(
|
|
94
|
+
body,
|
|
95
|
+
`[bind-label] { data-ready: ""; }`
|
|
96
|
+
);
|
|
97
|
+
const meter = measureComplexity(quark);
|
|
98
|
+
register();
|
|
99
|
+
await flush();
|
|
100
|
+
const budget = meter.take();
|
|
101
|
+
meter.stop();
|
|
102
|
+
|
|
103
|
+
expect(root.querySelectorAll("[bind-label][data-ready]")).toHaveLength(
|
|
104
|
+
SCALE
|
|
105
|
+
);
|
|
106
|
+
expectComplexity(budget);
|
|
107
|
+
quark.unregister();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("mutates elements rendered after register", async () => {
|
|
111
|
+
const { root, quark, register } = createSheet(
|
|
112
|
+
``,
|
|
113
|
+
`span { data-name: "my-span"; }`
|
|
114
|
+
);
|
|
115
|
+
register();
|
|
116
|
+
await flush();
|
|
117
|
+
|
|
118
|
+
const meter = measureComplexity(quark);
|
|
119
|
+
const span = document.createElement("span");
|
|
120
|
+
root.appendChild(span);
|
|
121
|
+
await flush();
|
|
122
|
+
const budget = meter.take();
|
|
123
|
+
meter.stop();
|
|
124
|
+
|
|
125
|
+
expect(span.getAttribute("data-name")).toBe("my-span");
|
|
126
|
+
expectComplexity(budget);
|
|
127
|
+
quark.unregister();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("removes an attribute with none", async () => {
|
|
131
|
+
const { root, quark, register } = createSheet(
|
|
132
|
+
`<span data-name="stale"></span>`,
|
|
133
|
+
`span { data-name: none; }`
|
|
134
|
+
);
|
|
135
|
+
const meter = measureComplexity(quark);
|
|
136
|
+
register();
|
|
137
|
+
await flush();
|
|
138
|
+
const budget = meter.take();
|
|
139
|
+
meter.stop();
|
|
140
|
+
|
|
141
|
+
expect(root.querySelector("span")?.hasAttribute("data-name")).toBe(false);
|
|
142
|
+
expectComplexity(budget);
|
|
143
|
+
quark.unregister();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("sets text content from a string", async () => {
|
|
147
|
+
const { root, quark, register } = createSheet(
|
|
148
|
+
`<p></p>`,
|
|
149
|
+
`p { content: "hello"; }`
|
|
150
|
+
);
|
|
151
|
+
const meter = measureComplexity(quark);
|
|
152
|
+
register();
|
|
153
|
+
await flush();
|
|
154
|
+
const budget = meter.take();
|
|
155
|
+
meter.stop();
|
|
156
|
+
|
|
157
|
+
expect(root.querySelector("p")?.textContent).toBe("hello");
|
|
158
|
+
expectComplexity(budget);
|
|
159
|
+
quark.unregister();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("passes nested variables into child content", async () => {
|
|
163
|
+
const { root, quark, register } = createSheet(
|
|
164
|
+
`<main><span bind-msg></span></main>`,
|
|
165
|
+
`main {
|
|
166
|
+
$msg: "hi";
|
|
167
|
+
[bind-msg] { content: $msg; }
|
|
168
|
+
}`
|
|
169
|
+
);
|
|
170
|
+
const meter = measureComplexity(quark);
|
|
171
|
+
register();
|
|
172
|
+
await flush();
|
|
173
|
+
const budget = meter.take();
|
|
174
|
+
meter.stop();
|
|
175
|
+
|
|
176
|
+
expect(root.querySelector("[bind-msg]")?.textContent).toBe("hi");
|
|
177
|
+
expectComplexity(budget);
|
|
178
|
+
quark.unregister();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("calls registered module functions for attributes and listeners", async () => {
|
|
182
|
+
const handleClick = vi.fn();
|
|
183
|
+
const { root, quark, register } = createSheet(
|
|
184
|
+
`<button type="button" id="target"></button>`,
|
|
185
|
+
`#target {
|
|
186
|
+
data-label: getLabel();
|
|
187
|
+
@on click (handle: handleClick);
|
|
188
|
+
}`,
|
|
189
|
+
{
|
|
190
|
+
getLabel: () => "from-js",
|
|
191
|
+
handleClick,
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
const meter = measureComplexity(quark);
|
|
195
|
+
register();
|
|
196
|
+
await flush();
|
|
197
|
+
const budget = meter.take();
|
|
198
|
+
meter.stop();
|
|
199
|
+
|
|
200
|
+
const button = root.querySelector("#target") as HTMLButtonElement;
|
|
201
|
+
expect(button.getAttribute("data-label")).toBe("from-js");
|
|
202
|
+
button.click();
|
|
203
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
204
|
+
expectComplexity(budget);
|
|
205
|
+
quark.unregister();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("reads .provision via prop() and re-runs when it is assigned", async () => {
|
|
209
|
+
const root = fixture<HTMLElement>(`<section>
|
|
210
|
+
<div id="sheet"></div>
|
|
211
|
+
<data-provider is-success>
|
|
212
|
+
<span bind-title></span>
|
|
213
|
+
</data-provider>
|
|
214
|
+
</section>`);
|
|
215
|
+
const provider = root.querySelector("data-provider") as HTMLElement & {
|
|
216
|
+
provision?: { title: string };
|
|
217
|
+
};
|
|
218
|
+
provider.provision = { title: "Hello" };
|
|
219
|
+
|
|
220
|
+
const quark = new Quark({
|
|
221
|
+
options: { isScoped: true },
|
|
222
|
+
src: `data-provider[is-success] {
|
|
223
|
+
$todo: prop("provision");
|
|
224
|
+
[bind-title] { content: $todo.title; }
|
|
225
|
+
}`,
|
|
226
|
+
});
|
|
227
|
+
const meter = measureComplexity(quark);
|
|
228
|
+
quark.register({
|
|
229
|
+
sheetElement: root.querySelector("#sheet") as HTMLElement,
|
|
230
|
+
});
|
|
231
|
+
await flush();
|
|
232
|
+
expect(root.querySelector("[bind-title]")?.textContent).toBe("Hello");
|
|
233
|
+
|
|
234
|
+
provider.provision = { title: "Hello, world!" };
|
|
235
|
+
await flush();
|
|
236
|
+
const budget = meter.take();
|
|
237
|
+
meter.stop();
|
|
238
|
+
|
|
239
|
+
expect(root.querySelector("[bind-title]")?.textContent).toBe(
|
|
240
|
+
"Hello, world!"
|
|
241
|
+
);
|
|
242
|
+
expectComplexity(budget);
|
|
243
|
+
quark.unregister();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("iterates an array into a template", async () => {
|
|
247
|
+
const items = Array.from({ length: SCALE }, (_, i) => ({
|
|
248
|
+
label: `item-${i}`,
|
|
249
|
+
}));
|
|
250
|
+
const root = fixture<HTMLElement>(`<section>
|
|
251
|
+
<div id="sheet"></div>
|
|
252
|
+
<data-provider is-success>
|
|
253
|
+
<ul>
|
|
254
|
+
<template><li bind-label></li></template>
|
|
255
|
+
</ul>
|
|
256
|
+
</data-provider>
|
|
257
|
+
</section>`);
|
|
258
|
+
const provider = root.querySelector("data-provider") as HTMLElement & {
|
|
259
|
+
provision?: { label: string }[];
|
|
260
|
+
};
|
|
261
|
+
provider.provision = items;
|
|
262
|
+
|
|
263
|
+
const quark = new Quark({
|
|
264
|
+
options: { isScoped: true },
|
|
265
|
+
src: `data-provider[is-success] {
|
|
266
|
+
$items: prop("provision");
|
|
267
|
+
ul { content: iterate($items); }
|
|
268
|
+
[bind-label] { content: item.label; }
|
|
269
|
+
}`,
|
|
270
|
+
});
|
|
271
|
+
const meter = measureComplexity(quark);
|
|
272
|
+
quark.register({
|
|
273
|
+
sheetElement: root.querySelector("#sheet") as HTMLElement,
|
|
274
|
+
});
|
|
275
|
+
await flush();
|
|
276
|
+
|
|
277
|
+
const rows = [...root.querySelectorAll("ul > li")];
|
|
278
|
+
expect(rows).toHaveLength(SCALE);
|
|
279
|
+
|
|
280
|
+
// the iterate paint's insertions reach the row rules via childList
|
|
281
|
+
const budget = meter.take();
|
|
282
|
+
meter.stop();
|
|
283
|
+
|
|
284
|
+
expect(rows.map((el) => el.textContent)).toEqual(
|
|
285
|
+
items.map((item) => item.label)
|
|
286
|
+
);
|
|
287
|
+
expectComplexity(budget);
|
|
288
|
+
quark.unregister();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it("re-runs when a watched attribute changes", async () => {
|
|
292
|
+
const body = `<details>${repeatHtml(
|
|
293
|
+
SCALE,
|
|
294
|
+
() => `<p bind-status></p>`
|
|
295
|
+
)}</details>`;
|
|
296
|
+
const { root, quark, register } = createSheet(
|
|
297
|
+
body,
|
|
298
|
+
`details[open] [bind-status] {
|
|
299
|
+
content: "open";
|
|
300
|
+
}
|
|
301
|
+
details:not([open]) [bind-status] {
|
|
302
|
+
content: none;
|
|
303
|
+
}`
|
|
304
|
+
);
|
|
305
|
+
// Spy before register so MutationObserver closes over the wrapper.
|
|
306
|
+
const spy = vi.spyOn(quark, "queueRunRules");
|
|
307
|
+
register();
|
|
308
|
+
await flush();
|
|
309
|
+
spy.mockClear();
|
|
310
|
+
|
|
311
|
+
const details = root.querySelector("details")!;
|
|
312
|
+
details.setAttribute("open", "");
|
|
313
|
+
await wait(0);
|
|
314
|
+
expect(spy).toHaveBeenCalledWith(
|
|
315
|
+
expect.objectContaining({ attribute: "open", element: details })
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
const meter = measureComplexity(quark);
|
|
319
|
+
await flush();
|
|
320
|
+
const budget = meter.take();
|
|
321
|
+
meter.stop();
|
|
322
|
+
|
|
323
|
+
expect(
|
|
324
|
+
[...root.querySelectorAll("[bind-status]")].every(
|
|
325
|
+
(el) => el.textContent === "open"
|
|
326
|
+
)
|
|
327
|
+
).toBe(true);
|
|
328
|
+
expectComplexity(budget);
|
|
329
|
+
quark.unregister();
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it("activates many articles when data-active is set", async () => {
|
|
333
|
+
const body = `<main>${repeatHtml(
|
|
334
|
+
SCALE,
|
|
335
|
+
(i) => `<article data-id="${i}"><span bind-label></span></article>`
|
|
336
|
+
)}</main>`;
|
|
337
|
+
const { root, quark } = mount(
|
|
338
|
+
body,
|
|
339
|
+
`article[data-active] [bind-label] {
|
|
340
|
+
content: "active";
|
|
341
|
+
}`
|
|
342
|
+
);
|
|
343
|
+
await flush();
|
|
344
|
+
|
|
345
|
+
for (const article of root.querySelectorAll("article")) {
|
|
346
|
+
article.setAttribute("data-active", "");
|
|
347
|
+
}
|
|
348
|
+
const meter = measureComplexity(quark);
|
|
349
|
+
await flush();
|
|
350
|
+
const budget = meter.take();
|
|
351
|
+
meter.stop();
|
|
352
|
+
|
|
353
|
+
expect(
|
|
354
|
+
[...root.querySelectorAll("[bind-label]")].map((el) => el.textContent)
|
|
355
|
+
).toEqual(Array.from({ length: SCALE }, () => "active"));
|
|
356
|
+
expectComplexity(budget);
|
|
357
|
+
quark.unregister();
|
|
358
|
+
});
|
|
359
|
+
});
|