@luna_ui/luna 0.3.3 → 0.3.5

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 (174) hide show
  1. package/dist/cli.mjs +1264 -27
  2. package/dist/css/index.d.ts +194 -0
  3. package/dist/css/index.js +721 -0
  4. package/dist/css/runtime.d.ts +92 -0
  5. package/dist/css/runtime.js +179 -0
  6. package/dist/index.js +1 -1
  7. package/dist/jsx-dev-runtime.js +1 -1
  8. package/dist/jsx-runtime.d.ts +5 -0
  9. package/dist/jsx-runtime.js +1 -1
  10. package/dist/src-CHiGeWfy.js +1 -0
  11. package/dist/vite-plugin.d.ts +122 -0
  12. package/dist/vite-plugin.js +1518 -0
  13. package/package.json +16 -2
  14. package/src/css/extract.ts +798 -0
  15. package/src/css/index.ts +10 -0
  16. package/src/css/inject.ts +205 -0
  17. package/src/css/inline.ts +182 -0
  18. package/src/css/minify.ts +70 -0
  19. package/src/css/optimizer.ts +6 -0
  20. package/src/css/runtime.ts +344 -0
  21. package/src/css-optimizer/README.md +353 -0
  22. package/src/css-optimizer/cooccurrence.ts +100 -0
  23. package/src/css-optimizer/core.ts +263 -0
  24. package/src/css-optimizer/extractors.ts +243 -0
  25. package/src/css-optimizer/hash.ts +54 -0
  26. package/src/css-optimizer/index.ts +129 -0
  27. package/src/css-optimizer/merge.ts +109 -0
  28. package/src/css-optimizer/moonbit-analyzer.ts +210 -0
  29. package/src/css-optimizer/parser.ts +120 -0
  30. package/src/css-optimizer/pattern.ts +171 -0
  31. package/src/css-optimizer/transformers.ts +301 -0
  32. package/src/css-optimizer/types.ts +128 -0
  33. package/src/event-utils.ts +227 -0
  34. package/src/index.ts +890 -0
  35. package/src/jsx-dev-runtime.ts +2 -0
  36. package/src/jsx-runtime.ts +398 -0
  37. package/src/vite-plugin.ts +718 -0
  38. package/tests/__screenshots__/context.test.ts/Context-API-context-with-reactive-effects-context-value-accessible-in-effect-1.png +0 -0
  39. package/tests/__screenshots__/dom.test.ts/DOM-API-For-component--SolidJS-style--For-updates-when-signal-changes-1.png +0 -0
  40. package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-accepts-children-as-function-1.png +0 -0
  41. package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-toggles-visibility-1.png +0 -0
  42. package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-attribute-1.png +0 -0
  43. package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-style-1.png +0 -0
  44. package/tests/__screenshots__/dom.test.ts/DOM-API-createElementNs--SVG-support--createElementNs-with-dynamic-attribute-1.png +0 -0
  45. package/tests/__screenshots__/dom.test.ts/DOM-API-effect-with-DOM-effect-tracks-signal-changes-1.png +0 -0
  46. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-clear-to-empty-1.png +0 -0
  47. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-empty-array-1.png +0 -0
  48. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-removes-items-1.png +0 -0
  49. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-renders-initial-list-1.png +0 -0
  50. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-updates-when-items-change-1.png +0 -0
  51. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-handles-empty-to-non-empty-transition-in-SVG-1.png +0 -0
  52. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-handles-reordering-in-SVG-1.png +0 -0
  53. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-updates-SVG-elements-when-signal-changes-1.png +0 -0
  54. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-with-nested-SVG-groups-1.png +0 -0
  55. package/tests/__screenshots__/dom.test.ts/DOM-API-ref-callback--JSX-style--ref-callback-with-nested-elements-1.png +0 -0
  56. package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-creates-a-node-1.png +0 -0
  57. package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-with-false-condition-creates-placeholder-1.png +0 -0
  58. package/tests/__screenshots__/dom.test.ts/DOM-API-text-nodes-textDyn-creates-reactive-text-node-1.png +0 -0
  59. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-forEach-renders-correctly-without-show--initial-items--1.png +0 -0
  60. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-forEach-with-context-renders-correctly-without-show-1.png +0 -0
  61. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-nested-components-with-context--forEach--and-show-1.png +0 -0
  62. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-show-and-forEach-inherit-context-from-Owner--fixed--1.png +0 -0
  63. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Complex-nested-scenario-show-and-forEach-work-together--context-uses-default--1.png +0 -0
  64. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Context---ForEach-integration-forEach-items-can-access-context-1.png +0 -0
  65. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-renders-initial-list-1.png +0 -0
  66. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-updates-when-signal-changes-1.png +0 -0
  67. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-with-object-items-1.png +0 -0
  68. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-hides-when-condition-is-false-1.png +0 -0
  69. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-renders-when-condition-is-true-1.png +0 -0
  70. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-from-false-to-true-1.png +0 -0
  71. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-reactively-1.png +0 -0
  72. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--event-listener-pattern--Solid-js-docs-example--1.png +0 -0
  73. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--multiple-cleanups-in-component-body--LIFO-order--1.png +0 -0
  74. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--onCleanup-in-component-body-runs-on-unmount-1.png +0 -0
  75. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--onCleanup-works-with-For-loop-items--component-body-style--1.png +0 -0
  76. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--timer-cleanup-pattern--Solid-js-style--1.png +0 -0
  77. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Effects-effect-cleanup-runs-before-re-run-1.png +0 -0
  78. package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-large-list-update-1.png +0 -0
  79. package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-nested-batch-operations-1.png +0 -0
  80. package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-rapid-sequential-updates-1.png +0 -0
  81. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-Show-component---visible-1.png +0 -0
  82. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-show-hide-element---visible-1.png +0 -0
  83. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-fragment-with-list-1.png +0 -0
  84. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-nested-fragments-1.png +0 -0
  85. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-simple-fragment-1.png +0 -0
  86. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-conditional-toggle-updates-1.png +0 -0
  87. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-addition-updates-match-1.png +0 -0
  88. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-removal-updates-match-1.png +0 -0
  89. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-text-updates-match-1.png +0 -0
  90. package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-className-updates-1.png +0 -0
  91. package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-style-updates-1.png +0 -0
  92. package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-multiple-dynamic-attributes-1.png +0 -0
  93. package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-deeply-nested-conditionals-1.png +0 -0
  94. package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-empty-to-populated-1.png +0 -0
  95. package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-populated-to-empty-1.png +0 -0
  96. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-cleanup-order-1.png +0 -0
  97. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-with-inner-signal-change-1.png +0 -0
  98. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-is-called-when-effect-re-runs-1.png +0 -0
  99. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-with-resource-simulation-1.png +0 -0
  100. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-multiple-children--no-wrapper--1.png +0 -0
  101. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-no-children-1.png +0 -0
  102. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-fragment-with-list-1.png +0 -0
  103. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-nested-Fragments-work-correctly-1.png +0 -0
  104. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-complex-reordering-with-additions-and-removals-1.png +0 -0
  105. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-insert-in-middle-1.png +0 -0
  106. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-remove-from-middle-1.png +0 -0
  107. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-reverse-list-order-1.png +0 -0
  108. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-shuffle-list-1.png +0 -0
  109. package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-Show-component-renders-when-condition-is-true-1.png +0 -0
  110. package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-renders-content-when-initially-true-1.png +0 -0
  111. package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-toggles-visibility-dynamically-1.png +0 -0
  112. package/tests/__screenshots__/preact-signals-comparison.test.ts/Memo-Dependency-Chain-conditional-memo-dependencies-1.png +0 -0
  113. package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-basic-signal-get-set-produces-same-values-1.png +0 -0
  114. package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-batch-updates-produce-same-final-values-1.png +0 -0
  115. package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-peek-reads-value-without-tracking-1.png +0 -0
  116. package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-selective-tracking-with-untrack-1.png +0 -0
  117. package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-untrack-prevents-dependency-tracking-1.png +0 -0
  118. package/tests/__screenshots__/resource.test.ts/Resource-API--SolidJS-style--reactivity-accessor-is-reactive-1.png +0 -0
  119. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-empty-string-for-non-failure-1.png +0 -0
  120. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-undefined-for-non-failure-1.png +0 -0
  121. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsFailure-and-stateError-1.png +0 -0
  122. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsPending-1.png +0 -0
  123. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsSuccess-and-stateValue-1.png +0 -0
  124. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateValue-returns-undefined-for-non-success-1.png +0 -0
  125. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-reject-transitions-to-failure-1.png +0 -0
  126. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-resolve-transitions-to-success-1.png +0 -0
  127. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-returns-resource--resolve--and-reject-functions-1.png +0 -0
  128. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-starts-in-pending-state-1.png +0 -0
  129. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-async-resolve-works-1.png +0 -0
  130. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-starts-in-pending-state-1.png +0 -0
  131. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-failure-on-reject-1.png +0 -0
  132. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-success-on-resolve-1.png +0 -0
  133. package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-can-wrap-fetch-like-async-operations-1.png +0 -0
  134. package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-works-with-setTimeout-simulation-1.png +0 -0
  135. package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourceGet-tracks-dependencies-1.png +0 -0
  136. package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourcePeek-does-not-track-dependencies-1.png +0 -0
  137. package/tests/__screenshots__/resource.test.ts/Resource-API-resourceRefetch-refetch-resets-to-pending-and-re-runs-fetcher-1.png +0 -0
  138. package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-children-to-body-by-default-1.png +0 -0
  139. package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-to-selector-mount-target-1.png +0 -0
  140. package/tests/__screenshots__/solidjs-api.test.ts/SolidJS-API-compatibility-createEffect-tracks-dependencies-automatically-1.png +0 -0
  141. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-accessor-condition-in-Match-1.png +0 -0
  142. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-multiple-Match-components-1.png +0 -0
  143. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-single-Match-and-fallback-1.png +0 -0
  144. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-components-Switch-updates-DOM-when-signal-changes-1.png +0 -0
  145. package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-multiple-dependencies-1.png +0 -0
  146. package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-single-dependency-1.png +0 -0
  147. package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-with-defer-option-skips-initial-run-1.png +0 -0
  148. package/tests/__screenshots__/store.test.ts/createStore-Arrays-array-updates-work-1.png +0 -0
  149. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-only-triggers-when-accessed-property-changes-1.png +0 -0
  150. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-parent-path-change-notifies-child-accessors-1.png +0 -0
  151. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-nested-property-access-1.png +0 -0
  152. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-property-access-in-effects-1.png +0 -0
  153. package/tests/context.test.ts +118 -0
  154. package/tests/css-optimizer-extractors.test.ts +264 -0
  155. package/tests/css-optimizer-integration.test.ts +566 -0
  156. package/tests/css-optimizer-transformers.test.ts +301 -0
  157. package/tests/css-optimizer.test.ts +646 -0
  158. package/tests/css-runtime.bench.ts +442 -0
  159. package/tests/css-runtime.test.ts +342 -0
  160. package/tests/dom.test.ts +872 -0
  161. package/tests/integration.test.ts +405 -0
  162. package/tests/issue-5-for-infinite-loop.test.ts +516 -0
  163. package/tests/jsx-runtime.test.tsx +393 -0
  164. package/tests/lifecycle.test.ts +833 -0
  165. package/tests/move-before.bench.ts +304 -0
  166. package/tests/preact-signals-comparison.test.ts +1608 -0
  167. package/tests/resource.test.ts +160 -0
  168. package/tests/router.test.ts +117 -0
  169. package/tests/show-initial-mount-leak.test.tsx +182 -0
  170. package/tests/solidjs-api.test.ts +659 -0
  171. package/tests/static-perf.bench.ts +64 -0
  172. package/tests/store.test.ts +263 -0
  173. package/tests/tsx-syntax.test.tsx +404 -0
  174. package/dist/src-DGWY0NYx.js +0 -1
