@adobe/data-lit 0.10.15 → 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 +12 -6
- 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 +26 -14
- package/dist/hooks/component/hooks-controller.js.map +1 -1
- package/dist/hooks/component/hooks-controller.test.js +19 -6
- package/dist/hooks/component/hooks-controller.test.js.map +1 -1
- package/dist/hooks/component/hooks-lifecycle.integration.test.js +71 -3
- package/dist/hooks/component/hooks-lifecycle.integration.test.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 +20 -6
- package/src/hooks/component/hooks-controller.ts +26 -14
- package/src/hooks/component/hooks-lifecycle.integration.test.ts +78 -3
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -96,6 +96,24 @@ class LifecycleProbeElement extends LitElement {
|
|
|
96
96
|
}
|
|
97
97
|
customElements.define("lifecycle-probe-element", LifecycleProbeElement);
|
|
98
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Renders a plain mutable field (no hook, no requestUpdate on change). Used to prove
|
|
101
|
+
* that a reconnect forces a re-render so mutable non-hook state refreshes after a move.
|
|
102
|
+
*/
|
|
103
|
+
class MutableProbeElement extends LitElement {
|
|
104
|
+
flag = "INITIAL";
|
|
105
|
+
rendered = "";
|
|
106
|
+
constructor() {
|
|
107
|
+
super();
|
|
108
|
+
attachDecorator(this, "render", withHooks);
|
|
109
|
+
}
|
|
110
|
+
render() {
|
|
111
|
+
this.rendered = this.flag;
|
|
112
|
+
return html`<span>${this.flag}</span>`;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
customElements.define("mutable-probe-element", MutableProbeElement);
|
|
116
|
+
|
|
99
117
|
function resetProbes(obs: Observe<number>): void {
|
|
100
118
|
connectEdges = [];
|
|
101
119
|
disconnectEdges = [];
|
|
@@ -164,7 +182,7 @@ describe("two-slot hook lifecycle — real DOM", () => {
|
|
|
164
182
|
expect(connectEdges).toEqual(["connect", "connect"]);
|
|
165
183
|
});
|
|
166
184
|
|
|
167
|
-
it("value + subscription slots SURVIVE a MOVE (
|
|
185
|
+
it("value + subscription slots SURVIVE a MOVE (no re-subscribe), and the reconnect re-renders once", async () => {
|
|
168
186
|
const { observable, subscriberCount, totalSubscribes, totalUnsubscribes } = createTrackedObservable();
|
|
169
187
|
resetProbes(observable);
|
|
170
188
|
const parentA = document.createElement("div");
|
|
@@ -180,11 +198,37 @@ describe("two-slot hook lifecycle — real DOM", () => {
|
|
|
180
198
|
await flushMicrotasks();
|
|
181
199
|
await el.updateComplete;
|
|
182
200
|
|
|
183
|
-
//
|
|
201
|
+
// The subscription is untouched — this is the perf win (no re-subscribe, no
|
|
202
|
+
// producer re-run, no state reset)...
|
|
184
203
|
expect(subscriberCount()).toBe(1);
|
|
185
204
|
expect(totalSubscribes()).toBe(1);
|
|
186
205
|
expect(totalUnsubscribes()).toBe(0);
|
|
187
|
-
|
|
206
|
+
// ...but the reconnect forces exactly one re-render (a cheap Lit diff over the
|
|
207
|
+
// preserved hook state) so an element reading mutable non-hook state refreshes.
|
|
208
|
+
expect(renderCount).toBe(rendersBeforeMove + 1);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("an element reading MUTABLE non-hook state refreshes across a MOVE (the reconnect re-render)", async () => {
|
|
212
|
+
// Regression: a move fires no DOM re-render, so without the controller's forced
|
|
213
|
+
// reconnect re-render an element whose render reads a plain mutable field shows
|
|
214
|
+
// stale output after a re-parent.
|
|
215
|
+
const { observable } = createTrackedObservable();
|
|
216
|
+
resetProbes(observable);
|
|
217
|
+
const parentA = document.createElement("div");
|
|
218
|
+
const parentB = document.createElement("div");
|
|
219
|
+
document.body.append(parentA, parentB);
|
|
220
|
+
const el = document.createElement("mutable-probe-element") as MutableProbeElement;
|
|
221
|
+
parentA.appendChild(el);
|
|
222
|
+
await el.updateComplete;
|
|
223
|
+
expect(el.rendered).toBe("INITIAL");
|
|
224
|
+
|
|
225
|
+
el.flag = "CHANGED"; // mutate a plain field (no hook, no requestUpdate)
|
|
226
|
+
parentB.appendChild(el); // MOVE
|
|
227
|
+
await flushMicrotasks();
|
|
228
|
+
await el.updateComplete;
|
|
229
|
+
|
|
230
|
+
expect(el.isConnected).toBe(true);
|
|
231
|
+
expect(el.rendered).toBe("CHANGED");
|
|
188
232
|
});
|
|
189
233
|
|
|
190
234
|
it("finalizes on a genuine unmount — subscription disposed, no leak", async () => {
|
|
@@ -276,4 +320,35 @@ describe("two-slot hook lifecycle — real DOM", () => {
|
|
|
276
320
|
await flushMicrotasks();
|
|
277
321
|
expect(effectCleanups).toBe(1);
|
|
278
322
|
});
|
|
323
|
+
|
|
324
|
+
it("a subscription that has not yet emitted heals on a post-MOVE emit (preserved-subscription data contract)", async () => {
|
|
325
|
+
// Data-layer contract: the move preserves the single mount-time subscription, so
|
|
326
|
+
// a source that emits only LATER still reaches the element. This guards against a
|
|
327
|
+
// regression that tears subscriptions down on move (which would strand a source
|
|
328
|
+
// that does not replay-on-subscribe — the root of the studio "renders nothing").
|
|
329
|
+
let deliver: ((value: number) => void) | undefined;
|
|
330
|
+
const stalled: Observe<number> = observer => {
|
|
331
|
+
deliver = observer; // note: does NOT emit synchronously on subscribe
|
|
332
|
+
return () => {
|
|
333
|
+
deliver = undefined;
|
|
334
|
+
};
|
|
335
|
+
};
|
|
336
|
+
resetProbes(stalled);
|
|
337
|
+
const parentA = document.createElement("div");
|
|
338
|
+
const parentB = document.createElement("div");
|
|
339
|
+
document.body.append(parentA, parentB);
|
|
340
|
+
const el = document.createElement("lifecycle-probe-element") as LifecycleProbeElement;
|
|
341
|
+
parentA.appendChild(el);
|
|
342
|
+
await el.updateComplete;
|
|
343
|
+
expect(el.shadowRoot?.textContent).toContain("41:"); // value undefined, not yet emitted
|
|
344
|
+
|
|
345
|
+
// Move BEFORE the source emits, then emit.
|
|
346
|
+
parentB.appendChild(el);
|
|
347
|
+
await flushMicrotasks();
|
|
348
|
+
deliver?.(99);
|
|
349
|
+
await el.updateComplete;
|
|
350
|
+
|
|
351
|
+
// The preserved subscription delivered the post-move emit → element healed.
|
|
352
|
+
expect(el.shadowRoot?.textContent).toContain("99");
|
|
353
|
+
});
|
|
279
354
|
});
|