import React from 'react'
import { render, screen } from '@testing-library/react'
import { MemoryRouter } from 'react-router'
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { useAuthStore } from './store/authStore'
import { useAddonStore } from './store/addonStore'
import { resetAllStores } from '../tests/helpers/store'
import { buildUser } from '../tests/helpers/factories'
import App from './App'
/**
* The viewport decides which chunk gets fetched, so the switch lives in App.tsx
* and no longer in the pages. This is that contract, for all ten routes in one
* place — it used to be five scattered spot checks, one per page test.
*
* A stub per screen keeps the test about one thing: below the breakpoint the M
* root hangs off the route, and the desktop page is never rendered at all.
*/
const isPhone = vi.hoisted(() => ({ value: false }))
vi.mock('./mobile/useIsPhone', () => ({ useIsPhone: () => isPhone.value }))
vi.mock('./mobile/screens/dashboard/MDashboard', () => ({ default: () =>
m-dashboard
}))
vi.mock('./mobile/screens/trip/MTripShell', () => ({ default: () => m-trip
}))
vi.mock('./mobile/screens/admin/MAdmin', () => ({ default: () => m-admin
}))
vi.mock('./mobile/screens/settings/MSettings', () => ({ default: () => m-settings
}))
vi.mock('./mobile/screens/vacay/MVacay', () => ({ default: () => m-vacay
}))
vi.mock('./mobile/screens/atlas/MAtlas', () => ({ default: () => m-atlas
}))
vi.mock('./mobile/screens/journey/MJourney', () => ({ default: () => m-journey
}))
vi.mock('./mobile/screens/journey/MJourneyDetail', () => ({ default: () => m-journey-detail
}))
vi.mock('./mobile/screens/collections/MCollections', () => ({ default: () => m-collections
}))
vi.mock('./mobile/screens/notifications/MNotifications', () => ({ default: () => m-notifications
}))
vi.mock('./pages/DashboardPage', () => ({ default: () => d-dashboard
}))
vi.mock('./pages/TripPlannerPage', () => ({ default: () => d-trip
}))
vi.mock('./pages/AdminPage', () => ({ default: () => d-admin
}))
vi.mock('./pages/SettingsPage', () => ({ default: () => d-settings
}))
vi.mock('./pages/VacayPage', () => ({ default: () => d-vacay
}))
vi.mock('./pages/AtlasPage', () => ({ default: () => d-atlas
}))
vi.mock('./pages/JourneyPage', () => ({ default: () => d-journey
}))
vi.mock('./pages/JourneyDetailPage', () => ({ default: () => d-journey-detail
}))
vi.mock('./pages/CollectionsPage', () => ({ default: () => d-collections
}))
vi.mock('./pages/InAppNotificationsPage.tsx', () => ({ default: () => d-notifications
}))
// The notification listener opens a WebSocket on mount.
vi.mock('./hooks/useInAppNotificationListener.ts', () => ({
useInAppNotificationListener: vi.fn(),
}))
/** path, mobile marker, desktop marker */
const ROUTES: [string, string, string][] = [
['/dashboard', 'm-dashboard', 'd-dashboard'],
['/trips/1', 'm-trip', 'd-trip'],
['/admin', 'm-admin', 'd-admin'],
['/settings', 'm-settings', 'd-settings'],
['/vacay', 'm-vacay', 'd-vacay'],
['/atlas', 'm-atlas', 'd-atlas'],
['/journey', 'm-journey', 'd-journey'],
['/journey/1', 'm-journey-detail', 'd-journey-detail'],
['/collections', 'm-collections', 'd-collections'],
['/collections/1', 'm-collections', 'd-collections'],
['/notifications', 'm-notifications', 'd-notifications'],
]
function renderAt(path: string) {
return render(
)
}
beforeEach(() => {
resetAllStores()
vi.clearAllMocks()
isPhone.value = false
// An admin passes the role check on /admin; every other route ignores it.
useAuthStore.setState({
isLoading: false,
isAuthenticated: true,
user: buildUser({ role: 'admin', mfa_enabled: true }),
appRequireMfa: false,
loadUser: vi.fn().mockResolvedValue(undefined),
})
// /journey and /collections sit behind addons; without this they redirect to
// the dashboard and the route under test never renders.
useAddonStore.setState({ loaded: true, isEnabled: () => true })
})
describe('App — viewport routing', () => {
it.each(ROUTES)(
'FE-COMP-APPVP-001: %s renders the mobile screen below the breakpoint',
async (path, mobile, desktop) => {
isPhone.value = true
renderAt(path)
expect(await screen.findByText(mobile)).toBeInTheDocument()
expect(screen.queryByText(desktop)).not.toBeInTheDocument()
}
)
it.each(ROUTES)(
'FE-COMP-APPVP-002: %s renders the desktop page above the breakpoint',
async (path, mobile, desktop) => {
renderAt(path)
expect(await screen.findByText(desktop)).toBeInTheDocument()
expect(screen.queryByText(mobile)).not.toBeInTheDocument()
}
)
})