@alloy-js/core 0.23.0-dev.0 → 0.23.0-dev.10

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 (316) hide show
  1. package/CHANGELOG.md +0 -22
  2. package/dist/devtools/index.html +68 -0
  3. package/dist/src/binder.d.ts +2 -0
  4. package/dist/src/binder.d.ts.map +1 -1
  5. package/dist/src/binder.js +55 -12
  6. package/dist/src/binder.js.map +1 -1
  7. package/dist/src/components/AccessExpression.d.ts +78 -0
  8. package/dist/src/components/AccessExpression.d.ts.map +1 -0
  9. package/dist/src/components/AccessExpression.js +218 -0
  10. package/dist/src/components/AccessExpression.js.map +1 -0
  11. package/dist/src/components/AccessExpression.test.d.ts +2 -0
  12. package/dist/src/components/AccessExpression.test.d.ts.map +1 -0
  13. package/dist/src/components/AccessExpression.test.js +137 -0
  14. package/dist/src/components/AccessExpression.test.js.map +1 -0
  15. package/dist/src/components/AppendFile.d.ts.map +1 -1
  16. package/dist/src/components/AppendFile.js +14 -3
  17. package/dist/src/components/AppendFile.js.map +1 -1
  18. package/dist/src/components/Block.js +1 -1
  19. package/dist/src/components/Block.js.map +1 -1
  20. package/dist/src/components/Declaration.d.ts.map +1 -1
  21. package/dist/src/components/Declaration.js +2 -1
  22. package/dist/src/components/Declaration.js.map +1 -1
  23. package/dist/src/components/Prose.js +2 -2
  24. package/dist/src/components/Prose.js.map +1 -1
  25. package/dist/src/components/Scope.d.ts.map +1 -1
  26. package/dist/src/components/Scope.js +6 -1
  27. package/dist/src/components/Scope.js.map +1 -1
  28. package/dist/src/components/SourceDirectory.d.ts.map +1 -1
  29. package/dist/src/components/SourceDirectory.js +1 -2
  30. package/dist/src/components/SourceDirectory.js.map +1 -1
  31. package/dist/src/components/TemplateFile.d.ts.map +1 -1
  32. package/dist/src/components/TemplateFile.js +18 -3
  33. package/dist/src/components/TemplateFile.js.map +1 -1
  34. package/dist/src/components/index.d.ts +1 -0
  35. package/dist/src/components/index.d.ts.map +1 -1
  36. package/dist/src/components/index.js +1 -0
  37. package/dist/src/components/index.js.map +1 -1
  38. package/dist/src/content-slot.d.ts.map +1 -1
  39. package/dist/src/content-slot.js +7 -6
  40. package/dist/src/content-slot.js.map +1 -1
  41. package/dist/src/context.d.ts.map +1 -1
  42. package/dist/src/context.js +10 -3
  43. package/dist/src/context.js.map +1 -1
  44. package/dist/src/debug/cli.d.ts +6 -0
  45. package/dist/src/debug/cli.d.ts.map +1 -0
  46. package/dist/src/{debug.js → debug/cli.js} +78 -84
  47. package/dist/src/debug/cli.js.map +1 -0
  48. package/dist/src/debug/diagnostics.test.d.ts +2 -0
  49. package/dist/src/debug/diagnostics.test.d.ts.map +1 -0
  50. package/dist/src/debug/diagnostics.test.js +45 -0
  51. package/dist/src/debug/diagnostics.test.js.map +1 -0
  52. package/dist/src/debug/effects.d.ts +73 -0
  53. package/dist/src/debug/effects.d.ts.map +1 -0
  54. package/dist/src/debug/effects.js +228 -0
  55. package/dist/src/debug/effects.js.map +1 -0
  56. package/dist/src/debug/effects.test.d.ts +2 -0
  57. package/dist/src/debug/effects.test.d.ts.map +1 -0
  58. package/dist/src/debug/effects.test.js +84 -0
  59. package/dist/src/debug/effects.test.js.map +1 -0
  60. package/dist/src/debug/files.d.ts +14 -0
  61. package/dist/src/debug/files.d.ts.map +1 -0
  62. package/dist/src/debug/files.js +40 -0
  63. package/dist/src/debug/files.js.map +1 -0
  64. package/dist/src/debug/files.test.d.ts +2 -0
  65. package/dist/src/debug/files.test.d.ts.map +1 -0
  66. package/dist/src/debug/files.test.js +89 -0
  67. package/dist/src/debug/files.test.js.map +1 -0
  68. package/dist/src/debug/index.d.ts +61 -0
  69. package/dist/src/debug/index.d.ts.map +1 -0
  70. package/dist/src/debug/index.js +69 -0
  71. package/dist/src/debug/index.js.map +1 -0
  72. package/dist/src/debug/render.d.ts +57 -0
  73. package/dist/src/debug/render.d.ts.map +1 -0
  74. package/dist/src/debug/render.js +519 -0
  75. package/dist/src/debug/render.js.map +1 -0
  76. package/dist/src/debug/render.test.d.ts +2 -0
  77. package/dist/src/debug/render.test.d.ts.map +1 -0
  78. package/dist/src/debug/render.test.js +328 -0
  79. package/dist/src/debug/render.test.js.map +1 -0
  80. package/dist/src/debug/serialize.d.ts +9 -0
  81. package/dist/src/debug/serialize.d.ts.map +1 -0
  82. package/dist/src/debug/serialize.js +70 -0
  83. package/dist/src/debug/serialize.js.map +1 -0
  84. package/dist/src/debug/symbols.d.ts +15 -0
  85. package/dist/src/debug/symbols.d.ts.map +1 -0
  86. package/dist/src/debug/symbols.js +173 -0
  87. package/dist/src/debug/symbols.js.map +1 -0
  88. package/dist/src/debug/symbols.test.d.ts +2 -0
  89. package/dist/src/debug/symbols.test.d.ts.map +1 -0
  90. package/dist/src/debug/symbols.test.js +104 -0
  91. package/dist/src/debug/symbols.test.js.map +1 -0
  92. package/dist/src/debug/trace.d.ts +342 -0
  93. package/dist/src/debug/trace.d.ts.map +1 -0
  94. package/dist/src/debug/trace.js +443 -0
  95. package/dist/src/debug/trace.js.map +1 -0
  96. package/dist/src/devtools/devtools-protocol.d.ts +232 -0
  97. package/dist/src/devtools/devtools-protocol.d.ts.map +1 -0
  98. package/dist/src/devtools/devtools-protocol.js +2 -0
  99. package/dist/src/devtools/devtools-protocol.js.map +1 -0
  100. package/dist/src/devtools/devtools-server.browser.d.ts +28 -0
  101. package/dist/src/devtools/devtools-server.browser.d.ts.map +1 -0
  102. package/dist/src/devtools/devtools-server.browser.js +36 -0
  103. package/dist/src/devtools/devtools-server.browser.js.map +1 -0
  104. package/dist/src/devtools/devtools-server.d.ts +72 -0
  105. package/dist/src/devtools/devtools-server.d.ts.map +1 -0
  106. package/dist/src/devtools/devtools-server.js +256 -0
  107. package/dist/src/devtools/devtools-server.js.map +1 -0
  108. package/dist/src/devtools/devtools-transport.d.ts +23 -0
  109. package/dist/src/devtools/devtools-transport.d.ts.map +1 -0
  110. package/dist/src/devtools/devtools-transport.js +114 -0
  111. package/dist/src/devtools/devtools-transport.js.map +1 -0
  112. package/dist/src/devtools-entry.browser.d.ts +4 -0
  113. package/dist/src/devtools-entry.browser.d.ts.map +1 -0
  114. package/dist/src/devtools-entry.browser.js +2 -0
  115. package/dist/src/devtools-entry.browser.js.map +1 -0
  116. package/dist/src/devtools-entry.d.ts +4 -0
  117. package/dist/src/devtools-entry.d.ts.map +1 -0
  118. package/dist/src/devtools-entry.js +2 -0
  119. package/dist/src/devtools-entry.js.map +1 -0
  120. package/dist/src/diagnostics.d.ts +34 -0
  121. package/dist/src/diagnostics.d.ts.map +1 -0
  122. package/dist/src/diagnostics.js +89 -0
  123. package/dist/src/diagnostics.js.map +1 -0
  124. package/dist/src/index.d.ts +3 -2
  125. package/dist/src/index.d.ts.map +1 -1
  126. package/dist/src/index.js +3 -2
  127. package/dist/src/index.js.map +1 -1
  128. package/dist/src/print-hook.d.ts +14 -0
  129. package/dist/src/print-hook.d.ts.map +1 -0
  130. package/dist/src/print-hook.js +10 -0
  131. package/dist/src/print-hook.js.map +1 -0
  132. package/dist/src/reactive-union-set.d.ts.map +1 -1
  133. package/dist/src/reactive-union-set.js +28 -3
  134. package/dist/src/reactive-union-set.js.map +1 -1
  135. package/dist/src/reactivity.d.ts +50 -8
  136. package/dist/src/reactivity.d.ts.map +1 -1
  137. package/dist/src/reactivity.js +225 -39
  138. package/dist/src/reactivity.js.map +1 -1
  139. package/dist/src/render-stack.d.ts +30 -0
  140. package/dist/src/render-stack.d.ts.map +1 -0
  141. package/dist/src/render-stack.js +251 -0
  142. package/dist/src/render-stack.js.map +1 -0
  143. package/dist/src/render.d.ts +9 -19
  144. package/dist/src/render.d.ts.map +1 -1
  145. package/dist/src/render.js +371 -159
  146. package/dist/src/render.js.map +1 -1
  147. package/dist/src/resource.d.ts.map +1 -1
  148. package/dist/src/resource.js +5 -0
  149. package/dist/src/resource.js.map +1 -1
  150. package/dist/src/runtime/component.d.ts +7 -1
  151. package/dist/src/runtime/component.d.ts.map +1 -1
  152. package/dist/src/runtime/component.js +4 -1
  153. package/dist/src/runtime/component.js.map +1 -1
  154. package/dist/src/scheduler.d.ts +8 -0
  155. package/dist/src/scheduler.d.ts.map +1 -1
  156. package/dist/src/scheduler.js +69 -3
  157. package/dist/src/scheduler.js.map +1 -1
  158. package/dist/src/symbols/basic-symbol.d.ts.map +1 -1
  159. package/dist/src/symbols/basic-symbol.js +6 -1
  160. package/dist/src/symbols/basic-symbol.js.map +1 -1
  161. package/dist/src/symbols/decl.d.ts.map +1 -1
  162. package/dist/src/symbols/decl.js +5 -1
  163. package/dist/src/symbols/decl.js.map +1 -1
  164. package/dist/src/symbols/output-scope.d.ts +2 -1
  165. package/dist/src/symbols/output-scope.d.ts.map +1 -1
  166. package/dist/src/symbols/output-scope.js +13 -8
  167. package/dist/src/symbols/output-scope.js.map +1 -1
  168. package/dist/src/symbols/output-symbol.d.ts +1 -0
  169. package/dist/src/symbols/output-symbol.d.ts.map +1 -1
  170. package/dist/src/symbols/output-symbol.js +25 -8
  171. package/dist/src/symbols/output-symbol.js.map +1 -1
  172. package/dist/src/symbols/symbol-flow.d.ts.map +1 -1
  173. package/dist/src/symbols/symbol-flow.js +24 -8
  174. package/dist/src/symbols/symbol-flow.js.map +1 -1
  175. package/dist/src/symbols/symbol-slot.d.ts.map +1 -1
  176. package/dist/src/symbols/symbol-slot.js +15 -0
  177. package/dist/src/symbols/symbol-slot.js.map +1 -1
  178. package/dist/src/symbols/symbol-slot.test.d.ts +2 -0
  179. package/dist/src/symbols/symbol-slot.test.d.ts.map +1 -0
  180. package/dist/src/symbols/symbol-slot.test.js +35 -0
  181. package/dist/src/symbols/symbol-slot.test.js.map +1 -0
  182. package/dist/src/symbols/symbol-table.d.ts.map +1 -1
  183. package/dist/src/symbols/symbol-table.js +6 -5
  184. package/dist/src/symbols/symbol-table.js.map +1 -1
  185. package/dist/src/trace.d.ts +2 -0
  186. package/dist/src/trace.d.ts.map +1 -0
  187. package/dist/src/trace.js +2 -0
  188. package/dist/src/trace.js.map +1 -0
  189. package/dist/src/tracer.d.ts +2 -228
  190. package/dist/src/tracer.d.ts.map +1 -1
  191. package/dist/src/tracer.js +5 -298
  192. package/dist/src/tracer.js.map +1 -1
  193. package/dist/src/utils.d.ts.map +1 -1
  194. package/dist/src/utils.js +7 -5
  195. package/dist/src/utils.js.map +1 -1
  196. package/dist/test/components/append-file.test.d.ts.map +1 -1
  197. package/dist/test/components/append-file.test.js +18 -10
  198. package/dist/test/components/append-file.test.js.map +1 -1
  199. package/dist/test/components/template-file.test.d.ts.map +1 -1
  200. package/dist/test/components/template-file.test.js +6 -4
  201. package/dist/test/components/template-file.test.js.map +1 -1
  202. package/dist/test/lazy-isempty.test.d.ts +2 -0
  203. package/dist/test/lazy-isempty.test.d.ts.map +1 -0
  204. package/dist/test/lazy-isempty.test.js +89 -0
  205. package/dist/test/lazy-isempty.test.js.map +1 -0
  206. package/dist/test/reactive-union-set-disposers.test.d.ts +2 -0
  207. package/dist/test/reactive-union-set-disposers.test.d.ts.map +1 -0
  208. package/dist/test/reactive-union-set-disposers.test.js +98 -0
  209. package/dist/test/reactive-union-set-disposers.test.js.map +1 -0
  210. package/dist/test/reactivity/shallow-reactive.test.d.ts +2 -0
  211. package/dist/test/reactivity/shallow-reactive.test.d.ts.map +1 -0
  212. package/dist/test/reactivity/shallow-reactive.test.js +52 -0
  213. package/dist/test/reactivity/shallow-reactive.test.js.map +1 -0
  214. package/dist/test/rendering/basic.test.js +3 -0
  215. package/dist/test/rendering/basic.test.js.map +1 -1
  216. package/dist/test/rendering/print-render-stack.test.d.ts +2 -0
  217. package/dist/test/rendering/print-render-stack.test.d.ts.map +1 -0
  218. package/dist/test/rendering/print-render-stack.test.js +207 -0
  219. package/dist/test/rendering/print-render-stack.test.js.map +1 -0
  220. package/dist/test/scheduler-extended.test.d.ts +2 -0
  221. package/dist/test/scheduler-extended.test.d.ts.map +1 -0
  222. package/dist/test/scheduler-extended.test.js +96 -0
  223. package/dist/test/scheduler-extended.test.js.map +1 -0
  224. package/dist/test/scheduler.test.d.ts +2 -0
  225. package/dist/test/scheduler.test.d.ts.map +1 -0
  226. package/dist/test/scheduler.test.js +46 -0
  227. package/dist/test/scheduler.test.js.map +1 -0
  228. package/dist/testing/create-test-wrapper.d.ts +1 -1
  229. package/dist/testing/create-test-wrapper.d.ts.map +1 -1
  230. package/dist/testing/create-test-wrapper.js +1 -1
  231. package/dist/testing/create-test-wrapper.js.map +1 -1
  232. package/dist/testing/devtools-utils.d.ts +26 -0
  233. package/dist/testing/devtools-utils.d.ts.map +1 -0
  234. package/dist/testing/devtools-utils.js +140 -0
  235. package/dist/testing/devtools-utils.js.map +1 -0
  236. package/dist/testing/extend-expect.d.ts.map +1 -1
  237. package/dist/testing/extend-expect.js +63 -1
  238. package/dist/testing/extend-expect.js.map +1 -1
  239. package/dist/testing/render.d.ts +2 -2
  240. package/dist/testing/render.d.ts.map +1 -1
  241. package/dist/testing/render.js +2 -2
  242. package/dist/testing/render.js.map +1 -1
  243. package/dist/tsconfig.tsbuildinfo +1 -1
  244. package/package.json +21 -7
  245. package/scripts/copy-devtools-ui.mjs +26 -0
  246. package/src/binder.ts +71 -16
  247. package/src/components/AccessExpression.test.tsx +132 -0
  248. package/src/components/AccessExpression.tsx +344 -0
  249. package/src/components/AppendFile.tsx +14 -9
  250. package/src/components/Block.tsx +1 -1
  251. package/src/components/Declaration.tsx +2 -1
  252. package/src/components/Prose.tsx +1 -1
  253. package/src/components/Scope.tsx +6 -1
  254. package/src/components/SourceDirectory.tsx +1 -2
  255. package/src/components/TemplateFile.tsx +18 -9
  256. package/src/components/index.tsx +1 -0
  257. package/src/content-slot.tsx +7 -7
  258. package/src/context.ts +17 -6
  259. package/src/{debug.ts → debug/cli.ts} +112 -127
  260. package/src/debug/diagnostics.test.tsx +55 -0
  261. package/src/debug/effects.test.tsx +89 -0
  262. package/src/debug/effects.ts +317 -0
  263. package/src/debug/files.test.tsx +96 -0
  264. package/src/debug/files.ts +40 -0
  265. package/src/debug/index.ts +128 -0
  266. package/src/debug/render.test.tsx +379 -0
  267. package/src/debug/render.ts +639 -0
  268. package/src/debug/serialize.ts +85 -0
  269. package/src/debug/symbols.test.tsx +106 -0
  270. package/src/debug/symbols.ts +239 -0
  271. package/src/debug/trace.ts +312 -0
  272. package/src/devtools/devtools-protocol.ts +312 -0
  273. package/src/devtools/devtools-server.browser.ts +71 -0
  274. package/src/devtools/devtools-server.ts +290 -0
  275. package/src/devtools/devtools-transport.ts +154 -0
  276. package/src/devtools-entry.browser.ts +52 -0
  277. package/src/devtools-entry.ts +54 -0
  278. package/src/diagnostics.ts +141 -0
  279. package/src/index.ts +2 -7
  280. package/src/print-hook.ts +22 -0
  281. package/src/reactive-union-set.ts +85 -44
  282. package/src/reactivity.ts +301 -59
  283. package/src/render-stack.ts +294 -0
  284. package/src/render.ts +470 -216
  285. package/src/resource.ts +28 -19
  286. package/src/runtime/component.ts +11 -0
  287. package/src/scheduler.ts +80 -4
  288. package/src/symbols/basic-symbol.ts +6 -1
  289. package/src/symbols/decl.ts +5 -1
  290. package/src/symbols/output-scope.ts +21 -13
  291. package/src/symbols/output-symbol.ts +34 -14
  292. package/src/symbols/symbol-flow.ts +76 -39
  293. package/src/symbols/symbol-slot.test.tsx +41 -0
  294. package/src/symbols/symbol-slot.tsx +47 -20
  295. package/src/symbols/symbol-table.ts +6 -10
  296. package/src/trace.ts +1 -0
  297. package/src/tracer.ts +13 -242
  298. package/src/utils.tsx +24 -17
  299. package/temp/api.json +4187 -1603
  300. package/test/components/append-file.test.tsx +36 -29
  301. package/test/components/template-file.test.tsx +11 -11
  302. package/test/lazy-isempty.test.tsx +106 -0
  303. package/test/reactive-union-set-disposers.test.tsx +112 -0
  304. package/test/reactivity/shallow-reactive.test.tsx +56 -0
  305. package/test/rendering/basic.test.tsx +4 -0
  306. package/test/rendering/print-render-stack.test.tsx +244 -0
  307. package/test/scheduler-extended.test.tsx +122 -0
  308. package/test/scheduler.test.tsx +50 -0
  309. package/testing/create-test-wrapper.tsx +1 -1
  310. package/testing/devtools-utils.ts +203 -0
  311. package/testing/extend-expect.ts +89 -0
  312. package/testing/render.ts +2 -2
  313. package/testing/vitest.d.ts +9 -0
  314. package/dist/src/debug.d.ts +0 -15
  315. package/dist/src/debug.d.ts.map +0 -1
  316. package/dist/src/debug.js.map +0 -1
