@alloy-js/core 0.10.0 → 0.12.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/CHANGELOG.md +28 -0
- package/dist/src/binder.d.ts.map +1 -1
- package/dist/src/binder.js +100 -19
- package/dist/src/code.js +1 -2
- package/dist/src/components/Block.js +2 -5
- package/dist/src/components/Declaration.js +2 -4
- package/dist/src/components/For.d.ts +2 -2
- package/dist/src/components/For.d.ts.map +1 -1
- package/dist/src/components/For.js +1 -2
- package/dist/src/components/Indent.js +2 -4
- package/dist/src/components/List.js +2 -5
- package/dist/src/components/MemberDeclaration.js +2 -4
- package/dist/src/components/MemberName.js +1 -2
- package/dist/src/components/MemberScope.js +2 -4
- package/dist/src/components/Name.js +1 -2
- package/dist/src/components/Output.js +2 -4
- package/dist/src/components/Prose.js +1 -2
- package/dist/src/components/ReferenceOrContent.d.ts +8 -0
- package/dist/src/components/ReferenceOrContent.d.ts.map +1 -0
- package/dist/src/components/ReferenceOrContent.js +11 -0
- package/dist/src/components/Scope.js +2 -4
- package/dist/src/components/Show.js +1 -2
- package/dist/src/components/SourceDirectory.js +2 -4
- package/dist/src/components/SourceFile.js +2 -5
- package/dist/src/components/StatementList.js +2 -4
- package/dist/src/components/Switch.d.ts +1 -1
- package/dist/src/components/Switch.d.ts.map +1 -1
- package/dist/src/components/Switch.js +1 -2
- package/dist/src/components/Wrap.js +2 -4
- package/dist/src/components/index.d.ts +1 -0
- package/dist/src/components/index.d.ts.map +1 -1
- package/dist/src/components/index.js +2 -2
- package/dist/src/components/stc/index.d.ts +1 -0
- package/dist/src/components/stc/index.d.ts.map +1 -1
- package/dist/src/components/stc/index.js +2 -2
- package/dist/src/components/stc/sti.js +1 -2
- package/dist/src/context/assignment.js +1 -2
- package/dist/src/context/binder.js +1 -2
- package/dist/src/context/declaration.js +1 -2
- package/dist/src/context/index.js +1 -2
- package/dist/src/context/member-declaration.js +1 -2
- package/dist/src/context/member-scope.js +1 -2
- package/dist/src/context/name-policy.js +1 -2
- package/dist/src/context/scope.js +1 -2
- package/dist/src/context/source-directory.js +1 -2
- package/dist/src/context/source-file.js +1 -2
- package/dist/src/context.js +1 -2
- package/dist/src/debug.js +13 -15
- package/dist/src/index.browser.js +1 -2
- package/dist/src/index.js +1 -2
- package/dist/src/jsx-runtime.d.ts +1 -1
- package/dist/src/jsx-runtime.d.ts.map +1 -1
- package/dist/src/jsx-runtime.js +10 -5
- package/dist/src/name-policy.js +1 -2
- package/dist/src/refkey.js +1 -2
- package/dist/src/render.d.ts.map +1 -1
- package/dist/src/render.js +6 -2
- package/dist/src/scheduler.d.ts +8 -0
- package/dist/src/scheduler.d.ts.map +1 -0
- package/dist/src/scheduler.js +17 -0
- package/dist/src/slot.js +1 -2
- package/dist/src/stc.js +1 -2
- package/dist/src/sti.js +1 -2
- package/dist/src/tap.js +1 -2
- package/dist/src/tsdoc-metadata.json +1 -1
- package/dist/src/utils.js +2 -4
- package/dist/src/write-output.browser.js +1 -2
- package/dist/src/write-output.js +1 -2
- package/dist/test/browser-build.test.js +85 -0
- package/dist/test/children.test.js +27 -0
- package/dist/test/components/block.test.js +45 -0
- package/dist/test/components/declaration.test.js +32 -0
- package/dist/test/components/list.test.js +86 -0
- package/dist/test/components/prose.test.js +25 -0
- package/dist/test/components/reference-or-content.test.d.ts +2 -0
- package/dist/test/components/reference-or-content.test.d.ts.map +1 -0
- package/dist/test/components/reference-or-content.test.js +149 -0
- package/dist/test/components/slot.test.js +134 -0
- package/dist/test/components/source-file.test.js +64 -0
- package/dist/test/components/wrap.test.js +35 -0
- package/dist/test/control-flow/for.test.js +219 -0
- package/dist/test/control-flow/match.test.js +67 -0
- package/dist/test/control-flow/show.test.js +29 -0
- package/dist/test/name-policy.test.js +19 -0
- package/dist/test/props-with-defaults.test.js +93 -0
- package/dist/test/reactivity/circular-reactives.test.d.ts +2 -0
- package/dist/test/reactivity/circular-reactives.test.d.ts.map +1 -0
- package/dist/test/reactivity/circular-reactives.test.js +31 -0
- package/dist/test/reactivity/cleanup.test.js +82 -0
- package/dist/test/reactivity/memo.test.js +16 -0
- package/dist/test/reactivity/ref-rendering.test.js +37 -0
- package/dist/test/reactivity/test.test.js +61 -0
- package/dist/test/reactivity/untrack.test.js +26 -0
- package/dist/test/refkey.test.js +25 -0
- package/dist/test/rendering/basic.test.js +96 -0
- package/dist/test/rendering/code.test.js +55 -0
- package/dist/test/rendering/formatting.test.js +402 -0
- package/dist/test/rendering/indent.test.js +90 -0
- package/dist/test/rendering/memoization.test.js +27 -0
- package/dist/test/rendering/refkeys.test.js +32 -0
- package/dist/test/split-props.test.js +77 -0
- package/dist/test/stc.test.js +34 -0
- package/dist/test/symbols.test.js +877 -0
- package/dist/test/utils.test.d.ts.map +1 -1
- package/dist/test/utils.test.js +223 -0
- package/dist/testing/extend-expect.js +1 -2
- package/dist/testing/index.js +1 -2
- package/dist/testing/render.js +1 -2
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -22
- package/src/binder.ts +100 -17
- package/src/components/For.tsx +6 -6
- package/src/components/ReferenceOrContent.tsx +22 -0
- package/src/components/index.tsx +1 -0
- package/src/components/stc/index.ts +1 -0
- package/src/debug.ts +12 -13
- package/src/jsx-runtime.ts +24 -14
- package/src/render.ts +5 -0
- package/src/scheduler.ts +24 -0
- package/temp/api.json +216 -15
- package/test/components/declaration.test.tsx +2 -0
- package/test/components/list.test.tsx +0 -1
- package/test/components/reference-or-content.test.tsx +138 -0
- package/test/control-flow/for.test.tsx +34 -4
- package/test/reactivity/circular-reactives.test.tsx +32 -0
- package/test/reactivity/cleanup.test.tsx +5 -0
- package/test/reactivity/untrack.test.ts +3 -0
- package/test/rendering/memoization.test.tsx +2 -0
- package/test/symbols.test.ts +392 -13
- package/test/utils.test.tsx +2 -0
- package/babel.config.cjs +0 -4
- package/dist/src/binder.js.map +0 -1
- package/dist/src/code.js.map +0 -1
- package/dist/src/components/Block.js.map +0 -1
- package/dist/src/components/Declaration.js.map +0 -1
- package/dist/src/components/For.js.map +0 -1
- package/dist/src/components/Indent.js.map +0 -1
- package/dist/src/components/List.js.map +0 -1
- package/dist/src/components/MemberDeclaration.js.map +0 -1
- package/dist/src/components/MemberName.js.map +0 -1
- package/dist/src/components/MemberScope.js.map +0 -1
- package/dist/src/components/Name.js.map +0 -1
- package/dist/src/components/Output.js.map +0 -1
- package/dist/src/components/Prose.js.map +0 -1
- package/dist/src/components/Scope.js.map +0 -1
- package/dist/src/components/Show.js.map +0 -1
- package/dist/src/components/SourceDirectory.js.map +0 -1
- package/dist/src/components/SourceFile.js.map +0 -1
- package/dist/src/components/StatementList.js.map +0 -1
- package/dist/src/components/Switch.js.map +0 -1
- package/dist/src/components/Wrap.js.map +0 -1
- package/dist/src/components/index.js.map +0 -1
- package/dist/src/components/stc/index.js.map +0 -1
- package/dist/src/components/stc/sti.js.map +0 -1
- package/dist/src/context/assignment.js.map +0 -1
- package/dist/src/context/binder.js.map +0 -1
- package/dist/src/context/declaration.js.map +0 -1
- package/dist/src/context/index.js.map +0 -1
- package/dist/src/context/member-declaration.js.map +0 -1
- package/dist/src/context/member-scope.js.map +0 -1
- package/dist/src/context/name-policy.js.map +0 -1
- package/dist/src/context/scope.js.map +0 -1
- package/dist/src/context/source-directory.js.map +0 -1
- package/dist/src/context/source-file.js.map +0 -1
- package/dist/src/context.js.map +0 -1
- package/dist/src/debug.js.map +0 -1
- package/dist/src/index.browser.js.map +0 -1
- package/dist/src/index.js.map +0 -1
- package/dist/src/jsx-runtime.js.map +0 -1
- package/dist/src/name-policy.js.map +0 -1
- package/dist/src/refkey.js.map +0 -1
- package/dist/src/render.js.map +0 -1
- package/dist/src/slot.js.map +0 -1
- package/dist/src/stc.js.map +0 -1
- package/dist/src/sti.js.map +0 -1
- package/dist/src/tap.js.map +0 -1
- package/dist/src/utils.js.map +0 -1
- package/dist/src/write-output.browser.js.map +0 -1
- package/dist/src/write-output.js.map +0 -1
- package/dist/testing/extend-expect.js.map +0 -1
- package/dist/testing/index.js.map +0 -1
- package/dist/testing/render.js.map +0 -1
- package/dist/testing/vitest.d.js.map +0 -1
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { createComponent as _$createComponent, memo as _$memo } from "@alloy-js/core/jsx-runtime";
|
|
2
|
+
import "@alloy-js/core/testing";
|
|
3
|
+
import { d } from "@alloy-js/core/testing";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { For } from "../../src/components/For.js";
|
|
6
|
+
import { onCleanup, printTree, reactive, renderTree } from "../../src/index.js";
|
|
7
|
+
import { flushJobs } from "../../src/scheduler.js";
|
|
8
|
+
it("works", () => {
|
|
9
|
+
const messages = ["hi", "bye"];
|
|
10
|
+
const template = _$createComponent(For, {
|
|
11
|
+
each: messages,
|
|
12
|
+
children: message => [message, ", Jose!"]
|
|
13
|
+
});
|
|
14
|
+
expect(template).toRenderTo(`
|
|
15
|
+
hi, Jose!
|
|
16
|
+
bye, Jose!
|
|
17
|
+
`);
|
|
18
|
+
});
|
|
19
|
+
describe("readonly collections", () => {
|
|
20
|
+
const out = d`
|
|
21
|
+
a
|
|
22
|
+
b
|
|
23
|
+
`;
|
|
24
|
+
it("array", () => {
|
|
25
|
+
const messages = ["a", "b"];
|
|
26
|
+
expect(_$createComponent(For, {
|
|
27
|
+
each: messages,
|
|
28
|
+
children: x => [x]
|
|
29
|
+
})).toRenderTo(out);
|
|
30
|
+
});
|
|
31
|
+
it("map", () => {
|
|
32
|
+
const messages = new Map([["a", "a"], ["b", "b"]]);
|
|
33
|
+
expect(_$createComponent(For, {
|
|
34
|
+
each: messages,
|
|
35
|
+
children: x => [x]
|
|
36
|
+
})).toRenderTo(out);
|
|
37
|
+
});
|
|
38
|
+
it("set", () => {
|
|
39
|
+
const messages = new Set(["a", "b"]);
|
|
40
|
+
expect(_$createComponent(For, {
|
|
41
|
+
each: messages,
|
|
42
|
+
children: x => [x]
|
|
43
|
+
})).toRenderTo(out);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
it("handles map entries", () => {
|
|
47
|
+
const map = new Map([["a", {
|
|
48
|
+
name: "foo"
|
|
49
|
+
}]]);
|
|
50
|
+
const entries = Array.from(map.entries());
|
|
51
|
+
const template = _$createComponent(For, {
|
|
52
|
+
each: entries,
|
|
53
|
+
children: ([key, value]) => [key, ": ", _$memo(() => value.name)]
|
|
54
|
+
});
|
|
55
|
+
expect(template).toRenderTo(`
|
|
56
|
+
a: foo
|
|
57
|
+
`);
|
|
58
|
+
});
|
|
59
|
+
it("handles iterators", () => {
|
|
60
|
+
const iterator = new Map([["a", {
|
|
61
|
+
name: "foo"
|
|
62
|
+
}]]).entries();
|
|
63
|
+
const template = _$createComponent(For, {
|
|
64
|
+
each: iterator,
|
|
65
|
+
children: ([key, value]) => [key, ": ", _$memo(() => value.name)]
|
|
66
|
+
});
|
|
67
|
+
expect(template).toRenderTo(`
|
|
68
|
+
a: foo
|
|
69
|
+
`);
|
|
70
|
+
});
|
|
71
|
+
it("handles maps", () => {
|
|
72
|
+
const map = new Map([["a", {
|
|
73
|
+
name: "foo"
|
|
74
|
+
}]]);
|
|
75
|
+
const template = _$createComponent(For, {
|
|
76
|
+
each: map,
|
|
77
|
+
children: (key, value) => [key, ": ", _$memo(() => value.name)]
|
|
78
|
+
});
|
|
79
|
+
expect(template).toRenderTo(`
|
|
80
|
+
a: foo
|
|
81
|
+
`);
|
|
82
|
+
});
|
|
83
|
+
it("doesn't rerender mappers", () => {
|
|
84
|
+
const messages = reactive(["hi", "bye"]);
|
|
85
|
+
let count = 0;
|
|
86
|
+
const template = _$createComponent(For, {
|
|
87
|
+
each: messages,
|
|
88
|
+
children: () => ["item ", count++]
|
|
89
|
+
});
|
|
90
|
+
const tree = renderTree(template);
|
|
91
|
+
expect(count).toBe(2);
|
|
92
|
+
messages.push("maybe");
|
|
93
|
+
flushJobs();
|
|
94
|
+
expect(count).toBe(3);
|
|
95
|
+
expect(printTree(tree)).toBe(d`
|
|
96
|
+
item 0
|
|
97
|
+
item 1
|
|
98
|
+
item 2
|
|
99
|
+
`);
|
|
100
|
+
});
|
|
101
|
+
it("doesn't rerender mappers with sets", () => {
|
|
102
|
+
const messages = reactive(new Set(["hi", "bye"]));
|
|
103
|
+
let count = 0;
|
|
104
|
+
const template = _$createComponent(For, {
|
|
105
|
+
each: messages,
|
|
106
|
+
children: () => ["item ", count++]
|
|
107
|
+
});
|
|
108
|
+
const tree = renderTree(template);
|
|
109
|
+
expect(count).toBe(2);
|
|
110
|
+
messages.add("maybe");
|
|
111
|
+
flushJobs();
|
|
112
|
+
expect(count).toBe(3);
|
|
113
|
+
expect(printTree(tree)).toBe(d`
|
|
114
|
+
item 0
|
|
115
|
+
item 1
|
|
116
|
+
item 2
|
|
117
|
+
`);
|
|
118
|
+
});
|
|
119
|
+
it("doesn't rerender mappers with maps", () => {
|
|
120
|
+
const messages = reactive(new Map([["hi", "one"], ["bye", "two"]]));
|
|
121
|
+
let count = 0;
|
|
122
|
+
const template = _$createComponent(For, {
|
|
123
|
+
each: messages,
|
|
124
|
+
children: () => ["item ", count++]
|
|
125
|
+
});
|
|
126
|
+
const tree = renderTree(template);
|
|
127
|
+
expect(count).toBe(2);
|
|
128
|
+
messages.set("maybe", "three");
|
|
129
|
+
flushJobs();
|
|
130
|
+
expect(count).toBe(3);
|
|
131
|
+
expect(printTree(tree)).toBe(d`
|
|
132
|
+
item 0
|
|
133
|
+
item 1
|
|
134
|
+
item 2
|
|
135
|
+
`);
|
|
136
|
+
});
|
|
137
|
+
it("doesn't rerender mappers (with splice)", () => {
|
|
138
|
+
const messages = reactive(["hi", "maybe", "bye"]);
|
|
139
|
+
let count = 0;
|
|
140
|
+
const template = _$createComponent(For, {
|
|
141
|
+
each: messages,
|
|
142
|
+
children: msg => ["item ", count++]
|
|
143
|
+
});
|
|
144
|
+
const tree = renderTree(template);
|
|
145
|
+
expect(count).toBe(3);
|
|
146
|
+
messages.splice(1, 1);
|
|
147
|
+
flushJobs();
|
|
148
|
+
// A sufficiently smart mapJoin would be able to handle this case...
|
|
149
|
+
// but for now we re-render everything after the splice point.
|
|
150
|
+
expect(count).toBe(4);
|
|
151
|
+
expect(printTree(tree)).toBe(d`
|
|
152
|
+
item 0
|
|
153
|
+
item 3
|
|
154
|
+
`);
|
|
155
|
+
});
|
|
156
|
+
it("cleans up things which end up removed (with push)", () => {
|
|
157
|
+
const cleanups = [];
|
|
158
|
+
function Letter(props) {
|
|
159
|
+
onCleanup(() => {
|
|
160
|
+
cleanups.push(props.letter);
|
|
161
|
+
});
|
|
162
|
+
return ["Letter ", _$memo(() => props.letter)];
|
|
163
|
+
}
|
|
164
|
+
const items = reactive(["a", "b"]);
|
|
165
|
+
const template = _$createComponent(For, {
|
|
166
|
+
each: items,
|
|
167
|
+
children: item => _$createComponent(Letter, {
|
|
168
|
+
letter: item
|
|
169
|
+
})
|
|
170
|
+
});
|
|
171
|
+
const tree = renderTree(template);
|
|
172
|
+
expect(cleanups).toEqual([]);
|
|
173
|
+
expect(printTree(tree)).toBe(d`
|
|
174
|
+
Letter a
|
|
175
|
+
Letter b
|
|
176
|
+
`);
|
|
177
|
+
items.pop();
|
|
178
|
+
flushJobs();
|
|
179
|
+
expect(cleanups).toEqual(["b"]);
|
|
180
|
+
expect(printTree(tree)).toBe(d`
|
|
181
|
+
Letter a
|
|
182
|
+
`);
|
|
183
|
+
items.pop();
|
|
184
|
+
flushJobs();
|
|
185
|
+
expect(cleanups).toEqual(["b", "a"]);
|
|
186
|
+
expect(printTree(tree)).toBe("");
|
|
187
|
+
});
|
|
188
|
+
it("cleans up things which end up removed (with splice)", () => {
|
|
189
|
+
const cleanups = [];
|
|
190
|
+
function Letter(props) {
|
|
191
|
+
onCleanup(() => {
|
|
192
|
+
cleanups.push(props.letter);
|
|
193
|
+
});
|
|
194
|
+
return ["Letter ", _$memo(() => props.letter)];
|
|
195
|
+
}
|
|
196
|
+
const items = reactive(["a", "b", "c"]);
|
|
197
|
+
const template = _$createComponent(For, {
|
|
198
|
+
each: items,
|
|
199
|
+
children: item => _$createComponent(Letter, {
|
|
200
|
+
letter: item
|
|
201
|
+
})
|
|
202
|
+
});
|
|
203
|
+
const tree = renderTree(template);
|
|
204
|
+
expect(cleanups).toEqual([]);
|
|
205
|
+
expect(printTree(tree)).toBe(d`
|
|
206
|
+
Letter a
|
|
207
|
+
Letter b
|
|
208
|
+
Letter c
|
|
209
|
+
`);
|
|
210
|
+
items.splice(1, 1);
|
|
211
|
+
flushJobs();
|
|
212
|
+
// A sufficiently smart mapJoin would be able to handle this case...
|
|
213
|
+
// but for now we re-render everything after the splice point.
|
|
214
|
+
expect(cleanups).toEqual(["b", "c"]);
|
|
215
|
+
expect(printTree(tree)).toBe(d`
|
|
216
|
+
Letter a
|
|
217
|
+
Letter c
|
|
218
|
+
`);
|
|
219
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { createComponent as _$createComponent } from "@alloy-js/core/jsx-runtime";
|
|
2
|
+
import "@alloy-js/core/testing";
|
|
3
|
+
import { ref } from "@vue/reactivity";
|
|
4
|
+
import { expect, it } from "vitest";
|
|
5
|
+
import { Match, Switch } from "../../src/components/Switch.js";
|
|
6
|
+
import { printTree, renderTree } from "../../src/render.js";
|
|
7
|
+
it("selects the true branch", () => {
|
|
8
|
+
const template = _$createComponent(Switch, {
|
|
9
|
+
get children() {
|
|
10
|
+
return [_$createComponent(Match, {
|
|
11
|
+
when: true,
|
|
12
|
+
children: "true"
|
|
13
|
+
}), _$createComponent(Match, {
|
|
14
|
+
"else": true,
|
|
15
|
+
children: "false"
|
|
16
|
+
})];
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
expect(template).toRenderTo(`true`);
|
|
20
|
+
});
|
|
21
|
+
it("selects the else branch", () => {
|
|
22
|
+
const template = _$createComponent(Switch, {
|
|
23
|
+
get children() {
|
|
24
|
+
return [_$createComponent(Match, {
|
|
25
|
+
when: false,
|
|
26
|
+
children: "true"
|
|
27
|
+
}), _$createComponent(Match, {
|
|
28
|
+
"else": true,
|
|
29
|
+
children: "false"
|
|
30
|
+
})];
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
expect(template).toRenderTo(`false`);
|
|
34
|
+
});
|
|
35
|
+
it("renders to nothing when no branch matches", () => {
|
|
36
|
+
const template = _$createComponent(Switch, {
|
|
37
|
+
get children() {
|
|
38
|
+
return _$createComponent(Match, {
|
|
39
|
+
when: false,
|
|
40
|
+
children: "true"
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
expect(template).toRenderTo(``);
|
|
45
|
+
});
|
|
46
|
+
it("works with reactivity", () => {
|
|
47
|
+
const count = ref(0);
|
|
48
|
+
const template = _$createComponent(Switch, {
|
|
49
|
+
get children() {
|
|
50
|
+
return [_$createComponent(Match, {
|
|
51
|
+
get when() {
|
|
52
|
+
return count.value % 2 === 0;
|
|
53
|
+
},
|
|
54
|
+
children: "even"
|
|
55
|
+
}), _$createComponent(Match, {
|
|
56
|
+
"else": true,
|
|
57
|
+
children: "odd"
|
|
58
|
+
})];
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
const tree = renderTree(template);
|
|
62
|
+
expect(printTree(tree)).toBe(`even`);
|
|
63
|
+
count.value++;
|
|
64
|
+
expect(printTree(tree)).toBe(`odd`);
|
|
65
|
+
count.value++;
|
|
66
|
+
expect(printTree(tree)).toBe(`even`);
|
|
67
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createComponent as _$createComponent } from "@alloy-js/core/jsx-runtime";
|
|
2
|
+
import "@alloy-js/core/testing";
|
|
3
|
+
import { ref } from "@vue/reactivity";
|
|
4
|
+
import { expect, it } from "vitest";
|
|
5
|
+
import { Show } from "../../src/components/Show.js";
|
|
6
|
+
import { printTree, renderTree } from "../../src/render.js";
|
|
7
|
+
it("selects the true branch", () => {
|
|
8
|
+
const template = _$createComponent(Show, {
|
|
9
|
+
when: true,
|
|
10
|
+
children: "true"
|
|
11
|
+
});
|
|
12
|
+
expect(template).toRenderTo(`true`);
|
|
13
|
+
});
|
|
14
|
+
it("works with reactivity", () => {
|
|
15
|
+
const count = ref(0);
|
|
16
|
+
const template = _$createComponent(Show, {
|
|
17
|
+
get when() {
|
|
18
|
+
return count.value % 2 === 0;
|
|
19
|
+
},
|
|
20
|
+
fallback: "odd",
|
|
21
|
+
children: "even"
|
|
22
|
+
});
|
|
23
|
+
const tree = renderTree(template);
|
|
24
|
+
expect(printTree(tree)).toBe(`even`);
|
|
25
|
+
count.value++;
|
|
26
|
+
expect(printTree(tree)).toBe(`odd`);
|
|
27
|
+
count.value++;
|
|
28
|
+
expect(printTree(tree)).toBe(`even`);
|
|
29
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createComponent as _$createComponent } from "@alloy-js/core/jsx-runtime";
|
|
2
|
+
import { createNamePolicy, Output, useNamePolicy } from "@alloy-js/core";
|
|
3
|
+
import { expect, it } from "vitest";
|
|
4
|
+
import "../testing/extend-expect.js";
|
|
5
|
+
it("is applied by output", () => {
|
|
6
|
+
const policy = createNamePolicy(name => {
|
|
7
|
+
return "name" + name;
|
|
8
|
+
});
|
|
9
|
+
function Foo() {
|
|
10
|
+
const namer = useNamePolicy();
|
|
11
|
+
return namer.getName("hi", "name");
|
|
12
|
+
}
|
|
13
|
+
expect(_$createComponent(Output, {
|
|
14
|
+
namePolicy: policy,
|
|
15
|
+
get children() {
|
|
16
|
+
return _$createComponent(Foo, {});
|
|
17
|
+
}
|
|
18
|
+
})).toRenderTo("namehi");
|
|
19
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { effect, reactive } from "@vue/reactivity";
|
|
2
|
+
import { expect, it, vi } from "vitest";
|
|
3
|
+
import { defaultProps } from "../src/jsx-runtime.js";
|
|
4
|
+
it("applies defaults to regular object props", () => {
|
|
5
|
+
const props = {
|
|
6
|
+
a: 1,
|
|
7
|
+
b: 2,
|
|
8
|
+
c: undefined
|
|
9
|
+
};
|
|
10
|
+
const dProps = defaultProps(props, {
|
|
11
|
+
a: 10,
|
|
12
|
+
c: 20
|
|
13
|
+
});
|
|
14
|
+
expect(dProps).toEqual({
|
|
15
|
+
a: 1,
|
|
16
|
+
b: 2,
|
|
17
|
+
c: 20
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
it("applies defaults with getters without invoking them", () => {
|
|
21
|
+
const getterA = vi.fn(() => 1);
|
|
22
|
+
const getterB = vi.fn(() => undefined);
|
|
23
|
+
const props = {
|
|
24
|
+
get a() {
|
|
25
|
+
return getterA();
|
|
26
|
+
},
|
|
27
|
+
get b() {
|
|
28
|
+
return getterB();
|
|
29
|
+
},
|
|
30
|
+
c: 3
|
|
31
|
+
};
|
|
32
|
+
const dProps = defaultProps(props, {
|
|
33
|
+
b: 20,
|
|
34
|
+
c: 30
|
|
35
|
+
});
|
|
36
|
+
expect(getterA).not.toHaveBeenCalled();
|
|
37
|
+
expect(getterB).not.toHaveBeenCalled();
|
|
38
|
+
const value = dProps.b;
|
|
39
|
+
expect(getterB).toHaveBeenCalledTimes(1);
|
|
40
|
+
expect(value).toEqual(20);
|
|
41
|
+
});
|
|
42
|
+
it("applies defaults to reactives without observing them initially", () => {
|
|
43
|
+
const props = reactive({
|
|
44
|
+
a: 1,
|
|
45
|
+
b: 2,
|
|
46
|
+
c: undefined
|
|
47
|
+
});
|
|
48
|
+
const defaults = defaultProps(props, {
|
|
49
|
+
c: 10
|
|
50
|
+
});
|
|
51
|
+
expect(defaults.a).toBe(1);
|
|
52
|
+
expect(defaults.b).toBe(2);
|
|
53
|
+
expect(defaults.c).toBe(10);
|
|
54
|
+
});
|
|
55
|
+
it("ensures effect is not triggered by defaults but by accessing reactive props", () => {
|
|
56
|
+
const props = reactive({
|
|
57
|
+
a: 1,
|
|
58
|
+
b: 2,
|
|
59
|
+
c: undefined
|
|
60
|
+
});
|
|
61
|
+
let withDefaults;
|
|
62
|
+
const splitEffect = vi.fn(() => {
|
|
63
|
+
withDefaults = defaultProps(props, {
|
|
64
|
+
c: 10
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
effect(splitEffect);
|
|
68
|
+
expect(splitEffect).toHaveBeenCalledTimes(1);
|
|
69
|
+
const observeEffect = vi.fn(() => {
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
71
|
+
withDefaults.a;
|
|
72
|
+
});
|
|
73
|
+
effect(observeEffect);
|
|
74
|
+
expect(observeEffect).toHaveBeenCalledTimes(1);
|
|
75
|
+
props.a = 2;
|
|
76
|
+
expect(splitEffect).toHaveBeenCalledTimes(1);
|
|
77
|
+
expect(observeEffect).toHaveBeenCalledTimes(2);
|
|
78
|
+
});
|
|
79
|
+
it("applies defaults to reactives", () => {
|
|
80
|
+
const props = reactive({
|
|
81
|
+
a: 1,
|
|
82
|
+
b: 2,
|
|
83
|
+
c: undefined
|
|
84
|
+
});
|
|
85
|
+
const withDefaults = defaultProps(props, {
|
|
86
|
+
c: 10
|
|
87
|
+
});
|
|
88
|
+
expect(withDefaults.c).toBe(10);
|
|
89
|
+
props.c = 20;
|
|
90
|
+
expect(withDefaults.c).toBe(20);
|
|
91
|
+
props.c = undefined;
|
|
92
|
+
expect(withDefaults.c).toBe(10);
|
|
93
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"circular-reactives.test.d.ts","sourceRoot":"","sources":["../../../test/reactivity/circular-reactives.test.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { memo as _$memo, createComponent as _$createComponent } from "@alloy-js/core/jsx-runtime";
|
|
2
|
+
import { shallowReactive } from "@vue/reactivity";
|
|
3
|
+
import { expect, it } from "vitest";
|
|
4
|
+
import { For } from "../../src/index.js";
|
|
5
|
+
import { printTree, renderTree } from "../../src/render.js";
|
|
6
|
+
import { d } from "../../testing/render.js";
|
|
7
|
+
it("it should work with circular reactives", () => {
|
|
8
|
+
const items = shallowReactive([]);
|
|
9
|
+
let added = false;
|
|
10
|
+
function MaybeAddString(props) {
|
|
11
|
+
if (!added) {
|
|
12
|
+
items.push("item " + items.length);
|
|
13
|
+
added = true;
|
|
14
|
+
}
|
|
15
|
+
return [_$memo(() => props.item)];
|
|
16
|
+
}
|
|
17
|
+
const template = [_$createComponent(For, {
|
|
18
|
+
each: items,
|
|
19
|
+
children: item => {
|
|
20
|
+
return _$createComponent(MaybeAddString, {
|
|
21
|
+
item: item
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
})];
|
|
25
|
+
const tree = renderTree(template);
|
|
26
|
+
items.push("item start");
|
|
27
|
+
expect(printTree(tree)).toBe(d`
|
|
28
|
+
item start
|
|
29
|
+
item 1
|
|
30
|
+
`);
|
|
31
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { effect, memo, onCleanup, createComponent as _$createComponent } from "@alloy-js/core/jsx-runtime";
|
|
2
|
+
import { ref } from "@vue/reactivity";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { renderTree } from "../../src/render.js";
|
|
5
|
+
import { flushJobs } from "../../src/scheduler.js";
|
|
6
|
+
describe("memo cleanup", () => {
|
|
7
|
+
it("cleans up when memo value is recomputed", () => {
|
|
8
|
+
const r = ref(1);
|
|
9
|
+
let callCount = 0;
|
|
10
|
+
const m = memo(() => {
|
|
11
|
+
onCleanup(() => {
|
|
12
|
+
callCount++;
|
|
13
|
+
});
|
|
14
|
+
return r.value;
|
|
15
|
+
});
|
|
16
|
+
expect(m()).toBe(1);
|
|
17
|
+
expect(callCount).toBe(0);
|
|
18
|
+
r.value = 2;
|
|
19
|
+
flushJobs();
|
|
20
|
+
expect(m()).toBe(2);
|
|
21
|
+
expect(callCount).toBe(1);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
describe("effect cleanup", () => {
|
|
25
|
+
it("cleans up when the effect is run", () => {
|
|
26
|
+
const r = ref(1);
|
|
27
|
+
let cleanedUp = false;
|
|
28
|
+
effect(() => {
|
|
29
|
+
onCleanup(() => {
|
|
30
|
+
cleanedUp = true;
|
|
31
|
+
});
|
|
32
|
+
return r.value;
|
|
33
|
+
});
|
|
34
|
+
expect(cleanedUp).toBe(false);
|
|
35
|
+
r.value = 2;
|
|
36
|
+
flushJobs();
|
|
37
|
+
expect(cleanedUp).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
describe("element cleanup", () => {
|
|
41
|
+
it("should clean up when the element is unmounted", () => {
|
|
42
|
+
let cleanedUp = false;
|
|
43
|
+
function Component() {
|
|
44
|
+
onCleanup(() => {
|
|
45
|
+
cleanedUp = true;
|
|
46
|
+
});
|
|
47
|
+
return "hi!";
|
|
48
|
+
}
|
|
49
|
+
const el = ref(_$createComponent(Component, {}));
|
|
50
|
+
const template = [el];
|
|
51
|
+
renderTree(template);
|
|
52
|
+
el.value = "";
|
|
53
|
+
flushJobs();
|
|
54
|
+
expect(cleanedUp).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
it("should clean up when the element is unmounted, recursively", () => {
|
|
57
|
+
let cleanedUpC1 = false;
|
|
58
|
+
let cleanedUpC2 = false;
|
|
59
|
+
function C1(props) {
|
|
60
|
+
onCleanup(() => {
|
|
61
|
+
cleanedUpC1 = true;
|
|
62
|
+
});
|
|
63
|
+
return props.children;
|
|
64
|
+
}
|
|
65
|
+
function C2() {
|
|
66
|
+
onCleanup(() => {
|
|
67
|
+
cleanedUpC2 = true;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
const el = ref(_$createComponent(C1, {
|
|
71
|
+
get children() {
|
|
72
|
+
return _$createComponent(C2, {});
|
|
73
|
+
}
|
|
74
|
+
}));
|
|
75
|
+
const template = [el];
|
|
76
|
+
renderTree(template);
|
|
77
|
+
el.value = "";
|
|
78
|
+
flushJobs();
|
|
79
|
+
expect(cleanedUpC1).toBe(true);
|
|
80
|
+
expect(cleanedUpC2).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { memo } from "@alloy-js/core/jsx-runtime";
|
|
2
|
+
import { ref } from "@vue/reactivity";
|
|
3
|
+
import { expect, it } from "vitest";
|
|
4
|
+
it("doesn't recalculate when dependencies don't change", () => {
|
|
5
|
+
const signal = ref(0);
|
|
6
|
+
let callCount = 0;
|
|
7
|
+
const m = memo(() => {
|
|
8
|
+
callCount += 1;
|
|
9
|
+
return signal.value;
|
|
10
|
+
});
|
|
11
|
+
expect(callCount).toBe(1);
|
|
12
|
+
m();
|
|
13
|
+
expect(callCount).toBe(1);
|
|
14
|
+
m();
|
|
15
|
+
expect(callCount).toBe(1);
|
|
16
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { code, memo, printTree, renderTree } from "@alloy-js/core";
|
|
2
|
+
import { ref } from "@vue/reactivity";
|
|
3
|
+
import { expect, it } from "vitest";
|
|
4
|
+
it("handles refs in the tree", () => {
|
|
5
|
+
const r = ref(42);
|
|
6
|
+
const tree = renderTree(["The number is ", r]);
|
|
7
|
+
expect(printTree(tree)).toBe("The number is 42");
|
|
8
|
+
r.value = 12;
|
|
9
|
+
expect(printTree(tree)).toBe("The number is 12");
|
|
10
|
+
});
|
|
11
|
+
it("handles refs in the tree with code", () => {
|
|
12
|
+
const r = ref(42);
|
|
13
|
+
const tree = renderTree(code`
|
|
14
|
+
The number is ${r}
|
|
15
|
+
`);
|
|
16
|
+
expect(printTree(tree)).toBe("The number is 42");
|
|
17
|
+
r.value = 12;
|
|
18
|
+
expect(printTree(tree)).toBe("The number is 12");
|
|
19
|
+
});
|
|
20
|
+
it("handles memos in the tree", () => {
|
|
21
|
+
const r = ref(42);
|
|
22
|
+
const m = memo(() => r.value + 10);
|
|
23
|
+
const tree = renderTree(["The number is ", m]);
|
|
24
|
+
expect(printTree(tree)).toBe("The number is 52");
|
|
25
|
+
r.value = 12;
|
|
26
|
+
expect(printTree(tree)).toBe("The number is 22");
|
|
27
|
+
});
|
|
28
|
+
it("handles memos in the tree with code", () => {
|
|
29
|
+
const r = ref(42);
|
|
30
|
+
const m = memo(() => r.value + 10);
|
|
31
|
+
const tree = renderTree(code`
|
|
32
|
+
The number is ${m}
|
|
33
|
+
`);
|
|
34
|
+
expect(printTree(tree)).toBe("The number is 52");
|
|
35
|
+
r.value = 12;
|
|
36
|
+
expect(printTree(tree)).toBe("The number is 22");
|
|
37
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { memo as _$memo, createComponent as _$createComponent } from "@alloy-js/core/jsx-runtime";
|
|
2
|
+
import { memo, printTree, renderTree } from "@alloy-js/core";
|
|
3
|
+
import { computed, reactive, ref } from "@vue/reactivity";
|
|
4
|
+
import { expect, it } from "vitest";
|
|
5
|
+
import { mapJoin } from "../../src/utils.js";
|
|
6
|
+
import { d } from "../../testing/render.js";
|
|
7
|
+
it("splices in new nodes", () => {
|
|
8
|
+
const r = ref(["one"]);
|
|
9
|
+
const mapped = computed(() => {
|
|
10
|
+
return r.value.map(v => `mapped ${v}`).join(" ");
|
|
11
|
+
});
|
|
12
|
+
function Foo() {
|
|
13
|
+
return [_$memo(() => mapped.value), " done"];
|
|
14
|
+
}
|
|
15
|
+
const tree = renderTree(_$createComponent(Foo, {}));
|
|
16
|
+
expect(printTree(tree)).toEqual("mapped one done");
|
|
17
|
+
r.value = [...r.value, "two"];
|
|
18
|
+
expect(printTree(tree)).toEqual("mapped one mapped two done");
|
|
19
|
+
});
|
|
20
|
+
it("works with a complex case", () => {
|
|
21
|
+
const importRecords = reactive(new Map());
|
|
22
|
+
function addImport(path, type) {
|
|
23
|
+
if (!importRecords.has(path)) {
|
|
24
|
+
importRecords.set(path, new Set());
|
|
25
|
+
}
|
|
26
|
+
importRecords.get(path).add(type);
|
|
27
|
+
}
|
|
28
|
+
function ImportStatements(props) {
|
|
29
|
+
return memo(() => mapJoin(() => props.records, (path, types) => _$createComponent(ImportStatement, {
|
|
30
|
+
path: path,
|
|
31
|
+
types: types
|
|
32
|
+
})));
|
|
33
|
+
}
|
|
34
|
+
function ImportStatement(props) {
|
|
35
|
+
// when the `code` template tag is implemented, the lambda won't be needed.
|
|
36
|
+
return () => `import { ${[...props.types.values()].join(", ")} } from "${props.path}";`;
|
|
37
|
+
}
|
|
38
|
+
const tree = renderTree(_$createComponent(ImportStatements, {
|
|
39
|
+
records: importRecords
|
|
40
|
+
}));
|
|
41
|
+
// the tree is empty.
|
|
42
|
+
|
|
43
|
+
expect(printTree(tree)).toEqual("");
|
|
44
|
+
addImport("./foo.js", "hi");
|
|
45
|
+
printTree(tree);
|
|
46
|
+
expect(printTree(tree)).toEqual('import { hi } from "./foo.js";');
|
|
47
|
+
addImport("./foo.js", "bye");
|
|
48
|
+
expect(printTree(tree)).toEqual('import { hi, bye } from "./foo.js";');
|
|
49
|
+
addImport("node:assert", "strictEqual");
|
|
50
|
+
expect(printTree(tree)).toEqual(d`
|
|
51
|
+
import { hi, bye } from "./foo.js";
|
|
52
|
+
import { strictEqual } from "node:assert";
|
|
53
|
+
`);
|
|
54
|
+
});
|
|
55
|
+
it("works with memos of memos", () => {
|
|
56
|
+
const test = ref(1);
|
|
57
|
+
const tree = renderTree(memo(() => memo(() => test.value)));
|
|
58
|
+
expect(printTree(tree)).toEqual("1");
|
|
59
|
+
test.value = 2;
|
|
60
|
+
expect(printTree(tree)).toEqual("2");
|
|
61
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ref } from "@vue/reactivity";
|
|
2
|
+
import { expect, it } from "vitest";
|
|
3
|
+
import { memo, untrack } from "../../src/jsx-runtime.js";
|
|
4
|
+
import { flushJobs } from "../../src/scheduler.js";
|
|
5
|
+
it("ignores signals for dependency tracking", () => {
|
|
6
|
+
const signal = ref(0);
|
|
7
|
+
const m = memo(() => {
|
|
8
|
+
return untrack(() => signal.value);
|
|
9
|
+
});
|
|
10
|
+
expect(m()).toBe(0);
|
|
11
|
+
signal.value = 1;
|
|
12
|
+
flushJobs();
|
|
13
|
+
expect(m()).toBe(0);
|
|
14
|
+
});
|
|
15
|
+
it("doesn't affect signal changes", () => {
|
|
16
|
+
const signal = ref(0);
|
|
17
|
+
const m = memo(() => {
|
|
18
|
+
return signal.value;
|
|
19
|
+
});
|
|
20
|
+
expect(m()).toBe(0);
|
|
21
|
+
untrack(() => {
|
|
22
|
+
signal.value = 1;
|
|
23
|
+
});
|
|
24
|
+
flushJobs();
|
|
25
|
+
expect(m()).toBe(1);
|
|
26
|
+
});
|