@luna_ui/luna 0.3.4 → 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 (172) 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.js +1 -1
  9. package/dist/{src-BDdxGwvq.js → src-CHiGeWfy.js} +1 -1
  10. package/dist/vite-plugin.d.ts +122 -0
  11. package/dist/vite-plugin.js +1518 -0
  12. package/package.json +16 -2
  13. package/src/css/extract.ts +798 -0
  14. package/src/css/index.ts +10 -0
  15. package/src/css/inject.ts +205 -0
  16. package/src/css/inline.ts +182 -0
  17. package/src/css/minify.ts +70 -0
  18. package/src/css/optimizer.ts +6 -0
  19. package/src/css/runtime.ts +344 -0
  20. package/src/css-optimizer/README.md +353 -0
  21. package/src/css-optimizer/cooccurrence.ts +100 -0
  22. package/src/css-optimizer/core.ts +263 -0
  23. package/src/css-optimizer/extractors.ts +243 -0
  24. package/src/css-optimizer/hash.ts +54 -0
  25. package/src/css-optimizer/index.ts +129 -0
  26. package/src/css-optimizer/merge.ts +109 -0
  27. package/src/css-optimizer/moonbit-analyzer.ts +210 -0
  28. package/src/css-optimizer/parser.ts +120 -0
  29. package/src/css-optimizer/pattern.ts +171 -0
  30. package/src/css-optimizer/transformers.ts +301 -0
  31. package/src/css-optimizer/types.ts +128 -0
  32. package/src/event-utils.ts +227 -0
  33. package/src/index.ts +890 -0
  34. package/src/jsx-dev-runtime.ts +2 -0
  35. package/src/jsx-runtime.ts +398 -0
  36. package/src/vite-plugin.ts +718 -0
  37. package/tests/__screenshots__/context.test.ts/Context-API-context-with-reactive-effects-context-value-accessible-in-effect-1.png +0 -0
  38. package/tests/__screenshots__/dom.test.ts/DOM-API-For-component--SolidJS-style--For-updates-when-signal-changes-1.png +0 -0
  39. package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-accepts-children-as-function-1.png +0 -0
  40. package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-toggles-visibility-1.png +0 -0
  41. package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-attribute-1.png +0 -0
  42. package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-style-1.png +0 -0
  43. package/tests/__screenshots__/dom.test.ts/DOM-API-createElementNs--SVG-support--createElementNs-with-dynamic-attribute-1.png +0 -0
  44. package/tests/__screenshots__/dom.test.ts/DOM-API-effect-with-DOM-effect-tracks-signal-changes-1.png +0 -0
  45. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-clear-to-empty-1.png +0 -0
  46. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-empty-array-1.png +0 -0
  47. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-removes-items-1.png +0 -0
  48. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-renders-initial-list-1.png +0 -0
  49. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-updates-when-items-change-1.png +0 -0
  50. 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
  51. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-handles-reordering-in-SVG-1.png +0 -0
  52. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-updates-SVG-elements-when-signal-changes-1.png +0 -0
  53. package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-with-nested-SVG-groups-1.png +0 -0
  54. package/tests/__screenshots__/dom.test.ts/DOM-API-ref-callback--JSX-style--ref-callback-with-nested-elements-1.png +0 -0
  55. package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-creates-a-node-1.png +0 -0
  56. package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-with-false-condition-creates-placeholder-1.png +0 -0
  57. package/tests/__screenshots__/dom.test.ts/DOM-API-text-nodes-textDyn-creates-reactive-text-node-1.png +0 -0
  58. 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
  59. 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
  60. 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
  61. 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
  62. 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
  63. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Context---ForEach-integration-forEach-items-can-access-context-1.png +0 -0
  64. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-renders-initial-list-1.png +0 -0
  65. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-updates-when-signal-changes-1.png +0 -0
  66. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-with-object-items-1.png +0 -0
  67. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-hides-when-condition-is-false-1.png +0 -0
  68. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-renders-when-condition-is-true-1.png +0 -0
  69. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-from-false-to-true-1.png +0 -0
  70. package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-reactively-1.png +0 -0
  71. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--event-listener-pattern--Solid-js-docs-example--1.png +0 -0
  72. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--multiple-cleanups-in-component-body--LIFO-order--1.png +0 -0
  73. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--onCleanup-in-component-body-runs-on-unmount-1.png +0 -0
  74. 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
  75. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--timer-cleanup-pattern--Solid-js-style--1.png +0 -0
  76. package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Effects-effect-cleanup-runs-before-re-run-1.png +0 -0
  77. package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-large-list-update-1.png +0 -0
  78. package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-nested-batch-operations-1.png +0 -0
  79. package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-rapid-sequential-updates-1.png +0 -0
  80. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-Show-component---visible-1.png +0 -0
  81. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-show-hide-element---visible-1.png +0 -0
  82. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-fragment-with-list-1.png +0 -0
  83. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-nested-fragments-1.png +0 -0
  84. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-simple-fragment-1.png +0 -0
  85. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-conditional-toggle-updates-1.png +0 -0
  86. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-addition-updates-match-1.png +0 -0
  87. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-removal-updates-match-1.png +0 -0
  88. package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-text-updates-match-1.png +0 -0
  89. package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-className-updates-1.png +0 -0
  90. package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-style-updates-1.png +0 -0
  91. package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-multiple-dynamic-attributes-1.png +0 -0
  92. package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-deeply-nested-conditionals-1.png +0 -0
  93. package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-empty-to-populated-1.png +0 -0
  94. package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-populated-to-empty-1.png +0 -0
  95. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-cleanup-order-1.png +0 -0
  96. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-with-inner-signal-change-1.png +0 -0
  97. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-is-called-when-effect-re-runs-1.png +0 -0
  98. package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-with-resource-simulation-1.png +0 -0
  99. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-multiple-children--no-wrapper--1.png +0 -0
  100. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-no-children-1.png +0 -0
  101. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-fragment-with-list-1.png +0 -0
  102. package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-nested-Fragments-work-correctly-1.png +0 -0
  103. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-complex-reordering-with-additions-and-removals-1.png +0 -0
  104. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-insert-in-middle-1.png +0 -0
  105. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-remove-from-middle-1.png +0 -0
  106. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-reverse-list-order-1.png +0 -0
  107. package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-shuffle-list-1.png +0 -0
  108. package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-Show-component-renders-when-condition-is-true-1.png +0 -0
  109. package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-renders-content-when-initially-true-1.png +0 -0
  110. package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-toggles-visibility-dynamically-1.png +0 -0
  111. package/tests/__screenshots__/preact-signals-comparison.test.ts/Memo-Dependency-Chain-conditional-memo-dependencies-1.png +0 -0
  112. package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-basic-signal-get-set-produces-same-values-1.png +0 -0
  113. package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-batch-updates-produce-same-final-values-1.png +0 -0
  114. package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-peek-reads-value-without-tracking-1.png +0 -0
  115. package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-selective-tracking-with-untrack-1.png +0 -0
  116. package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-untrack-prevents-dependency-tracking-1.png +0 -0
  117. package/tests/__screenshots__/resource.test.ts/Resource-API--SolidJS-style--reactivity-accessor-is-reactive-1.png +0 -0
  118. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-empty-string-for-non-failure-1.png +0 -0
  119. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-undefined-for-non-failure-1.png +0 -0
  120. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsFailure-and-stateError-1.png +0 -0
  121. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsPending-1.png +0 -0
  122. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsSuccess-and-stateValue-1.png +0 -0
  123. package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateValue-returns-undefined-for-non-success-1.png +0 -0
  124. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-reject-transitions-to-failure-1.png +0 -0
  125. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-resolve-transitions-to-success-1.png +0 -0
  126. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-returns-resource--resolve--and-reject-functions-1.png +0 -0
  127. package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-starts-in-pending-state-1.png +0 -0
  128. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-async-resolve-works-1.png +0 -0
  129. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-starts-in-pending-state-1.png +0 -0
  130. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-failure-on-reject-1.png +0 -0
  131. package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-success-on-resolve-1.png +0 -0
  132. package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-can-wrap-fetch-like-async-operations-1.png +0 -0
  133. package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-works-with-setTimeout-simulation-1.png +0 -0
  134. package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourceGet-tracks-dependencies-1.png +0 -0
  135. package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourcePeek-does-not-track-dependencies-1.png +0 -0
  136. package/tests/__screenshots__/resource.test.ts/Resource-API-resourceRefetch-refetch-resets-to-pending-and-re-runs-fetcher-1.png +0 -0
  137. package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-children-to-body-by-default-1.png +0 -0
  138. package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-to-selector-mount-target-1.png +0 -0
  139. package/tests/__screenshots__/solidjs-api.test.ts/SolidJS-API-compatibility-createEffect-tracks-dependencies-automatically-1.png +0 -0
  140. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-accessor-condition-in-Match-1.png +0 -0
  141. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-multiple-Match-components-1.png +0 -0
  142. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-single-Match-and-fallback-1.png +0 -0
  143. package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-components-Switch-updates-DOM-when-signal-changes-1.png +0 -0
  144. package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-multiple-dependencies-1.png +0 -0
  145. package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-single-dependency-1.png +0 -0
  146. package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-with-defer-option-skips-initial-run-1.png +0 -0
  147. package/tests/__screenshots__/store.test.ts/createStore-Arrays-array-updates-work-1.png +0 -0
  148. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-only-triggers-when-accessed-property-changes-1.png +0 -0
  149. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-parent-path-change-notifies-child-accessors-1.png +0 -0
  150. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-nested-property-access-1.png +0 -0
  151. package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-property-access-in-effects-1.png +0 -0
  152. package/tests/context.test.ts +118 -0
  153. package/tests/css-optimizer-extractors.test.ts +264 -0
  154. package/tests/css-optimizer-integration.test.ts +566 -0
  155. package/tests/css-optimizer-transformers.test.ts +301 -0
  156. package/tests/css-optimizer.test.ts +646 -0
  157. package/tests/css-runtime.bench.ts +442 -0
  158. package/tests/css-runtime.test.ts +342 -0
  159. package/tests/dom.test.ts +872 -0
  160. package/tests/integration.test.ts +405 -0
  161. package/tests/issue-5-for-infinite-loop.test.ts +516 -0
  162. package/tests/jsx-runtime.test.tsx +393 -0
  163. package/tests/lifecycle.test.ts +833 -0
  164. package/tests/move-before.bench.ts +304 -0
  165. package/tests/preact-signals-comparison.test.ts +1608 -0
  166. package/tests/resource.test.ts +160 -0
  167. package/tests/router.test.ts +117 -0
  168. package/tests/show-initial-mount-leak.test.tsx +182 -0
  169. package/tests/solidjs-api.test.ts +659 -0
  170. package/tests/static-perf.bench.ts +64 -0
  171. package/tests/store.test.ts +263 -0
  172. package/tests/tsx-syntax.test.tsx +404 -0
