import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { render, screen } from '@testing-library/react' import { MemoryRouter, Routes, Route } from 'react-router-dom' import { AppLayout } from '../AppLayout' import { useAuthStore } from '@/store/authStore' import type { User } from '@/types' // Mock heavy/external pieces so this stays a focused integration test for the // gate placement. We don't care that TopBar/Sidebar render real content here — // only that the EmailVerificationGate is in the tree and gates the outlet. vi.mock('@/hooks/useBillingPoll', () => ({ useBillingPoll: () => undefined, })) vi.mock('@/hooks/usePermissions', () => ({ usePermissions: () => ({ effectiveRole: 'engineer' }), })) vi.mock('../TopBar', () => ({ TopBar: () =>
, })) vi.mock('../Sidebar', () => ({ Sidebar: () =>
, })) vi.mock('../EmailVerificationBanner', () => ({ EmailVerificationBanner: () =>
, })) vi.mock('@/components/common/FeedbackWidget', () => ({ FeedbackWidget: () => null, })) vi.mock('@/api/auth', () => ({ authApi: { getVerificationStatus: vi.fn().mockResolvedValue({ enabled: true }), sendVerificationEmail: vi.fn().mockResolvedValue(undefined), }, })) vi.mock('@/lib/toast', () => ({ toast: { success: vi.fn(), error: vi.fn() }, })) function makeUser(overrides: Partial = {}): User { return { id: 'user-1', email: 'test@example.com', name: 'Test User', role: 'engineer', is_super_admin: false, is_active: true, must_change_password: false, account_id: 'acct-1', account_role: 'engineer', team_id: null, created_at: '2026-05-01T00:00:00Z', last_login: null, phone: null, job_title: null, timezone: 'UTC', avatar_url: null, email_verified_at: null, ...overrides, } } const FROZEN_NOW = new Date('2026-05-06T00:00:00Z') function renderAppLayout() { return render( }> child route
} /> , ) } describe('AppLayout — EmailVerificationGate wiring', () => { beforeEach(() => { vi.useFakeTimers() vi.setSystemTime(FROZEN_NOW) useAuthStore.setState({ user: null, token: null, isAuthenticated: false }) }) afterEach(() => { vi.useRealTimers() useAuthStore.setState({ user: null, token: null, isAuthenticated: false }) }) it('renders the wall and hides the child route on day 8 unverified', () => { // created 8 days before frozen now -> elapsed=8, > grace=6 -> wall. useAuthStore.setState({ user: makeUser({ created_at: '2026-04-28T00:00:00Z' }), }) renderAppLayout() expect(screen.getByTestId('email-verification-wall')).toBeInTheDocument() expect(screen.queryByTestId('child-route-content')).not.toBeInTheDocument() }) it('renders the child route within the grace period (day 1 unverified)', () => { useAuthStore.setState({ user: makeUser({ created_at: '2026-05-05T00:00:00Z' }), }) renderAppLayout() expect(screen.getByTestId('child-route-content')).toBeInTheDocument() expect( screen.queryByTestId('email-verification-wall'), ).not.toBeInTheDocument() }) })