/** * 9D-4: Web Frontend Build — static file serving tests. * * Verifies the server serves a React frontend from dist/ when available, * with SPA fallback (non-API routes → index.html). */ import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import fs from 'node:fs'; import path from 'node:path'; import os from 'node:os'; describe('Web Frontend Static Serving', () => { const tmpDir = path.join(os.tmpdir(), `waggle-web-test-${Date.now()}`); const distDir = path.join(tmpDir, 'app', 'dist'); beforeAll(() => { // Create a mock dist/ directory with index.html and a JS file fs.mkdirSync(path.join(distDir, 'assets'), { recursive: true }); fs.writeFileSync( path.join(distDir, 'index.html'), 'Waggle
', ); fs.writeFileSync( path.join(distDir, 'assets', 'main.js'), 'console.log("waggle");', ); }); afterAll(() => { fs.rmSync(tmpDir, { recursive: true, force: true }); }); it('detects dist/ directory presence', () => { expect(fs.existsSync(distDir)).toBe(true); expect(fs.existsSync(path.join(distDir, 'index.html'))).toBe(true); }); it('index.html contains expected structure', () => { const html = fs.readFileSync(path.join(distDir, 'index.html'), 'utf-8'); expect(html).toContain('
'); expect(html).toContain(''); }); it('static assets exist in dist/assets/', () => { const assets = fs.readdirSync(path.join(distDir, 'assets')); expect(assets).toContain('main.js'); }); it('WAGGLE_FRONTEND_DIR env var is respected', () => { // The server reads WAGGLE_FRONTEND_DIR to locate the frontend. // When not set, it falls back to /dist (canonical since Apr-12 // apps/web migration — commit a883050); app/dist is a legacy fallback. const envDir = process.env.WAGGLE_FRONTEND_DIR; const fallback = path.resolve(process.cwd(), 'dist'); const resolved = envDir ?? fallback; expect(typeof resolved).toBe('string'); expect(resolved.length).toBeGreaterThan(0); }); }); describe('getServerBaseUrl logic', () => { it('returns localhost:3333 when TAURI_INTERNALS is present', () => { // Simulate Tauri environment const tauriGlobal = globalThis as { __TAURI_INTERNALS__?: unknown }; const origTauri = tauriGlobal.__TAURI_INTERNALS__; tauriGlobal.__TAURI_INTERNALS__ = {}; // The function checks window, so we test the logic directly const isTauri = !!tauriGlobal.__TAURI_INTERNALS__; expect(isTauri).toBe(true); // Cleanup if (origTauri === undefined) { delete tauriGlobal.__TAURI_INTERNALS__; } else { tauriGlobal.__TAURI_INTERNALS__ = origTauri; } }); it('returns origin when not in Tauri', () => { const isTauri = !!(globalThis as { __TAURI_INTERNALS__?: unknown }).__TAURI_INTERNALS__; expect(isTauri).toBe(false); }); }); describe('SPA fallback routing', () => { it('API routes should not be intercepted by SPA fallback', () => { const apiPaths = [ '/api/health', '/api/workspaces', '/api/chat', '/api/settings', '/v1/chat/completions', '/health', '/ws', ]; for (const p of apiPaths) { const isApi = p.startsWith('/api/') || p.startsWith('/v1/') || p === '/health' || p === '/ws'; expect(isApi).toBe(true); } }); it('non-API routes should be served index.html', () => { const spaPaths = ['/', '/chat', '/settings', '/cockpit', '/memory']; for (const p of spaPaths) { const isApi = p.startsWith('/api/') || p.startsWith('/v1/') || p === '/health' || p === '/ws'; expect(isApi).toBe(false); } }); });