@@ -0,0 +1,160 @@
1
+ import { describe, test, expect, vi } from "vitest";
2
+ import {
3
+ createResource,
4
+ createDeferred,
5
+ createRenderEffect,
6
+ createRoot,
7
+ } from "../src/index";
8
+
9
+ describe("Resource API (SolidJS-style)", () => {
10
+ describe("createResource", () => {
11
+ test("starts in pending state", () => {
12
+ const [resource] = createResource<string>(() => {
13
+ // Never resolves
14
+ });
15
+
16
+ expect(resource.loading).toBe(true);
17
+ expect(resource.state).toBe("pending");
18
+ });
19
+
20
+ test("transitions to success on resolve", async () => {
21
+ const [resource] = createResource<string>((resolve) => {
22
+ resolve("success-data");
23
+ });
24
+
25
+ // Resolve happens synchronously in this case
26
+ expect(resource.state).toBe("ready");
27
+ expect(resource()).toBe("success-data");
28
+ });
29
+
30
+ test("transitions to failure on reject", () => {
31
+ const [resource] = createResource<string>((_, reject) => {
32
+ reject("error-message");
33
+ });
34
+
35
+ expect(resource.state).toBe("errored");
36
+ expect(resource.error).toBe("error-message");
37
+ });
38
+
39
+ test("async resolve works", async () => {
40
+ let resolveRef: ((value: string) => void) | null = null;
41
+
42
+ const [resource] = createResource<string>((resolve) => {
43
+ resolveRef = resolve;
44
+ });
45
+
46
+ expect(resource.loading).toBe(true);
47
+
48
+ resolveRef!("async-data");
49
+
50
+ expect(resource.state).toBe("ready");
51
+ expect(resource()).toBe("async-data");
52
+ });
53
+
54
+ test("refetch re-runs fetcher", () => {
55
+ let fetchCount = 0;
56
+ const [resource, { refetch }] = createResource<string>((resolve) => {
57
+ fetchCount++;
58
+ resolve(`fetch-${fetchCount}`);
59
+ });
60
+
61
+ expect(fetchCount).toBe(1);
62
+ expect(resource()).toBe("fetch-1");
63
+
64
+ refetch();
65
+
66
+ expect(fetchCount).toBe(2);
67
+ expect(resource()).toBe("fetch-2");
68
+ });
69
+ });
70
+
71
+ describe("createDeferred", () => {
72
+ test("returns accessor, resolve, and reject", () => {
73
+ const [resource, resolve, reject] = createDeferred<number>();
74
+
75
+ expect(typeof resource).toBe("function");
76
+ expect(typeof resolve).toBe("function");
77
+ expect(typeof reject).toBe("function");
78
+ });
79
+
80
+ test("starts in pending state", () => {
81
+ const [resource] = createDeferred<number>();
82
+ expect(resource.loading).toBe(true);
83
+ });
84
+
85
+ test("resolve transitions to success", () => {
86
+ const [resource, resolve] = createDeferred<number>();
87
+
88
+ resolve(42);
89
+
90
+ expect(resource.loading).toBe(false);
91
+ expect(resource()).toBe(42);
92
+ });
93
+
94
+ test("reject transitions to failure", () => {
95
+ const [resource, , reject] = createDeferred<number>();
96
+
97
+ reject("deferred-error");
98
+
99
+ expect(resource.loading).toBe(false);
100
+ expect(resource.error).toBe("deferred-error");
101
+ });
102
+ });
103
+
104
+ describe("reactivity", () => {
105
+ test("accessor is reactive", () => {
106
+ const [resource, resolve] = createDeferred<string>();
107
+ let effectRunCount = 0;
108
+
109
+ createRoot((dispose) => {
110
+ createRenderEffect(() => {
111
+ resource(); // Access value to track
112
+ effectRunCount++;
113
+ });
114
+
115
+ expect(effectRunCount).toBe(1);
116
+
117
+ resolve("data");
118
+ // Effect should re-run because accessor is reactive
119
+ expect(effectRunCount).toBe(2);
120
+ dispose();
121
+ });
122
+ });
123
+ });
124
+
125
+ describe("integration with Promise", () => {
126
+ test("works with setTimeout simulation", async () => {
127
+ vi.useFakeTimers();
128
+
129
+ const [resource] = createResource<string>((resolve) => {
130
+ setTimeout(() => resolve("delayed"), 100);
131
+ });
132
+
133
+ expect(resource.loading).toBe(true);
134
+
135
+ vi.advanceTimersByTime(100);
136
+
137
+ expect(resource.state).toBe("ready");
138
+ expect(resource()).toBe("delayed");
139
+
140
+ vi.useRealTimers();
141
+ });
142
+
143
+ test("can wrap fetch-like async operations", async () => {
144
+ const mockFetch = vi.fn().mockResolvedValue({ data: "fetched" });
145
+
146
+ const [resource] = createResource<{ data: string }>((resolve, reject) => {
147
+ mockFetch()
148
+ .then(resolve)
149
+ .catch((e: Error) => reject(e.message));
150
+ });
151
+
152
+ // Wait for promise to resolve
153
+ await vi.waitFor(() => {
154
+ expect(resource.state).toBe("ready");
155
+ });
156
+
157
+ expect(resource()).toEqual({ data: "fetched" });
158
+ });
159
+ });
160
+ });
@@ -0,0 +1,117 @@
1
+ import { describe, test, expect, beforeEach } from "vitest";
2
+ import {
3
+ routePage,
4
+ routePageTitled,
5
+ routePageFull,
6
+ createRouter,
7
+ routerNavigate,
8
+ routerReplace,
9
+ routerGetPath,
10
+ routerGetMatch,
11
+ routerGetBase,
12
+ type Routes,
13
+ type BrowserRouter,
14
+ } from "../src/index";
15
+
16
+ describe("Route definitions", () => {
17
+ test("routePage creates a page route", () => {
18
+ const route = routePage("/home", "HomePage");
19
+ expect(route).toBeDefined();
20
+ // MoonBit enum representation
21
+ expect((route as any).$tag).toBeDefined();
22
+ });
23
+
24
+ test("routePageTitled with title", () => {
25
+ const route = routePageTitled("/about", "AboutPage", "About Us");
26
+ expect(route).toBeDefined();
27
+ });
28
+
29
+ test("routePageFull with title and meta", () => {
30
+ const route = routePageFull("/about", "AboutPage", "About Us", [
31
+ { _0: "description", _1: "About our company" },
32
+ ]);
33
+ expect(route).toBeDefined();
34
+ });
35
+
36
+ test("multiple page routes", () => {
37
+ const routes: Routes[] = [
38
+ routePage("/", "Home"),
39
+ routePage("/about", "About"),
40
+ routePage("/contact", "Contact"),
41
+ ];
42
+ expect(routes.length).toBe(3);
43
+ });
44
+ });
45
+
46
+ describe("BrowserRouter", () => {
47
+ let router: BrowserRouter;
48
+
49
+ beforeEach(() => {
50
+ // Reset location to root
51
+ window.history.replaceState(null, "", "/");
52
+ });
53
+
54
+ test("createRouter creates a router", () => {
55
+ const routes = [routePage("/", "Home"), routePage("/about", "About")];
56
+ router = createRouter(routes);
57
+ expect(router).toBeDefined();
58
+ });
59
+
60
+ test("createRouter with base path", () => {
61
+ const routes = [routePage("/", "Home")];
62
+ router = createRouter(routes, "/app");
63
+ expect(routerGetBase(router)).toBe("/app");
64
+ });
65
+
66
+ test("routerGetPath returns current path", () => {
67
+ const routes = [routePage("/", "Home")];
68
+ router = createRouter(routes);
69
+ // Initial path should be "/"
70
+ const path = routerGetPath(router);
71
+ expect(typeof path).toBe("string");
72
+ });
73
+
74
+ test("routerNavigate changes path", () => {
75
+ const routes = [routePage("/", "Home"), routePage("/about", "About")];
76
+ router = createRouter(routes);
77
+
78
+ routerNavigate(router, "/about");
79
+ expect(routerGetPath(router)).toBe("/about");
80
+ });
81
+
82
+ test("routerReplace changes path without adding to history", () => {
83
+ const routes = [routePage("/", "Home"), routePage("/about", "About")];
84
+ router = createRouter(routes);
85
+
86
+ const historyLength = window.history.length;
87
+ routerReplace(router, "/about");
88
+ expect(routerGetPath(router)).toBe("/about");
89
+ // Replace should not add to history
90
+ expect(window.history.length).toBe(historyLength);
91
+ });
92
+
93
+ test("routerGetMatch returns match for current route", () => {
94
+ const routes = [routePage("/", "Home"), routePage("/about", "About")];
95
+ router = createRouter(routes);
96
+
97
+ routerNavigate(router, "/about");
98
+ const match = routerGetMatch(router);
99
+
100
+ expect(match).toBeDefined();
101
+ if (match) {
102
+ expect(match.route.component).toBe("About");
103
+ expect(match.path).toBe("/about");
104
+ }
105
+ });
106
+
107
+ test("routerGetMatch returns undefined for unmatched route", () => {
108
+ const routes = [routePage("/", "Home")];
109
+ router = createRouter(routes);
110
+
111
+ routerNavigate(router, "/nonexistent");
112
+ const match = routerGetMatch(router);
113
+ // Should be undefined or match might still return something
114
+ // depending on implementation
115
+ expect(typeof match === "undefined" || match !== null).toBe(true);
116
+ });
117
+ });
@@ -0,0 +1,182 @@
1
+ /** @jsxImportSource ../src */
2
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
3
+ import "global-jsdom/register";
4
+ import {
5
+ createSignal,
6
+ onMount,
7
+ onCleanup,
8
+ render,
9
+ mount,
10
+ Show,
11
+ show,
12
+ } from "../src/index";
13
+ import type { JSX } from "../src/jsx-runtime";
14
+
15
+ const flushMicrotasks = () => new Promise<void>((resolve) => queueMicrotask(resolve));
16
+ const countLogs = (log: string[]) => {
17
+ const created = log.filter((item) => item.endsWith(" created")).length;
18
+ const mounted = log.filter((item) => item.endsWith(" mounted")).length;
19
+ const cleanups = log.filter((item) => item.endsWith(" cleanup")).length;
20
+ return { created, mounted, cleanups };
21
+ };
22
+
23
+ const expectCounts = (
24
+ log: string[],
25
+ expected: { created: number; mounted: number; cleanups: number },
26
+ ) => {
27
+ const { created, mounted, cleanups } = countLogs(log);
28
+ try {
29
+ expect(created).toBe(expected.created);
30
+ expect(mounted).toBe(expected.mounted);
31
+ expect(cleanups).toBe(expected.cleanups);
32
+ } catch (error) {
33
+ console.log({ counts: { created, mounted, cleanups }, log });
34
+ throw error;
35
+ }
36
+ };
37
+
38
+ describe("Show initial mount behavior", () => {
39
+ let container: HTMLElement;
40
+
41
+ beforeEach(() => {
42
+ container = document.createElement("div");
43
+ document.body.appendChild(container);
44
+ });
45
+
46
+ afterEach(() => {
47
+ container.remove();
48
+ });
49
+
50
+ it("reproduces multiple instances with Show when=true on initial render", async () => {
51
+ const log: string[] = [];
52
+ let gId = 1;
53
+
54
+ function Child(): JSX.Element {
55
+ const id = gId++;
56
+
57
+ onMount(() => {
58
+ log.push(`${id} mounted`);
59
+ });
60
+
61
+ onCleanup(() => {
62
+ log.push(`${id} cleanup`);
63
+ });
64
+
65
+ log.push(`${id} created`);
66
+
67
+ return <p>I'm here</p>;
68
+ }
69
+
70
+ function Counter(): JSX.Element {
71
+ return (
72
+ <div>
73
+ <Show when={true}>{() => <Child />}</Show>
74
+ </div>
75
+ );
76
+ }
77
+
78
+ render(container, <Counter />);
79
+
80
+ await flushMicrotasks();
81
+ await flushMicrotasks();
82
+ await flushMicrotasks();
83
+ await flushMicrotasks();
84
+
85
+ expectCounts(log, { created: 1, mounted: 1, cleanups: 0 });
86
+ });
87
+
88
+ it("does not create multiple instances without Show", async () => {
89
+ const log: string[] = [];
90
+ let gId = 1;
91
+
92
+ function Child(): JSX.Element {
93
+ const id = gId++;
94
+
95
+ onMount(() => {
96
+ log.push(`${id} mounted`);
97
+ });
98
+
99
+ onCleanup(() => {
100
+ log.push(`${id} cleanup`);
101
+ });
102
+
103
+ log.push(`${id} created`);
104
+
105
+ return <p>I'm here</p>;
106
+ }
107
+
108
+ function Counter(): JSX.Element {
109
+ return (
110
+ <div>
111
+ <Child />
112
+ </div>
113
+ );
114
+ }
115
+
116
+ render(container, <Counter />);
117
+
118
+ await flushMicrotasks();
119
+ await flushMicrotasks();
120
+
121
+ expectCounts(log, { created: 1, mounted: 1, cleanups: 0 });
122
+ });
123
+
124
+ it("does not create multiple instances with Show when=true using mount", async () => {
125
+ const log: string[] = [];
126
+ let gId = 1;
127
+
128
+ function Child(): JSX.Element {
129
+ const id = gId++;
130
+
131
+ onMount(() => {
132
+ log.push(`${id} mounted`);
133
+ });
134
+
135
+ onCleanup(() => {
136
+ log.push(`${id} cleanup`);
137
+ });
138
+
139
+ log.push(`${id} created`);
140
+
141
+ return <p>I'm here</p>;
142
+ }
143
+
144
+ const node = Show({ when: true, children: () => <Child /> });
145
+ mount(container, node);
146
+
147
+ await flushMicrotasks();
148
+ await flushMicrotasks();
149
+
150
+ expectCounts(log, { created: 1, mounted: 1, cleanups: 0 });
151
+ });
152
+
153
+ it("does not create multiple instances when render uses show() primitive", async () => {
154
+ const log: string[] = [];
155
+ let gId = 1;
156
+ const [visible] = createSignal(true);
157
+
158
+ function Child(): JSX.Element {
159
+ const id = gId++;
160
+
161
+ onMount(() => {
162
+ log.push(`${id} mounted`);
163
+ });
164
+
165
+ onCleanup(() => {
166
+ log.push(`${id} cleanup`);
167
+ });
168
+
169
+ log.push(`${id} created`);
170
+
171
+ return <p>I'm here</p>;
172
+ }
173
+
174
+ const node = show(visible, () => <Child />);
175
+ render(container, node);
176
+
177
+ await flushMicrotasks();
178
+ await flushMicrotasks();
179
+
180
+ expectCounts(log, { created: 1, mounted: 1, cleanups: 0 });
181
+ });
182
+ });