From cd94de7ea428b74a6f1de6400ad3837f5d349b39 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Tue, 6 Oct 2026 11:24:42 -0500 Subject: [PATCH] test(desktop): tool disclosure cache evicts on unmount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Regression test for #131121: after subscribe -> unsubscribe (a row's mount -> unmount), a second call with the same id must return a fresh atom — the entry was released instead of being retained for the window's lifetime — while identity stays stable while subscribed. Co-authored-by: Yuan Li --- apps/desktop/src/store/tool-view.test.ts | 55 ++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 apps/desktop/src/store/tool-view.test.ts diff --git a/apps/desktop/src/store/tool-view.test.ts b/apps/desktop/src/store/tool-view.test.ts new file mode 100644 index 0000000000..3307f67d72 --- /dev/null +++ b/apps/desktop/src/store/tool-view.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, it } from 'vitest' + +import { $anyToolDisclosureOpen, $toolDisclosureOpen } from './tool-view' + +// Mount and unmount exactly as a rendered tool row does: subscribe (useStore) +// then unsubscribe (unmount). Everything else — an un-subscribed call — never +// attaches a listener, so eviction could only be observed through this shape. +const mountAndUnmount = (atom: { subscribe: (listener: () => void) => () => void }) => { + const unsubscribe = atom.subscribe(() => undefined) + unsubscribe() +} + +describe('$toolDisclosureOpen cache', () => { + it('keeps a stable identity while the row is subscribed', () => { + const unsubscribe = $toolDisclosureOpen('tool-entry:stable:0').subscribe(() => undefined) + + try { + expect($toolDisclosureOpen('tool-entry:stable:0')).toBe($toolDisclosureOpen('tool-entry:stable:0')) + } finally { + unsubscribe() + } + }) + + it('releases the cache entry once its row unmounts', () => { + const row = $toolDisclosureOpen('tool-entry:evicted:0') + + mountAndUnmount(row) + + // A remount gets a fresh atom: the unmounted row's entry was released + // instead of being retained for the window's lifetime (#131121). + expect($toolDisclosureOpen('tool-entry:evicted:0')).not.toBe(row) + }) +}) + +describe('$anyToolDisclosureOpen cache', () => { + it('keeps a stable identity while the run is subscribed', () => { + const ids = ['tool-entry:stable-run:0', 'tool-entry:stable-run:1'] as const + const unsubscribe = $anyToolDisclosureOpen(ids).subscribe(() => undefined) + + try { + expect($anyToolDisclosureOpen(ids)).toBe($anyToolDisclosureOpen(ids)) + } finally { + unsubscribe() + } + }) + + it('releases the cache entry once its run unmounts', () => { + const ids = ['tool-entry:evicted-run:0', 'tool-entry:evicted-run:1'] as const + const run = $anyToolDisclosureOpen(ids) + + mountAndUnmount(run) + + expect($anyToolDisclosureOpen(ids)).not.toBe(run) + }) +})