From 1c1377c6ae3018eded37657d4e5bf32e21116bdd Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Sat, 1 Aug 2026 11:34:58 +0000 Subject: [PATCH] test: stabilize app branch coverage --- src/App.dom.test.tsx | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/App.dom.test.tsx b/src/App.dom.test.tsx index 2bac27a..710dc99 100644 --- a/src/App.dom.test.tsx +++ b/src/App.dom.test.tsx @@ -10,6 +10,8 @@ const state = vi.hoisted(() => ({ identity: null as Identity | null, resolveIdentity: null as Identity | null, resolveError: null as unknown, + resolveDeferred: null as Promise | null, + resolveCalls: 0, board: null as Board | null, importBoard: null as Board | null, importError: false, @@ -99,6 +101,8 @@ vi.mock('./lib/auth', () => { state.identity = null; }, resolveAzureIdentity: async () => { + state.resolveCalls += 1; + if (state.resolveDeferred) return state.resolveDeferred; if (state.resolveError) throw state.resolveError; if (!state.resolveIdentity) throw new Error('no restored identity configured'); return state.resolveIdentity; @@ -422,7 +426,7 @@ vi.mock('./components/Board', () => ({ const moving = tasks.find((item) => item.id === id); if (!moving) return tasks; const rest = tasks.filter((item) => item.id !== id); - const next = { ...moving, status, movedAt }; + const next = moving.status === status ? moving : { ...moving, status, movedAt }; const positions = rest.reduce((all, item, i) => { if (item.status === status) all.push(i); return all; @@ -440,7 +444,7 @@ vi.mock('./components/Board', () => ({ onPurge, }: { board: Board; - onMove: (id: string, status: Task['status']) => void; + onMove: (id: string, status: Task['status'], index?: number) => void; onTick: (id: string, index: number, pos: { x: number; y: number }) => void; onEdit: (id: string) => void; onAdd: (status: Task['status']) => void; @@ -458,6 +462,7 @@ vi.mock('./components/Board', () => ({ + @@ -552,6 +557,8 @@ beforeEach(() => { state.identity = manual; state.resolveIdentity = null; state.resolveError = null; + state.resolveDeferred = null; + state.resolveCalls = 0; state.board = board(); state.importBoard = null; state.importError = false; @@ -638,6 +645,24 @@ describe('App DOM orchestration', () => { ); }); + it('ignores resolved Azure identity after unmount', async () => { + const azureIdentity: Identity = { kind: 'azure', id: 'alice@example.com' }; + const resolvedIdentity: Identity = { + ...azureIdentity, + name: 'Alice Azure', + homeAccountId: 'home-1', + }; + let resolveIdentity!: (identity: Identity) => void; + state.identity = azureIdentity; + state.resolveDeferred = new Promise((resolve) => { resolveIdentity = resolve; }); + + const resolved = render(); + expect(state.resolveCalls).toBe(1); + resolved.unmount(); + await act(async () => { resolveIdentity(resolvedIdentity); }); + expect(state.savedIdentities).toEqual([]); + }); + it('adds, edits, moves, checks, ships, cancels, restores, and purges cards', async () => { const user = userEvent.setup(); render(); @@ -722,6 +747,16 @@ describe('App DOM orchestration', () => { expect(await screen.findByText(/First task:cancelled:false,false/)).not.toBeNull(); }); + it('does not persist an indexed same-slot move', async () => { + const user = userEvent.setup(); + render(); + + await user.click(screen.getByRole('button', { name: 'keep task-1 in same slot' })); + + expect(state.dirtyWrites).toEqual([]); + expect(screen.getByText(/First task:todo:false/)).not.toBeNull(); + }); + it('blocks restore when its durable tombstone cannot be removed', async () => { state.board = board([task({ status: 'cancelled' })]); state.outboxReject = true;