@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.
Files changed (183) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/src/binder.d.ts.map +1 -1
  3. package/dist/src/binder.js +100 -19
  4. package/dist/src/code.js +1 -2
  5. package/dist/src/components/Block.js +2 -5
  6. package/dist/src/components/Declaration.js +2 -4
  7. package/dist/src/components/For.d.ts +2 -2
  8. package/dist/src/components/For.d.ts.map +1 -1
  9. package/dist/src/components/For.js +1 -2
  10. package/dist/src/components/Indent.js +2 -4
  11. package/dist/src/components/List.js +2 -5
  12. package/dist/src/components/MemberDeclaration.js +2 -4
  13. package/dist/src/components/MemberName.js +1 -2
  14. package/dist/src/components/MemberScope.js +2 -4
  15. package/dist/src/components/Name.js +1 -2
  16. package/dist/src/components/Output.js +2 -4
  17. package/dist/src/components/Prose.js +1 -2
  18. package/dist/src/components/ReferenceOrContent.d.ts +8 -0
  19. package/dist/src/components/ReferenceOrContent.d.ts.map +1 -0
  20. package/dist/src/components/ReferenceOrContent.js +11 -0
  21. package/dist/src/components/Scope.js +2 -4
  22. package/dist/src/components/Show.js +1 -2
  23. package/dist/src/components/SourceDirectory.js +2 -4
  24. package/dist/src/components/SourceFile.js +2 -5
  25. package/dist/src/components/StatementList.js +2 -4
  26. package/dist/src/components/Switch.d.ts +1 -1
  27. package/dist/src/components/Switch.d.ts.map +1 -1
  28. package/dist/src/components/Switch.js +1 -2
  29. package/dist/src/components/Wrap.js +2 -4
  30. package/dist/src/components/index.d.ts +1 -0
  31. package/dist/src/components/index.d.ts.map +1 -1
  32. package/dist/src/components/index.js +2 -2
  33. package/dist/src/components/stc/index.d.ts +1 -0
  34. package/dist/src/components/stc/index.d.ts.map +1 -1
  35. package/dist/src/components/stc/index.js +2 -2
  36. package/dist/src/components/stc/sti.js +1 -2
  37. package/dist/src/context/assignment.js +1 -2
  38. package/dist/src/context/binder.js +1 -2
  39. package/dist/src/context/declaration.js +1 -2
  40. package/dist/src/context/index.js +1 -2
  41. package/dist/src/context/member-declaration.js +1 -2
  42. package/dist/src/context/member-scope.js +1 -2
  43. package/dist/src/context/name-policy.js +1 -2
  44. package/dist/src/context/scope.js +1 -2
  45. package/dist/src/context/source-directory.js +1 -2
  46. package/dist/src/context/source-file.js +1 -2
  47. package/dist/src/context.js +1 -2
  48. package/dist/src/debug.js +13 -15
  49. package/dist/src/index.browser.js +1 -2
  50. package/dist/src/index.js +1 -2
  51. package/dist/src/jsx-runtime.d.ts +1 -1
  52. package/dist/src/jsx-runtime.d.ts.map +1 -1
  53. package/dist/src/jsx-runtime.js +10 -5
  54. package/dist/src/name-policy.js +1 -2
  55. package/dist/src/refkey.js +1 -2
  56. package/dist/src/render.d.ts.map +1 -1
  57. package/dist/src/render.js +6 -2
  58. package/dist/src/scheduler.d.ts +8 -0
  59. package/dist/src/scheduler.d.ts.map +1 -0
  60. package/dist/src/scheduler.js +17 -0
  61. package/dist/src/slot.js +1 -2
  62. package/dist/src/stc.js +1 -2
  63. package/dist/src/sti.js +1 -2
  64. package/dist/src/tap.js +1 -2
  65. package/dist/src/tsdoc-metadata.json +1 -1
  66. package/dist/src/utils.js +2 -4
  67. package/dist/src/write-output.browser.js +1 -2
  68. package/dist/src/write-output.js +1 -2
  69. package/dist/test/browser-build.test.js +85 -0
  70. package/dist/test/children.test.js +27 -0
  71. package/dist/test/components/block.test.js +45 -0
  72. package/dist/test/components/declaration.test.js +32 -0
  73. package/dist/test/components/list.test.js +86 -0
  74. package/dist/test/components/prose.test.js +25 -0
  75. package/dist/test/components/reference-or-content.test.d.ts +2 -0
  76. package/dist/test/components/reference-or-content.test.d.ts.map +1 -0
  77. package/dist/test/components/reference-or-content.test.js +149 -0
  78. package/dist/test/components/slot.test.js +134 -0
  79. package/dist/test/components/source-file.test.js +64 -0
  80. package/dist/test/components/wrap.test.js +35 -0
  81. package/dist/test/control-flow/for.test.js +219 -0
  82. package/dist/test/control-flow/match.test.js +67 -0
  83. package/dist/test/control-flow/show.test.js +29 -0
  84. package/dist/test/name-policy.test.js +19 -0
  85. package/dist/test/props-with-defaults.test.js +93 -0
  86. package/dist/test/reactivity/circular-reactives.test.d.ts +2 -0
  87. package/dist/test/reactivity/circular-reactives.test.d.ts.map +1 -0
  88. package/dist/test/reactivity/circular-reactives.test.js +31 -0
  89. package/dist/test/reactivity/cleanup.test.js +82 -0
  90. package/dist/test/reactivity/memo.test.js +16 -0
  91. package/dist/test/reactivity/ref-rendering.test.js +37 -0
  92. package/dist/test/reactivity/test.test.js +61 -0
  93. package/dist/test/reactivity/untrack.test.js +26 -0
  94. package/dist/test/refkey.test.js +25 -0
  95. package/dist/test/rendering/basic.test.js +96 -0
  96. package/dist/test/rendering/code.test.js +55 -0
  97. package/dist/test/rendering/formatting.test.js +402 -0
  98. package/dist/test/rendering/indent.test.js +90 -0
  99. package/dist/test/rendering/memoization.test.js +27 -0
  100. package/dist/test/rendering/refkeys.test.js +32 -0
  101. package/dist/test/split-props.test.js +77 -0
  102. package/dist/test/stc.test.js +34 -0
  103. package/dist/test/symbols.test.js +877 -0
  104. package/dist/test/utils.test.d.ts.map +1 -1
  105. package/dist/test/utils.test.js +223 -0
  106. package/dist/testing/extend-expect.js +1 -2
  107. package/dist/testing/index.js +1 -2
  108. package/dist/testing/render.js +1 -2
  109. package/dist/tsconfig.tsbuildinfo +1 -1
  110. package/package.json +14 -22
  111. package/src/binder.ts +100 -17
  112. package/src/components/For.tsx +6 -6
  113. package/src/components/ReferenceOrContent.tsx +22 -0
  114. package/src/components/index.tsx +1 -0
  115. package/src/components/stc/index.ts +1 -0
  116. package/src/debug.ts +12 -13
  117. package/src/jsx-runtime.ts +24 -14
  118. package/src/render.ts +5 -0
  119. package/src/scheduler.ts +24 -0
  120. package/temp/api.json +216 -15
  121. package/test/components/declaration.test.tsx +2 -0
  122. package/test/components/list.test.tsx +0 -1
  123. package/test/components/reference-or-content.test.tsx +138 -0
  124. package/test/control-flow/for.test.tsx +34 -4
  125. package/test/reactivity/circular-reactives.test.tsx +32 -0
  126. package/test/reactivity/cleanup.test.tsx +5 -0
  127. package/test/reactivity/untrack.test.ts +3 -0
  128. package/test/rendering/memoization.test.tsx +2 -0
  129. package/test/symbols.test.ts +392 -13
  130. package/test/utils.test.tsx +2 -0
  131. package/babel.config.cjs +0 -4
  132. package/dist/src/binder.js.map +0 -1
  133. package/dist/src/code.js.map +0 -1
  134. package/dist/src/components/Block.js.map +0 -1
  135. package/dist/src/components/Declaration.js.map +0 -1
  136. package/dist/src/components/For.js.map +0 -1
  137. package/dist/src/components/Indent.js.map +0 -1
  138. package/dist/src/components/List.js.map +0 -1
  139. package/dist/src/components/MemberDeclaration.js.map +0 -1
  140. package/dist/src/components/MemberName.js.map +0 -1
  141. package/dist/src/components/MemberScope.js.map +0 -1
  142. package/dist/src/components/Name.js.map +0 -1
  143. package/dist/src/components/Output.js.map +0 -1
  144. package/dist/src/components/Prose.js.map +0 -1
  145. package/dist/src/components/Scope.js.map +0 -1
  146. package/dist/src/components/Show.js.map +0 -1
  147. package/dist/src/components/SourceDirectory.js.map +0 -1
  148. package/dist/src/components/SourceFile.js.map +0 -1
  149. package/dist/src/components/StatementList.js.map +0 -1
  150. package/dist/src/components/Switch.js.map +0 -1
  151. package/dist/src/components/Wrap.js.map +0 -1
  152. package/dist/src/components/index.js.map +0 -1
  153. package/dist/src/components/stc/index.js.map +0 -1
  154. package/dist/src/components/stc/sti.js.map +0 -1
  155. package/dist/src/context/assignment.js.map +0 -1
  156. package/dist/src/context/binder.js.map +0 -1
  157. package/dist/src/context/declaration.js.map +0 -1
  158. package/dist/src/context/index.js.map +0 -1
  159. package/dist/src/context/member-declaration.js.map +0 -1
  160. package/dist/src/context/member-scope.js.map +0 -1
  161. package/dist/src/context/name-policy.js.map +0 -1
  162. package/dist/src/context/scope.js.map +0 -1
  163. package/dist/src/context/source-directory.js.map +0 -1
  164. package/dist/src/context/source-file.js.map +0 -1
  165. package/dist/src/context.js.map +0 -1
  166. package/dist/src/debug.js.map +0 -1
  167. package/dist/src/index.browser.js.map +0 -1
  168. package/dist/src/index.js.map +0 -1
  169. package/dist/src/jsx-runtime.js.map +0 -1
  170. package/dist/src/name-policy.js.map +0 -1
  171. package/dist/src/refkey.js.map +0 -1
  172. package/dist/src/render.js.map +0 -1
  173. package/dist/src/slot.js.map +0 -1
  174. package/dist/src/stc.js.map +0 -1
  175. package/dist/src/sti.js.map +0 -1
  176. package/dist/src/tap.js.map +0 -1
  177. package/dist/src/utils.js.map +0 -1
  178. package/dist/src/write-output.browser.js.map +0 -1
  179. package/dist/src/write-output.js.map +0 -1
  180. package/dist/testing/extend-expect.js.map +0 -1
  181. package/dist/testing/index.js.map +0 -1
  182. package/dist/testing/render.js.map +0 -1
  183. 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=circular-reactives.test.d.ts.map
@@ -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
+ });