/** * InstructionSheet.test.tsx — wiring test for SettingsSheet "How to enable" fix (UAT-05-T4). * * Asserts: * - Clicking "How to enable" in SettingsSheet opens the InstructionSheet dialog * - The dialog has heading "How to enable notifications" * - onClose (the sheet-close prop) is NOT called when the dialog opens */ import React from 'react'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/react'; import { MemoryRouter } from 'react-router'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; // ── Module mocks ────────────────────────────────────────────────────────────── vi.mock('../hooks/usePushSubscription.js', () => ({ usePushSubscription: vi.fn(() => ({ permission: 'denied', isSubscribed: false, subscribe: vi.fn(), setEnabled: vi.fn(), })), readNotificationsEnabled: vi.fn(() => false), })); // Phase 19: SettingsSheet now calls fetchMe and fetchAuthMode inside useQuery. // Mock client so the test doesn't make real network calls. vi.mock('../api/client.js', () => ({ fetchMe: vi.fn().mockResolvedValue({ user: { id: 1, displayName: 'Test', color: '#4a90d9', isAdmin: false, needsProviderSetup: false, hasLocalCredential: false, }, }), fetchAuthMode: vi.fn().mockResolvedValue({ localEnabled: true, oidcEnabled: false }), fetchChangePassword: vi.fn().mockResolvedValue(undefined), fetchLinkOidc: vi.fn().mockResolvedValue({ redirectUrl: '/oidc' }), })); // ── Minimal Notification stub (jsdom lacks it) ──────────────────────────────── beforeEach(() => { if (typeof globalThis.Notification === 'undefined') { Object.defineProperty(globalThis, 'Notification', { value: { permission: 'denied' }, writable: true, configurable: true, }); } else { Object.defineProperty(globalThis.Notification, 'permission', { value: 'denied', writable: true, configurable: true, }); } }); // ── Import component (after mocks) ──────────────────────────────────────────── import { SettingsSheet } from './SettingsSheet.js'; // ── Test helper ─────────────────────────────────────────────────────────────── function renderWithQueryClient(ui: React.ReactElement) { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false } }, }); return render( {ui} , ); } // ── Tests ───────────────────────────────────────────────────────────────────── describe('SettingsSheet — "How to enable" wiring (UAT-05-T4)', () => { it('clicking "How to enable" opens the InstructionSheet dialog and does NOT call onClose', () => { const onCloseSpy = vi.fn(); renderWithQueryClient(); // No instruction dialog yet expect(screen.queryByRole('dialog', { name: /re-enable notifications/i })).toBeNull(); // Click the "How to enable" button const howToEnableBtn = screen.getByText('How to enable'); fireEvent.click(howToEnableBtn); // The InstructionSheet dialog should now be visible const instructionDialog = screen.getByRole('dialog', { name: /re-enable notifications/i }); expect(instructionDialog).toBeDefined(); // The heading inside the dialog expect(screen.getByText('How to enable notifications')).toBeDefined(); // onClose (sheet close prop) must NOT have been called expect(onCloseSpy).not.toHaveBeenCalled(); }); it('InstructionSheet "Done" button closes the instruction dialog without calling sheet onClose', () => { const onCloseSpy = vi.fn(); renderWithQueryClient(); // Open the instruction sheet fireEvent.click(screen.getByText('How to enable')); expect(screen.getByRole('dialog', { name: /re-enable notifications/i })).toBeDefined(); // Click Done — closes the instruction sheet fireEvent.click(screen.getByText('Done')); // Instruction dialog should be gone expect(screen.queryByRole('dialog', { name: /re-enable notifications/i })).toBeNull(); // Sheet onClose must NOT have been called expect(onCloseSpy).not.toHaveBeenCalled(); }); });