@@ -3,7 +3,6 @@ import { tmpdir } from "os";
3
3
  import { join } from "path";
4
4
  import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
5
  import { AppendFile, AppendRegion } from "../../src/components/AppendFile.jsx";
6
- import { render, renderAsync } from "../../src/render.js";
7
6
  import "../../testing/extend-expect.js";
8
7
  import { d } from "../../testing/render.js";
9
8
 
@@ -34,6 +33,7 @@ describe("AppendFile", () => {
34
33
 
35
34
  await expect(result).toRenderToAsync("Initial content\nNew content");
36
35
  });
36
+
37
37
  it("should append content to end of file when no sigils present with no explicit append region", async () => {
38
38
  // Create initial file content
39
39
  writeFileSync(testFilePath, "Initial content", "utf-8");
@@ -148,30 +148,35 @@ describe("AppendFile", () => {
148
148
  await expect(result).toRenderToAsync("Content\ndefault region");
149
149
  });
150
150
 
151
- it("should throw error when region is missing corresponding AppendRegion", async () => {
151
+ it("should emit diagnostic when region is missing corresponding AppendRegion", async () => {
152
152
  writeFileSync(testFilePath, "content", "utf-8");
153
153
 
154
- expect(() =>
155
- render(
156
- <AppendFile path={testFilePath} regions={["missing"]}>
157
- <AppendRegion id="append">content</AppendRegion>
158
- </AppendFile>,
159
- ),
160
- ).toThrow(
161
- 'Region "missing" specified but no corresponding AppendRegion child found',
162
- );
154
+ await expect(
155
+ <AppendFile path={testFilePath} regions={["missing"]}>
156
+ <AppendRegion id="append">content</AppendRegion>
157
+ </AppendFile>,
158
+ ).toHaveDiagnosticsAsync([
159
+ {
160
+ message:
161
+ 'Region "missing" specified but no corresponding AppendRegion child found',
162
+ severity: "error",
163
+ },
164
+ ]);
163
165
  });
164
166
 
165
- it("should throw error when AppendRegion has neither children nor content", async () => {
167
+ it("should emit diagnostic when AppendRegion has neither children nor content", async () => {
166
168
  writeFileSync(testFilePath, "content", "utf-8");
167
169
 
168
- expect(() =>
169
- render(
170
- <AppendFile path={testFilePath}>
171
- <AppendRegion id="append" />
172
- </AppendFile>,
173
- ),
174
- ).toThrow('AppendRegion "append" must have either children or content');
170
+ await expect(
171
+ <AppendFile path={testFilePath}>
172
+ <AppendRegion id="append" />
173
+ </AppendFile>,
174
+ ).toHaveDiagnosticsAsync([
175
+ {
176
+ message: 'AppendRegion "append" must have either children or content',
177
+ severity: "error",
178
+ },
179
+ ]);
175
180
  });
176
181
 
177
182
  it("should throw error when region has missing start sigil", async () => {
@@ -196,7 +201,7 @@ describe("AppendFile", () => {
196
201
  `);
197
202
  });
198
203
 
199
- it("should throw error when region has missing end sigil", async () => {
204
+ it("should emit diagnostic when region has missing end sigil", async () => {
200
205
  const contentWithOnlyStart = d`
201
206
  Content
202
207
  <!-- alloy-incomplete-start -->
@@ -204,15 +209,17 @@ describe("AppendFile", () => {
204
209
 
205
210
  writeFileSync(testFilePath, contentWithOnlyStart, "utf-8");
206
211
 
207
- await expect(async () =>
208
- renderAsync(
209
- <AppendFile path={testFilePath} regions={["incomplete"]}>
210
- <AppendRegion id="incomplete">content</AppendRegion>
211
- </AppendFile>,
212
- ),
213
- ).rejects.toThrow(
214
- 'Region "incomplete" has start sigil but no corresponding end sigil',
215
- );
212
+ await expect(
213
+ <AppendFile path={testFilePath} regions={["incomplete"]}>
214
+ <AppendRegion id="incomplete">content</AppendRegion>
215
+ </AppendFile>,
216
+ ).toHaveDiagnosticsAsync([
217
+ {
218
+ message:
219
+ 'Region "incomplete" has start sigil but no corresponding end sigil',
220
+ severity: "error",
221
+ },
222
+ ]);
216
223
  });
217
224
 
218
225
  it("should handle complex nested content", async () => {
@@ -6,7 +6,6 @@ import {
6
6
  TemplateFile,
7
7
  TemplateVariable,
8
8
  } from "../../src/components/TemplateFile.jsx";
9
- import { renderAsync } from "../../src/render.js";
10
9
  import "../../testing/extend-expect.js";
11
10
  import { d } from "../../testing/render.js";
12
11
 
@@ -82,21 +81,22 @@ describe("TemplateFile", () => {
82
81
  await expect(result).toRenderToAsync("Hello Bob!");
83
82
  });
84
83
 
85
- it("should throw error for missing template variables", async () => {
84
+ it("should emit diagnostic for missing template variables", async () => {
86
85
  const templatePath = join(tmpdir(), "test-missing-var-template.txt");
87
86
  const templateContent = "Hello {{ name }}! Your age is {{ age }}.";
88
87
  writeFileSync(templatePath, templateContent);
89
88
 
90
89
  await expect(
91
- async () =>
92
- await renderAsync(
93
- <TemplateFile src={templatePath} path="output.txt">
94
- <TemplateVariable name="name" value="Charlie" />
95
- </TemplateFile>,
96
- ),
97
- ).rejects.toThrow(
98
- 'Template variable "age" not found in TemplateVariable children',
99
- );
90
+ <TemplateFile src={templatePath} path="output.txt">
91
+ <TemplateVariable name="name" value="Charlie" />
92
+ </TemplateFile>,
93
+ ).toHaveDiagnosticsAsync([
94
+ {
95
+ message:
96
+ 'Template variable "age" not found in TemplateVariable children',
97
+ severity: "error",
98
+ },
99
+ ]);
100
100
  });
101
101
 
102
102
  it("should handle template with no variables", async () => {
@@ -0,0 +1,106 @@
1
+ import { ref } from "@vue/reactivity";
2
+ import { describe, expect, it } from "vitest";
3
+ import { Show } from "../src/components/Show.jsx";
4
+ import { createContentSlot } from "../src/content-slot.jsx";
5
+ import { Context, ensureIsEmpty, getContext } from "../src/reactivity.js";
6
+ import { printTree, renderTree } from "../src/render.js";
7
+ import "../testing/extend-expect.js";
8
+
9
+ describe("lazy isEmpty / _lastEmpty", () => {
10
+ it("context starts without isEmpty ref allocated", () => {
11
+ let ctx: Context | null = null;
12
+
13
+ function Capture() {
14
+ ctx = getContext()!;
15
+ return "content";
16
+ }
17
+
18
+ renderTree(<Capture />);
19
+
20
+ // The isEmpty ref should NOT be allocated unless someone observes it.
21
+ expect(ctx).not.toBeNull();
22
+ expect(ctx!.isEmpty).toBeUndefined();
23
+ expect(ctx!._lastEmpty).toBe(false);
24
+ });
25
+
26
+ it("ensureIsEmpty lazily allocates the isEmpty ref", () => {
27
+ let ctx: Context | null = null;
28
+
29
+ function Capture() {
30
+ ctx = getContext()!;
31
+ return "content";
32
+ }
33
+
34
+ renderTree(<Capture />);
35
+
36
+ expect(ctx!.isEmpty).toBeUndefined();
37
+ const isEmptyRef = ensureIsEmpty(ctx!);
38
+ expect(ctx!.isEmpty).toBeDefined();
39
+ expect(isEmptyRef).toBe(ctx!.isEmpty);
40
+ });
41
+
42
+ it("_lastEmpty is true for empty component, false for non-empty", () => {
43
+ let emptyCtx: Context | null = null;
44
+ let fullCtx: Context | null = null;
45
+
46
+ function EmptyCapture() {
47
+ emptyCtx = getContext()!;
48
+ return false;
49
+ }
50
+
51
+ function FullCapture() {
52
+ fullCtx = getContext()!;
53
+ return "has content";
54
+ }
55
+
56
+ renderTree(
57
+ <>
58
+ <EmptyCapture />
59
+ <FullCapture />
60
+ </>,
61
+ );
62
+
63
+ expect(emptyCtx!._lastEmpty).toBe(true);
64
+ expect(fullCtx!._lastEmpty).toBe(false);
65
+ });
66
+
67
+ it("ContentSlot triggers ensureIsEmpty and tracks reactively", () => {
68
+ const ContentSlot = createContentSlot();
69
+ const showContent = ref(false);
70
+
71
+ const tree = renderTree(
72
+ <>
73
+ {ContentSlot.isEmpty && "empty"}
74
+ <ContentSlot>
75
+ <Show when={showContent.value}>content</Show>
76
+ </ContentSlot>
77
+ </>,
78
+ );
79
+
80
+ expect(printTree(tree)).toBe("empty");
81
+
82
+ showContent.value = true;
83
+ expect(printTree(tree)).toBe("content");
84
+ });
85
+
86
+ it("propagates empty state up through parent contexts", () => {
87
+ const OuterSlot = createContentSlot();
88
+ const showContent = ref(false);
89
+
90
+ const tree = renderTree(
91
+ <>
92
+ {OuterSlot.isEmpty && "outer-empty"}
93
+ <OuterSlot>
94
+ <Show when={showContent.value}>content</Show>
95
+ </OuterSlot>
96
+ </>,
97
+ );
98
+
99
+ // Outer should be empty initially.
100
+ expect(printTree(tree)).toBe("outer-empty");
101
+
102
+ // Show content — outer should become non-empty.
103
+ showContent.value = true;
104
+ expect(printTree(tree)).toBe("content");
105
+ });
106
+ });
@@ -0,0 +1,112 @@
1
+ import { reactive } from "@vue/reactivity";
2
+ import { describe, expect, it } from "vitest";
3
+ import { ReactiveUnionSet } from "../src/reactive-union-set.js";
4
+ import { effect } from "../src/reactivity.js";
5
+ import { flushJobs } from "../src/scheduler.js";
6
+
7
+ describe("ReactiveUnionSet: per-item disposers via addSubset", () => {
8
+ it("calls onDelete when item is removed from subset", () => {
9
+ const parentSet = new ReactiveUnionSet<string>();
10
+ const subset = reactive(new Set<string>());
11
+ const deleted: string[] = [];
12
+
13
+ parentSet.addSubset(subset, {
14
+ onDelete(value) {
15
+ deleted.push(value);
16
+ },
17
+ });
18
+
19
+ subset.add("a");
20
+ subset.add("b");
21
+ flushJobs();
22
+ expect(parentSet.has("a")).toBe(true);
23
+ expect(parentSet.has("b")).toBe(true);
24
+
25
+ subset.delete("a");
26
+ flushJobs();
27
+ expect(parentSet.has("a")).toBe(false);
28
+ expect(deleted).toContain("a");
29
+ });
30
+
31
+ it("calls onDelete for all items when subset is cleared", () => {
32
+ const parentSet = new ReactiveUnionSet<string>();
33
+ const subset = reactive(new Set<string>());
34
+ const deleted: string[] = [];
35
+
36
+ parentSet.addSubset(subset, {
37
+ onDelete(value) {
38
+ deleted.push(value);
39
+ },
40
+ });
41
+
42
+ subset.add("a");
43
+ subset.add("b");
44
+ subset.add("c");
45
+ flushJobs();
46
+ expect(parentSet.size).toBe(3);
47
+
48
+ subset.clear();
49
+ flushJobs();
50
+ expect(parentSet.size).toBe(0);
51
+ expect(deleted.sort()).toEqual(["a", "b", "c"]);
52
+ });
53
+
54
+ it("disposes root scopes created by onAdd when item is removed", () => {
55
+ const parentSet = new ReactiveUnionSet<string>();
56
+ const subset = reactive(new Set<string>());
57
+ let disposeCount = 0;
58
+
59
+ // Use the constructor-level onAdd so items are still added to the set,
60
+ // plus per-subset onAdd with root scope tracking.
61
+ parentSet.addSubset(subset, {
62
+ onAdd(value) {
63
+ // The onAdd in addSubset wraps in root() internally.
64
+ // Side-effects created here are cleaned up when the item is removed.
65
+ effect(() => {
66
+ void value; // track nothing real, just proving the effect exists
67
+ });
68
+ return value;
69
+ },
70
+ onDelete() {
71
+ disposeCount++;
72
+ },
73
+ });
74
+
75
+ subset.add("x");
76
+ flushJobs();
77
+
78
+ // Remove item — root scope (and its effects) should be disposed.
79
+ subset.delete("x");
80
+ flushJobs();
81
+ expect(disposeCount).toBe(1);
82
+ });
83
+
84
+ it("re-adding after delete triggers fresh onAdd", () => {
85
+ const parentSet = new ReactiveUnionSet<string>();
86
+ const subset = reactive(new Set<string>());
87
+ let addCount = 0;
88
+ const deleted: string[] = [];
89
+
90
+ parentSet.addSubset(subset, {
91
+ onAdd(value) {
92
+ addCount++;
93
+ return value;
94
+ },
95
+ onDelete(value) {
96
+ deleted.push(value);
97
+ },
98
+ });
99
+
100
+ subset.add("a");
101
+ flushJobs();
102
+ expect(addCount).toBe(1);
103
+
104
+ subset.delete("a");
105
+ flushJobs();
106
+ expect(deleted).toEqual(["a"]);
107
+
108
+ subset.add("a");
109
+ flushJobs();
110
+ expect(addCount).toBe(2);
111
+ });
112
+ });
@@ -0,0 +1,56 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
+ import {
3
+ getReactiveCreationLocation,
4
+ shallowReactive,
5
+ } from "../../src/reactivity.js";
6
+
7
+ describe("shallowReactive creation location", () => {
8
+ let origDebug: string | undefined;
9
+
10
+ beforeEach(() => {
11
+ origDebug = process.env.ALLOY_DEBUG;
12
+ process.env.ALLOY_DEBUG = "1";
13
+ });
14
+
15
+ afterEach(() => {
16
+ if (origDebug === undefined) {
17
+ delete process.env.ALLOY_DEBUG;
18
+ } else {
19
+ process.env.ALLOY_DEBUG = origDebug;
20
+ }
21
+ });
22
+
23
+ it("stores creation location keyed by raw target when debug enabled", () => {
24
+ const raw = { x: 1 };
25
+ shallowReactive(raw);
26
+
27
+ const location = getReactiveCreationLocation(raw);
28
+ // When ALLOY_DEBUG is set, captureSourceLocation should return something.
29
+ expect(location).toBeDefined();
30
+ });
31
+
32
+ it("does not store location when debug is disabled", () => {
33
+ delete process.env.ALLOY_DEBUG;
34
+ const raw = { y: 2 };
35
+ shallowReactive(raw);
36
+
37
+ const location = getReactiveCreationLocation(raw);
38
+ expect(location).toBeUndefined();
39
+ });
40
+
41
+ it("location is keyed by raw target, not proxy", () => {
42
+ const raw = { z: 3 };
43
+ const proxy = shallowReactive(raw);
44
+
45
+ // Proxy and raw are different objects.
46
+ expect(proxy).not.toBe(raw);
47
+
48
+ // Location should be on the raw target.
49
+ const location = getReactiveCreationLocation(raw);
50
+ expect(location).toBeDefined();
51
+
52
+ // The proxy itself should NOT have a location stored (WeakMap keyed by raw).
53
+ // Note: Vue may unwrap the proxy to raw internally, but our code explicitly
54
+ // stores on `target` (the raw object passed to shallowReactive).
55
+ });
56
+ });
@@ -166,3 +166,7 @@ it("keeps spaces between expressions", () => {
166
166
  </>,
167
167
  ).toRenderTo("a str str getStr getStr c");
168
168
  });
169
+
170
+ it("renders numbers", () => {
171
+ expect(<>200</>).toRenderTo("200");
172
+ });
@@ -0,0 +1,244 @@
1
+ import {
2
+ Output,
3
+ SourceDirectory,
4
+ SourceFile,
5
+ renderAsync,
6
+ } from "@alloy-js/core";
7
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
8
+ import WebSocket from "ws";
9
+ import { createNamedContext } from "../../src/context.js";
10
+ import {
11
+ enableDevtools,
12
+ resetDevtoolsServerForTests,
13
+ } from "../../src/devtools/devtools-server.js";
14
+ import { clearRenderStack } from "../../src/render-stack.js";
15
+ import "../../testing/extend-expect.js";
16
+
17
+ // Strip ANSI escape codes from a string for consistent testing across environments
18
+ function stripAnsi(str: string): string {
19
+ // eslint-disable-next-line no-control-regex
20
+ return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
21
+ }
22
+
23
+ // Helper to check if any console.error call contains a string (after stripping ANSI codes)
24
+ function expectErrorContaining(
25
+ spy: ReturnType<typeof vi.spyOn>,
26
+ substring: string,
27
+ ) {
28
+ const calls = spy.mock.calls.map((call) => stripAnsi(String(call[0])));
29
+ expect(calls.some((msg) => msg.includes(substring))).toBe(true);
30
+ }
31
+
32
+ describe("printRenderStack", () => {
33
+ let socket: WebSocket | undefined;
34
+
35
+ beforeEach(async () => {
36
+ const server = await enableDevtools({ port: 0 });
37
+ socket = new WebSocket(`ws://127.0.0.1:${server.port}`);
38
+ await new Promise<void>((resolve, reject) => {
39
+ socket?.once("open", resolve);
40
+ socket?.once("error", reject);
41
+ });
42
+ });
43
+
44
+ afterEach(async () => {
45
+ if (socket) {
46
+ socket.close();
47
+ socket = undefined;
48
+ }
49
+ await resetDevtoolsServerForTests();
50
+
51
+ // Clear render stack to prevent state leakage between tests
52
+ clearRenderStack();
53
+ });
54
+
55
+ it("prints the current file when an error occurs", async () => {
56
+ const consoleErrorSpy = vi.spyOn(console, "error");
57
+
58
+ function ThrowingComponent() {
59
+ throw new Error("Test error");
60
+ }
61
+
62
+ function ParentComponent() {
63
+ return <ThrowingComponent />;
64
+ }
65
+
66
+ await expect(
67
+ renderAsync(
68
+ <Output>
69
+ <SourceFile path="test.ts" filetype="typescript">
70
+ <ParentComponent />
71
+ </SourceFile>
72
+ </Output>,
73
+ ),
74
+ ).rejects.toThrow("Test error");
75
+
76
+ // Check that console.error was called with file path
77
+ expectErrorContaining(consoleErrorSpy, "Error rendering in file test.ts");
78
+ expectErrorContaining(consoleErrorSpy, "ParentComponent");
79
+ expectErrorContaining(consoleErrorSpy, "ThrowingComponent");
80
+
81
+ consoleErrorSpy.mockRestore();
82
+ });
83
+
84
+ it("prints joined path from nested directories", async () => {
85
+ const consoleErrorSpy = vi.spyOn(console, "error");
86
+
87
+ function ThrowingComponent() {
88
+ throw new Error("Nested error");
89
+ }
90
+
91
+ await expect(
92
+ renderAsync(
93
+ <Output>
94
+ <SourceDirectory path="dir1">
95
+ <SourceDirectory path="dir2">
96
+ <SourceFile path="test.ts" filetype="typescript">
97
+ <ThrowingComponent />
98
+ </SourceFile>
99
+ </SourceDirectory>
100
+ </SourceDirectory>
101
+ </Output>,
102
+ ),
103
+ ).rejects.toThrow("Nested error");
104
+
105
+ // Should show the joined path of all directories
106
+ expectErrorContaining(
107
+ consoleErrorSpy,
108
+ "Error rendering in file dir1/dir2/test.ts",
109
+ );
110
+
111
+ consoleErrorSpy.mockRestore();
112
+ });
113
+
114
+ it("works when no file context is present", async () => {
115
+ const consoleErrorSpy = vi.spyOn(console, "error");
116
+
117
+ function ThrowingComponent() {
118
+ throw new Error("No file context error");
119
+ }
120
+
121
+ // Track the number of calls before our test
122
+ const callsBefore = consoleErrorSpy.mock.calls.length;
123
+
124
+ await expect(
125
+ renderAsync(
126
+ <Output>
127
+ <ThrowingComponent />
128
+ </Output>,
129
+ ),
130
+ ).rejects.toThrow("No file context error");
131
+
132
+ // Get only the calls from THIS test (after callsBefore)
133
+ const callsFromThisTest = consoleErrorSpy.mock.calls.slice(callsBefore);
134
+ const messagesFromThisTest = callsFromThisTest.map((call: any) =>
135
+ stripAnsi(String(call[0])),
136
+ );
137
+
138
+ // Output component creates a SourceDirectory with path "./"
139
+ // The error message should be "Error rendering in file ./"
140
+ expect(
141
+ messagesFromThisTest.some(
142
+ (msg: string) => msg && msg.includes("Error rendering in file ./"),
143
+ ),
144
+ ).toBe(true);
145
+
146
+ consoleErrorSpy.mockRestore();
147
+ });
148
+
149
+ it("includes component stack with props", async () => {
150
+ const consoleErrorSpy = vi.spyOn(console, "error");
151
+
152
+ function ThrowingComponent(props: { message: string; count: number }) {
153
+ throw new Error("Component error");
154
+ }
155
+
156
+ function WrapperComponent(props: { value: string }) {
157
+ return <ThrowingComponent message={props.value} count={42} />;
158
+ }
159
+
160
+ await expect(
161
+ renderAsync(
162
+ <Output>
163
+ <SourceFile path="props-test.ts" filetype="typescript">
164
+ <WrapperComponent value="test" />
165
+ </SourceFile>
166
+ </Output>,
167
+ ),
168
+ ).rejects.toThrow("Component error");
169
+
170
+ expectErrorContaining(
171
+ consoleErrorSpy,
172
+ "Error rendering in file props-test.ts",
173
+ );
174
+ expectErrorContaining(consoleErrorSpy, "WrapperComponent");
175
+ expectErrorContaining(consoleErrorSpy, 'value: "test"');
176
+ expectErrorContaining(consoleErrorSpy, "ThrowingComponent");
177
+ expectErrorContaining(consoleErrorSpy, 'message: "test", count: 42');
178
+
179
+ consoleErrorSpy.mockRestore();
180
+ });
181
+
182
+ it("prints 'Error rendering:' when no file or directory context is present", async () => {
183
+ const consoleErrorSpy = vi.spyOn(console, "error");
184
+
185
+ function ThrowingComponent() {
186
+ throw new Error("No context error");
187
+ }
188
+
189
+ // Track the number of calls before our test
190
+ const callsBefore = consoleErrorSpy.mock.calls.length;
191
+
192
+ // Don't use Output wrapper to avoid SourceDirectory context
193
+ await expect(renderAsync(<ThrowingComponent />)).rejects.toThrow();
194
+
195
+ // Get only the calls from THIS test (after callsBefore)
196
+ const callsFromThisTest = consoleErrorSpy.mock.calls.slice(callsBefore);
197
+ const messagesFromThisTest = callsFromThisTest.map((call: any) =>
198
+ stripAnsi(String(call[0])),
199
+ );
200
+
201
+ // Should have "Error rendering:" without file path
202
+ expect(
203
+ messagesFromThisTest.some(
204
+ (msg: string) => msg && msg.includes("Error rendering:"),
205
+ ),
206
+ ).toBe(true);
207
+ // Should NOT have any message with "in file"
208
+ expect(
209
+ messagesFromThisTest.some(
210
+ (msg: string) => msg && msg.includes("in file"),
211
+ ),
212
+ ).toBe(false);
213
+
214
+ consoleErrorSpy.mockRestore();
215
+ });
216
+
217
+ it("shows context name for named context providers", async () => {
218
+ const consoleErrorSpy = vi.spyOn(console, "error");
219
+
220
+ const MyContext = createNamedContext<string>("MyContext");
221
+
222
+ function ThrowingComponent() {
223
+ throw new Error("Context error");
224
+ }
225
+
226
+ await expect(
227
+ renderAsync(
228
+ <Output>
229
+ <SourceFile path="context-test.ts" filetype="typescript">
230
+ <MyContext.Provider value="test-value">
231
+ <ThrowingComponent />
232
+ </MyContext.Provider>
233
+ </SourceFile>
234
+ </Output>,
235
+ ),
236
+ ).rejects.toThrow("Context error");
237
+
238
+ // Check that the named context provider is shown as a separate component
239
+ expectErrorContaining(consoleErrorSpy, "at MyContext");
240
+ expectErrorContaining(consoleErrorSpy, 'value: "test-value"');
241
+
242
+ consoleErrorSpy.mockRestore();
243
+ });
244
+ });