fix: ignore stale observer map array notifications#7629
Conversation
janechu
left a comment
There was a problem hiding this comment.
Thanks for the contribution @pradeep-ramola! I've added a few comments but overall this looks like a good fix.
| target: any, | ||
| rootProperty: string, | ||
| ): void { | ||
| let registrations = observedArraysMap.get(data); |
There was a problem hiding this comment.
registrations are never cleaned up, a subscriber may have an old notifier attached, can you ensure these get cleaned up?
| }; | ||
| } | ||
|
|
||
| public async mutateStaleNestedItemsAfterDeepMerge() { |
There was a problem hiding this comment.
This test does not actually fail on the issue, consider this change:
public async mutateStaleNestedItemsAfterSecondDeepMerge() {
deepMerge(this.users[0], {
orders: [this.createOrder(901, "First replacement")],
});
await Updates.next();
const oldItems = this.users[0].orders[0].items;
deepMerge(this.users[0], {
orders: [this.createOrder(902, "Second replacement")],
});
await Updates.next();
let notifications = 0;
const notifier = Observable.getNotifier(this);
const subscriber = {
handleChange() {
notifications++;
},
};
notifier.subscribe(subscriber, "users");
oldItems.push(this.createProduct(9000, "Stale item"));
await Updates.next();
notifier.unsubscribe(subscriber, "users");
return {
notifications,
currentItemCount: this.users[0].orders[0].items.length,
staleItemCount: oldItems.length,
};
}Pair this with the following test in the spec file:
test("should ignore stale nested array mutations after replacing arrays with deepMerge", async ({
page,
}) => {
const hydrationCompleted = page.waitForFunction(
() => (window as any).hydrationCompleted === true,
);
await page.goto("/fixtures/extensions/observer-map-deep-merge/");
await hydrationCompleted;
const result = await page
.locator("deep-merge-test-element")
.evaluate((element: any) =>
element.mutateStaleNestedItemsAfterSecondDeepMerge(),
);
expect(result.notifications).toBe(0);
expect(result.currentItemCount).toBe(1);
expect(result.staleItemCount).toBe(2);
});This pattern fails without the fix because oldItems.push(...) incorrectly notifies the element’s users root property after the second replacement.
There was a problem hiding this comment.
Thanks, updated in 8937b49.
- stale array registrations are now removed and their array notifier subscriber is unsubscribed
- the nested stale-array regression now uses the second deepMerge repro case from your comment
The focused Playwright fixture test, tsc build, Biome check, and Beachball check all pass locally.
Pull Request
📖 Description
Fixes stale observer-map array notifications after arrays are replaced.
Previously, observer-map tracked observed arrays with a single
WeakSet, so the first owner/root context for an array could keep receiving notifications even after that array was no longer reachable from the current data. This changes array observation to use owner-specific registrations and guards notifications by checking whether the changed array is still reachable from the registered owner’s current root property.This is a fix and is not a breaking change.
🎫 Issues
Fixes #7579
👩💻 Reviewer Notes
The main area to review is the new array registration/reachability logic in
observer-map-utilities.ts.A useful smoke test is the observer-map deep-merge fixture. It covers stale root arrays, stale nested arrays, direct assignment replacement, proxy assignment replacement, and shared arrays where only the owner that still references the array should be notified.
📑 Test Plan
npm run build:tsc -w @microsoft/fast-elementnpx biome check packages/fast-element/src/declarative/observer-map-utilities.ts packages/fast-element/test/declarative/fixtures/extensions/observer-map-deep-merge/main.ts packages/fast-element/test/declarative/fixtures/extensions/observer-map-deep-merge/observer-map-deep-merge.spec.ts change/@microsoft-fast-element-ab7cde20-cff1-47a9-8e95-5895dd961321.jsonnpx beachball check --verbosenpx playwright test --config=packages/fast-element/playwright.declarative.config.ts --project=chromium fixtures/extensions/observer-map-deep-merge/observer-map-deep-merge.spec.ts✅ Checklist
General
$ npm run changeAgents
⏭ Next Steps
None.