// @vitest-environment jsdom // // The dashboard shell + AUTH GATE. The loader redirects an unauthenticated // request (throws RedirectError) and otherwise returns the shared user data the // layout renders. We mock the framework hooks (useLoaderData / useLocation) so // the render is standalone, and import the REAL RedirectError so the loader's // throw is observable. import { afterEach, describe, expect, test, vi } from 'vitest' import { act, createElement, type ReactNode } from 'react' import { createRoot, type Root } from 'react-dom/client' import { RedirectError } from '@voltro/web' import { I18nProvider } from '@voltro/i18n' import enCatalog from '../../locales/en' const loaderData = vi.fn<() => unknown>() const location = vi.fn<() => string>() vi.mock('@voltro/web', async () => { const actual = await vi.importActual('@voltro/web') return { ...actual, useLoaderData: () => loaderData(), useLocation: () => location() } }) const { default: DashboardLayout, loader } = await import('./layout') ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true let container: HTMLDivElement let root: Root // DashboardLayout uses /useLocale + , so wrap renders in the // framework's (auto-wired in the running app). const render = (node: ReactNode): void => { container = document.createElement('div') document.body.appendChild(container) act(() => { root = createRoot(container) root.render( createElement(I18nProvider, { locale: 'en', messages: enCatalog, defaultLocale: 'en', children: node }), ) }) } afterEach(() => { loaderData.mockReset() location.mockReset() if (root) act(() => root.unmount()) container?.remove() document.body.innerHTML = '' }) describe('dashboard layout — loader (auth gate)', () => { test('redirects to /login (remembering the destination) when no session cookie is present', async () => { const err = await Promise.resolve(loader({ headers: {} } as never)).catch((e: unknown) => e) expect(err).toBeInstanceOf(RedirectError) expect((err as RedirectError).location).toBe('/login?from=/dashboard') }) test('returns the shared user for a signed-in request', async () => { const data = await loader({ headers: { cookie: 'demo_session=ok' } } as never) expect(data.user.name).toBe('Demo User') expect(data.user.plan).toBe('Pro') }) }) describe('dashboard layout — render', () => { test('renders the user, plan, and nav links', () => { loaderData.mockReturnValue({ user: { name: 'Ada', plan: 'Enterprise' } }) location.mockReturnValue('/dashboard') render(createElement(DashboardLayout, { children: createElement('p', null, 'page body') })) expect(container.textContent).toContain('Ada') expect(container.textContent).toContain('Enterprise plan') const hrefs = Array.from(container.querySelectorAll('a')).map((a) => a.getAttribute('href')) expect(hrefs).toContain('/dashboard') expect(hrefs).toContain('/dashboard/settings') expect(container.querySelector('main')!.textContent).toContain('page body') }) test('marks the active nav link with aria-current for the current path', () => { loaderData.mockReturnValue({ user: { name: 'Ada', plan: 'Pro' } }) location.mockReturnValue('/dashboard/settings') render(createElement(DashboardLayout, { children: null })) const active = Array.from(container.querySelectorAll('a')).find( (a) => a.getAttribute('aria-current') === 'page', )! expect(active.getAttribute('href')).toBe('/dashboard/settings') }) })