@alloy-js/core 0.10.0 → 0.11.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 (162) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/src/binder.js +33 -2
  3. package/dist/src/code.js +1 -2
  4. package/dist/src/components/Block.js +2 -5
  5. package/dist/src/components/Declaration.js +2 -4
  6. package/dist/src/components/For.d.ts +1 -1
  7. package/dist/src/components/For.d.ts.map +1 -1
  8. package/dist/src/components/For.js +1 -2
  9. package/dist/src/components/Indent.js +2 -4
  10. package/dist/src/components/List.js +2 -5
  11. package/dist/src/components/MemberDeclaration.js +2 -4
  12. package/dist/src/components/MemberName.js +1 -2
  13. package/dist/src/components/MemberScope.js +2 -4
  14. package/dist/src/components/Name.js +1 -2
  15. package/dist/src/components/Output.js +2 -4
  16. package/dist/src/components/Prose.js +1 -2
  17. package/dist/src/components/ReferenceOrContent.d.ts +8 -0
  18. package/dist/src/components/ReferenceOrContent.d.ts.map +1 -0
  19. package/dist/src/components/ReferenceOrContent.js +11 -0
  20. package/dist/src/components/Scope.js +2 -4
  21. package/dist/src/components/Show.js +1 -2
  22. package/dist/src/components/SourceDirectory.js +2 -4
  23. package/dist/src/components/SourceFile.js +2 -5
  24. package/dist/src/components/StatementList.js +2 -4
  25. package/dist/src/components/Switch.d.ts +1 -1
  26. package/dist/src/components/Switch.d.ts.map +1 -1
  27. package/dist/src/components/Switch.js +1 -2
  28. package/dist/src/components/Wrap.js +2 -4
  29. package/dist/src/components/index.d.ts +1 -0
  30. package/dist/src/components/index.d.ts.map +1 -1
  31. package/dist/src/components/index.js +2 -2
  32. package/dist/src/components/stc/index.d.ts +1 -0
  33. package/dist/src/components/stc/index.d.ts.map +1 -1
  34. package/dist/src/components/stc/index.js +2 -2
  35. package/dist/src/components/stc/sti.js +1 -2
  36. package/dist/src/context/assignment.js +1 -2
  37. package/dist/src/context/binder.js +1 -2
  38. package/dist/src/context/declaration.js +1 -2
  39. package/dist/src/context/index.js +1 -2
  40. package/dist/src/context/member-declaration.js +1 -2
  41. package/dist/src/context/member-scope.js +1 -2
  42. package/dist/src/context/name-policy.js +1 -2
  43. package/dist/src/context/scope.js +1 -2
  44. package/dist/src/context/source-directory.js +1 -2
  45. package/dist/src/context/source-file.js +1 -2
  46. package/dist/src/context.js +1 -2
  47. package/dist/src/debug.js +13 -15
  48. package/dist/src/index.browser.js +1 -2
  49. package/dist/src/index.js +1 -2
  50. package/dist/src/jsx-runtime.d.ts +1 -1
  51. package/dist/src/jsx-runtime.d.ts.map +1 -1
  52. package/dist/src/jsx-runtime.js +1 -2
  53. package/dist/src/name-policy.js +1 -2
  54. package/dist/src/refkey.js +1 -2
  55. package/dist/src/render.js +1 -2
  56. package/dist/src/slot.js +1 -2
  57. package/dist/src/stc.js +1 -2
  58. package/dist/src/sti.js +1 -2
  59. package/dist/src/tap.js +1 -2
  60. package/dist/src/tsdoc-metadata.json +1 -1
  61. package/dist/src/utils.js +2 -4
  62. package/dist/src/write-output.browser.js +1 -2
  63. package/dist/src/write-output.js +1 -2
  64. package/dist/test/browser-build.test.js +85 -0
  65. package/dist/test/children.test.js +27 -0
  66. package/dist/test/components/block.test.js +45 -0
  67. package/dist/test/components/declaration.test.js +30 -0
  68. package/dist/test/components/list.test.js +86 -0
  69. package/dist/test/components/prose.test.js +25 -0
  70. package/dist/test/components/reference-or-content.test.d.ts +2 -0
  71. package/dist/test/components/reference-or-content.test.d.ts.map +1 -0
  72. package/dist/test/components/reference-or-content.test.js +149 -0
  73. package/dist/test/components/slot.test.js +134 -0
  74. package/dist/test/components/source-file.test.js +64 -0
  75. package/dist/test/components/wrap.test.js +35 -0
  76. package/dist/test/control-flow/for.test.js +185 -0
  77. package/dist/test/control-flow/match.test.js +67 -0
  78. package/dist/test/control-flow/show.test.js +29 -0
  79. package/dist/test/name-policy.test.js +19 -0
  80. package/dist/test/props-with-defaults.test.js +93 -0
  81. package/dist/test/reactivity/cleanup.test.js +77 -0
  82. package/dist/test/reactivity/memo.test.js +16 -0
  83. package/dist/test/reactivity/ref-rendering.test.js +37 -0
  84. package/dist/test/reactivity/test.test.js +61 -0
  85. package/dist/test/reactivity/untrack.test.js +23 -0
  86. package/dist/test/refkey.test.js +25 -0
  87. package/dist/test/rendering/basic.test.js +96 -0
  88. package/dist/test/rendering/code.test.js +55 -0
  89. package/dist/test/rendering/formatting.test.js +402 -0
  90. package/dist/test/rendering/indent.test.js +90 -0
  91. package/dist/test/rendering/memoization.test.js +25 -0
  92. package/dist/test/rendering/refkeys.test.js +32 -0
  93. package/dist/test/split-props.test.js +77 -0
  94. package/dist/test/stc.test.js +34 -0
  95. package/dist/test/symbols.test.js +504 -0
  96. package/dist/test/utils.test.js +221 -0
  97. package/dist/testing/extend-expect.js +1 -2
  98. package/dist/testing/index.js +1 -2
  99. package/dist/testing/render.js +1 -2
  100. package/dist/tsconfig.tsbuildinfo +1 -1
  101. package/package.json +14 -22
  102. package/src/components/For.tsx +2 -2
  103. package/src/components/ReferenceOrContent.tsx +22 -0
  104. package/src/components/index.tsx +1 -0
  105. package/src/components/stc/index.ts +1 -0
  106. package/src/debug.ts +12 -13
  107. package/src/jsx-runtime.ts +2 -2
  108. package/temp/api.json +208 -7
  109. package/test/components/reference-or-content.test.tsx +138 -0
  110. package/babel.config.cjs +0 -4
  111. package/dist/src/binder.js.map +0 -1
  112. package/dist/src/code.js.map +0 -1
  113. package/dist/src/components/Block.js.map +0 -1
  114. package/dist/src/components/Declaration.js.map +0 -1
  115. package/dist/src/components/For.js.map +0 -1
  116. package/dist/src/components/Indent.js.map +0 -1
  117. package/dist/src/components/List.js.map +0 -1
  118. package/dist/src/components/MemberDeclaration.js.map +0 -1
  119. package/dist/src/components/MemberName.js.map +0 -1
  120. package/dist/src/components/MemberScope.js.map +0 -1
  121. package/dist/src/components/Name.js.map +0 -1
  122. package/dist/src/components/Output.js.map +0 -1
  123. package/dist/src/components/Prose.js.map +0 -1
  124. package/dist/src/components/Scope.js.map +0 -1
  125. package/dist/src/components/Show.js.map +0 -1
  126. package/dist/src/components/SourceDirectory.js.map +0 -1
  127. package/dist/src/components/SourceFile.js.map +0 -1
  128. package/dist/src/components/StatementList.js.map +0 -1
  129. package/dist/src/components/Switch.js.map +0 -1
  130. package/dist/src/components/Wrap.js.map +0 -1
  131. package/dist/src/components/index.js.map +0 -1
  132. package/dist/src/components/stc/index.js.map +0 -1
  133. package/dist/src/components/stc/sti.js.map +0 -1
  134. package/dist/src/context/assignment.js.map +0 -1
  135. package/dist/src/context/binder.js.map +0 -1
  136. package/dist/src/context/declaration.js.map +0 -1
  137. package/dist/src/context/index.js.map +0 -1
  138. package/dist/src/context/member-declaration.js.map +0 -1
  139. package/dist/src/context/member-scope.js.map +0 -1
  140. package/dist/src/context/name-policy.js.map +0 -1
  141. package/dist/src/context/scope.js.map +0 -1
  142. package/dist/src/context/source-directory.js.map +0 -1
  143. package/dist/src/context/source-file.js.map +0 -1
  144. package/dist/src/context.js.map +0 -1
  145. package/dist/src/debug.js.map +0 -1
  146. package/dist/src/index.browser.js.map +0 -1
  147. package/dist/src/index.js.map +0 -1
  148. package/dist/src/jsx-runtime.js.map +0 -1
  149. package/dist/src/name-policy.js.map +0 -1
  150. package/dist/src/refkey.js.map +0 -1
  151. package/dist/src/render.js.map +0 -1
  152. package/dist/src/slot.js.map +0 -1
  153. package/dist/src/stc.js.map +0 -1
  154. package/dist/src/sti.js.map +0 -1
  155. package/dist/src/tap.js.map +0 -1
  156. package/dist/src/utils.js.map +0 -1
  157. package/dist/src/write-output.browser.js.map +0 -1
  158. package/dist/src/write-output.js.map +0 -1
  159. package/dist/testing/extend-expect.js.map +0 -1
  160. package/dist/testing/index.js.map +0 -1
  161. package/dist/testing/render.js.map +0 -1
  162. package/dist/testing/vitest.d.js.map +0 -1
