diff --git a/packages/admin/src/components/AdminDetailDrawer.web.test.tsx b/packages/admin/src/components/AdminDetailDrawer.web.test.tsx index faadee70..59bec11f 100644 --- a/packages/admin/src/components/AdminDetailDrawer.web.test.tsx +++ b/packages/admin/src/components/AdminDetailDrawer.web.test.tsx @@ -308,7 +308,8 @@ describe('AdminDetailDrawer', () => { expect(onClose).toHaveBeenCalledTimes(1); }); - it('does not trap Tab when focus is not on the last element', () => { + it('does not trap Tab when focus is not on the last element', async () => { + const user = userEvent.setup(); render( @@ -316,10 +317,10 @@ describe('AdminDetailDrawer', () => { ); const notes = screen.getByLabelText('Notes'); - notes.focus(); - document.dispatchEvent( - new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }) - ); + const save = screen.getByRole('button', { name: 'Save' }); + await user.click(notes); expect(document.activeElement).toBe(notes); + await user.tab(); + expect(document.activeElement).toBe(save); }); }); diff --git a/packages/billing/src/BillingProvider.test.tsx b/packages/billing/src/BillingProvider.test.tsx index 41eed02b..51f15e8e 100644 --- a/packages/billing/src/BillingProvider.test.tsx +++ b/packages/billing/src/BillingProvider.test.tsx @@ -11,6 +11,15 @@ import { testSubscription, } from './test/billingFixtures.js'; +type ConsoleErrorSpy = ReturnType>; + +function expectNoUnmountedConsoleWarnings(consoleSpy: ConsoleErrorSpy) { + const unmountedWarnings = consoleSpy.mock.calls.filter(args => + args.some(arg => String(arg).toLowerCase().includes('unmounted')) + ); + expect(unmountedWarnings).toHaveLength(0); +} + function Reader() { const { userId, subscription } = useBillingContext(); return ( @@ -591,99 +600,138 @@ describe('BillingProvider', () => { data: ReturnType | null; error: null; }) => void = () => {}; - auth.state.session = { user: { id: 'u-plan-unmount' } }; - const row = { - ...testSubscription(), - user_id: 'u-plan-unmount', - plan_id: 'plan_free', - }; - db.maybeSingle.mockResolvedValue({ data: row, error: null }); - db.planMaybeSingle.mockImplementationOnce( - () => - new Promise(resolve => { - resolvePlan = resolve; - }) - ); - const { unmount } = render( - - - - ); - await waitFor(() => expect(db.planMaybeSingle).toHaveBeenCalled()); - unmount(); - resolvePlan({ data: testPlan({ display_name: 'Late plan' }), error: null }); - await Promise.resolve(); + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + try { + auth.state.session = { user: { id: 'u-plan-unmount' } }; + const row = { + ...testSubscription(), + user_id: 'u-plan-unmount', + plan_id: 'plan_free', + }; + db.maybeSingle.mockResolvedValue({ data: row, error: null }); + db.planMaybeSingle.mockImplementationOnce( + () => + new Promise(resolve => { + resolvePlan = resolve; + }) + ); + const { unmount } = render( + + + + ); + await waitFor(() => expect(db.planMaybeSingle).toHaveBeenCalled()); + unmount(); + resolvePlan({ + data: testPlan({ display_name: 'Late plan' }), + error: null, + }); + await act(async () => { + await Promise.resolve(); + }); + + expectNoUnmountedConsoleWarnings(consoleSpy); + } finally { + consoleSpy.mockRestore(); + } }); it('ignores ensure_billing_subscription result after unmount', async () => { let resolveRpc: (value: { error: null }) => void = () => {}; - auth.state.session = { user: { id: 'u-rpc-unmount' } }; - db.rpc.mockImplementationOnce( - () => - new Promise(resolve => { - resolveRpc = resolve; - }) - ); - const { unmount } = render( - - - - ); - await waitFor(() => - expect(screen.getByTestId('uid').textContent).toBe('u-rpc-unmount') - ); - await waitFor(() => expect(db.rpc).toHaveBeenCalled()); - unmount(); - resolveRpc({ error: null }); - await Promise.resolve(); + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + try { + auth.state.session = { user: { id: 'u-rpc-unmount' } }; + db.rpc.mockImplementationOnce( + () => + new Promise(resolve => { + resolveRpc = resolve; + }) + ); + const { unmount } = render( + + + + ); + await waitFor(() => + expect(screen.getByTestId('uid').textContent).toBe('u-rpc-unmount') + ); + await waitFor(() => expect(db.rpc).toHaveBeenCalled()); + unmount(); + resolveRpc({ error: null }); + await act(async () => { + await Promise.resolve(); + }); + + expectNoUnmountedConsoleWarnings(consoleSpy); + } finally { + consoleSpy.mockRestore(); + } }); it('ignores plan query errors after unmount', async () => { let rejectPlan: (error: Error) => void = () => {}; - auth.state.session = { user: { id: 'u-plan-err-unmount' } }; - const row = { - ...testSubscription(), - user_id: 'u-plan-err-unmount', - plan_id: 'plan_free', - }; - db.maybeSingle.mockResolvedValue({ data: row, error: null }); - db.planMaybeSingle.mockImplementationOnce( - () => - new Promise((_resolve, reject) => { - rejectPlan = reject; - }) - ); - const { unmount } = render( - - - - ); - await waitFor(() => expect(db.planMaybeSingle).toHaveBeenCalled()); - unmount(); - rejectPlan(new Error('late plan fail')); - await Promise.resolve(); + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + try { + auth.state.session = { user: { id: 'u-plan-err-unmount' } }; + const row = { + ...testSubscription(), + user_id: 'u-plan-err-unmount', + plan_id: 'plan_free', + }; + db.maybeSingle.mockResolvedValue({ data: row, error: null }); + db.planMaybeSingle.mockImplementationOnce( + () => + new Promise((_resolve, reject) => { + rejectPlan = reject; + }) + ); + const { unmount } = render( + + + + ); + await waitFor(() => expect(db.planMaybeSingle).toHaveBeenCalled()); + unmount(); + rejectPlan(new Error('late plan fail')); + await act(async () => { + await Promise.resolve(); + }); + + expectNoUnmountedConsoleWarnings(consoleSpy); + } finally { + consoleSpy.mockRestore(); + } }); it('ignores ensure_billing_subscription errors after unmount', async () => { let rejectRpc: (error: Error) => void = () => {}; - auth.state.session = { user: { id: 'u-rpc-err-unmount' } }; - db.rpc.mockImplementationOnce( - () => - new Promise((_resolve, reject) => { - rejectRpc = reject; - }) - ); - const { unmount } = render( - - - - ); - await waitFor(() => - expect(screen.getByTestId('uid').textContent).toBe('u-rpc-err-unmount') - ); - await waitFor(() => expect(db.rpc).toHaveBeenCalled()); - unmount(); - rejectRpc(new Error('late rpc fail')); - await Promise.resolve(); + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + try { + auth.state.session = { user: { id: 'u-rpc-err-unmount' } }; + db.rpc.mockImplementationOnce( + () => + new Promise((_resolve, reject) => { + rejectRpc = reject; + }) + ); + const { unmount } = render( + + + + ); + await waitFor(() => + expect(screen.getByTestId('uid').textContent).toBe('u-rpc-err-unmount') + ); + await waitFor(() => expect(db.rpc).toHaveBeenCalled()); + unmount(); + rejectRpc(new Error('late rpc fail')); + await act(async () => { + await Promise.resolve(); + }); + + expectNoUnmountedConsoleWarnings(consoleSpy); + } finally { + consoleSpy.mockRestore(); + } }); }); diff --git a/packages/waitlist/src/hooks/useSignupMode.test.tsx b/packages/waitlist/src/hooks/useSignupMode.test.tsx index 76640cf8..3c53bc90 100644 --- a/packages/waitlist/src/hooks/useSignupMode.test.tsx +++ b/packages/waitlist/src/hooks/useSignupMode.test.tsx @@ -3,6 +3,15 @@ import { renderHook, waitFor } from '@testing-library/react'; import { useSignupMode } from './useSignupMode.js'; import * as client from '../waitlistClient.js'; +type ConsoleErrorSpy = ReturnType>; + +function expectNoUnmountedConsoleWarnings(consoleSpy: ConsoleErrorSpy) { + const unmountedWarnings = consoleSpy.mock.calls.filter(args => + args.some(arg => String(arg).toLowerCase().includes('unmounted')) + ); + expect(unmountedWarnings).toHaveLength(0); +} + describe('useSignupMode', () => { it('loads public settings and exposes mode flags', async () => { vi.spyOn(client, 'getPublicWaitlistSettings').mockResolvedValue({ @@ -31,14 +40,21 @@ describe('useSignupMode', () => { resolveSettings = resolve; }) ); - const supabase = {} as never; - const { unmount } = renderHook(() => useSignupMode(supabase)); - await waitFor(() => - expect(client.getPublicWaitlistSettings).toHaveBeenCalled() - ); - unmount(); - resolveSettings({ signup_mode: 'open', copy: {}, metadata_schema: [] }); - await Promise.resolve(); - vi.restoreAllMocks(); + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + try { + const supabase = {} as never; + const { unmount } = renderHook(() => useSignupMode(supabase)); + await waitFor(() => + expect(client.getPublicWaitlistSettings).toHaveBeenCalled() + ); + unmount(); + resolveSettings({ signup_mode: 'open', copy: {}, metadata_schema: [] }); + await Promise.resolve(); + + expectNoUnmountedConsoleWarnings(consoleSpy); + } finally { + consoleSpy.mockRestore(); + vi.restoreAllMocks(); + } }); });