| |
| |
| |
| |
| |
| |
| |
| |
| import { test, expect } from '@playwright/test'; |
|
|
| test.describe('/q/sample (prerendered demo)', () => { |
| test('renders four-section briefing with claim glyphs + cite anchors', async ({ page }) => { |
| await page.goto('/q/sample'); |
|
|
| |
| await expect(page.locator('.riprap-wordmark')).toContainText('riprap'); |
| await expect(page.locator('h1.brief-h1')).toContainText('Flood-exposure briefing'); |
|
|
| |
| const heads = page.locator('.briefing-section-head .briefing-section-num'); |
| await expect(heads).toHaveCount(4); |
| await expect(heads.nth(0)).toHaveText('01'); |
| await expect(heads.nth(1)).toHaveText('02'); |
| await expect(heads.nth(2)).toHaveText('03'); |
| await expect(heads.nth(3)).toHaveText('04'); |
|
|
| |
| const claimGlyphs = page.locator('.claim-glyph svg[role="img"]'); |
| expect(await claimGlyphs.count()).toBeGreaterThan(5); |
|
|
| |
| const cites = page.locator('a.inline-cite'); |
| expect(await cites.count()).toBeGreaterThan(5); |
| }); |
|
|
| test('renders citation drawer with all 10 sample sources', async ({ page }) => { |
| await page.goto('/q/sample'); |
| const items = page.locator('.citation-drawer .citation-item'); |
| await expect(items).toHaveCount(10); |
| |
| await expect(items.first().locator('.citation-source')).toBeVisible(); |
| await expect(items.first().locator('.citation-docid')).toBeVisible(); |
| }); |
|
|
| test('renders trace UI with all run steps', async ({ page }) => { |
| await page.goto('/q/sample'); |
| await expect(page.locator('.trace-ui')).toBeVisible(); |
| |
| await expect(page.locator('.trace-head-meta')).toContainText('s total'); |
| }); |
|
|
| test('renders evidence grid with all 6 viz formats', async ({ page }) => { |
| await page.goto('/q/sample'); |
| const cards = page.locator('.evidence-card'); |
| await expect(cards).toHaveCount(8); |
| |
| for (const t of ['empirical', 'modeled', 'proxy', 'synthetic']) { |
| expect(await page.locator(`.evidence-card-${t}`).count()).toBeGreaterThan(0); |
| } |
| }); |
|
|
| test('legend hides layers with zero features', async ({ page }) => { |
| await page.goto('/q/sample'); |
| |
| |
| |
| await expect(page.locator('.map-legend')).toBeVisible({ timeout: 10_000 }); |
| const items = page.locator('.map-legend-item'); |
| await expect(items).toHaveCount(1); |
| await expect(items.first().locator('.map-legend-label')) |
| .toContainText(/Synthetic SAR/); |
| |
| await expect(page.locator('.map-legend-item', { hasText: 'Sandy' })).toHaveCount(0); |
| await expect(page.locator('.map-legend-item', { hasText: '311' })).toHaveCount(0); |
| }); |
|
|
| test('MapLibre map mounts and registers syn-stripe-45 pattern', async ({ page }) => { |
| const consoleErrors: string[] = []; |
| page.on('console', (msg) => { |
| if (msg.type() === 'error') consoleErrors.push(msg.text()); |
| }); |
| await page.goto('/q/sample'); |
|
|
| |
| const canvas = page.locator('.maplibregl-canvas'); |
| await expect(canvas).toBeVisible({ timeout: 15_000 }); |
|
|
| |
| |
| |
| await page.waitForFunction( |
| () => Boolean((window as unknown as { __riprapMap?: unknown }).__riprapMap), |
| undefined, |
| { timeout: 15_000 } |
| ); |
| |
| await page.waitForFunction( |
| () => { |
| const m = (window as unknown as { __riprapMap?: { hasImage: (s: string) => boolean } }) |
| .__riprapMap; |
| return Boolean(m && m.hasImage('syn-stripe-45')); |
| }, |
| undefined, |
| { timeout: 5_000 } |
| ); |
|
|
| const mapState = await page.evaluate<{ |
| hasStripe: boolean; |
| hasStripe2x: boolean; |
| hasStripeLow: boolean; |
| sources: string[]; |
| layers: string[]; |
| } | null>(() => { |
| type MlMap = { |
| loaded: () => boolean; |
| hasImage: (id: string) => boolean; |
| getStyle: () => { sources: Record<string, unknown>; layers: Array<{ id: string }> }; |
| }; |
| const map = (window as unknown as { __riprapMap?: MlMap }).__riprapMap; |
| if (!map) return null; |
| const style = map.getStyle(); |
| return { |
| hasStripe: map.hasImage('syn-stripe-45'), |
| hasStripe2x: map.hasImage('syn-stripe-45-2x'), |
| hasStripeLow: map.hasImage('syn-stripe-45-low'), |
| sources: Object.keys(style.sources), |
| layers: style.layers.map((l) => l.id) |
| }; |
| }); |
|
|
| expect(mapState, 'map instance should be reachable from the DOM').not.toBeNull(); |
| if (!mapState) return; |
|
|
| |
| |
| |
| const synFeatureCount = await page.evaluate<number>(() => { |
| type Src = { _data?: { features?: unknown[] } } | undefined; |
| type MlMap = { getSource: (id: string) => Src }; |
| const map = (window as unknown as { __riprapMap?: MlMap }).__riprapMap; |
| const src = map?.getSource('syn-prior'); |
| const data = src?._data as { features?: unknown[] } | undefined; |
| return data?.features?.length ?? 0; |
| }); |
| expect(synFeatureCount, |
| 'syn-prior source should have at least one feature in the /q/sample fixture') |
| .toBeGreaterThan(0); |
|
|
| |
| expect(mapState.sources).toContain('sandy-empirical'); |
| expect(mapState.sources).toContain('dep-modeled'); |
| expect(mapState.sources).toContain('syn-prior'); |
| expect(mapState.sources).toContain('proxy-311'); |
| expect(mapState.sources).toContain('queried-address'); |
|
|
| |
| expect(mapState.layers).toEqual(expect.arrayContaining([ |
| 'tier-empirical-fill', |
| 'tier-empirical-line', |
| 'tier-modeled-fill', |
| 'tier-modeled-line', |
| 'tier-synthetic-fill', |
| 'tier-synthetic-line', |
| 'tier-proxy-dots', |
| 'queried-pin' |
| ])); |
|
|
| |
| |
| |
| |
| expect(mapState.hasStripe, 'syn-stripe-45 image should be registered').toBe(true); |
| expect(mapState.hasStripe2x, 'syn-stripe-45-2x image should be registered').toBe(true); |
| expect(mapState.hasStripeLow, 'syn-stripe-45-low image should be registered').toBe(true); |
|
|
| |
| expect(consoleErrors.filter((e) => !e.includes('favicon'))) |
| .toEqual([]); |
| }); |
| }); |
|
|