@luna_ui/luna 0.3.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +1264 -27
- package/dist/css/index.d.ts +194 -0
- package/dist/css/index.js +721 -0
- package/dist/css/runtime.d.ts +92 -0
- package/dist/css/runtime.js +179 -0
- package/dist/{index-CyEkcO3_.d.ts → index-CDWzWF-h.d.ts} +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/jsx-dev-runtime.js +1 -1
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/jsx-runtime.js +1 -1
- package/dist/src-DEjrAhrg.js +1 -0
- package/dist/vite-plugin.d.ts +122 -0
- package/dist/vite-plugin.js +1518 -0
- package/package.json +16 -2
- package/src/css/extract.ts +798 -0
- package/src/css/index.ts +10 -0
- package/src/css/inject.ts +205 -0
- package/src/css/inline.ts +182 -0
- package/src/css/minify.ts +70 -0
- package/src/css/optimizer.ts +6 -0
- package/src/css/runtime.ts +344 -0
- package/src/css-optimizer/README.md +353 -0
- package/src/css-optimizer/cooccurrence.ts +100 -0
- package/src/css-optimizer/core.ts +263 -0
- package/src/css-optimizer/extractors.ts +243 -0
- package/src/css-optimizer/hash.ts +54 -0
- package/src/css-optimizer/index.ts +129 -0
- package/src/css-optimizer/merge.ts +109 -0
- package/src/css-optimizer/moonbit-analyzer.ts +210 -0
- package/src/css-optimizer/parser.ts +120 -0
- package/src/css-optimizer/pattern.ts +171 -0
- package/src/css-optimizer/transformers.ts +301 -0
- package/src/css-optimizer/types.ts +128 -0
- package/src/event-utils.ts +227 -0
- package/src/hydration/createHydrator.ts +62 -0
- package/src/hydration/delegate.ts +62 -0
- package/src/hydration/drag.ts +214 -0
- package/src/hydration/index.ts +12 -0
- package/src/hydration/keyboard.ts +64 -0
- package/src/hydration/toggle.ts +101 -0
- package/src/index.ts +890 -0
- package/src/jsx-dev-runtime.ts +2 -0
- package/src/jsx-runtime.ts +398 -0
- package/src/vite-plugin.ts +718 -0
- package/tests/__screenshots__/context.test.ts/Context-API-context-with-reactive-effects-context-value-accessible-in-effect-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-For-component--SolidJS-style--For-updates-when-signal-changes-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-accepts-children-as-function-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-Show-component--SolidJS-style--Show-toggles-visibility-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-attribute-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-createElement-createElement-with-dynamic-style-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-createElementNs--SVG-support--createElementNs-with-dynamic-attribute-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-effect-with-DOM-effect-tracks-signal-changes-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-clear-to-empty-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-handles-empty-array-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-removes-items-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-renders-initial-list-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach--list-rendering--forEach-updates-when-items-change-1.png +0 -0
- 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
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-handles-reordering-in-SVG-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-updates-SVG-elements-when-signal-changes-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-forEach-with-SVG-elements-forEach-with-nested-SVG-groups-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-ref-callback--JSX-style--ref-callback-with-nested-elements-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-creates-a-node-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-show--conditional-rendering--show-with-false-condition-creates-placeholder-1.png +0 -0
- package/tests/__screenshots__/dom.test.ts/DOM-API-text-nodes-textDyn-creates-reactive-text-node-1.png +0 -0
- 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
- 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
- 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
- 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
- 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
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Context---ForEach-integration-forEach-items-can-access-context-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-renders-initial-list-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-updates-when-signal-changes-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-ForEach-with-reactive-updates-forEach-with-object-items-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-hides-when-condition-is-false-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-renders-when-condition-is-true-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-from-false-to-true-1.png +0 -0
- package/tests/__screenshots__/integration.test.ts/Integration--Nested-Components-with-Context-Show--conditional-rendering--show-toggles-reactively-1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--event-listener-pattern--Solid-js-docs-example--1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--multiple-cleanups-in-component-body--LIFO-order--1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--onCleanup-in-component-body-runs-on-unmount-1.png +0 -0
- 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
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Component-Body--Solid-js-style--timer-cleanup-pattern--Solid-js-style--1.png +0 -0
- package/tests/__screenshots__/lifecycle.test.ts/onCleanup-in-Effects-effect-cleanup-runs-before-re-run-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-large-list-update-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-nested-batch-operations-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Bulk-Updates-rapid-sequential-updates-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-Show-component---visible-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Conditional-show-hide-element---visible-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-fragment-with-list-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-nested-fragments-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Fragments-simple-fragment-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-conditional-toggle-updates-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-addition-updates-match-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-list-removal-updates-match-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/DOM-Rendering-Comparison---Reactive-Updates-text-updates-match-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-className-updates-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-dynamic-style-updates-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Dynamic-Attributes-Comparison-multiple-dynamic-attributes-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-deeply-nested-conditionals-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-empty-to-populated-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Edge-Cases-list-transitions-from-populated-to-empty-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-cleanup-order-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-nested-effects-with-inner-signal-change-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-is-called-when-effect-re-runs-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Effect-Cleanup-Comparison-onCleanup-with-resource-simulation-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-multiple-children--no-wrapper--1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-Fragment-with-no-children-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-fragment-with-list-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Fragment-Comparison-with-Preact-nested-Fragments-work-correctly-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-complex-reordering-with-additions-and-removals-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-insert-in-middle-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-remove-from-middle-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-reverse-list-order-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/List-Reordering-shuffle-list-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-Show-component-renders-when-condition-is-true-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-renders-content-when-initially-true-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Luna-Conditional-Rendering-show-toggles-visibility-dynamically-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Memo-Dependency-Chain-conditional-memo-dependencies-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-basic-signal-get-set-produces-same-values-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Signal-Behavior-Comparison-batch-updates-produce-same-final-values-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-peek-reads-value-without-tracking-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-selective-tracking-with-untrack-1.png +0 -0
- package/tests/__screenshots__/preact-signals-comparison.test.ts/Untrack-and-Peek-Behavior-untrack-prevents-dependency-tracking-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API--SolidJS-style--reactivity-accessor-is-reactive-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-empty-string-for-non-failure-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateError-returns-undefined-for-non-failure-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsFailure-and-stateError-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsPending-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateIsSuccess-and-stateValue-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-AsyncState-helpers-stateValue-returns-undefined-for-non-success-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-reject-transitions-to-failure-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-resolve-transitions-to-success-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-returns-resource--resolve--and-reject-functions-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createDeferred-starts-in-pending-state-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-async-resolve-works-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-starts-in-pending-state-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-failure-on-reject-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-createResource-transitions-to-success-on-resolve-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-can-wrap-fetch-like-async-operations-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-integration-with-Promise-works-with-setTimeout-simulation-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourceGet-tracks-dependencies-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-resourceGet-vs-resourcePeek-resourcePeek-does-not-track-dependencies-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API-resourceRefetch-refetch-resets-to-pending-and-re-runs-fetcher-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-children-to-body-by-default-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Portal-component-Portal-renders-to-selector-mount-target-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/SolidJS-API-compatibility-createEffect-tracks-dependencies-automatically-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-accessor-condition-in-Match-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-multiple-Match-components-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-component-Switch-with-single-Match-and-fallback-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/Switch-Match-components-Switch-updates-DOM-when-signal-changes-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-multiple-dependencies-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-tracks-single-dependency-1.png +0 -0
- package/tests/__screenshots__/solidjs-api.test.ts/on---utility-on-with-defer-option-skips-initial-run-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Arrays-array-updates-work-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Reactivity-only-triggers-when-accessed-property-changes-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Reactivity-parent-path-change-notifies-child-accessors-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-nested-property-access-1.png +0 -0
- package/tests/__screenshots__/store.test.ts/createStore-Reactivity-tracks-property-access-in-effects-1.png +0 -0
- package/tests/context.test.ts +118 -0
- package/tests/css-optimizer-extractors.test.ts +264 -0
- package/tests/css-optimizer-integration.test.ts +566 -0
- package/tests/css-optimizer-transformers.test.ts +301 -0
- package/tests/css-optimizer.test.ts +646 -0
- package/tests/css-runtime.bench.ts +442 -0
- package/tests/css-runtime.test.ts +342 -0
- package/tests/dom.test.ts +872 -0
- package/tests/integration.test.ts +405 -0
- package/tests/issue-5-for-infinite-loop.test.ts +516 -0
- package/tests/jsx-runtime.test.tsx +393 -0
- package/tests/lifecycle.test.ts +833 -0
- package/tests/move-before.bench.ts +304 -0
- package/tests/preact-signals-comparison.test.ts +1608 -0
- package/tests/resource.test.ts +160 -0
- package/tests/router.test.ts +117 -0
- package/tests/show-initial-mount-leak.test.tsx +182 -0
- package/tests/solidjs-api.test.ts +659 -0
- package/tests/static-perf.bench.ts +64 -0
- package/tests/store.test.ts +263 -0
- package/tests/tsx-syntax.test.tsx +404 -0
- package/dist/src-BDdxGwvq.js +0 -1
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #5: For component causes infinite loop inside nested Show components
|
|
3
|
+
* https://github.com/mizchi/luna.mbt/issues/5
|
|
4
|
+
*
|
|
5
|
+
* This test reproduces the bug where For inside nested Show components
|
|
6
|
+
* causes "Maximum call stack size exceeded" when a signal is updated
|
|
7
|
+
* in a ref callback.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, test, expect, beforeEach, afterEach, vi } from "vitest";
|
|
10
|
+
import {
|
|
11
|
+
text,
|
|
12
|
+
createElement,
|
|
13
|
+
render,
|
|
14
|
+
mount,
|
|
15
|
+
For,
|
|
16
|
+
Show,
|
|
17
|
+
createSignal,
|
|
18
|
+
createMemo,
|
|
19
|
+
createRenderEffect,
|
|
20
|
+
onMount,
|
|
21
|
+
createRoot,
|
|
22
|
+
} from "../src/index";
|
|
23
|
+
|
|
24
|
+
function attr(name: string, value: unknown) {
|
|
25
|
+
return { _0: name, _1: value };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const AttrValue = {
|
|
29
|
+
Static: (value: string) => ({ $tag: 0, _0: value }),
|
|
30
|
+
Dynamic: (getter: () => string) => ({ $tag: 1, _0: getter }),
|
|
31
|
+
Handler: (handler: (e: unknown) => void) => ({ $tag: 2, _0: handler }),
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
describe("Issue #5: For infinite loop in nested Show", () => {
|
|
35
|
+
let container: HTMLElement;
|
|
36
|
+
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
container = document.createElement("div");
|
|
39
|
+
document.body.appendChild(container);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
container.remove();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* EXACT reproduction from GitHub issue comment:
|
|
48
|
+
* https://github.com/mizchi/luna.mbt/issues/5#issuecomment-3696194021
|
|
49
|
+
*
|
|
50
|
+
* This is the "CASE 2: Infinite Loop" scenario:
|
|
51
|
+
* - Double nested Show
|
|
52
|
+
* - Signal updated synchronously in ref callback
|
|
53
|
+
* - Outer Show starts hidden (isReady = false)
|
|
54
|
+
* - onMount sets isReady = true
|
|
55
|
+
*/
|
|
56
|
+
test("EXACT REPRODUCTION: Double nested Show with ref callback signal update", async () => {
|
|
57
|
+
const [lineCount, setLineCount] = createSignal(1);
|
|
58
|
+
const [isReady, setIsReady] = createSignal(false);
|
|
59
|
+
|
|
60
|
+
const lines = createMemo(() =>
|
|
61
|
+
Array.from({ length: lineCount() }, (_, i) => i + 1)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// This ref callback updates lineCount signal - should NOT cause infinite loop
|
|
65
|
+
const setupInput = (el: HTMLTextAreaElement) => {
|
|
66
|
+
const count = el.value.split("\n").length;
|
|
67
|
+
setLineCount(count);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const node = Show({
|
|
71
|
+
when: isReady,
|
|
72
|
+
children: () =>
|
|
73
|
+
Show({
|
|
74
|
+
when: () => true,
|
|
75
|
+
children: () =>
|
|
76
|
+
createElement("div", [], [
|
|
77
|
+
For({
|
|
78
|
+
each: lines,
|
|
79
|
+
children: (num: number) =>
|
|
80
|
+
createElement("div", [attr("class", AttrValue.Static("line"))], [text(`${num}`)]),
|
|
81
|
+
}),
|
|
82
|
+
createElement(
|
|
83
|
+
"textarea",
|
|
84
|
+
[
|
|
85
|
+
attr("value", AttrValue.Static("line1\nline2\nline3")),
|
|
86
|
+
attr("__ref", AttrValue.Handler(setupInput)),
|
|
87
|
+
],
|
|
88
|
+
[]
|
|
89
|
+
),
|
|
90
|
+
]),
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
mount(container, node);
|
|
95
|
+
|
|
96
|
+
// Initially hidden
|
|
97
|
+
expect(container.querySelectorAll(".line").length).toBe(0);
|
|
98
|
+
|
|
99
|
+
// Simulate onMount behavior - set isReady to true
|
|
100
|
+
// This is where the infinite loop would occur
|
|
101
|
+
setIsReady(true);
|
|
102
|
+
|
|
103
|
+
// Wait for any async effects
|
|
104
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
105
|
+
|
|
106
|
+
// Should render correctly without infinite loop
|
|
107
|
+
expect(container.querySelectorAll(".line").length).toBe(3);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* EXACT reproduction with onMount - matches the issue exactly
|
|
112
|
+
*/
|
|
113
|
+
test("EXACT REPRODUCTION WITH ONMOUNT: Double nested Show with ref callback signal update", async () => {
|
|
114
|
+
const [lineCount, setLineCount] = createSignal(1);
|
|
115
|
+
const [isReady, setIsReady] = createSignal(false);
|
|
116
|
+
|
|
117
|
+
const lines = createMemo(() =>
|
|
118
|
+
Array.from({ length: lineCount() }, (_, i) => i + 1)
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const setupInput = (el: HTMLTextAreaElement) => {
|
|
122
|
+
const count = el.value.split("\n").length;
|
|
123
|
+
setLineCount(count);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Use createRoot to establish owner context for onMount
|
|
127
|
+
createRoot(() => {
|
|
128
|
+
// This simulates onMount(() => setIsReady(true)) from the issue
|
|
129
|
+
onMount(() => {
|
|
130
|
+
setIsReady(true);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const node = Show({
|
|
134
|
+
when: isReady,
|
|
135
|
+
children: () =>
|
|
136
|
+
Show({
|
|
137
|
+
when: () => true,
|
|
138
|
+
children: () =>
|
|
139
|
+
createElement("div", [], [
|
|
140
|
+
For({
|
|
141
|
+
each: lines,
|
|
142
|
+
children: (num: number) =>
|
|
143
|
+
createElement("div", [attr("class", AttrValue.Static("line2"))], [text(`${num}`)]),
|
|
144
|
+
}),
|
|
145
|
+
createElement(
|
|
146
|
+
"textarea",
|
|
147
|
+
[
|
|
148
|
+
attr("value", AttrValue.Static("line1\nline2\nline3")),
|
|
149
|
+
attr("__ref", AttrValue.Handler(setupInput)),
|
|
150
|
+
],
|
|
151
|
+
[]
|
|
152
|
+
),
|
|
153
|
+
]),
|
|
154
|
+
}),
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
mount(container, node);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Wait for onMount to fire and effects to settle
|
|
161
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
162
|
+
|
|
163
|
+
// Should render correctly without infinite loop
|
|
164
|
+
expect(container.querySelectorAll(".line2").length).toBe(3);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Test with logging to trace execution flow
|
|
169
|
+
*/
|
|
170
|
+
test("DEBUG: Trace execution flow in nested Show + For", async () => {
|
|
171
|
+
const log: string[] = [];
|
|
172
|
+
const refCallCount = { value: 0 };
|
|
173
|
+
const memoCallCount = { value: 0 };
|
|
174
|
+
|
|
175
|
+
const [lineCount, setLineCount] = createSignal(1);
|
|
176
|
+
const [isReady, setIsReady] = createSignal(false);
|
|
177
|
+
|
|
178
|
+
const lines = createMemo(() => {
|
|
179
|
+
memoCallCount.value++;
|
|
180
|
+
const count = lineCount();
|
|
181
|
+
log.push(`memo: lineCount=${count}, call #${memoCallCount.value}`);
|
|
182
|
+
if (memoCallCount.value > 20) {
|
|
183
|
+
throw new Error("Infinite loop detected in memo");
|
|
184
|
+
}
|
|
185
|
+
return Array.from({ length: count }, (_, i) => i + 1);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const setupInput = (el: HTMLTextAreaElement) => {
|
|
189
|
+
refCallCount.value++;
|
|
190
|
+
const count = el.value.split("\n").length;
|
|
191
|
+
log.push(`ref callback: value has ${count} lines, call #${refCallCount.value}`);
|
|
192
|
+
if (refCallCount.value > 20) {
|
|
193
|
+
throw new Error("Infinite loop detected in ref callback");
|
|
194
|
+
}
|
|
195
|
+
setLineCount(count);
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
createRoot(() => {
|
|
199
|
+
onMount(() => {
|
|
200
|
+
log.push("onMount: setIsReady(true)");
|
|
201
|
+
setIsReady(true);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const node = Show({
|
|
205
|
+
when: isReady,
|
|
206
|
+
children: () => {
|
|
207
|
+
log.push("outer Show children called");
|
|
208
|
+
return Show({
|
|
209
|
+
when: () => true,
|
|
210
|
+
children: () => {
|
|
211
|
+
log.push("inner Show children called");
|
|
212
|
+
return createElement("div", [], [
|
|
213
|
+
For({
|
|
214
|
+
each: lines,
|
|
215
|
+
children: (num: number) => {
|
|
216
|
+
log.push(`For render item: ${num}`);
|
|
217
|
+
return createElement("div", [attr("class", AttrValue.Static("line3"))], [text(`${num}`)]);
|
|
218
|
+
},
|
|
219
|
+
}),
|
|
220
|
+
createElement(
|
|
221
|
+
"textarea",
|
|
222
|
+
[
|
|
223
|
+
attr("value", AttrValue.Static("line1\nline2\nline3")),
|
|
224
|
+
attr("__ref", AttrValue.Handler(setupInput)),
|
|
225
|
+
],
|
|
226
|
+
[]
|
|
227
|
+
),
|
|
228
|
+
]);
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
mount(container, node);
|
|
235
|
+
log.push("mount completed");
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
239
|
+
|
|
240
|
+
// Log the execution trace for debugging
|
|
241
|
+
console.log("Execution trace:", log);
|
|
242
|
+
|
|
243
|
+
// Verify no infinite loop
|
|
244
|
+
expect(refCallCount.value).toBeLessThan(5);
|
|
245
|
+
expect(memoCallCount.value).toBeLessThan(10);
|
|
246
|
+
expect(container.querySelectorAll(".line3").length).toBe(3);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("WORKING: simple For usage", () => {
|
|
250
|
+
const [count] = createSignal(3);
|
|
251
|
+
const items = createMemo(() =>
|
|
252
|
+
Array.from({ length: count() }, (_, i) => i + 1)
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
const node = For({
|
|
256
|
+
each: items,
|
|
257
|
+
children: (item: number) => createElement("div", [], [text(`Item ${item}`)]),
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
mount(container, node);
|
|
261
|
+
expect(container.querySelectorAll("div").length).toBe(3);
|
|
262
|
+
expect(container.textContent).toBe("Item 1Item 2Item 3");
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("WORKING: For inside single Show", () => {
|
|
266
|
+
const [isInit] = createSignal(true);
|
|
267
|
+
const [count] = createSignal(3);
|
|
268
|
+
const items = createMemo(() =>
|
|
269
|
+
Array.from({ length: count() }, (_, i) => i + 1)
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
const node = Show({
|
|
273
|
+
when: isInit,
|
|
274
|
+
children: () =>
|
|
275
|
+
For({
|
|
276
|
+
each: items,
|
|
277
|
+
children: (item: number) =>
|
|
278
|
+
createElement("div", [], [text(`Item ${item}`)]),
|
|
279
|
+
}),
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
mount(container, node);
|
|
283
|
+
expect(container.querySelectorAll("div").length).toBe(3);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("WORKING: For inside nested Show (no ref callback)", () => {
|
|
287
|
+
const [isInit] = createSignal(true);
|
|
288
|
+
const [count] = createSignal(3);
|
|
289
|
+
const items = createMemo(() =>
|
|
290
|
+
Array.from({ length: count() }, (_, i) => i + 1)
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
const node = Show({
|
|
294
|
+
when: isInit,
|
|
295
|
+
children: () =>
|
|
296
|
+
Show({
|
|
297
|
+
when: () => true,
|
|
298
|
+
children: () =>
|
|
299
|
+
For({
|
|
300
|
+
each: items,
|
|
301
|
+
children: (item: number) =>
|
|
302
|
+
createElement("div", [], [text(`Item ${item}`)]),
|
|
303
|
+
}),
|
|
304
|
+
}),
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
mount(container, node);
|
|
308
|
+
expect(container.querySelectorAll("div").length).toBe(3);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test("VERIFY: For with ref callback signal update inside nested Show", () => {
|
|
312
|
+
// Based on issue #5 - test if ref callback can update signal without infinite loop
|
|
313
|
+
|
|
314
|
+
const [lineCount, setLineCount] = createSignal(1);
|
|
315
|
+
const lineNumbers = createMemo(() =>
|
|
316
|
+
Array.from({ length: lineCount() }, (_, i) => i + 1)
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
const setupEditor = (el: HTMLTextAreaElement) => {
|
|
320
|
+
const lines = el.value.split("\n").length;
|
|
321
|
+
setLineCount(lines); // Signal update in ref callback
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const node = Show({
|
|
325
|
+
when: () => true,
|
|
326
|
+
children: () =>
|
|
327
|
+
Show({
|
|
328
|
+
when: () => true,
|
|
329
|
+
children: () =>
|
|
330
|
+
createElement("div", [], [
|
|
331
|
+
createElement("div", [attr("class", AttrValue.Static("line-numbers"))], [
|
|
332
|
+
For({
|
|
333
|
+
each: lineNumbers,
|
|
334
|
+
children: (num: number) =>
|
|
335
|
+
createElement("div", [], [text(`${num}`)]),
|
|
336
|
+
}),
|
|
337
|
+
]),
|
|
338
|
+
createElement(
|
|
339
|
+
"textarea",
|
|
340
|
+
[
|
|
341
|
+
attr("value", AttrValue.Static("line1\nline2\nline3")),
|
|
342
|
+
attr("__ref", AttrValue.Handler(setupEditor)),
|
|
343
|
+
],
|
|
344
|
+
[]
|
|
345
|
+
),
|
|
346
|
+
]),
|
|
347
|
+
}),
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// This should not throw
|
|
351
|
+
mount(container, node);
|
|
352
|
+
|
|
353
|
+
// Verify correct rendering
|
|
354
|
+
expect(container.querySelectorAll(".line-numbers div").length).toBe(3);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test("VERIFY: Deferred Show with For and signal update in ref", async () => {
|
|
358
|
+
// More realistic reproduction: Show with setTimeout-deferred condition
|
|
359
|
+
const [isInit, setIsInit] = createSignal(false);
|
|
360
|
+
const [lineCount, setLineCount] = createSignal(1);
|
|
361
|
+
const lineNumbers = createMemo(() =>
|
|
362
|
+
Array.from({ length: lineCount() }, (_, i) => i + 1)
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
const setupEditor = (el: HTMLTextAreaElement) => {
|
|
366
|
+
const lines = el.value.split("\n").length;
|
|
367
|
+
setLineCount(lines);
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const node = Show({
|
|
371
|
+
when: isInit,
|
|
372
|
+
children: () =>
|
|
373
|
+
Show({
|
|
374
|
+
when: () => true,
|
|
375
|
+
children: () =>
|
|
376
|
+
createElement("div", [], [
|
|
377
|
+
createElement("div", [attr("class", AttrValue.Static("line-numbers"))], [
|
|
378
|
+
For({
|
|
379
|
+
each: lineNumbers,
|
|
380
|
+
children: (num: number) =>
|
|
381
|
+
createElement("div", [], [text(`${num}`)]),
|
|
382
|
+
}),
|
|
383
|
+
]),
|
|
384
|
+
createElement(
|
|
385
|
+
"textarea",
|
|
386
|
+
[
|
|
387
|
+
attr("value", AttrValue.Static("line1\nline2\nline3")),
|
|
388
|
+
attr("__ref", AttrValue.Handler(setupEditor)),
|
|
389
|
+
],
|
|
390
|
+
[]
|
|
391
|
+
),
|
|
392
|
+
]),
|
|
393
|
+
}),
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
mount(container, node);
|
|
397
|
+
|
|
398
|
+
// Initially hidden
|
|
399
|
+
expect(container.querySelectorAll(".line-numbers div").length).toBe(0);
|
|
400
|
+
|
|
401
|
+
// Trigger show after delay (simulating setTimeout)
|
|
402
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
403
|
+
setIsInit(true);
|
|
404
|
+
|
|
405
|
+
// Wait for effects to process
|
|
406
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
407
|
+
|
|
408
|
+
// Should render correctly without infinite loop
|
|
409
|
+
expect(container.querySelectorAll(".line-numbers div").length).toBe(3);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
test("BUG: Simplified reproduction - For + createRenderEffect with signal update", () => {
|
|
413
|
+
// Simplified version that demonstrates the core issue
|
|
414
|
+
const renderCount = { value: 0 };
|
|
415
|
+
const [count, setCount] = createSignal(1);
|
|
416
|
+
const items = createMemo(() => {
|
|
417
|
+
renderCount.value++;
|
|
418
|
+
if (renderCount.value > 100) {
|
|
419
|
+
throw new Error("Infinite loop detected: render count exceeded 100");
|
|
420
|
+
}
|
|
421
|
+
return Array.from({ length: count() }, (_, i) => i + 1);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
const node = Show({
|
|
425
|
+
when: () => true,
|
|
426
|
+
children: () =>
|
|
427
|
+
Show({
|
|
428
|
+
when: () => true,
|
|
429
|
+
children: () =>
|
|
430
|
+
createElement("div", [], [
|
|
431
|
+
For({
|
|
432
|
+
each: items,
|
|
433
|
+
children: (item: number) =>
|
|
434
|
+
createElement("div", [], [text(`${item}`)]),
|
|
435
|
+
}),
|
|
436
|
+
]),
|
|
437
|
+
}),
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
mount(container, node);
|
|
441
|
+
|
|
442
|
+
// Initial render should not trigger infinite loop
|
|
443
|
+
expect(renderCount.value).toBeLessThan(10);
|
|
444
|
+
|
|
445
|
+
// Signal update should not trigger infinite loop
|
|
446
|
+
renderCount.value = 0;
|
|
447
|
+
setCount(3);
|
|
448
|
+
expect(renderCount.value).toBeLessThan(10);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
test("BUG: Effect firing multiple times inside Show", () => {
|
|
452
|
+
const effectRunCount = { value: 0 };
|
|
453
|
+
const [visible, setVisible] = createSignal(true);
|
|
454
|
+
|
|
455
|
+
const node = Show({
|
|
456
|
+
when: visible,
|
|
457
|
+
children: () => {
|
|
458
|
+
// Track how many times this child function is called
|
|
459
|
+
createRenderEffect(() => {
|
|
460
|
+
effectRunCount.value++;
|
|
461
|
+
});
|
|
462
|
+
return createElement("div", [], [text("content")]);
|
|
463
|
+
},
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
mount(container, node);
|
|
467
|
+
|
|
468
|
+
// Effect should only run once on mount
|
|
469
|
+
// If it runs multiple times (2-3x), this indicates the bug reported in issue #5
|
|
470
|
+
expect(effectRunCount.value).toBe(1);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test("BUG: Show instantiates nodes multiple times", () => {
|
|
474
|
+
const instanceCount = { value: 0 };
|
|
475
|
+
const [visible] = createSignal(true);
|
|
476
|
+
|
|
477
|
+
const node = Show({
|
|
478
|
+
when: visible,
|
|
479
|
+
children: () => {
|
|
480
|
+
instanceCount.value++;
|
|
481
|
+
return createElement("div", [], [text("content")]);
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
mount(container, node);
|
|
486
|
+
|
|
487
|
+
// Child function should only be called once
|
|
488
|
+
// Multiple calls indicate the root cause of the infinite loop issue
|
|
489
|
+
expect(instanceCount.value).toBe(1);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test("BUG: Nested Show instantiates nodes multiple times", () => {
|
|
493
|
+
const outerCount = { value: 0 };
|
|
494
|
+
const innerCount = { value: 0 };
|
|
495
|
+
|
|
496
|
+
const node = Show({
|
|
497
|
+
when: () => true,
|
|
498
|
+
children: () => {
|
|
499
|
+
outerCount.value++;
|
|
500
|
+
return Show({
|
|
501
|
+
when: () => true,
|
|
502
|
+
children: () => {
|
|
503
|
+
innerCount.value++;
|
|
504
|
+
return createElement("div", [], [text("nested")]);
|
|
505
|
+
},
|
|
506
|
+
});
|
|
507
|
+
},
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
mount(container, node);
|
|
511
|
+
|
|
512
|
+
// Each child function should only be called once
|
|
513
|
+
expect(outerCount.value).toBe(1);
|
|
514
|
+
expect(innerCount.value).toBe(1);
|
|
515
|
+
});
|
|
516
|
+
});
|