// @ts-check // Voice archetype gallery; every /api route is mocked (no live writes, no generation). const { test, expect } = require('./_guard'); const items = Array.from({ length: 24 }, (_, i) => ({ id: 'v' + i, name_de: 'Stimme ' + i, name_en: 'Voice ' + i, instruct: 'Tiefe Stimme ' + i, category: 'Narration & Story', language: 'German', facets: { gender: i % 2 ? 'female' : 'male', age: 'elderly', pitch: 'low', accent: 'none', tone: 'warm' }, })); test('gallery lazy-renders, filters, previews, uses and opens in design', async ({ page }) => { const calls = []; await page.route('**/api/archetypes**', route => { const req = route.request(), url = new URL(req.url()); calls.push(req.method() + ' ' + url.pathname + url.search); if (req.method() === 'GET') { const g = url.searchParams.get('gender'); const rows = g ? items.filter(i => i.facets.gender === g) : items; return route.fulfill({ json: { total: 30, offset: 0, limit: 24, items: rows, categories: ['Narration & Story'], cached: [] } }); } if (url.pathname.endsWith('/preview')) return route.fulfill({ status: 502, json: { detail: 'mocked engine down' } }); return route.fulfill({ json: { voice_id: 'archetype_v0', created: true, instruct: 'Tiefe Stimme 0', language: 'German' } }); }); await page.route(/\/api\/(?!archetypes)/, route => route.request().method() === 'GET' ? route.fulfill({ json: [] }) : route.fulfill({ json: { ok: true } })); await page.route('**/static/dist/main.min.js*', r => r.abort()); await page.goto('/', { waitUntil: 'domcontentloaded' }); await page.waitForFunction(() => typeof window.navTo === 'function', null, { timeout: 15_000 }); await page.waitForSelector('#app-skeleton', { state: 'detached', timeout: 15_000 }); await page.evaluate(() => window.navTo('s-design')); await expect(page.locator('.arch-card')).toHaveCount(0); // lazy: nothing before opening expect(calls).toEqual([]); await page.locator('#archetype-gallery > summary').click(); await expect(page.locator('.arch-card')).toHaveCount(24); await page.locator('#arch-chips [data-k="gender"][data-v="female"]').click(); await expect(page.locator('.arch-card')).toHaveCount(12); await expect(page.locator('#arch-chips [data-v="female"]')).toHaveAttribute('aria-pressed', 'true'); const play = page.locator('.arch-play').first(); const box = await play.boundingBox(); expect(box && box.height).toBeGreaterThanOrEqual(44); await play.click(); await expect(page.locator('#arch-status')).toContainText('mocked engine down'); await page.locator('.arch-fav').first().click(); const stored = await page.evaluate(() => localStorage.getItem('ttsvc-archetype-favs')); expect(JSON.parse(stored || '[]')).toHaveLength(1); await page.locator('.arch-use').first().click(); await expect(page.locator('.arch-open').first()).toBeVisible(); await page.locator('.arch-open').first().click(); await expect(page.locator('#design-instruct')).toHaveValue(/Tiefe Stimme/); });