- SettingsSheet: void the navigate('/login') promise (react-router v7 returns Promise) — eslint no-floating-promises
- InstructionSheet.test: wrap SettingsSheet render in MemoryRouter — 17-05 added useNavigate() which needs Router context
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
124 lines
4.8 KiB
TypeScript
124 lines
4.8 KiB
TypeScript
/**
|
|
* 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(
|
|
<QueryClientProvider client={queryClient}>
|
|
<MemoryRouter>{ui}</MemoryRouter>
|
|
</QueryClientProvider>,
|
|
);
|
|
}
|
|
|
|
// ── 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(<SettingsSheet isOpen={true} onClose={onCloseSpy} />);
|
|
|
|
// 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(<SettingsSheet isOpen={true} onClose={onCloseSpy} />);
|
|
|
|
// 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();
|
|
});
|
|
});
|