/** * AppNav persistence test — verifies AppNav renders on ALL routes (/calendar, /lists). * * FIX 3: AppNav must be a persistent app-shell element outside so it * survives route transitions. Before the fix, AppNav was rendered INSIDE * CalendarShell, which caused it to unmount when navigating to /lists. * * These tests render the full App component (same as the user experiences) and * verify the "FamilySync" brand text (present in both PhoneNav and DesktopNav) * is visible on both the /calendar and /lists routes. */ import React from 'react'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; // ── Module mocks ──────────────────────────────────────────────────────────── vi.mock('@schedule-x/react', () => ({ useCalendarApp: vi.fn(() => ({})), ScheduleXCalendar: () =>
, })); vi.mock('@schedule-x/events-service', () => ({ createEventsServicePlugin: vi.fn(() => ({ name: 'events-service', set: vi.fn(), get: vi.fn(), getAll: vi.fn(() => []), add: vi.fn(), remove: vi.fn(), update: vi.fn(), })), })); vi.mock('@schedule-x/event-modal', () => ({ createEventModalPlugin: vi.fn(() => ({ name: 'event-modal' })), })); vi.mock('../api/client.js', () => ({ fetchMe: vi.fn(), fetchEvents: vi.fn(), })); vi.mock('../api/listsClient.js', () => ({ fetchLists: vi.fn().mockResolvedValue({ lists: [] }), deleteList: vi.fn(), })); vi.mock('../lib/hydrateEvents.js', async (importOriginal) => { const actual = await importOriginal(); return { ...actual, hydrateEvents: vi.fn(actual.hydrateEvents) }; }); // ── Imports (after mocks) ─────────────────────────────────────────────────── import { fetchMe, fetchEvents } from '../api/client.js'; import type { Mock } from 'vitest'; // ── Helpers ───────────────────────────────────────────────────────────────── function makeQueryClient() { return new QueryClient({ defaultOptions: { queries: { retry: false, staleTime: 0 } }, }); } // ── Tests ──────────────────────────────────────────────────────────────────── describe('AppNav persistence across routes (FIX 3)', () => { beforeEach(() => { vi.clearAllMocks(); sessionStorage.clear(); (fetchMe as Mock).mockResolvedValue({ user: { id: 1, displayName: 'Lucas', color: '#4A90D9' }, }); (fetchEvents as Mock).mockResolvedValue({ occurrences: [] }); }); it('renders AppNav "FamilySync" brand on /lists route (FIX 3: nav persists across routes)', async () => { // This test verifies the fix: AppNav is a persistent app-shell element that // renders on ALL routes, not only inside CalendarShell (/calendar). // It renders the app shell layout (AppNav + Routes) with MemoryRouter at /lists // and asserts AppNav's brand text is visible — which would fail if AppNav were // only inside CalendarShell (which unmounts on /lists). const { AppNav } = await import('./AppNav.js'); const { Routes, Route, MemoryRouter } = await import('react-router'); const client = makeQueryClient(); // Simulate the app-shell layout: AppNav is OUTSIDE Routes (persistent), and // Routes renders the /lists page. The 'FamilySync' brand in AppNav must be visible // on the /lists route — not just on /calendar. render( {/* AppNav at app-shell level — sibling of Routes, not inside any Route */} Lists page
} />
, ); // FamilySync brand text from AppNav must be present while the /lists route renders const brand = screen.queryAllByText('FamilySync'); expect(brand.length).toBeGreaterThan(0); // The Lists page route content also renders expect(screen.getByText('Lists page')).toBeDefined(); }); });