@alloy-js/core 0.5.0 → 0.7.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 (212) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/babel.config.cjs +4 -1
  3. package/dist/src/binder.d.ts +8 -2
  4. package/dist/src/binder.d.ts.map +1 -1
  5. package/dist/src/binder.js +41 -15
  6. package/dist/src/binder.js.map +1 -1
  7. package/dist/src/code.d.ts +2 -2
  8. package/dist/src/code.d.ts.map +1 -1
  9. package/dist/src/code.js +4 -4
  10. package/dist/src/code.js.map +1 -1
  11. package/dist/src/components/Block.d.ts +25 -0
  12. package/dist/src/components/Block.d.ts.map +1 -0
  13. package/dist/src/components/Block.js +25 -0
  14. package/dist/src/components/Block.js.map +1 -0
  15. package/dist/src/components/Declaration.d.ts.map +1 -1
  16. package/dist/src/components/Declaration.js +4 -0
  17. package/dist/src/components/Declaration.js.map +1 -1
  18. package/dist/src/components/For.d.ts +44 -0
  19. package/dist/src/components/For.d.ts.map +1 -0
  20. package/dist/src/components/For.js +41 -0
  21. package/dist/src/components/For.js.map +1 -0
  22. package/dist/src/components/Indent.d.ts +5 -9
  23. package/dist/src/components/Indent.d.ts.map +1 -1
  24. package/dist/src/components/Indent.js +7 -18
  25. package/dist/src/components/Indent.js.map +1 -1
  26. package/dist/src/components/List.d.ts +38 -0
  27. package/dist/src/components/List.d.ts.map +1 -0
  28. package/dist/src/components/List.js +40 -0
  29. package/dist/src/components/List.js.map +1 -0
  30. package/dist/src/components/MemberDeclaration.d.ts.map +1 -1
  31. package/dist/src/components/MemberDeclaration.js.map +1 -1
  32. package/dist/src/components/MemberName.js +1 -1
  33. package/dist/src/components/MemberName.js.map +1 -1
  34. package/dist/src/components/MemberScope.d.ts.map +1 -1
  35. package/dist/src/components/MemberScope.js.map +1 -1
  36. package/dist/src/components/Name.js +1 -1
  37. package/dist/src/components/Name.js.map +1 -1
  38. package/dist/src/components/Output.d.ts +2 -1
  39. package/dist/src/components/Output.d.ts.map +1 -1
  40. package/dist/src/components/Output.js +9 -1
  41. package/dist/src/components/Output.js.map +1 -1
  42. package/dist/src/components/Scope.d.ts.map +1 -1
  43. package/dist/src/components/Scope.js.map +1 -1
  44. package/dist/src/components/Show.d.ts +8 -0
  45. package/dist/src/components/Show.d.ts.map +1 -0
  46. package/dist/src/components/Show.js +4 -0
  47. package/dist/src/components/Show.js.map +1 -0
  48. package/dist/src/components/SourceDirectory.d.ts.map +1 -1
  49. package/dist/src/components/SourceDirectory.js +1 -0
  50. package/dist/src/components/SourceDirectory.js.map +1 -1
  51. package/dist/src/components/SourceFile.d.ts +2 -6
  52. package/dist/src/components/SourceFile.d.ts.map +1 -1
  53. package/dist/src/components/SourceFile.js +6 -13
  54. package/dist/src/components/SourceFile.js.map +1 -1
  55. package/dist/src/components/StatementList.d.ts +9 -0
  56. package/dist/src/components/StatementList.d.ts.map +1 -0
  57. package/dist/src/components/StatementList.js +17 -0
  58. package/dist/src/components/StatementList.js.map +1 -0
  59. package/dist/src/components/Switch.d.ts +41 -0
  60. package/dist/src/components/Switch.d.ts.map +1 -0
  61. package/dist/src/components/Switch.js +41 -0
  62. package/dist/src/components/Switch.js.map +1 -0
  63. package/dist/src/components/Wrap.d.ts +20 -0
  64. package/dist/src/components/Wrap.d.ts.map +1 -0
  65. package/dist/src/components/Wrap.js +15 -0
  66. package/dist/src/components/Wrap.js.map +1 -0
  67. package/dist/src/components/index.d.ts +8 -1
  68. package/dist/src/components/index.d.ts.map +1 -1
  69. package/dist/src/components/index.js +7 -0
  70. package/dist/src/components/index.js.map +1 -1
  71. package/dist/src/components/stc/index.d.ts +77 -6
  72. package/dist/src/components/stc/index.d.ts.map +1 -1
  73. package/dist/src/components/stc/index.js +17 -1
  74. package/dist/src/components/stc/index.js.map +1 -1
  75. package/dist/src/context/index.d.ts +0 -1
  76. package/dist/src/context/index.d.ts.map +1 -1
  77. package/dist/src/context/index.js +0 -1
  78. package/dist/src/context/index.js.map +1 -1
  79. package/dist/src/context.d.ts.map +1 -1
  80. package/dist/src/context.js +3 -3
  81. package/dist/src/context.js.map +1 -1
  82. package/dist/src/index.browser.d.ts +3 -0
  83. package/dist/src/index.browser.d.ts.map +1 -0
  84. package/dist/src/index.browser.js +3 -0
  85. package/dist/src/index.browser.js.map +1 -0
  86. package/dist/src/index.d.ts +1 -0
  87. package/dist/src/index.d.ts.map +1 -1
  88. package/dist/src/index.js +1 -0
  89. package/dist/src/index.js.map +1 -1
  90. package/dist/src/jsx-runtime.d.ts +139 -8
  91. package/dist/src/jsx-runtime.d.ts.map +1 -1
  92. package/dist/src/jsx-runtime.js +102 -12
  93. package/dist/src/jsx-runtime.js.map +1 -1
  94. package/dist/src/render.d.ts +107 -132
  95. package/dist/src/render.d.ts.map +1 -1
  96. package/dist/src/render.js +281 -177
  97. package/dist/src/render.js.map +1 -1
  98. package/dist/src/stc.d.ts +14 -0
  99. package/dist/src/stc.d.ts.map +1 -0
  100. package/dist/src/stc.js +52 -0
  101. package/dist/src/stc.js.map +1 -0
  102. package/dist/src/utils.d.ts +22 -15
  103. package/dist/src/utils.d.ts.map +1 -1
  104. package/dist/src/utils.js +95 -59
  105. package/dist/src/utils.js.map +1 -1
  106. package/dist/src/write-output.js +3 -3
  107. package/dist/src/write-output.js.map +1 -1
  108. package/dist/test/browser-build.test.d.ts +2 -0
  109. package/dist/test/browser-build.test.d.ts.map +1 -0
  110. package/dist/test/components/block.test.d.ts +2 -0
  111. package/dist/test/components/block.test.d.ts.map +1 -0
  112. package/dist/test/components/declaration.test.d.ts +2 -0
  113. package/dist/test/components/declaration.test.d.ts.map +1 -0
  114. package/dist/test/components/list.test.d.ts +2 -0
  115. package/dist/test/components/list.test.d.ts.map +1 -0
  116. package/dist/test/components/wrap.test.d.ts +2 -0
  117. package/dist/test/components/wrap.test.d.ts.map +1 -0
  118. package/dist/test/control-flow/for.test.d.ts +2 -0
  119. package/dist/test/control-flow/for.test.d.ts.map +1 -0
  120. package/dist/test/control-flow/match.test.d.ts +2 -0
  121. package/dist/test/control-flow/match.test.d.ts.map +1 -0
  122. package/dist/test/control-flow/show.test.d.ts +2 -0
  123. package/dist/test/control-flow/show.test.d.ts.map +1 -0
  124. package/dist/test/reactivity/cleanup.test.d.ts +2 -0
  125. package/dist/test/reactivity/cleanup.test.d.ts.map +1 -0
  126. package/dist/test/reactivity/memo.test.d.ts +2 -0
  127. package/dist/test/reactivity/memo.test.d.ts.map +1 -0
  128. package/dist/test/reactivity/untrack.test.d.ts +2 -0
  129. package/dist/test/reactivity/untrack.test.d.ts.map +1 -0
  130. package/dist/test/rendering/formatting.test.d.ts +2 -0
  131. package/dist/test/rendering/formatting.test.d.ts.map +1 -0
  132. package/dist/test/rendering/memoization.test.d.ts +2 -0
  133. package/dist/test/rendering/memoization.test.d.ts.map +1 -0
  134. package/dist/test/split-props.test.d.ts +2 -0
  135. package/dist/test/split-props.test.d.ts.map +1 -0
  136. package/dist/test/stc.test.d.ts.map +1 -1
  137. package/dist/test/utils.test.d.ts.map +1 -1
  138. package/dist/testing/extend-expect.js +4 -4
  139. package/dist/testing/extend-expect.js.map +1 -1
  140. package/dist/testing/render.d.ts +2 -3
  141. package/dist/testing/render.d.ts.map +1 -1
  142. package/dist/testing/render.js +2 -4
  143. package/dist/testing/render.js.map +1 -1
  144. package/dist/tsconfig.tsbuildinfo +1 -1
  145. package/package.json +6 -8
  146. package/src/binder.ts +54 -18
  147. package/src/code.ts +17 -12
  148. package/src/components/Block.tsx +44 -0
  149. package/src/components/Declaration.tsx +10 -4
  150. package/src/components/For.tsx +81 -0
  151. package/src/components/Indent.tsx +20 -27
  152. package/src/components/List.tsx +94 -0
  153. package/src/components/MemberDeclaration.tsx +9 -6
  154. package/src/components/MemberScope.tsx +4 -2
  155. package/src/components/Output.tsx +25 -13
  156. package/src/components/Scope.tsx +4 -2
  157. package/src/components/Show.tsx +11 -0
  158. package/src/components/SourceDirectory.tsx +5 -1
  159. package/src/components/SourceFile.tsx +12 -16
  160. package/src/components/StatementList.tsx +16 -0
  161. package/src/components/Switch.tsx +62 -0
  162. package/src/components/Wrap.tsx +29 -0
  163. package/src/components/index.tsx +8 -1
  164. package/src/components/stc/index.ts +18 -1
  165. package/src/context/index.ts +0 -1
  166. package/src/context.ts +2 -3
  167. package/src/index.browser.ts +2 -0
  168. package/src/index.ts +1 -0
  169. package/src/jsx-runtime.ts +245 -23
  170. package/src/render.ts +392 -198
  171. package/src/stc.ts +95 -0
  172. package/src/utils.ts +162 -95
  173. package/src/write-output.ts +3 -3
  174. package/temp/api.json +8407 -3301
  175. package/test/browser-build.test.ts +91 -0
  176. package/test/children.test.tsx +8 -10
  177. package/test/components/block.test.tsx +48 -0
  178. package/test/components/declaration.test.tsx +37 -0
  179. package/test/components/list.test.tsx +91 -0
  180. package/test/components/slot.test.tsx +31 -25
  181. package/test/components/source-file.test.tsx +11 -31
  182. package/test/components/wrap.test.tsx +42 -0
  183. package/test/control-flow/for.test.tsx +194 -0
  184. package/test/control-flow/match.test.tsx +49 -0
  185. package/test/control-flow/show.test.tsx +25 -0
  186. package/test/name-policy.test.tsx +5 -5
  187. package/test/reactivity/cleanup.test.tsx +91 -0
  188. package/test/reactivity/memo.test.tsx +17 -0
  189. package/test/reactivity/ref-rendering.test.tsx +3 -8
  190. package/test/reactivity/test.test.tsx +7 -6
  191. package/test/reactivity/untrack.test.ts +33 -0
  192. package/test/rendering/basic.test.tsx +25 -47
  193. package/test/rendering/code.test.tsx +3 -3
  194. package/test/rendering/formatting.test.tsx +487 -0
  195. package/test/rendering/indent.test.tsx +42 -529
  196. package/test/rendering/memoization.test.tsx +30 -0
  197. package/test/split-props.test.ts +87 -0
  198. package/test/stc.test.tsx +29 -8
  199. package/test/symbols.test.ts +87 -8
  200. package/test/utils.test.tsx +129 -20
  201. package/testing/extend-expect.ts +14 -4
  202. package/testing/render.ts +2 -4
  203. package/testing/vitest.d.ts +6 -1
  204. package/vitest.config.ts +1 -1
  205. package/dist/src/context/indent.d.ts +0 -5
  206. package/dist/src/context/indent.d.ts.map +0 -1
  207. package/dist/src/context/indent.js +0 -8
  208. package/dist/src/context/indent.js.map +0 -1
  209. package/dist/test/rendering/linebreaks.test.d.ts +0 -2
  210. package/dist/test/rendering/linebreaks.test.d.ts.map +0 -1
  211. package/src/context/indent.ts +0 -17
  212. package/test/rendering/linebreaks.test.tsx +0 -72