@@ -0,0 +1,405 @@
1
+ /**
2
+ * Integration tests for nested components, ForEach, and provide
3
+ * These tests document current Luna API behavior and highlight differences from SolidJS
4
+ */
5
+ import { describe, test, expect, beforeEach } from "vitest";
6
+ import {
7
+ // Signal API (SolidJS-style)
8
+ createSignal,
9
+ createEffect,
10
+ createRoot,
11
+ // Context API
12
+ createContext,
13
+ provide,
14
+ useContext,
15
+ // DOM API
16
+ createElement,
17
+ text,
18
+ render,
19
+ mount,
20
+ forEach,
21
+ show,
22
+ } from "../src/index";
23
+
24
+ // MoonBit AttrValue constructors
25
+ const AttrValue = {
26
+ Static: (value: string) => ({ $tag: 0, _0: value }),
27
+ Dynamic: (getter: () => string) => ({ $tag: 1, _0: getter }),
28
+ };
29
+
30
+ function attr(name: string, value: unknown) {
31
+ return { _0: name, _1: value };
32
+ }
33
+
34
+ describe("Integration: Nested Components with Context", () => {
35
+ let container: HTMLElement;
36
+
37
+ beforeEach(() => {
38
+ container = document.createElement("div");
39
+ document.body.appendChild(container);
40
+ });
41
+
42
+ describe("Context across component boundaries", () => {
43
+ test("context flows through nested function components", () => {
44
+ const themeCtx = createContext("light");
45
+ const values: string[] = [];
46
+
47
+ // Simulate nested component structure
48
+ const GrandChild = () => {
49
+ values.push(`grandchild: ${useContext(themeCtx)}`);
50
+ return text(useContext(themeCtx));
51
+ };
52
+
53
+ const Child = () => {
54
+ values.push(`child: ${useContext(themeCtx)}`);
55
+ return createElement("span", [], [GrandChild()]);
56
+ };
57
+
58
+ const Parent = () => {
59
+ values.push(`parent: ${useContext(themeCtx)}`);
60
+ return createElement("div", [], [Child()]);
61
+ };
62
+
63
+ // Provide dark theme at root
64
+ provide(themeCtx, "dark", () => {
65
+ render(container, Parent());
66
+ });
67
+
68
+ expect(values).toEqual([
69
+ "parent: dark",
70
+ "child: dark",
71
+ "grandchild: dark",
72
+ ]);
73
+ expect(container.textContent).toBe("dark");
74
+ });
75
+
76
+ test("nested provide overrides parent context", () => {
77
+ const themeCtx = createContext("light");
78
+ const values: string[] = [];
79
+
80
+ const Inner = () => {
81
+ values.push(useContext(themeCtx));
82
+ return text(useContext(themeCtx));
83
+ };
84
+
85
+ const Middle = () => {
86
+ // Override context for children
87
+ return provide(themeCtx, "blue", () => {
88
+ values.push(`middle sees: ${useContext(themeCtx)}`);
89
+ return createElement("div", [], [Inner()]);
90
+ });
91
+ };
92
+
93
+ const Outer = () => {
94
+ values.push(`outer sees: ${useContext(themeCtx)}`);
95
+ return createElement("div", [], [Middle()]);
96
+ };
97
+
98
+ provide(themeCtx, "dark", () => {
99
+ render(container, Outer());
100
+ });
101
+
102
+ expect(values).toEqual([
103
+ "outer sees: dark",
104
+ "middle sees: blue",
105
+ "blue",
106
+ ]);
107
+ });
108
+
109
+ test("multiple contexts work independently", () => {
110
+ const themeCtx = createContext("light");
111
+ const langCtx = createContext("en");
112
+ const sizeCtx = createContext("medium");
113
+
114
+ let capturedTheme: string;
115
+ let capturedLang: string;
116
+ let capturedSize: string;
117
+
118
+ const DeepComponent = () => {
119
+ capturedTheme = useContext(themeCtx);
120
+ capturedLang = useContext(langCtx);
121
+ capturedSize = useContext(sizeCtx);
122
+ return text("deep");
123
+ };
124
+
125
+ provide(themeCtx, "dark", () => {
126
+ provide(langCtx, "ja", () => {
127
+ // sizeCtx not provided, should use default
128
+ render(container, DeepComponent());
129
+ });
130
+ });
131
+
132
+ expect(capturedTheme!).toBe("dark");
133
+ expect(capturedLang!).toBe("ja");
134
+ expect(capturedSize!).toBe("medium"); // default
135
+ });
136
+ });
137
+
138
+ describe("ForEach with reactive updates", () => {
139
+ test("forEach renders initial list", () => {
140
+ const [items] = createSignal(["a", "b", "c"]);
141
+
142
+ const List = () =>
143
+ createElement(
144
+ "ul",
145
+ [],
146
+ [
147
+ forEach(items, (item, index) =>
148
+ createElement(
149
+ "li",
150
+ [attr("data-index", AttrValue.Static(String(index)))],
151
+ [text(item)]
152
+ )
153
+ ),
154
+ ]
155
+ );
156
+
157
+ render(container, List());
158
+
159
+ const lis = container.querySelectorAll("li");
160
+ expect(lis.length).toBe(3);
161
+ expect(lis[0].textContent).toBe("a");
162
+ expect(lis[1].textContent).toBe("b");
163
+ expect(lis[2].textContent).toBe("c");
164
+ });
165
+
166
+ test("forEach updates when signal changes", () => {
167
+ const [items, setItems] = createSignal(["x", "y"]);
168
+
169
+ createRoot((dispose) => {
170
+ const List = () =>
171
+ createElement("ul", [], [
172
+ forEach(items, (item) => createElement("li", [], [text(item)])),
173
+ ]);
174
+
175
+ render(container, List());
176
+
177
+ expect(container.querySelectorAll("li").length).toBe(2);
178
+
179
+ // Update the list
180
+ setItems(["x", "y", "z"]);
181
+
182
+ expect(container.querySelectorAll("li").length).toBe(3);
183
+ expect(container.querySelectorAll("li")[2].textContent).toBe("z");
184
+
185
+ dispose();
186
+ });
187
+ });
188
+
189
+ test("forEach with object items", () => {
190
+ interface User {
191
+ id: number;
192
+ name: string;
193
+ }
194
+
195
+ const [users] = createSignal<User[]>([
196
+ { id: 1, name: "Alice" },
197
+ { id: 2, name: "Bob" },
198
+ ]);
199
+
200
+ createRoot((dispose) => {
201
+ const UserList = () =>
202
+ createElement("div", [], [
203
+ forEach(users, (user) =>
204
+ createElement(
205
+ "div",
206
+ [attr("data-id", AttrValue.Static(String(user.id)))],
207
+ [text(user.name)]
208
+ )
209
+ ),
210
+ ]);
211
+
212
+ render(container, UserList());
213
+
214
+ const divs = container.querySelectorAll("[data-id]");
215
+ expect(divs.length).toBe(2);
216
+ expect(divs[0].textContent).toBe("Alice");
217
+ expect(divs[1].textContent).toBe("Bob");
218
+
219
+ dispose();
220
+ });
221
+ });
222
+ });
223
+
224
+ describe("Context + ForEach integration", () => {
225
+ test("forEach items can access context", () => {
226
+ const prefixCtx = createContext("");
227
+ const [items] = createSignal(["one", "two", "three"]);
228
+ const renderedTexts: string[] = [];
229
+
230
+ const ItemComponent = (item: string) => {
231
+ const prefix = useContext(prefixCtx);
232
+ const fullText = `${prefix}${item}`;
233
+ renderedTexts.push(fullText);
234
+ return createElement("span", [], [text(fullText)]);
235
+ };
236
+
237
+ provide(prefixCtx, "item-", () => {
238
+ const List = () =>
239
+ createElement("div", [], [
240
+ forEach(items, (item) => ItemComponent(item)),
241
+ ]);
242
+
243
+ render(container, List());
244
+ });
245
+
246
+ expect(renderedTexts).toEqual(["item-one", "item-two", "item-three"]);
247
+ });
248
+ });
249
+
250
+ describe("Show (conditional rendering)", () => {
251
+ // NOTE: show() has a known limitation - it doesn't render initially when true
252
+ // because the effect runs before the placeholder is in the DOM.
253
+ // This is a design difference from SolidJS's <Show> component.
254
+
255
+ test("show hides when condition is false", () => {
256
+ const [visible] = createSignal(false);
257
+
258
+ const node = show(visible, () =>
259
+ createElement("span", [], [text("visible")])
260
+ );
261
+
262
+ mount(container, node);
263
+
264
+ // When false, show renders a placeholder (comment node)
265
+ expect(container.querySelector("span")).toBeNull();
266
+ });
267
+
268
+ test("show toggles from false to true", () => {
269
+ // Start with false, then toggle to true
270
+ const [visible, setVisible] = createSignal(false);
271
+
272
+ const node = show(visible, () =>
273
+ createElement("span", [], [text("content")])
274
+ );
275
+
276
+ mount(container, node);
277
+
278
+ expect(container.querySelector("span")).toBeNull();
279
+
280
+ // Toggle to true - this should work because placeholder is now in DOM
281
+ setVisible(true);
282
+ expect(container.querySelector("span")).not.toBeNull();
283
+
284
+ // Toggle back to false
285
+ setVisible(false);
286
+ expect(container.querySelector("span")).toBeNull();
287
+ });
288
+
289
+ test("show renders when initial condition is true", () => {
290
+ const [visible] = createSignal(true);
291
+
292
+ const node = show(visible, () =>
293
+ createElement("span", [], [text("visible")])
294
+ );
295
+
296
+ mount(container, node);
297
+
298
+ expect(container.querySelector("span")).not.toBeNull();
299
+ });
300
+ });
301
+
302
+ describe("Complex nested scenario", () => {
303
+ /**
304
+ * Luna's provide() is now Owner-based (component-tree-scoped).
305
+ *
306
+ * Context values are associated with Owners (reactive scopes).
307
+ * When show() or forEach() render later, they inherit context from
308
+ * the Owner that was active when they were created.
309
+ *
310
+ * This matches SolidJS's <Provider> behavior.
311
+ */
312
+
313
+ test("show and forEach inherit context from Owner (fixed)", () => {
314
+ const themeCtx = createContext("light"); // default is "light"
315
+ const [showList, setShowList] = createSignal(false);
316
+ const [items, setItems] = createSignal(["A", "B"]);
317
+
318
+ const ListItem = (item: string) => {
319
+ const theme = useContext(themeCtx);
320
+ return createElement(
321
+ "li",
322
+ [attr("class", AttrValue.Static(`item-${theme}`))],
323
+ [text(item)]
324
+ );
325
+ };
326
+
327
+ // Context is Owner-based: show/forEach must be created inside provide scope
328
+ // to capture the Owner with the context value
329
+ provide(themeCtx, "dark", () => {
330
+ const listNode = show(showList, () =>
331
+ createElement("ul", [], [forEach(items, (item) => ListItem(item))])
332
+ );
333
+ mount(container, listNode);
334
+ });
335
+
336
+ // Initially hidden
337
+ expect(container.querySelector("ul")).toBeNull();
338
+
339
+ // Toggle to visible - context should be "dark" (inherited from Owner)
340
+ setShowList(true);
341
+ let lis = container.querySelectorAll("li");
342
+ expect(lis.length).toBe(2);
343
+ // Context is "dark" because Owner-based context persists
344
+ expect(lis[0].className).toBe("item-dark");
345
+
346
+ // Toggle visibility
347
+ setShowList(false);
348
+ expect(container.querySelector("ul")).toBeNull();
349
+
350
+ setShowList(true);
351
+ lis = container.querySelectorAll("li");
352
+ expect(lis.length).toBe(2);
353
+ // Still "dark" after re-render
354
+ expect(lis[0].className).toBe("item-dark");
355
+
356
+ // Update items - new items should also get "dark" context
357
+ setItems(["A", "B", "C"]);
358
+ lis = container.querySelectorAll("li");
359
+ expect(lis.length).toBe(3);
360
+ expect(lis[2].className).toBe("item-dark");
361
+ });
362
+
363
+ test("context works with immediate rendering (non-deferred)", () => {
364
+ const themeCtx = createContext("light");
365
+
366
+ // Context works when rendering is synchronous
367
+ provide(themeCtx, "dark", () => {
368
+ const theme = useContext(themeCtx);
369
+ const node = createElement(
370
+ "div",
371
+ [attr("class", AttrValue.Static(`theme-${theme}`))],
372
+ [text(theme)]
373
+ );
374
+ mount(container, node);
375
+ });
376
+
377
+ const div = container.querySelector("div");
378
+ expect(div?.className).toBe("theme-dark");
379
+ expect(div?.textContent).toBe("dark");
380
+ });
381
+
382
+ test("forEach renders correctly without show (initial items)", () => {
383
+ // This test shows that forEach initial render works,
384
+ // but context is still function-scoped
385
+ const [items, setItems] = createSignal(["A", "B"]);
386
+
387
+ const listNode = createElement("ul", [], [
388
+ forEach(items, (item) => createElement("li", [], [text(item)])),
389
+ ]);
390
+
391
+ mount(container, listNode);
392
+
393
+ let lis = container.querySelectorAll("li");
394
+ expect(lis.length).toBe(2);
395
+ expect(lis[0].textContent).toBe("A");
396
+ expect(lis[1].textContent).toBe("B");
397
+
398
+ // Update items
399
+ setItems(["X", "Y", "Z"]);
400
+ lis = container.querySelectorAll("li");
401
+ expect(lis.length).toBe(3);
402
+ expect(lis[2].textContent).toBe("Z");
403
+ });
404
+ });
405
+ });