fix(reader-dashboard): 500 crash for READ_ALL users — recentDocs always undefined #661

Merged
marcel merged 4 commits from worktree-fix+reader-dashboard-doc-undefined into main 2026-05-25 17:54:42 +02:00
Showing only changes of commit 2e0eb40aec - Show all commits

View File

@@ -409,19 +409,24 @@ describe('PersonMentionEditor — onExit cancels pending debounce', () => {
await new Promise((r) => setTimeout(r, SEARCH_DEBOUNCE_MS + POST_DEBOUNCE_SLACK_MS)); await new Promise((r) => setTimeout(r, SEARCH_DEBOUNCE_MS + POST_DEBOUNCE_SLACK_MS));
const fetchesBeforeEscape = fetchMock.mock.calls.length; const fetchesBeforeEscape = fetchMock.mock.calls.length;
// Trigger a new debounced search (queues runSearch after 150 ms), then // Freeze setTimeout so the 150 ms debounce cannot fire before Escape
// immediately Escape *while focus is back in the editor* so Tiptap's // triggers onExit. We install fake timers only now — after the setup
// suggestion-plugin Escape handler fires onExit before the debounce. // above — so that vi.waitFor()'s real-timer polling still worked.
// Without onExit cancelling the pending debounce, runSearch executes vi.useFakeTimers();
// against the now-unmounted dropdown's state. try {
// fill() dispatches the input event synchronously via CDP; by the
// time the await resolves, onSearch('Walter') has run and the fake
// debounce timer is set.
await page.getByRole('searchbox').fill('Walter'); await page.getByRole('searchbox').fill('Walter');
// Focus the editor so the Escape lands on Tiptap's suggestion handler. // Focus the editor so the Escape lands on Tiptap's suggestion handler.
(page.getByRole('textbox').element() as HTMLElement).focus(); (page.getByRole('textbox').element() as HTMLElement).focus();
await userEvent.keyboard('{Escape}'); await userEvent.keyboard('{Escape}');
// onExit has now called debouncedSearch.cancel(). Advance past the
// Wait past the debounce window. If onExit did not cancel the pending // debounce window — the cancelled timer must not fire.
// debounce, a fetch with q=Walter would still fire here. await vi.advanceTimersByTimeAsync(SEARCH_DEBOUNCE_MS + POST_DEBOUNCE_SLACK_MS);
await new Promise((r) => setTimeout(r, SEARCH_DEBOUNCE_MS + POST_DEBOUNCE_SLACK_MS)); } finally {
vi.useRealTimers();
}
const newFetches = fetchMock.mock.calls.slice(fetchesBeforeEscape); const newFetches = fetchMock.mock.calls.slice(fetchesBeforeEscape);
const walterFetches = newFetches.filter( const walterFetches = newFetches.filter(