@@ -0,0 +1,91 @@
1
+ import { execSync } from "child_process";
2
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
3
+ import { join } from "path";
4
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
5
+
6
+ const testDir = join(__dirname, ".temp", "vite-test-project");
7
+
8
+ describe("Browser Build Test", () => {
9
+ beforeAll(() => {
10
+ // Cleanup previous runs
11
+ if (existsSync(testDir)) {
12
+ rmSync(testDir, { recursive: true, force: true });
13
+ }
14
+
15
+ // Create a temporary Vite project
16
+ mkdirSync(testDir, { recursive: true });
17
+
18
+ execSync("npm init -y", { cwd: testDir });
19
+ execSync("npm install vite", { cwd: testDir });
20
+ execSync("npm install ../../..", { cwd: testDir });
21
+
22
+ // Create a minimal Vite app
23
+ writeFileSync(
24
+ join(testDir, "index.js"),
25
+ `
26
+ import { writeOutput } from "@alloy-js/core";
27
+ console.log("Alloy-js core imported successfully!", writeOutput);
28
+ `,
29
+ );
30
+
31
+ writeFileSync(
32
+ join(testDir, "vite.config.js"),
33
+ `
34
+ import { defineConfig } from "vite";
35
+
36
+ export default defineConfig({
37
+ build: {
38
+ outDir: "dist",
39
+ target: "esnext",
40
+ }
41
+ });
42
+ `,
43
+ );
44
+
45
+ // Create an index.html file
46
+ writeFileSync(
47
+ join(testDir, "index.html"),
48
+ `
49
+ <!DOCTYPE html>
50
+ <html lang="en">
51
+ <head>
52
+ <meta charset="UTF-8">
53
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
54
+ <title>Vite Test</title>
55
+ </head>
56
+ <body>
57
+ <script type="module" src="/index.js"></script>
58
+ </body>
59
+ </html>
60
+ `,
61
+ );
62
+
63
+ writeFileSync(
64
+ join(testDir, "package.json"),
65
+ JSON.stringify(
66
+ {
67
+ type: "module",
68
+ scripts: {
69
+ build: "vite build",
70
+ },
71
+ },
72
+ null,
73
+ 2,
74
+ ),
75
+ );
76
+ });
77
+
78
+ it("Vite should build successfully", () => {
79
+ // Run Vite build process and wait for completion
80
+ expect(() => {
81
+ execSync("npm run build", { cwd: testDir, stdio: "inherit" });
82
+ }).not.toThrow();
83
+ });
84
+
85
+ afterAll(() => {
86
+ // Ensure testDir exists before attempting to remove it
87
+ if (existsSync(testDir)) {
88
+ rmSync(testDir, { recursive: true, force: true });
89
+ }
90
+ });
91
+ });
@@ -1,6 +1,5 @@
1
- import { children, Children, renderTree } from "@alloy-js/core";
1
+ import { children, Children, printTree, renderTree } from "@alloy-js/core";
2
2
  import { expect, it } from "vitest";
