@adobe/data-lit 0.10.14 → 0.10.16
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/hooks/component/hooks-controller.d.ts +37 -27
- package/dist/hooks/component/hooks-controller.integration.test.js +8 -5
- package/dist/hooks/component/hooks-controller.integration.test.js.map +1 -1
- package/dist/hooks/component/hooks-controller.js +73 -60
- package/dist/hooks/component/hooks-controller.js.map +1 -1
- package/dist/hooks/component/hooks-controller.test.js +69 -23
- package/dist/hooks/component/hooks-controller.test.js.map +1 -1
- package/dist/hooks/component/hooks-lifecycle.integration.test.d.ts +1 -0
- package/dist/hooks/component/hooks-lifecycle.integration.test.js +312 -0
- package/dist/hooks/component/hooks-lifecycle.integration.test.js.map +1 -0
- package/dist/hooks/use-connected.d.ts +17 -0
- package/dist/hooks/use-connected.js +44 -16
- package/dist/hooks/use-connected.js.map +1 -1
- package/package.json +2 -2
- package/src/hooks/component/hooks-controller.integration.test.ts +8 -5
- package/src/hooks/component/hooks-controller.test.ts +78 -25
- package/src/hooks/component/hooks-controller.ts +74 -60
- package/src/hooks/component/hooks-lifecycle.integration.test.ts +354 -0
- package/src/hooks/use-connected.ts +47 -21
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,36 +1,46 @@
|
|
|
1
1
|
import type { Component } from "./component.js";
|
|
2
2
|
/**
|
|
3
|
-
* Give a hook host a
|
|
3
|
+
* Give a hook host a correct lifecycle by separating two concerns that the DOM
|
|
4
|
+
* connect/disconnect edge conflates.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
* but {@link useEffect} only invokes it when dependencies change — never when
|
|
7
|
-
* the element leaves the DOM. Left alone, every `useObservable` /
|
|
8
|
-
* `useObservableValues` / `useEffect` subscription leaks on unmount and keeps
|
|
9
|
-
* firing `requestUpdate` on a detached element. `useConnected` is likewise
|
|
10
|
-
* inert because nothing dispatches the `"connected"` / `"disconnected"`
|
|
11
|
-
* events it listens for.
|
|
6
|
+
* A hook host has two kinds of slot:
|
|
12
7
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
8
|
+
* - **Value / subscription slots** — {@link useState}, {@link useRef},
|
|
9
|
+
* {@link useMemo}, and effect-backed subscriptions ({@link useEffect},
|
|
10
|
+
* {@link useObservable}, {@link useObservableValues}). These are *stateful*
|
|
11
|
+
* and *expensive to rebuild*. They must SURVIVE a DOM move and be finalized
|
|
12
|
+
* only when the element is genuinely gone.
|
|
13
|
+
* - **Connectedness edges** — {@link useConnected}, the primitive for window /
|
|
14
|
+
* document listeners, portals, drag sessions: things that track raw DOM
|
|
15
|
+
* connectedness and must attach/detach *synchronously* on every edge.
|
|
21
16
|
*
|
|
22
|
-
*
|
|
17
|
+
* The trap: the DOM fires `disconnectedCallback` then `connectedCallback`
|
|
18
|
+
* **synchronously, in the same task,** whenever a connected element is merely
|
|
19
|
+
* *moved* (a Lit `repeat` reorder, a list re-parenting, a drag-reorder). There
|
|
20
|
+
* is no synchronous signal that distinguishes a move from a genuine unmount, so
|
|
21
|
+
* this controller does not try to classify the edge. Instead it handles each
|
|
22
|
+
* concern with the policy that concern actually wants:
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
24
|
+
* - **Edges fire synchronously, every time.** `hostConnected` /
|
|
25
|
+
* `hostDisconnected` dispatch the `"connected"` / `"disconnected"` events
|
|
26
|
+
* inline on every raw edge, so {@link useConnected} tears down and re-attaches
|
|
27
|
+
* deterministically — including across a move (cheap, and exactly what a
|
|
28
|
+
* listener/portal wants).
|
|
29
|
+
* - **Slot finalization is deferred behind an `isConnected` re-check.** A
|
|
30
|
+
* disconnect schedules a microtask; when it runs it disposes the slots only
|
|
31
|
+
* if the host is STILL disconnected. A move has reconnected by then, so its
|
|
32
|
+
* subscription/value slots survive untouched — no re-subscribe, no state
|
|
33
|
+
* reset. A genuine unmount is still disconnected, so it finalizes, preserving
|
|
34
|
+
* the unmount-leak fix.
|
|
35
|
+
* - **Every RE-connect forces one `requestUpdate`.** The DOM does not re-render
|
|
36
|
+
* an element on a bare re-parent, so without this an element whose render
|
|
37
|
+
* reads mutable non-hook state (a plain field, an ancestor value) would show
|
|
38
|
+
* stale output after a move. The re-render is cheap: across a move the slots
|
|
39
|
+
* are preserved, so it does NOT re-subscribe or re-run producers — it is a Lit
|
|
40
|
+
* diff over unchanged hook state. Across a genuine remount the slots were
|
|
41
|
+
* finalized (cleared), so the same re-render re-initializes and re-subscribes
|
|
42
|
+
* them. (The perf win is not re-running producers on a move — never the cheap
|
|
43
|
+
* re-render.)
|
|
34
44
|
*
|
|
35
45
|
* It is installed lazily from inside the wrapped `render` (see {@link withHooks}),
|
|
36
46
|
* which runs after `connectedCallback`, so `addController` fires `hostConnected`
|
|
@@ -107,13 +107,16 @@ describe("hook disposal on real DOM disconnect", () => {
|
|
|
107
107
|
// The element is still mounted and its subscription is intact...
|
|
108
108
|
expect(el.isConnected).toBe(true);
|
|
109
109
|
expect(subscriberCount()).toBe(1);
|
|
110
|
-
// ...and, crucially, the move neither disposed nor re-created
|
|
111
|
-
//
|
|
112
|
-
// and
|
|
113
|
-
// and transient empty lists on project switch
|
|
110
|
+
// ...and, crucially, the move neither disposed nor re-created the subscription:
|
|
111
|
+
// no re-subscribe, no producer re-run. That is the perf win (before this, a move
|
|
112
|
+
// disposed and re-subscribed — 2 subs / 1 unsub — the source of the studio-page
|
|
113
|
+
// lag, churn, and transient empty lists on project switch, FFP-111900).
|
|
114
114
|
expect(totalUnsubscribes()).toBe(0);
|
|
115
115
|
expect(totalSubscribes()).toBe(1);
|
|
116
|
-
|
|
116
|
+
// The reconnect DOES force exactly one re-render — a cheap Lit diff over the
|
|
117
|
+
// preserved subscription (no re-subscribe), required so an element that reads
|
|
118
|
+
// mutable non-hook state in render refreshes after a move.
|
|
119
|
+
expect(updatesDuringMove).toBe(1);
|
|
117
120
|
});
|
|
118
121
|
});
|
|
119
122
|
//# sourceMappingURL=hooks-controller.integration.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks-controller.integration.test.js","sourceRoot":"","sources":["../../../src/hooks/component/hooks-controller.integration.test.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"hooks-controller.integration.test.js","sourceRoot":"","sources":["../../../src/hooks/component/hooks-controller.integration.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,58 +9,63 @@ function isReactiveHost(host) {
|
|
|
9
9
|
return "addController" in host && typeof host.addController === "function";
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
* render re-initializes
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* Finalize a host: dispose every hook slot's cleanup and clear the slots so the
|
|
13
|
+
* next render re-initializes from scratch. Only slots that expose a `dispose`
|
|
14
|
+
* function (effects, subscriptions) are torn down; value slots (state, memo,
|
|
15
|
+
* ref) are simply dropped.
|
|
16
16
|
*/
|
|
17
|
-
function
|
|
17
|
+
function finalizeHooks(host) {
|
|
18
18
|
const { hooks } = host;
|
|
19
19
|
if (hooks) {
|
|
20
20
|
for (const hook of hooks) {
|
|
21
21
|
hook?.dispose?.();
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
-
// Invariant: every live subscription (useEffect / useObservable / ...) exposes a
|
|
25
|
-
// `dispose` and is torn down in the loop above, so nothing should fire after this.
|
|
26
|
-
// A useState setter closure that still runs post-disconnect would write into the
|
|
27
|
-
// emptied array and requestUpdate a detached host. That signals an un-torn-down
|
|
28
|
-
// subscription (a bug at the subscription site), not something to guard here.
|
|
29
24
|
host.hooks = [];
|
|
30
25
|
host.hookIndex = 0;
|
|
31
26
|
}
|
|
32
27
|
/**
|
|
33
|
-
* Give a hook host a
|
|
28
|
+
* Give a hook host a correct lifecycle by separating two concerns that the DOM
|
|
29
|
+
* connect/disconnect edge conflates.
|
|
34
30
|
*
|
|
35
|
-
*
|
|
36
|
-
* but {@link useEffect} only invokes it when dependencies change — never when
|
|
37
|
-
* the element leaves the DOM. Left alone, every `useObservable` /
|
|
38
|
-
* `useObservableValues` / `useEffect` subscription leaks on unmount and keeps
|
|
39
|
-
* firing `requestUpdate` on a detached element. `useConnected` is likewise
|
|
40
|
-
* inert because nothing dispatches the `"connected"` / `"disconnected"`
|
|
41
|
-
* events it listens for.
|
|
31
|
+
* A hook host has two kinds of slot:
|
|
42
32
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
33
|
+
* - **Value / subscription slots** — {@link useState}, {@link useRef},
|
|
34
|
+
* {@link useMemo}, and effect-backed subscriptions ({@link useEffect},
|
|
35
|
+
* {@link useObservable}, {@link useObservableValues}). These are *stateful*
|
|
36
|
+
* and *expensive to rebuild*. They must SURVIVE a DOM move and be finalized
|
|
37
|
+
* only when the element is genuinely gone.
|
|
38
|
+
* - **Connectedness edges** — {@link useConnected}, the primitive for window /
|
|
39
|
+
* document listeners, portals, drag sessions: things that track raw DOM
|
|
40
|
+
* connectedness and must attach/detach *synchronously* on every edge.
|
|
51
41
|
*
|
|
52
|
-
*
|
|
42
|
+
* The trap: the DOM fires `disconnectedCallback` then `connectedCallback`
|
|
43
|
+
* **synchronously, in the same task,** whenever a connected element is merely
|
|
44
|
+
* *moved* (a Lit `repeat` reorder, a list re-parenting, a drag-reorder). There
|
|
45
|
+
* is no synchronous signal that distinguishes a move from a genuine unmount, so
|
|
46
|
+
* this controller does not try to classify the edge. Instead it handles each
|
|
47
|
+
* concern with the policy that concern actually wants:
|
|
53
48
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
49
|
+
* - **Edges fire synchronously, every time.** `hostConnected` /
|
|
50
|
+
* `hostDisconnected` dispatch the `"connected"` / `"disconnected"` events
|
|
51
|
+
* inline on every raw edge, so {@link useConnected} tears down and re-attaches
|
|
52
|
+
* deterministically — including across a move (cheap, and exactly what a
|
|
53
|
+
* listener/portal wants).
|
|
54
|
+
* - **Slot finalization is deferred behind an `isConnected` re-check.** A
|
|
55
|
+
* disconnect schedules a microtask; when it runs it disposes the slots only
|
|
56
|
+
* if the host is STILL disconnected. A move has reconnected by then, so its
|
|
57
|
+
* subscription/value slots survive untouched — no re-subscribe, no state
|
|
58
|
+
* reset. A genuine unmount is still disconnected, so it finalizes, preserving
|
|
59
|
+
* the unmount-leak fix.
|
|
60
|
+
* - **Every RE-connect forces one `requestUpdate`.** The DOM does not re-render
|
|
61
|
+
* an element on a bare re-parent, so without this an element whose render
|
|
62
|
+
* reads mutable non-hook state (a plain field, an ancestor value) would show
|
|
63
|
+
* stale output after a move. The re-render is cheap: across a move the slots
|
|
64
|
+
* are preserved, so it does NOT re-subscribe or re-run producers — it is a Lit
|
|
65
|
+
* diff over unchanged hook state. Across a genuine remount the slots were
|
|
66
|
+
* finalized (cleared), so the same re-render re-initializes and re-subscribes
|
|
67
|
+
* them. (The perf win is not re-running producers on a move — never the cheap
|
|
68
|
+
* re-render.)
|
|
64
69
|
*
|
|
65
70
|
* It is installed lazily from inside the wrapped `render` (see {@link withHooks}),
|
|
66
71
|
* which runs after `connectedCallback`, so `addController` fires `hostConnected`
|
|
@@ -77,42 +82,50 @@ export function installHooksController(host) {
|
|
|
77
82
|
// addController fires hostConnected synchronously, and setting the flag first
|
|
78
83
|
// guards against a re-entrant render re-installing a duplicate controller.
|
|
79
84
|
host[HOOKS_CONTROLLER] = true;
|
|
85
|
+
// First connect is the initial mount (Lit renders it anyway); only a RE-connect
|
|
86
|
+
// needs a forced re-render.
|
|
80
87
|
let connectedOnce = false;
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
let pendingDisposal = false;
|
|
88
|
+
// A finalization microtask is already queued; don't queue a second.
|
|
89
|
+
let finalizationQueued = false;
|
|
84
90
|
const controller = {
|
|
85
91
|
hostConnected() {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
// has not run yet. Cancel it — the hooks are untouched, so there is
|
|
89
|
-
// nothing to re-subscribe and no reason to force a re-render.
|
|
90
|
-
pendingDisposal = false;
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
// The "connected" / "disconnected" events are for EXTERNAL listeners only.
|
|
94
|
-
// The internal useConnected does NOT depend on them: this fires before the
|
|
95
|
-
// render body attaches any listener, so useConnected is driven by its direct
|
|
96
|
-
// isConnected check plus useEffect cleanup instead.
|
|
92
|
+
// Fire the connect edge synchronously on EVERY connect (mount / move /
|
|
93
|
+
// remount) so edge hooks (useConnected) run their setup deterministically.
|
|
97
94
|
host.dispatchEvent(new Event("connected"));
|
|
98
95
|
if (connectedOnce) {
|
|
99
|
-
//
|
|
100
|
-
//
|
|
96
|
+
// Re-render on EVERY reconnect — a move as well as a genuine remount.
|
|
97
|
+
// The subscription slots are PRESERVED across a move (finalization is
|
|
98
|
+
// gated on isConnected below), so this re-render does NOT re-subscribe
|
|
99
|
+
// or re-run producers — it is a cheap Lit diff. It is required for
|
|
100
|
+
// correctness: an element whose render reads mutable non-hook state
|
|
101
|
+
// (a plain field, an ancestor value) would otherwise show stale output
|
|
102
|
+
// after a move, since Lit does not re-render on a bare re-parent. On a
|
|
103
|
+
// genuine remount the slots were finalized (cleared), so this same
|
|
104
|
+
// re-render re-initializes and re-subscribes them.
|
|
101
105
|
host.requestUpdate();
|
|
102
106
|
}
|
|
103
107
|
connectedOnce = true;
|
|
104
108
|
},
|
|
105
109
|
hostDisconnected() {
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
|
|
110
|
+
// Fire the disconnect edge synchronously on EVERY disconnect (unmount /
|
|
111
|
+
// move) so edge hooks tear down synchronously.
|
|
112
|
+
host.dispatchEvent(new Event("disconnected"));
|
|
113
|
+
// Defer slot finalization behind an isConnected re-check: a DOM move has
|
|
114
|
+
// reconnected by the time the microtask runs, so its value/subscription
|
|
115
|
+
// slots survive untouched; only a genuine unmount (still detached) is
|
|
116
|
+
// finalized. This is what keeps a move from churning subscriptions while
|
|
117
|
+
// still disposing them on a real unmount.
|
|
118
|
+
if (finalizationQueued) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
finalizationQueued = true;
|
|
109
122
|
queueMicrotask(() => {
|
|
110
|
-
|
|
123
|
+
finalizationQueued = false;
|
|
124
|
+
if (host.isConnected) {
|
|
125
|
+
// Moved / reconnected within the task — keep every slot alive.
|
|
111
126
|
return;
|
|
112
127
|
}
|
|
113
|
-
|
|
114
|
-
host.dispatchEvent(new Event("disconnected"));
|
|
115
|
-
disposeHooks(host);
|
|
128
|
+
finalizeHooks(host);
|
|
116
129
|
});
|
|
117
130
|
},
|
|
118
131
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks-controller.js","sourceRoot":"","sources":["../../../src/hooks/component/hooks-controller.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAKvD;;;;GAIG;AACH,MAAM,gBAAgB,GAAG,MAAM,CAAC,0BAA0B,CAAC,CAAC;AAI5D,SAAS,cAAc,CAAC,IAAe;IACnC,OAAO,eAAe,IAAI,IAAI,IAAI,OAAO,IAAI,CAAC,aAAa,KAAK,UAAU,CAAC;AAC/E,CAAC;AAED;;;;;GAKG;AACH,SAAS,
|
|
1
|
+
{"version":3,"file":"hooks-controller.js","sourceRoot":"","sources":["../../../src/hooks/component/hooks-controller.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAKvD;;;;GAIG;AACH,MAAM,gBAAgB,GAAG,MAAM,CAAC,0BAA0B,CAAC,CAAC;AAI5D,SAAS,cAAc,CAAC,IAAe;IACnC,OAAO,eAAe,IAAI,IAAI,IAAI,OAAO,IAAI,CAAC,aAAa,KAAK,UAAU,CAAC;AAC/E,CAAC;AAED;;;;;GAKG;AACH,SAAS,aAAa,CAAC,IAAe;IAClC,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;IACvB,IAAI,KAAK,EAAE,CAAC;QACR,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACtB,IAA6C,EAAE,OAAO,EAAE,EAAE,CAAC;QAChE,CAAC;IACL,CAAC;IACD,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;IAChB,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC;AACvB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAe;IAClD,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAClD,OAAO;IACX,CAAC;IACD,0EAA0E;IAC1E,8EAA8E;IAC9E,2EAA2E;IAC3E,IAAI,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAAC;IAE9B,gFAAgF;IAChF,4BAA4B;IAC5B,IAAI,aAAa,GAAG,KAAK,CAAC;IAC1B,oEAAoE;IACpE,IAAI,kBAAkB,GAAG,KAAK,CAAC;IAE/B,MAAM,UAAU,GAAuB;QACnC,aAAa;YACT,uEAAuE;YACvE,2EAA2E;YAC3E,IAAI,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC;YAC3C,IAAI,aAAa,EAAE,CAAC;gBAChB,sEAAsE;gBACtE,sEAAsE;gBACtE,uEAAuE;gBACvE,mEAAmE;gBACnE,oEAAoE;gBACpE,uEAAuE;gBACvE,uEAAuE;gBACvE,mEAAmE;gBACnE,mDAAmD;gBACnD,IAAI,CAAC,aAAa,EAAE,CAAC;YACzB,CAAC;YACD,aAAa,GAAG,IAAI,CAAC;QACzB,CAAC;QACD,gBAAgB;YACZ,wEAAwE;YACxE,+CAA+C;YAC/C,IAAI,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;YAC9C,yEAAyE;YACzE,wEAAwE;YACxE,sEAAsE;YACtE,yEAAyE;YACzE,0CAA0C;YAC1C,IAAI,kBAAkB,EAAE,CAAC;gBACrB,OAAO;YACX,CAAC;YACD,kBAAkB,GAAG,IAAI,CAAC;YAC1B,cAAc,CAAC,GAAG,EAAE;gBAChB,kBAAkB,GAAG,KAAK,CAAC;gBAC3B,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;oBACnB,+DAA+D;oBAC/D,OAAO;gBACX,CAAC;gBACD,aAAa,CAAC,IAAI,CAAC,CAAC;YACxB,CAAC,CAAC,CAAC;QACP,CAAC;KACJ,CAAC;IACF,IAAI,CAAC;QACD,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,4EAA4E;QAC5E,2EAA2E;QAC3E,IAAI,CAAC,gBAAgB,CAAC,GAAG,SAAS,CAAC;QACnC,MAAM,KAAK,CAAC;IAChB,CAAC;AACL,CAAC"}
|
|
@@ -4,8 +4,10 @@ import { installHooksController } from "./hooks-controller.js";
|
|
|
4
4
|
/**
|
|
5
5
|
* Minimal stand-in for a Lit host: an EventTarget that captures the controller
|
|
6
6
|
* installed on it so the test can drive `hostConnected` / `hostDisconnected`
|
|
7
|
-
* directly (a real Lit element would need a DOM). `
|
|
8
|
-
*
|
|
7
|
+
* directly (a real Lit element would need a DOM). `connect()` / `disconnect()`
|
|
8
|
+
* flip `isConnected` BEFORE dispatching, mirroring the DOM (the callback runs
|
|
9
|
+
* after connectedness has already changed) — the controller's deferred
|
|
10
|
+
* finalization reads `isConnected` to tell a move from a genuine unmount.
|
|
9
11
|
*/
|
|
10
12
|
class FakeHost extends EventTarget {
|
|
11
13
|
constructor() {
|
|
@@ -20,20 +22,21 @@ class FakeHost extends EventTarget {
|
|
|
20
22
|
addController(controller) {
|
|
21
23
|
this.controllers.push(controller);
|
|
22
24
|
}
|
|
23
|
-
// Simulate Lit dispatching lifecycle to every registered controller.
|
|
24
25
|
connect() {
|
|
26
|
+
this.isConnected = true;
|
|
25
27
|
for (const c of this.controllers)
|
|
26
28
|
c.hostConnected?.();
|
|
27
29
|
}
|
|
28
30
|
disconnect() {
|
|
31
|
+
this.isConnected = false;
|
|
29
32
|
for (const c of this.controllers)
|
|
30
33
|
c.hostDisconnected?.();
|
|
31
34
|
}
|
|
32
35
|
}
|
|
33
|
-
/** Resolves after the current microtask checkpoint, so deferred
|
|
36
|
+
/** Resolves after the current microtask checkpoint, so deferred finalization can run. */
|
|
34
37
|
const flushMicrotasks = () => new Promise(resolve => queueMicrotask(resolve));
|
|
35
38
|
describe("installHooksController", () => {
|
|
36
|
-
it("
|
|
39
|
+
it("finalizes every effect hook and resets the cursor on a genuine disconnect", async () => {
|
|
37
40
|
const host = new FakeHost();
|
|
38
41
|
const disposeA = vi.fn();
|
|
39
42
|
const disposeB = vi.fn();
|
|
@@ -46,52 +49,95 @@ describe("installHooksController", () => {
|
|
|
46
49
|
host.hookIndex = 4;
|
|
47
50
|
installHooksController(host);
|
|
48
51
|
host.disconnect();
|
|
49
|
-
//
|
|
50
|
-
// reconnect
|
|
52
|
+
// Finalization is deferred one microtask (so a move can cancel it); with no
|
|
53
|
+
// reconnect the host is still disconnected, so it runs on the next checkpoint.
|
|
51
54
|
await flushMicrotasks();
|
|
52
55
|
expect(disposeA).toHaveBeenCalledTimes(1);
|
|
53
56
|
expect(disposeB).toHaveBeenCalledTimes(1);
|
|
54
57
|
expect(host.hooks).toEqual([]);
|
|
55
58
|
expect(host.hookIndex).toBe(0);
|
|
56
59
|
});
|
|
57
|
-
it("
|
|
60
|
+
it("fires the connect / disconnect EDGE synchronously — no microtask needed", () => {
|
|
61
|
+
const host = new FakeHost();
|
|
62
|
+
const onConnected = vi.fn();
|
|
63
|
+
const onDisconnected = vi.fn();
|
|
64
|
+
host.addEventListener("connected", onConnected);
|
|
65
|
+
host.addEventListener("disconnected", onDisconnected);
|
|
66
|
+
installHooksController(host);
|
|
67
|
+
host.connect();
|
|
68
|
+
expect(onConnected).toHaveBeenCalledTimes(1);
|
|
69
|
+
host.disconnect();
|
|
70
|
+
// No flush: the edge is synchronous so connectedness hooks (useConnected)
|
|
71
|
+
// tear down inline. Under the old defer-everything controller this event
|
|
72
|
+
// only fired on a later microtask.
|
|
73
|
+
expect(onDisconnected).toHaveBeenCalledTimes(1);
|
|
74
|
+
});
|
|
75
|
+
it("across a MOVE: fires both edges synchronously, PRESERVES the slot, and re-renders on reconnect", async () => {
|
|
58
76
|
const host = new FakeHost();
|
|
59
77
|
const dispose = vi.fn();
|
|
60
78
|
host.hooks = [{ dispose, dependencies: [] }];
|
|
61
79
|
host.hookIndex = 1;
|
|
80
|
+
const edges = [];
|
|
81
|
+
host.addEventListener("disconnected", () => edges.push("disconnect"));
|
|
82
|
+
host.addEventListener("connected", () => edges.push("connect"));
|
|
62
83
|
installHooksController(host);
|
|
63
|
-
host.connect(); //
|
|
84
|
+
host.connect(); // initial mount
|
|
64
85
|
host.requestUpdate.mockClear();
|
|
65
|
-
|
|
86
|
+
edges.length = 0;
|
|
87
|
+
// Move: disconnect immediately followed by reconnect, same task.
|
|
66
88
|
host.disconnect();
|
|
67
89
|
host.connect();
|
|
90
|
+
expect(edges).toEqual(["disconnect", "connect"]); // both edges, synchronous, in order
|
|
68
91
|
await flushMicrotasks();
|
|
69
|
-
//
|
|
92
|
+
// The slot SURVIVED the move — not disposed, not re-created...
|
|
70
93
|
expect(dispose).not.toHaveBeenCalled();
|
|
71
94
|
expect(host.hooks).toHaveLength(1);
|
|
72
|
-
|
|
95
|
+
// ...but the reconnect DID force one re-render. This is correctness, not churn:
|
|
96
|
+
// the preserved slot is not re-subscribed, so it is a cheap Lit diff — required
|
|
97
|
+
// so an element reading mutable non-hook state in render refreshes after a move.
|
|
98
|
+
expect(host.requestUpdate).toHaveBeenCalledTimes(1);
|
|
73
99
|
});
|
|
74
|
-
it("
|
|
100
|
+
it("a rapid disconnect→connect→disconnect bounce finalizes exactly once (net state wins)", async () => {
|
|
75
101
|
const host = new FakeHost();
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
host.addEventListener("connected", onConnected);
|
|
79
|
-
host.addEventListener("disconnected", onDisconnected);
|
|
102
|
+
const dispose = vi.fn();
|
|
103
|
+
host.hooks = [{ dispose, dependencies: [] }];
|
|
80
104
|
installHooksController(host);
|
|
105
|
+
// Bounce within one task: only one finalization microtask is queued, and it
|
|
106
|
+
// reads the final isConnected (false) → finalizes once, not per disconnect.
|
|
107
|
+
host.disconnect();
|
|
81
108
|
host.connect();
|
|
82
|
-
expect(onConnected).toHaveBeenCalledTimes(1);
|
|
83
109
|
host.disconnect();
|
|
84
110
|
await flushMicrotasks();
|
|
85
|
-
expect(
|
|
111
|
+
expect(dispose).toHaveBeenCalledTimes(1);
|
|
112
|
+
expect(host.hooks).toEqual([]);
|
|
113
|
+
});
|
|
114
|
+
it("gates finalization on isConnected — a slot that reconnects before the microtask survives", async () => {
|
|
115
|
+
const host = new FakeHost();
|
|
116
|
+
const dispose = vi.fn();
|
|
117
|
+
host.hooks = [{ dispose, dependencies: [] }];
|
|
118
|
+
installHooksController(host);
|
|
119
|
+
host.disconnect(); // schedules finalization; isConnected now false
|
|
120
|
+
host.connect(); // reconnects in the same task; isConnected back to true
|
|
121
|
+
await flushMicrotasks();
|
|
122
|
+
expect(dispose).not.toHaveBeenCalled();
|
|
123
|
+
expect(host.hooks).toHaveLength(1);
|
|
86
124
|
});
|
|
87
|
-
it("forces a re-render
|
|
125
|
+
it("forces a re-render on EVERY reconnect — a move and a genuine remount — but not the first connect", async () => {
|
|
88
126
|
const host = new FakeHost();
|
|
89
127
|
installHooksController(host);
|
|
90
|
-
host.connect(); // first mount
|
|
128
|
+
host.connect(); // first mount — Lit renders it anyway, no forced update
|
|
91
129
|
expect(host.requestUpdate).not.toHaveBeenCalled();
|
|
130
|
+
// A MOVE (reconnect with slots preserved) forces a re-render.
|
|
92
131
|
host.disconnect();
|
|
93
|
-
|
|
94
|
-
|
|
132
|
+
host.connect();
|
|
133
|
+
await flushMicrotasks(); // finalization skipped (reconnected in the same task)
|
|
134
|
+
expect(host.requestUpdate).toHaveBeenCalledTimes(1);
|
|
135
|
+
// A genuine unmount+remount (slots finalized) also forces a re-render, which
|
|
136
|
+
// re-initializes and re-subscribes the cleared slots.
|
|
137
|
+
host.requestUpdate.mockClear();
|
|
138
|
+
host.disconnect();
|
|
139
|
+
await flushMicrotasks(); // genuine unmount: finalization runs
|
|
140
|
+
host.connect();
|
|
95
141
|
expect(host.requestUpdate).toHaveBeenCalledTimes(1);
|
|
96
142
|
});
|
|
97
143
|
it("is idempotent — repeated installs register a single controller", () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks-controller.test.js","sourceRoot":"","sources":["../../../src/hooks/component/hooks-controller.test.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAGlD,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAE/D
|
|
1
|
+
{"version":3,"file":"hooks-controller.test.js","sourceRoot":"","sources":["../../../src/hooks/component/hooks-controller.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|