@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.
Files changed (130) hide show
  1. package/.rush/temp/chunked-rush-logs/quark.apply-exports.chunks.jsonl +1 -0
  2. package/.rush/temp/chunked-rush-logs/quark.build_package-metas.chunks.jsonl +1 -0
  3. package/.rush/temp/operation/apply-exports/all.log +1 -0
  4. package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
  5. package/.rush/temp/operation/apply-exports/state.json +3 -0
  6. package/.rush/temp/operation/build_package-metas/all.log +1 -0
  7. package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
  8. package/.rush/temp/operation/build_package-metas/state.json +3 -0
  9. package/.rush/temp/shrinkwrap-deps.json +3 -0
  10. package/config/rig.json +6 -0
  11. package/index.ts +4 -0
  12. package/language.ts +7 -0
  13. package/package.json +45 -0
  14. package/rush-logs/quark.apply-exports.cache.log +1 -0
  15. package/rush-logs/quark.apply-exports.log +1 -0
  16. package/rush-logs/quark.build_package-metas.cache.log +1 -0
  17. package/rush-logs/quark.build_package-metas.log +1 -0
  18. package/src/actions.ts +445 -0
  19. package/src/ast.ts +187 -0
  20. package/src/bindings.ts +115 -0
  21. package/src/builtin-modules.ts +401 -0
  22. package/src/constants.ts +47 -0
  23. package/src/cycle-check.ts +121 -0
  24. package/src/devtools-hook.ts +303 -0
  25. package/src/element-api.ts +109 -0
  26. package/src/evaluator.ts +317 -0
  27. package/src/form-controls.ts +69 -0
  28. package/src/language-tables.ts +137 -0
  29. package/src/language.ts +1225 -0
  30. package/src/observer.ts +110 -0
  31. package/src/paint.ts +305 -0
  32. package/src/properties.ts +818 -0
  33. package/src/props.ts +94 -0
  34. package/src/quark-element.ts +7 -0
  35. package/src/quark-internal.ts +365 -0
  36. package/src/quark.ts +817 -0
  37. package/src/resolvers.ts +867 -0
  38. package/src/rule.ts +800 -0
  39. package/src/scope-id.ts +47 -0
  40. package/src/selector-utils.ts +430 -0
  41. package/src/settle.ts +117 -0
  42. package/src/types.ts +155 -0
  43. package/src/utils.ts +79 -0
  44. package/src/variables.ts +359 -0
  45. package/support/demos/css-variables.html +18 -0
  46. package/support/demos/dispatch.html +20 -0
  47. package/support/demos/events.html +9 -0
  48. package/support/demos/iterate.html +13 -0
  49. package/support/demos/js-api.html +17 -0
  50. package/support/demos/provider-list.html +16 -0
  51. package/support/demos/toggle-content.html +14 -0
  52. package/support/demos/unset.html +12 -0
  53. package/support/demos/view-transition.html +36 -0
  54. package/support/docs/ATTRIBUTES.md +37 -0
  55. package/support/docs/AT_RULES.md +45 -0
  56. package/support/docs/BUILTINS.md +57 -0
  57. package/support/docs/CONTENT.md +40 -0
  58. package/support/docs/CSS_VARIABLES.md +16 -0
  59. package/support/docs/DECLARATIONS.md +26 -0
  60. package/support/docs/DELAY.md +37 -0
  61. package/support/docs/DIAGNOSTICS.md +32 -0
  62. package/support/docs/DISPATCH.md +81 -0
  63. package/support/docs/ELEMENT_PROPERTIES.md +34 -0
  64. package/support/docs/EXPRESSIONS.md +31 -0
  65. package/support/docs/INTERNAL.md +8 -0
  66. package/support/docs/JS_API.md +53 -0
  67. package/support/docs/JS_WRITES.md +36 -0
  68. package/support/docs/LIMITATIONS.md +26 -0
  69. package/support/docs/LOOP_GUARD.md +30 -0
  70. package/support/docs/METHODS.md +43 -0
  71. package/support/docs/MODULES.md +111 -0
  72. package/support/docs/NO_REVERSION.md +18 -0
  73. package/support/docs/ON.md +87 -0
  74. package/support/docs/REACTIVITY.md +24 -0
  75. package/support/docs/README.md +143 -0
  76. package/support/docs/SELECTORS.md +87 -0
  77. package/support/docs/SHEETS.md +67 -0
  78. package/support/docs/SYNTAX.md +36 -0
  79. package/support/docs/USE.md +24 -0
  80. package/support/docs/VALUES.md +27 -0
  81. package/support/docs/VARIABLES.md +47 -0
  82. package/support/docs/VIEW_TRANSITION.md +64 -0
  83. package/support/docs-sections.json +65 -0
  84. package/support/package-meta.json +208 -0
  85. package/support/scripts/build-language-docs.mjs +196 -0
  86. package/support/tests/__snapshots__/css-variables.view.test.ts.snap +23 -0
  87. package/support/tests/__snapshots__/dispatch.view.test.ts.snap +23 -0
  88. package/support/tests/__snapshots__/events.view.test.ts.snap +23 -0
  89. package/support/tests/__snapshots__/iterate.view.test.ts.snap +23 -0
  90. package/support/tests/__snapshots__/js-api.view.test.ts.snap +23 -0
  91. package/support/tests/__snapshots__/provider-list.view.test.ts.snap +23 -0
  92. package/support/tests/__snapshots__/quark-features.test.ts.snap +1079 -0
  93. package/support/tests/__snapshots__/quark-with-providers.test.ts.snap +89 -0
  94. package/support/tests/__snapshots__/quark.test.ts.snap +221 -0
  95. package/support/tests/__snapshots__/selectors.test.ts.snap +133 -0
  96. package/support/tests/__snapshots__/toggle-content.view.test.ts.snap +23 -0
  97. package/support/tests/__snapshots__/unset.view.test.ts.snap +23 -0
  98. package/support/tests/__snapshots__/view-transition.view.test.ts.snap +23 -0
  99. package/support/tests/actions.test.ts +437 -0
  100. package/support/tests/builtin-modules.test.ts +337 -0
  101. package/support/tests/builtins.test.ts +189 -0
  102. package/support/tests/css-variables.view.test.ts +35 -0
  103. package/support/tests/delay.test.ts +244 -0
  104. package/support/tests/devtools-hook.test.ts +496 -0
  105. package/support/tests/diagnostics.test.ts +224 -0
  106. package/support/tests/dispatch.view.test.ts +43 -0
  107. package/support/tests/element-api.test.ts +202 -0
  108. package/support/tests/evaluator.test.ts +237 -0
  109. package/support/tests/events.view.test.ts +40 -0
  110. package/support/tests/form-controls.test.ts +452 -0
  111. package/support/tests/helpers.ts +354 -0
  112. package/support/tests/internals.test.ts +568 -0
  113. package/support/tests/iterate.view.test.ts +40 -0
  114. package/support/tests/js-api.view.test.ts +45 -0
  115. package/support/tests/language-docs.test.ts +147 -0
  116. package/support/tests/lifecycle.test.ts +440 -0
  117. package/support/tests/loop-guard.test.ts +323 -0
  118. package/support/tests/provider-list.view.test.ts +47 -0
  119. package/support/tests/quark-features.test.ts +2887 -0
  120. package/support/tests/quark-with-providers.test.ts +169 -0
  121. package/support/tests/quark.test.ts +359 -0
  122. package/support/tests/resolvers.test.ts +346 -0
  123. package/support/tests/selector-analysis.test.ts +281 -0
  124. package/support/tests/selectors.test.ts +670 -0
  125. package/support/tests/toggle-content.view.test.ts +40 -0
  126. package/support/tests/unset.view.test.ts +42 -0
  127. package/support/tests/view-helpers.ts +81 -0
  128. package/support/tests/view-transition.test.ts +1069 -0
  129. package/support/tests/view-transition.view.test.ts +76 -0
  130. package/tsconfig.json +5 -0
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Expression evaluator, exercised directly against a hand-built scope so
3
+ * every node type, operator and error path is reachable without a sheet.
4
+ */
5
+ import { VALUE_MAP } from "../../src/constants";
6
+ import {
7
+ collectAttrCalls,
8
+ collectPropCalls,
9
+ collectVariableNames,
10
+ evaluateExpression,
11
+ getExpressionAst,
12
+ QuarkEvalError,
13
+ } from "../../src/evaluator";
14
+ import {
15
+ describe,
16
+ expect,
17
+ it,
18
+ vi,
19
+ } from "@excom/heft-rig/profiles/default/config/test-utils";
20
+
21
+ const element = document.createElement("div");
22
+
23
+ /** Evaluate `src` with `$vars` and bare `names` resolved from plain objects. */
24
+ const ev = (
25
+ src: string,
26
+ vars: Record<string, unknown> = {},
27
+ names: Record<string, unknown> = {}
28
+ ) =>
29
+ evaluateExpression(getExpressionAst(src), {
30
+ element,
31
+ scope: {
32
+ lookup(name) {
33
+ if (name in VALUE_MAP) return VALUE_MAP[name];
34
+ if (name.startsWith("$")) return vars[name.slice(1)];
35
+ if (name in names) return names[name];
36
+ throw new QuarkEvalError(`"${name}" is not defined`);
37
+ },
38
+ },
39
+ });
40
+
41
+ describe("evaluator", () => {
42
+ describe("literals", () => {
43
+ it("caches parsed expressions per source string", () => {
44
+ expect(getExpressionAst("1 + 1")).toBe(getExpressionAst("1 + 1"));
45
+ });
46
+
47
+ it("unescapes string literals", () => {
48
+ expect(ev('"a\\nb\\tc\\rd\\"e"')).toBe('a\nb\tc\rd"e');
49
+ expect(ev('"plain"')).toBe("plain");
50
+ });
51
+
52
+ it("interpolates strings, rendering null as empty", () => {
53
+ expect(ev('"a #{$b} c\\n"', { b: 1 })).toBe("a 1 c\n");
54
+ expect(ev('"[#{$missing}]"')).toBe("[]");
55
+ });
56
+
57
+ it("keeps units on numbers", () => {
58
+ expect(ev("10px")).toBe("10px");
59
+ expect(ev("3")).toBe(3);
60
+ });
61
+
62
+ it("evaluates colors, booleans and null", () => {
63
+ expect(ev("#ccc")).toBe("#ccc");
64
+ expect(ev("true")).toBe(true);
65
+ expect(ev("false")).toBe(false);
66
+ expect(ev("null")).toBeNull();
67
+ });
68
+
69
+ it("resolves identifiers and variables through the scope", () => {
70
+ expect(ev("name", {}, { name: "n" })).toBe("n");
71
+ expect(ev("$x", { x: 2 })).toBe(2);
72
+ expect(ev("$unbound")).toBeUndefined();
73
+ expect(() => ev("nope")).toThrow('"nope" is not defined');
74
+ });
75
+
76
+ it("resolves & to the element's tag name", () => {
77
+ expect(ev("&")).toBe("div");
78
+ });
79
+
80
+ it("evaluates interpolations and unquoted urls", () => {
81
+ expect(ev("#{$a}", { a: 7 })).toBe(7);
82
+ expect(ev("url(/a/#{$b}/c)", { b: "x" })).toBe("/a/x/c");
83
+ expect(ev("url(/a/#{$none}/c)")).toBe("/a//c");
84
+ });
85
+
86
+ it("builds lists and maps", () => {
87
+ expect(ev("[1, 2]")).toEqual([1, 2]);
88
+ expect(ev("(1, 2)")).toEqual([1, 2]);
89
+ expect(ev('(a: 1, "b": 2, #{$k}: 3)', { k: "kk" })).toEqual({
90
+ a: 1,
91
+ b: 2,
92
+ kk: 3,
93
+ });
94
+ });
95
+ });
96
+
97
+ describe("accessors", () => {
98
+ it("reads members, namespaced $members and indexes", () => {
99
+ expect(ev("$o.x", { o: { x: 1 } })).toBe(1);
100
+ expect(ev("math.$pi", {}, { math: { $pi: 3 } })).toBe(3);
101
+ expect(ev("$a[1]", { a: ["p", "q"] })).toBe("q");
102
+ expect(ev('$o["x"]', { o: { x: "v" } })).toBe("v");
103
+ });
104
+
105
+ it("propagates null through members, indexes and method calls", () => {
106
+ expect(ev("$n.x")).toBeUndefined();
107
+ expect(ev("$n[0]")).toBeUndefined();
108
+ expect(ev("$n.toUpperCase()")).toBeUndefined();
109
+ expect(ev("null.x")).toBeUndefined();
110
+ });
111
+ });
112
+
113
+ describe("calls", () => {
114
+ it("calls module functions with spread and plain arguments", () => {
115
+ const f = vi.fn((...args: unknown[]) => args);
116
+ expect(ev("f($xs..., 1)", { xs: [1, 2] }, { f })).toEqual([1, 2, 1]);
117
+ // a non-array spread is passed through as one argument
118
+ expect(ev("f($x..., 1)", { x: "solo" }, { f })).toEqual(["solo", 1]);
119
+ expect(ev("#{$g}(4)", { g: (n: number) => n * 2 })).toBe(8);
120
+ });
121
+
122
+ it("rejects non-function callees with a descriptive message", () => {
123
+ expect(() => ev("nofn(1)", {}, { nofn: 3 })).toThrow(
124
+ '"nofn" is not a function'
125
+ );
126
+ expect(() => ev("#{$g}(1)", { g: 3 })).toThrow(
127
+ '"<expression>" is not a function'
128
+ );
129
+ });
130
+
131
+ it("calls own-property functions on module namespaces", () => {
132
+ expect(ev("api.version()", {}, { api: { version: () => "v2" } })).toBe(
133
+ "v2"
134
+ );
135
+ });
136
+
137
+ it("allows allowlisted prototype methods only", () => {
138
+ expect(ev("$s.toUpperCase()", { s: "ab" })).toBe("AB");
139
+ expect(ev('$l.join("-")', { l: ["a", "b"] })).toBe("a-b");
140
+ expect(() => ev("$s.splice(0)", { s: "ab" })).toThrow(
141
+ 'Method "splice" is not allowed'
142
+ );
143
+ // allowlisted name that is not a function on this value
144
+ expect(() => ev("$o.trim()", { o: { trim: 5 } })).toThrow(
145
+ '"trim" is not a function'
146
+ );
147
+ });
148
+ });
149
+
150
+ describe("if()", () => {
151
+ it("returns the first truthy arm, the else arm, or undefined", () => {
152
+ expect(ev("if($a: 1; $b: 2; else: 3)", { a: 0, b: 1 })).toBe(2);
153
+ expect(ev("if($a: 1; else: 3)", { a: 0 })).toBe(3);
154
+ expect(ev("if($a: 1; $b: 2)", { a: 0, b: 0 })).toBeUndefined();
155
+ });
156
+ });
157
+
158
+ describe("operators", () => {
159
+ it("applies unary operators", () => {
160
+ expect(ev("-$x", { x: 2 })).toBe(-2);
161
+ expect(ev("+$x", { x: "3" })).toBe(3);
162
+ expect(ev("not $x", { x: 0 })).toBe(true);
163
+ expect(ev("not $x", { x: 1 })).toBe(false);
164
+ });
165
+
166
+ it("short-circuits and / or", () => {
167
+ const boom = () => {
168
+ throw new Error("evaluated");
169
+ };
170
+ expect(ev("$a and boom()", { a: 0 }, { boom })).toBe(0);
171
+ expect(ev("$a and $b", { a: 1, b: "r" })).toBe("r");
172
+ expect(ev("$a or boom()", { a: "l" }, { boom })).toBe("l");
173
+ expect(ev("$a or $b", { a: 0, b: "r" })).toBe("r");
174
+ });
175
+
176
+ it("applies arithmetic", () => {
177
+ expect(ev("1 + 2")).toBe(3);
178
+ expect(ev("5 - 2")).toBe(3);
179
+ expect(ev("2 * 3")).toBe(6);
180
+ expect(ev("6 / 3")).toBe(2);
181
+ expect(ev("7 % 3")).toBe(1);
182
+ });
183
+
184
+ it("applies comparisons (loose equality)", () => {
185
+ expect(ev('1 == "1"')).toBe(true);
186
+ expect(ev("1 != 2")).toBe(true);
187
+ expect(ev("1 < 2")).toBe(true);
188
+ expect(ev("2 <= 2")).toBe(true);
189
+ expect(ev("3 > 2")).toBe(true);
190
+ expect(ev("3 >= 4")).toBe(false);
191
+ });
192
+ });
193
+
194
+ describe("malformed ASTs", () => {
195
+ it("rejects unknown operators and unsupported node types", () => {
196
+ const num = { type: "number", value: 1, unit: null, start: 0, end: 1 };
197
+ const badOp = {
198
+ type: "binary",
199
+ operator: "**",
200
+ left: num,
201
+ right: num,
202
+ start: 0,
203
+ end: 1,
204
+ } as any;
205
+ expect(() =>
206
+ evaluateExpression(badOp, { element, scope: { lookup: () => 0 } })
207
+ ).toThrow('Unknown operator "**"');
208
+ expect(() =>
209
+ evaluateExpression({ type: "bogus" } as any, {
210
+ element,
211
+ scope: { lookup: () => 0 },
212
+ })
213
+ ).toThrow('Unsupported expression node "bogus"');
214
+ });
215
+ });
216
+
217
+ describe("static analysis", () => {
218
+ it("collects $variable names once each, through nested nodes", () => {
219
+ expect(
220
+ collectVariableNames(getExpressionAst("f($a, [$b, $a], (k: $c.$d))"))
221
+ ).toEqual(["$a", "$b", "$c"]);
222
+ });
223
+
224
+ it("collects literal attr() / prop() names and flags dynamic ones", () => {
225
+ expect(collectAttrCalls(null)).toEqual({
226
+ names: [],
227
+ hasNonLiteral: false,
228
+ });
229
+ expect(
230
+ collectAttrCalls(getExpressionAst('attr("x") + attr($y)'))
231
+ ).toEqual({ names: ["x"], hasNonLiteral: true });
232
+ expect(
233
+ collectPropCalls(getExpressionAst('prop("value").length'))
234
+ ).toEqual({ names: ["value"], hasNonLiteral: false });
235
+ });
236
+ });
237
+ });
@@ -0,0 +1,40 @@
1
+ import "@excom/quark-sheet";
2
+ import {
3
+ afterEach,
4
+ beforeEach,
5
+ describe,
6
+ expect,
7
+ it,
8
+ } from "@excom/heft-rig/profiles/default/config/test-utils";
9
+ import {
10
+ click,
11
+ expectComplexity,
12
+ flush,
13
+ installDemoModules,
14
+ measureComplexity,
15
+ mountView,
16
+ readDemo,
17
+ restoreDemoModules,
18
+ } from "./view-helpers";
19
+
20
+ describe("events view", () => {
21
+ beforeEach(() => installDemoModules());
22
+ afterEach(() => {
23
+ document.body.innerHTML = "";
24
+ restoreDemoModules();
25
+ });
26
+
27
+ it("prevents navigation and notes the event", async () => {
28
+ const { root, quark } = await mountView(readDemo(import.meta.url, "events"));
29
+ const anchor = root.querySelector("a")!;
30
+ const meter = measureComplexity(quark!);
31
+ const ev = new MouseEvent("click", { bubbles: true, cancelable: true });
32
+ anchor.dispatchEvent(ev);
33
+ await flush();
34
+ const budget = meter.take();
35
+ meter.stop();
36
+ expect(ev.defaultPrevented).toBe(true);
37
+ expect(root.querySelector("output")?.textContent).toMatch(/handled/);
38
+ expectComplexity(budget);
39
+ });
40
+ });