@@ -0,0 +1,134 @@
1
+ import { memo as _$memo, createComponent as _$createComponent } from "@alloy-js/core/jsx-runtime";
2
+ import { it } from "vitest";
3
+ import { Output } from "../../src/components/Output.js";
4
+ import { SourceFile } from "../../src/components/SourceFile.js";
5
+ import { Declaration, Name, refkey, Scope, useBinder } from "../../src/index.js";
6
+ import { render } from "../../src/render.js";
7
+ import { defineSlot, rename, replace } from "../../src/slot.js";
8
+ import "../../testing/extend-expect.js";
9
+ it("works with string keys", () => {
10
+ const FunctionSlot = defineSlot(query => query.name);
11
+ function MyFunctionComponent(props) {
12
+ const FunctionSlotInstance = FunctionSlot.create(props.name, {
13
+ ...props,
14
+ additionalProp: "hi"
15
+ }, ["function ", _$memo(() => props.name), "() ", "{", "console.log(\"hello world\");", "}"]);
16
+ return _$createComponent(FunctionSlotInstance, {});
17
+ }
18
+
19
+ // extension.tsx
20
+ replace(FunctionSlot.find({
21
+ name: "foo"
22
+ }), props => {
23
+ return ["// original", _$memo(() => props.original)];
24
+ });
25
+ const tree = render(_$createComponent(Output, {
26
+ get children() {
27
+ return _$createComponent(SourceFile, {
28
+ path: "test.ts",
29
+ filetype: "ts",
30
+ get children() {
31
+ return _$createComponent(MyFunctionComponent, {
32
+ name: "foo"
33
+ });
34
+ }
35
+ });
36
+ }
37
+ }));
38
+ console.log(tree.contents[0].contents);
39
+ });
40
+ it("works with symbols", () => {
41
+ const FunctionSlot = defineSlot(query => {
42
+ const binder = useBinder();
43
+ return binder.resolveFQN(query.fqn);
44
+ });
45
+ function MyFunctionComponent(props) {
46
+ const binder = useBinder();
47
+ const sym = binder.createSymbol({
48
+ name: props.name,
49
+ refkey: refkey()
50
+ });
51
+ const FunctionSlotInstance = FunctionSlot.create(sym, {
52
+ ...props,
53
+ additionalProp: "hi"
54
+ }, _$createComponent(Declaration, {
55
+ symbol: sym,
56
+ get children() {
57
+ return ["function ", _$createComponent(Name, {}), "() ", "{", "console.log(\"hello world\");", "}"];
58
+ }
59
+ }));
60
+ return _$createComponent(FunctionSlotInstance, {});
61
+ }
62
+
63
+ // extension.tsx
64
+ replace(FunctionSlot.find({
65
+ fqn: "foo.bar"
66
+ }), props => {
67
+ return ["// original", _$memo(() => props.original)];
68
+ });
69
+ const tree = render(_$createComponent(Output, {
70
+ get children() {
71
+ return _$createComponent(SourceFile, {
72
+ path: "test.ts",
73
+ filetype: "ts",
74
+ get children() {
75
+ return _$createComponent(Scope, {
76
+ name: "foo",
77
+ get children() {
78
+ return _$createComponent(MyFunctionComponent, {
79
+ name: "bar"
80
+ });
81
+ }
82
+ });
83
+ }
84
+ });
85
+ }
86
+ }));
87
+ console.log(tree.contents[0].contents);
88
+ });
89
+ it("can rename", () => {
90
+ const FunctionSlot = defineSlot(query => {
91
+ const binder = useBinder();
92
+ return binder.resolveFQN(query.fqn);
93
+ });
94
+ function MyFunctionComponent(props) {
95
+ const binder = useBinder();
96
+ const sym = binder.createSymbol({
97
+ name: props.name,
98
+ refkey: refkey()
99
+ });
100
+ const FunctionSlotInstance = FunctionSlot.create(sym, {
101
+ ...props,
102
+ additionalProp: "hi"
103
+ }, _$createComponent(Declaration, {
104
+ symbol: sym,
105
+ get children() {
106
+ return ["function ", _$createComponent(Name, {}), "() ", "{", "console.log(\"hello world\");", "}"];
107
+ }
108
+ }));
109
+ return _$createComponent(FunctionSlotInstance, {});
110
+ }
111
+ rename(() => {
112
+ const binder = useBinder();
113
+ return binder.resolveFQN("foo.bar");
114
+ }, "bazxxx");
115
+ const tree = render(_$createComponent(Output, {
116
+ get children() {
117
+ return _$createComponent(SourceFile, {
118
+ path: "test.ts",
119
+ filetype: "ts",
120
+ get children() {
121
+ return _$createComponent(Scope, {
122
+ name: "foo",
123
+ get children() {
124
+ return _$createComponent(MyFunctionComponent, {
125
+ name: "bar"
126
+ });
127
+ }
128
+ });
129
+ }
130
+ });
131
+ }
132
+ }));
133
+ console.log(tree.contents[0].contents);
134
+ });
@@ -0,0 +1,64 @@
1
+ import { createComponent as _$createComponent, memo as _$memo } from "@alloy-js/core/jsx-runtime";
2
+ import { computed, Output, render, renderTree, SourceFile, useContext } from "@alloy-js/core";
3
+ import { expect, it } from "vitest";
4
+ import { SourceDirectoryContext } from "../../src/context/source-directory.js";
5
+ import "../../testing/extend-expect.js";
6
+ import { d } from "../../testing/render.js";
7
+ it("tracks its content", () => {
8
+ let context;
9
+ function Test() {
10
+ context = useContext(SourceDirectoryContext);
11
+ }
12
+ const _ = renderTree(_$createComponent(Output, {
13
+ get children() {
14
+ return [_$createComponent(Test, {}), _$createComponent(SourceFile, {
15
+ path: "hi.txt",
16
+ filetype: "text",
17
+ children: "hello!"
18
+ })];
19
+ }
20
+ }));
21
+ expect(context.contents.length).toEqual(1);
22
+ });
23
+ it("has reactive context", () => {
24
+ function TrackContents() {
25
+ const sdContext = useContext(SourceDirectoryContext);
26
+ const allFiles = computed(() => {
27
+ return sdContext.contents.map(v => v.path).join(" ");
28
+ });
29
+ return _$createComponent(SourceFile, {
30
+ path: "contents.txt",
31
+ filetype: "text",
32
+ get children() {
33
+ return allFiles.value;
34
+ }
35
+ });
36
+ }
37
+ const tree = render(_$createComponent(Output, {
38
+ get children() {
39
+ return [_$createComponent(SourceFile, {
40
+ path: "hi.txt",
41
+ filetype: "text",
42
+ children: "hello!"
43
+ }), _$createComponent(TrackContents, {})];
44
+ }
45
+ }));
46
+ expect(tree.contents[1].contents).toEqual("hi.txt contents.txt");
47
+ });
48
+ it("Includes header", () => {
49
+ const header = ["# This is a header"];
50
+ const tree = render(_$createComponent(Output, {
51
+ get children() {
52
+ return _$createComponent(SourceFile, {
53
+ path: "hi.txt",
54
+ filetype: "text",
55
+ header: header,
56
+ children: "hello!"
57
+ });
58
+ }
59
+ }));
60
+ expect(tree.contents[0].contents).toEqual(d`
61
+ # This is a header
62
+ hello!
63
+ `);
64
+ });
@@ -0,0 +1,35 @@
1
+ import { memo as _$memo, createComponent as _$createComponent } from "@alloy-js/core/jsx-runtime";
2
+ import { ref } from "@vue/reactivity";
3
+ import { expect, it } from "vitest";
4
+ import { Wrap } from "../../src/components/Wrap.js";
5
+ import { printTree, renderTree } from "../../src/render.js";
6
+ import "../../testing/extend-expect.js";
7
+ function Wrapper(props) {
8
+ return ["[", _$memo(() => props.children), "]"];
9
+ }
10
+ it("conditionally wraps", () => {
11
+ const template = [_$createComponent(Wrap, {
12
+ when: true,
13
+ "with": Wrapper,
14
+ children: "testing"
15
+ }), _$createComponent(Wrap, {
16
+ when: false,
17
+ "with": Wrapper,
18
+ children: "testing"
19
+ })];
20
+ expect(template).toRenderTo(`[testing]testing`);
21
+ });
22
+ it("works reactively", () => {
23
+ const doWrap = ref(false);
24
+ const template = [_$createComponent(Wrap, {
25
+ get when() {
26
+ return doWrap.value;
27
+ },
28
+ "with": Wrapper,
29
+ children: "testing"
30
+ })];
31
+ const tree = renderTree(template);
32
+ expect(printTree(tree)).toEqual(`testing`);
33
+ doWrap.value = true;
34
+ expect(printTree(tree)).toEqual(`testing`);
35
+ });
@@ -0,0 +1,185 @@
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 { expect, it } from "vitest";
5
+ import { For } from "../../src/components/For.js";
6
+ import { onCleanup, printTree, reactive, renderTree } from "../../src/index.js";
7
+ it("works", () => {
8
+ const messages = ["hi", "bye"];
9
+ const template = _$createComponent(For, {
10
+ each: messages,
11
+ children: message => [message, ", Jose!"]
12
+ });
13
+ expect(template).toRenderTo(`
14
+ hi, Jose!
15
+ bye, Jose!
16
+ `);
17
+ });
18
+ it("handles map entries", () => {
19
+ const map = new Map([["a", {
20
+ name: "foo"
21
+ }]]);
22
+ const entries = Array.from(map.entries());
23
+ const template = _$createComponent(For, {
24
+ each: entries,
25
+ children: ([key, value]) => [key, ": ", _$memo(() => value.name)]
26
+ });
27
+ expect(template).toRenderTo(`
28
+ a: foo
29
+ `);
30
+ });
31
+ it("handles iterators", () => {
32
+ const iterator = new Map([["a", {
33
+ name: "foo"
34
+ }]]).entries();
35
+ const template = _$createComponent(For, {
36
+ each: iterator,
37
+ children: ([key, value]) => [key, ": ", _$memo(() => value.name)]
38
+ });
39
+ expect(template).toRenderTo(`
40
+ a: foo
41
+ `);
42
+ });
43
+ it("handles maps", () => {
44
+ const map = new Map([["a", {
45
+ name: "foo"
46
+ }]]);
47
+ const template = _$createComponent(For, {
48
+ each: map,
49
+ children: (key, value) => [key, ": ", _$memo(() => value.name)]
50
+ });
51
+ expect(template).toRenderTo(`
52
+ a: foo
53
+ `);
54
+ });
55
+ it("doesn't rerender mappers", () => {
56
+ const messages = reactive(["hi", "bye"]);
57
+ let count = 0;
58
+ const template = _$createComponent(For, {
59
+ each: messages,
60
+ children: () => ["item ", count++]
61
+ });
62
+ const tree = renderTree(template);
63
+ expect(count).toBe(2);
64
+ messages.push("maybe");
65
+ expect(count).toBe(3);
66
+ expect(printTree(tree)).toBe(d`
67
+ item 0
68
+ item 1
69
+ item 2
70
+ `);
71
+ });
72
+ it("doesn't rerender mappers with sets", () => {
73
+ const messages = reactive(new Set(["hi", "bye"]));
74
+ let count = 0;
75
+ const template = _$createComponent(For, {
76
+ each: messages,
77
+ children: () => ["item ", count++]
78
+ });
79
+ const tree = renderTree(template);
80
+ expect(count).toBe(2);
81
+ messages.add("maybe");
82
+ expect(count).toBe(3);
83
+ expect(printTree(tree)).toBe(d`
84
+ item 0
85
+ item 1
86
+ item 2
87
+ `);
88
+ });
89
+ it("doesn't rerender mappers with maps", () => {
90
+ const messages = reactive(new Map([["hi", "one"], ["bye", "two"]]));
91
+ let count = 0;
92
+ const template = _$createComponent(For, {
93
+ each: messages,
94
+ children: () => ["item ", count++]
95
+ });
96
+ const tree = renderTree(template);
97
+ expect(count).toBe(2);
98
+ messages.set("maybe", "three");
99
+ expect(count).toBe(3);
100
+ expect(printTree(tree)).toBe(d`
101
+ item 0
102
+ item 1
103
+ item 2
104
+ `);
105
+ });
106
+ it("doesn't rerender mappers (with splice)", () => {
107
+ const messages = reactive(["hi", "maybe", "bye"]);
108
+ let count = 0;
109
+ const template = _$createComponent(For, {
110
+ each: messages,
111
+ children: msg => ["item ", count++]
112
+ });
113
+ const tree = renderTree(template);
114
+ expect(count).toBe(3);
115
+ messages.splice(1, 1);
116
+ // A sufficiently smart mapJoin would be able to handle this case...
117
+ // but for now we re-render everything after the splice point.
118
+ expect(count).toBe(4);
119
+ expect(printTree(tree)).toBe(d`
120
+ item 0
121
+ item 3
122
+ `);
123
+ });
124
+ it("cleans up things which end up removed (with push)", () => {
125
+ const cleanups = [];
126
+ function Letter(props) {
127
+ onCleanup(() => {
128
+ cleanups.push(props.letter);
129
+ });
130
+ return ["Letter ", _$memo(() => props.letter)];
131
+ }
132
+ const items = reactive(["a", "b"]);
133
+ const template = _$createComponent(For, {
134
+ each: items,
135
+ children: item => _$createComponent(Letter, {
136
+ letter: item
137
+ })
138
+ });
139
+ const tree = renderTree(template);
140
+ expect(cleanups).toEqual([]);
141
+ expect(printTree(tree)).toBe(d`
142
+ Letter a
143
+ Letter b
144
+ `);
145
+ items.pop();
146
+ expect(cleanups).toEqual(["b"]);
147
+ expect(printTree(tree)).toBe(d`
148
+ Letter a
149
+ `);
150
+ items.pop();
151
+ expect(cleanups).toEqual(["b", "a"]);
152
+ expect(printTree(tree)).toBe("");
153
+ });
154
+ it("cleans up things which end up removed (with splice)", () => {
155
+ const cleanups = [];
156
+ function Letter(props) {
157
+ onCleanup(() => {
158
+ cleanups.push(props.letter);
159
+ });
160
+ return ["Letter ", _$memo(() => props.letter)];
161
+ }
162
+ const items = reactive(["a", "b", "c"]);
163
+ const template = _$createComponent(For, {
164
+ each: items,
165
+ children: item => _$createComponent(Letter, {
166
+ letter: item
167
+ })
168
+ });
169
+ const tree = renderTree(template);
170
+ expect(cleanups).toEqual([]);
171
+ expect(printTree(tree)).toBe(d`
172
+ Letter a
173
+ Letter b
174
+ Letter c
175
+ `);
176
+ items.splice(1, 1);
177
+
178
+ // A sufficiently smart mapJoin would be able to handle this case...
179
+ // but for now we re-render everything after the splice point.
180
+ expect(cleanups).toEqual(["b", "c"]);
181
+ expect(printTree(tree)).toBe(d`
182
+ Letter a
183
+ Letter c
184
+ `);
185
+ });
@@ -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,77 @@
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
+ describe("memo cleanup", () => {
6
+ it("cleans up when memo value is recomputed", () => {
7
+ const r = ref(1);
8
+ let callCount = 0;
9
+ const m = memo(() => {
10
+ onCleanup(() => {
11
+ callCount++;
12
+ });
13
+ return r.value;
14
+ });
15
+ expect(m()).toBe(1);
16
+ expect(callCount).toBe(0);
17
+ r.value = 2;
18
+ expect(m()).toBe(2);
19
+ expect(callCount).toBe(1);
20
+ });
21
+ });
22
+ describe("effect cleanup", () => {
23
+ it("cleans up when the effect is run", () => {
24
+ const r = ref(1);
25
+ let cleanedUp = false;
26
+ effect(() => {
27
+ onCleanup(() => {
28
+ cleanedUp = true;
29
+ });
30
+ return r.value;
31
+ });
32
+ expect(cleanedUp).toBe(false);
33
+ r.value = 2;
34
+ expect(cleanedUp).toBe(true);
35
+ });
36
+ });
37
+ describe("element cleanup", () => {
38
+ it("should clean up when the element is unmounted", () => {
39
+ let cleanedUp = false;
40
+ function Component() {
41
+ onCleanup(() => {
42
+ cleanedUp = true;
43
+ });
44
+ return "hi!";
45
+ }
46
+ const el = ref(_$createComponent(Component, {}));
47
+ const template = [el];
48
+ renderTree(template);
49
+ el.value = "";
50
+ expect(cleanedUp).toBe(true);
51
+ });
52
+ it("should clean up when the element is unmounted, recursively", () => {
53
+ let cleanedUpC1 = false;
54
+ let cleanedUpC2 = false;
55
+ function C1(props) {
56
+ onCleanup(() => {
57
+ cleanedUpC1 = true;
58
+ });
59
+ return props.children;
60
+ }
61
+ function C2() {
62
+ onCleanup(() => {
63
+ cleanedUpC2 = true;
64
+ });
65
+ }
66
+ const el = ref(_$createComponent(C1, {
67
+ get children() {
68
+ return _$createComponent(C2, {});
69
+ }
70
+ }));
71
+ const template = [el];
72
+ renderTree(template);
73
+ el.value = "";
74
+ expect(cleanedUpC1).toBe(true);
75
+ expect(cleanedUpC2).toBe(true);
76
+ });
77
+ });