/** * @vitest-environment happy-dom */ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { cleanup, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { MemoryRouter } from 'react-router-dom'; import MissionDeckPage from './MissionDeckPage'; import { ForgeProvider } from '../context/ForgeContext'; import { routerFuture } from '../routerFuture'; import { mockServerConfig, mockServerInfo } from '../test/fixtures'; import { api } from '../api/client'; vi.mock('../context/PresenceContext', () => ({ usePresence: () => ({ othersOnPage: [], comrades: [], comradesHere: () => [], othersOnline: false, }), })); function renderMissionDeck() { return render( , ); } describe('MissionDeckPage', () => { beforeEach(() => { vi.clearAllMocks(); localStorage.clear(); vi.spyOn(api, 'getConfig').mockResolvedValue(mockServerConfig()); vi.spyOn(api, 'getServerInfo').mockResolvedValue(mockServerInfo); vi.spyOn(api, 'listBuilds').mockResolvedValue([]); vi.spyOn(api, 'buildAgent').mockResolvedValue({ success: true, build_id: 'deck-build-1', file_name: 'worker-deck.exe', file_size: 4096, download_url: '/api/v1/builds/deck-build-1/download', }); vi.spyOn(api, 'exportSpreadKit').mockResolvedValue(undefined); vi.stubGlobal('navigator', { clipboard: { writeText: vi.fn().mockResolvedValue(undefined) }, }); }); afterEach(() => { vi.unstubAllGlobals(); cleanup(); }); it('shows loading state then mission deck hero', async () => { renderMissionDeck(); expect(screen.getByText('Loading loadout defaults…')).toBeInTheDocument(); expect(await screen.findByRole('heading', { level: 1, name: /Mission Deck/i })).toBeInTheDocument(); expect(screen.getByText('FAST PATH')).toBeInTheDocument(); expect(screen.getByText(/Pick a preset loadout/i)).toBeInTheDocument(); }); it('renders Ghost / Loud / Spread mode chips in loadout layout', async () => { renderMissionDeck(); await screen.findByRole('heading', { level: 1, name: 'Mission Deck' }); expect(screen.getByRole('region', { name: 'Mission loadout' })).toBeInTheDocument(); const loadout = screen.getByRole('region', { name: 'Mission loadout' }); expect(loadout).toHaveTextContent('Ghost'); expect(loadout).toHaveTextContent('Loud'); expect(loadout).toHaveTextContent('Spread'); expect(screen.getByRole('heading', { level: 2, name: /Ghost|Loud|Spread/ })).toBeInTheDocument(); }); it('shows spread profile chips and campaign slug on the right panel', async () => { renderMissionDeck(); await screen.findByRole('heading', { level: 1, name: 'Mission Deck' }); expect(screen.getByRole('heading', { level: 3, name: /Spread profile/i })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'LAN Kindling' })).toBeInTheDocument(); expect(screen.getByLabelText(/Campaign slug/i)).toBeInTheDocument(); }); it('links to Forge, Emberwake, Builds, and field guide', async () => { renderMissionDeck(); await screen.findByRole('heading', { level: 1, name: /Mission Deck/i }); expect(screen.getAllByRole('link', { name: /^Forge$/i }).length).toBeGreaterThan(0); expect(screen.getAllByRole('link', { name: /^Emberwake$/i }).length).toBeGreaterThan(0); expect(screen.getAllByRole('link', { name: /^Builds$/i }).length).toBeGreaterThan(0); expect(screen.getByRole('link', { name: /Field guide/i })).toHaveAttribute('href', '/docs/#mission-deck'); expect(screen.getByRole('link', { name: /Full Forge/i })).toHaveAttribute('href', '/forge'); }); it('equips and strikes using runForgeMission pipeline', async () => { const user = userEvent.setup(); const buildSpy = vi.spyOn(api, 'buildAgent'); const exportSpy = vi.spyOn(api, 'exportSpreadKit'); renderMissionDeck(); await screen.findByRole('heading', { level: 1, name: 'Mission Deck' }); await user.click(screen.getByRole('button', { name: 'LAN Kindling' })); await user.click(screen.getByRole('button', { name: /Equip & Strike/i })); await waitFor(() => { expect(buildSpy).toHaveBeenCalled(); }); expect(exportSpy).toHaveBeenCalled(); expect(await screen.findByText(/Forge complete/i)).toBeInTheDocument(); expect(screen.getByRole('link', { name: /View install commands in Builds/i })).toHaveAttribute('href', '/builds'); }); });