3
- import { d, printTree } from "../testing/render.js";
4
3
 
5
4
  it("handles a single element", () => {
6
5
  function Foo(props: { children?: Children }) {
@@ -21,13 +20,12 @@ it("handles a multiple elements", () => {
21
20
  return "Bar";
22
21
  }
23
22
 
24
- const res = renderTree(<Foo>
25
- <Bar />
26
- <Bar />
27
- </Foo>);
23
+ const res = renderTree(
24
+ <Foo>
25
+ <Bar />
26
+ <Bar />
27
+ </Foo>,
28
+ );
28
29
 
29
- expect(printTree(res)).toBe(d`
30
- Bar
31
- Bar
32
- `);
30
+ expect(printTree(res)).toBe(`BarBar`);
33
31
  });
@@ -0,0 +1,48 @@
1
+ import { expect, it } from "vitest";
2
+ import { Block } from "../../src/components/Block.jsx";
3
+ import "../../testing/extend-expect.js";
4
+ it("renders properly with no children", () => {
5
+ const template = (
6
+ <Block>
7
+ <></>
8
+ </Block>
9
+ );
10
+ expect(template).toRenderTo(`{}`);
11
+ });
12
+
13
+ it("renders properly with children", () => {
14
+ const template = <Block>Contents!!</Block>;
15
+ expect(template).toRenderTo(`
16
+ {
17
+ Contents!!
18
+ }
19
+ `);
20
+ });
21
+
22
+ it("renders properly with newline and no children", () => {
23
+ const template = (
24
+ <>
25
+ class
26
+ <Block newline>
27
+ <></>
28
+ </Block>
29
+ </>
30
+ );
31
+ expect(template).toRenderTo(`
32
+ class {}
33
+ `);
34
+ });
35
+
36
+ it("renders properly with newline and no children", () => {
37
+ const template = (
38
+ <>
39
+ class<Block newline>contents!!</Block>
40
+ </>
41
+ );
42
+ expect(template).toRenderTo(`
43
+ class
44
+ {
45
+ contents!!
46
+ }
47
+ `);
48
+ });
@@ -0,0 +1,37 @@
1
+ import { expect, it } from "vitest";
2
+ import { Output } from "../../src/components/Output.jsx";
3
+ import {
4
+ Declaration,
5
+ ref,
6
+ renderTree,
7
+ Scope,
8
+ useBinder,
9
+ } from "../../src/index.js";
10
+ import { createTap } from "../../src/tap.js";
11
+
12
+ it("creates and cleans up a symbol", () => {
13
+ const GetBinder = createTap(() => {
14
+ return useBinder();
15
+ });
16
+
17
+ const binderRef = GetBinder.ref;
18
+ const doDecl = ref(true);
19
+ const template = (
20
+ <Output>
21
+ <GetBinder />
22
+ <Scope>
23
+ {doDecl.value ?
24
+ <Declaration name="foo"></Declaration>
25
+ : ""}
26
+ </Scope>
27
+ </Output>
28
+ );
29
+
30
+ renderTree(template);
31
+
32
+ const binder = binderRef.value!;
33
+ const subScope = [...binder.globalScope.children][0];
34
+ expect(subScope.symbols.size).toBe(1);
35
+ doDecl.value = false;
36
+ expect(subScope.symbols.size).toBe(0);
37
+ });
@@ -0,0 +1,91 @@
1
+ import { expect, it } from "vitest";
2
+ import { List, printTree, ref, renderTree } from "../../src/index.js";
3
+ import "../../testing/extend-expect.js";
4
+ import { d } from "../../testing/render.js";
5
+
6
+ it("creates a list with default options", () => {
7
+ const template = (
8
+ <group>
9
+ <List>
10
+ <>1</>
11
+ <>2</>
12
+ <>3</>
13
+ </List>
14
+ </group>
15
+ );
16
+
17
+ expect(template).toRenderTo(`
18
+ 1
19
+ 2
20
+ 3
21
+ `);
22
+ });
23
+
24
+ it("creates a list with some options", () => {
25
+ expect(
26
+ <group>
27
+ <List comma line>
28
+ <>1</>
29
+ <>2</>
30
+ <>3</>
31
+ </List>
32
+ </group>,
33
+ ).toRenderTo(`1, 2, 3`);
34
+ });
35
+
36
+ it("creates a list item for substitutions", () => {
37
+ expect(
38
+ <group>
39
+ <List comma line>
40
+ {1}
41
+ {2}
42
+ {3}
43
+ </List>
44
+ </group>,
45
+ ).toRenderTo(`1, 2, 3`);
46
+ });
47
+
48
+ it("doesn't create list items for undefined, null, and boolean values", () => {
49
+ expect(
50
+ <group>
51
+ <List comma line>
52
+ {false}
53
+ {1}
54
+ {true}
55
+ {2}
56
+ {null}
57
+ {3}
58
+ {undefined}
59
+ </List>
60
+ </group>,
61
+ ).toRenderTo(`1, 2, 3`);
62
+ });
63
+
64
+ it("is useful for statements", () => {
65
+ function Statement() {
66
+ return "console.log(true)";
67
+ }
68
+ const includeStatement = ref(false);
69
+ const tree = renderTree(
70
+ <List semicolon hardline ender>
71
+ <Statement />
72
+ {includeStatement.value && <Statement />}
73
+ <Statement />
74
+ </List>,
75
+ );
76
+
77
+ expect(printTree(tree)).toEqual(d`
78
+ console.log(true);
79
+ console.log(true);
80
+
81
+ `);
82
+
83
+ includeStatement.value = true;
84
+
85
+ expect(printTree(tree)).toEqual(d`
86
+ console.log(true);
87
+ console.log(true);
88
+ console.log(true);
89
+
90
+ `);
91
+ });
@@ -33,7 +33,7 @@ it("works with string keys", () => {
33
33
  { ...props, additionalProp: "hi" },
34
34
  <>
35
35
  function {props.name}() {"{"}
36
- console.log("hello world");
36
+ console.log("hello world");
37
37
  {"}"}
38
38
  </>,
39
39
  );
@@ -43,10 +43,12 @@ it("works with string keys", () => {
43
43
 
44
44
  // extension.tsx
45
45
  replace(FunctionSlot.find({ name: "foo" }), (props: any) => {
46
- return <>
47
- // original
48
- { props.original }
49
- </>;
46
+ return (
47
+ <>
48
+ // original
49
+ {props.original}
50
+ </>
51
+ );
50
52
  });
51
53
 
52
54
  const tree = render(
@@ -65,12 +67,12 @@ it("works with symbols", () => {
65
67
  additionalProp: string;
66
68
  }
67
69
 
68
- const FunctionSlot = defineSlot<FunctionSlotProps>((query: {
69
- fqn: string;
70
- }) => {
71
- const binder = useBinder();
72
- return binder.resolveFQN(query.fqn);
73
- });
70
+ const FunctionSlot = defineSlot<FunctionSlotProps>(
71
+ (query: { fqn: string }) => {
72
+ const binder = useBinder();
73
+ return binder.resolveFQN(query.fqn);
74
+ },
75
+ );
74
76
 
75
77
  interface FunctionComponentProps {
76
78
  name: string;
@@ -87,8 +89,9 @@ it("works with symbols", () => {
87
89
  sym,
88
90
  { ...props, additionalProp: "hi" },
89
91
  <Declaration symbol={sym}>
90
- function <Name />() {"{"}
91
- console.log("hello world");
92
+ function <Name />
93
+ () {"{"}
94
+ console.log("hello world");
92
95
  {"}"}
93
96
  </Declaration>,
94
97
  );
@@ -98,10 +101,12 @@ it("works with symbols", () => {
98
101
 
99
102
  // extension.tsx
100
103
  replace(FunctionSlot.find({ fqn: "foo.bar" }), (props: any) => {
101
- return <>
102
- // original
103
- { props.original }
104
- </>;
104
+ return (
105
+ <>
106
+ // original
107
+ {props.original}
108
+ </>
109
+ );
105
110
  });
106
111
 
107
112
  const tree = render(
@@ -122,12 +127,12 @@ it("can rename", () => {
122
127
  additionalProp: string;
123
128
  }
124
129
 
125
- const FunctionSlot = defineSlot<FunctionSlotProps>((query: {
126
- fqn: string;
127
- }) => {
128
- const binder = useBinder();
129
- return binder.resolveFQN(query.fqn);
130
- });
130
+ const FunctionSlot = defineSlot<FunctionSlotProps>(
131
+ (query: { fqn: string }) => {
132
+ const binder = useBinder();
133
+ return binder.resolveFQN(query.fqn);
134
+ },
135
+ );
131
136
 
132
137
  interface FunctionComponentProps {
133
138
  name: string;
@@ -144,8 +149,9 @@ it("can rename", () => {
144
149
  sym,
145
150
  { ...props, additionalProp: "hi" },
146
151
  <Declaration symbol={sym}>
147
- function <Name />() {"{"}
148
- console.log("hello world");
152
+ function <Name />
153
+ () {"{"}
154
+ console.log("hello world");
149
155
  {"}"}
150
156
  </Declaration>,
151
157
  );
@@ -18,7 +18,9 @@ it("tracks its content", () => {
18
18
  const _ = renderTree(
19
19
  <Output>
20
20
  <Test />
21
- <SourceFile path="hi.txt" filetype="text">hello!</SourceFile>
21
+ <SourceFile path="hi.txt" filetype="text">
22
+ hello!
23
+ </SourceFile>
22
24
  </Output>,
23
25
  );
24
26
  expect(context!.contents.length).toEqual(1);
@@ -31,43 +33,21 @@ it("has reactive context", () => {
31
33
  return sdContext.contents.map((v) => v.path).join(" ");
32
34
  });
33
35
 
34
- return <SourceFile path="contents.txt" filetype="text">{allFiles.value}</SourceFile>;
36
+ return (
37
+ <SourceFile path="contents.txt" filetype="text">
38
+ {allFiles.value}
39
+ </SourceFile>
40
+ );
35
41
  }
36
42
 
37
43
  const tree = render(
38
44
  <Output>
39
- <SourceFile path="hi.txt" filetype="text">hello!</SourceFile>
45
+ <SourceFile path="hi.txt" filetype="text">
46
+ hello!
47
+ </SourceFile>
40
48
  <TrackContents />
41
49
  </Output>,
42
50
  );
43
51
 
44
52
  expect(tree.contents[1].contents).toEqual("hi.txt contents.txt");
45
53
  });
46
-
47
- it("can change its indent level", () => {
48
- function Nested() {
49
- return <>
50
- base
51
- indented
52
- </>;
53
- }
54
- expect(
55
- <Output>
56
- <SourceFile path="hi.txt" filetype="text" indent=" ">
57
- hello
58
- there
59
-
60
- <Nested />
61
- <Nested />
62
- </SourceFile>
63
- </Output>,
64
- ).toRenderTo(`
65
- hello
66
- there
67
-
68
- base
69
- indented
70
- base
71
- indented
72
- `);
73
- });
@@ -0,0 +1,42 @@
1
+ import { ref } from "@vue/reactivity";
2
+ import { expect, it } from "vitest";
3
+ import { Wrap } from "../../src/components/Wrap.jsx";
4
+ import { printTree, renderTree } from "../../src/render.js";
5
+ import "../../testing/extend-expect.js";
6
+
7
+ function Wrapper(props: any) {
8
+ return <>[{props.children}]</>;
9
+ }
10
+
11
+ it("conditionally wraps", () => {
12
+ const template = (
13
+ <>
14
+ <Wrap when={true} with={Wrapper}>
15
+ testing
16
+ </Wrap>
17
+ <Wrap when={false} with={Wrapper}>
18
+ testing
19
+ </Wrap>
20
+ </>
21
+ );
22
+
23
+ expect(template).toRenderTo(`[testing]testing`);
24
+ });
25
+
26
+ it("works reactively", () => {
27
+ const doWrap = ref(false);
28
+ const template = (
29
+ <>
30
+ <Wrap when={doWrap.value} with={Wrapper}>
31
+ testing
32
+ </Wrap>
33
+ </>
34
+ );
35
+
36
+ const tree = renderTree(template);
37
+ expect(printTree(tree)).toEqual(`testing`);
38
+
39
+ doWrap.value = true;
40
+
41
+ expect(printTree(tree)).toEqual(`testing`);
42
+ });
@@ -0,0 +1,194 @@
1
+ import "@alloy-js/core/testing";
2
+ import { d } from "@alloy-js/core/testing";
3
+ import { expect, it } from "vitest";
4
+ import { For } from "../../src/components/For.jsx";
5
+ import { onCleanup, printTree, reactive, renderTree } from "../../src/index.js";
6
+
7
+ it("works", () => {
8
+ const messages = ["hi", "bye"];
9
+
10
+ const template = (
11
+ <For each={messages}>{(message) => <>{message}, Jose!</>}</For>
12
+ );
13
+
14
+ expect(template).toRenderTo(`
15
+ hi, Jose!
16
+ bye, Jose!
17
+ `);
18
+ });
19
+
20
+ it("handles map entries", () => {
21
+ const map = new Map([["a", { name: "foo" }]]);
22
+ const entries = Array.from(map.entries());
23
+ const template = (
24
+ <For each={entries}>
25
+ {([key, value]) => (
26
+ <>
27
+ {key}: {value.name}
28
+ </>
29
+ )}
30
+ </For>
31
+ );
32
+ expect(template).toRenderTo(`
33
+ a: foo
34
+ `);
35
+ });
36
+
37
+ it("handles maps", () => {
38
+ const map = new Map([["a", { name: "foo" }]]);
39
+ const template = (
40
+ <For each={map}>
41
+ {(key, value) => (
42
+ <>
43
+ {key}: {value.name}
44
+ </>
45
+ )}
46
+ </For>
47
+ );
48
+
49
+ expect(template).toRenderTo(`
50
+ a: foo
51
+ `);
52
+ });
53
+
54
+ it("doesn't rerender mappers", () => {
55
+ const messages = reactive(["hi", "bye"]);
56
+ let count = 0;
57
+ const template = <For each={messages}>{() => <>item {count++}</>}</For>;
58
+ const tree = renderTree(template);
59
+ expect(count).toBe(2);
60
+
61
+ messages.push("maybe");
62
+
63
+ expect(count).toBe(3);
64
+ expect(printTree(tree)).toBe(d`
65
+ item 0
66
+ item 1
67
+ item 2
68
+ `);
69
+ });
70
+
71
+ it("doesn't rerender mappers with sets", () => {
72
+ const messages = reactive(new Set(["hi", "bye"]));
73
+ let count = 0;
74
+ const template = <For each={messages}>{() => <>item {count++}</>}</For>;
75
+ const tree = renderTree(template);
76
+ expect(count).toBe(2);
77
+
78
+ messages.add("maybe");
79
+
80
+ expect(count).toBe(3);
81
+ expect(printTree(tree)).toBe(d`
82
+ item 0
83
+ item 1
84
+ item 2
85
+ `);
86
+ });
87
+
88
+ it("doesn't rerender mappers with maps", () => {
89
+ const messages = reactive(
90
+ new Map([
91
+ ["hi", "one"],
92
+ ["bye", "two"],
93
+ ]),
94
+ );
95
+
96
+ let count = 0;
97
+ const template = <For each={messages}>{() => <>item {count++}</>}</For>;
98
+ const tree = renderTree(template);
99
+ expect(count).toBe(2);
100
+
101
+ messages.set("maybe", "three");
102
+
103
+ expect(count).toBe(3);
104
+ expect(printTree(tree)).toBe(d`
105
+ item 0
106
+ item 1
107
+ item 2
108
+ `);
109
+ });
110
+
111
+ it("doesn't rerender mappers (with splice)", () => {
112
+ const messages = reactive(["hi", "maybe", "bye"]);
113
+ let count = 0;
114
+ const template = <For each={messages}>{(msg) => <>item {count++}</>}</For>;
115
+ const tree = renderTree(template);
116
+ expect(count).toBe(3);
117
+ messages.splice(1, 1);
118
+ // A sufficiently smart mapJoin would be able to handle this case...
119
+ // but for now we re-render everything after the splice point.
120
+ expect(count).toBe(4);
121
+ expect(printTree(tree)).toBe(d`
122
+ item 0
123
+ item 3
124
+ `);
125
+ });
126
+
127
+ it("cleans up things which end up removed (with push)", () => {
128
+ const cleanups: string[] = [];
129
+
130
+ function Letter(props: any) {
131
+ onCleanup(() => {
132
+ cleanups.push(props.letter);
133
+ });
134
+
135
+ return <>Letter {props.letter}</>;
136
+ }
137
+
138
+ const items = reactive(["a", "b"]);
139
+
140
+ const template = <For each={items}>{(item) => <Letter letter={item} />}</For>;
141
+
142
+ const tree = renderTree(template);
143
+
144
+ expect(cleanups).toEqual([]);
145
+ expect(printTree(tree)).toBe(d`
146
+ Letter a
147
+ Letter b
148
+ `);
149
+
150
+ items.pop();
151
+ expect(cleanups).toEqual(["b"]);
152
+ expect(printTree(tree)).toBe(d`
153
+ Letter a
154
+ `);
155
+
156
+ items.pop();
157
+ expect(cleanups).toEqual(["b", "a"]);
158
+ expect(printTree(tree)).toBe("");
159
+ });
160
+
161
+ it("cleans up things which end up removed (with splice)", () => {
162
+ const cleanups: string[] = [];
163
+
164
+ function Letter(props: any) {
165
+ onCleanup(() => {
166
+ cleanups.push(props.letter);
167
+ });
168
+
169
+ return <>Letter {props.letter}</>;
170
+ }
171
+
172
+ const items = reactive(["a", "b", "c"]);
173
+
174
+ const template = <For each={items}>{(item) => <Letter letter={item} />}</For>;
175
+
176
+ const tree = renderTree(template);
177
+
178
+ expect(cleanups).toEqual([]);
179
+ expect(printTree(tree)).toBe(d`
180
+ Letter a
181
+ Letter b
182
+ Letter c
183
+ `);
184
+
185
+ items.splice(1, 1);
186
+
187
+ // A sufficiently smart mapJoin would be able to handle this case...
188
+ // but for now we re-render everything after the splice point.
189
+ expect(cleanups).toEqual(["b", "c"]);
190
+ expect(printTree(tree)).toBe(d`
191
+ Letter a
192
+ Letter c
193
+ `);
194
+ });