150 lines
5.5 KiB
TypeScript
150 lines
5.5 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
import { imhotep } from 'imhotep-playwright'
|
|
import { loadFixtureInPage, waitForFixtureReady } from './harness.js'
|
|
|
|
async function fixtureUrl(category: string): Promise<string> {
|
|
const { resolveFixturePage } = await import('./harness.js')
|
|
return 'file://' + resolveFixturePage(category)
|
|
}
|
|
|
|
test.describe('E2E: Responsive', () => {
|
|
test('across multiple viewport widths captures layout shifts', async ({ page }) => {
|
|
const ui = await imhotep(page)
|
|
await page.goto(await fixtureUrl('responsive'))
|
|
|
|
const viewports = [
|
|
{ width: 375, height: 667, name: 'mobile' },
|
|
{ width: 768, height: 1024, name: 'tablet' },
|
|
{ width: 1280, height: 720, name: 'desktop' },
|
|
]
|
|
|
|
const results: Array<{ viewport: string; sidebarWidth: number; layoutDirection: string }> = []
|
|
|
|
for (const viewport of viewports) {
|
|
await ui.applyEnvironment({
|
|
viewport: { width: viewport.width, height: viewport.height },
|
|
})
|
|
|
|
await page.reload()
|
|
await waitForFixtureReady(page)
|
|
|
|
const sidebarData = await ui.extract('[data-testid="responsive-sidebar"]')
|
|
const contentData = await ui.extract('[data-testid="responsive-content"]')
|
|
|
|
const sidebarRect = (sidebarData as any[])[0].rect
|
|
const contentRect = (contentData as any[])[0].rect
|
|
|
|
// Determine layout direction by comparing y positions
|
|
const layoutDirection = sidebarRect.y === contentRect.y ? 'row' : 'column'
|
|
|
|
results.push({
|
|
viewport: viewport.name,
|
|
sidebarWidth: sidebarRect.width,
|
|
layoutDirection,
|
|
})
|
|
}
|
|
|
|
// Mobile: column layout, sidebar is nearly full width (accounting for body padding)
|
|
const mobileResult = results.find((r) => r.viewport === 'mobile')
|
|
expect(mobileResult!.layoutDirection).toBe('column')
|
|
expect(mobileResult!.sidebarWidth).toBeGreaterThanOrEqual(200)
|
|
expect(mobileResult!.sidebarWidth).toBeLessThanOrEqual(375)
|
|
|
|
// Tablet+: row layout, fixed width sidebar
|
|
const tabletResult = results.find((r) => r.viewport === 'tablet')
|
|
expect(tabletResult!.layoutDirection).toBe('row')
|
|
expect(tabletResult!.sidebarWidth).toBe(200)
|
|
|
|
// Desktop: wider gap, wider sidebar
|
|
const desktopResult = results.find((r) => r.viewport === 'desktop')
|
|
expect(desktopResult!.layoutDirection).toBe('row')
|
|
expect(desktopResult!.sidebarWidth).toBe(250)
|
|
})
|
|
|
|
test('when guard for breakpoint behavior evaluates correctly', async ({ page }) => {
|
|
const ui = await imhotep(page)
|
|
await page.goto(await fixtureUrl('responsive'))
|
|
|
|
// At mobile width, sidebar should be full width (column layout)
|
|
await ui.applyEnvironment({
|
|
viewport: { width: 375, height: 667 },
|
|
})
|
|
await page.reload()
|
|
await waitForFixtureReady(page)
|
|
|
|
const sidebarData = await ui.extract('[data-testid="responsive-sidebar"]')
|
|
const sidebarRect = (sidebarData as any[])[0].rect
|
|
|
|
expect(sidebarRect.width).toBeGreaterThanOrEqual(200)
|
|
|
|
// At desktop width, sidebar should be 250px
|
|
await ui.applyEnvironment({
|
|
viewport: { width: 1280, height: 720 },
|
|
})
|
|
await page.reload()
|
|
await waitForFixtureReady(page)
|
|
|
|
const desktopSidebarData = await ui.extract('[data-testid="responsive-sidebar"]')
|
|
const desktopSidebarRect = (desktopSidebarData as any[])[0].rect
|
|
|
|
expect(desktopSidebarRect.width).toBe(250)
|
|
})
|
|
|
|
test('invariant assertions hold across all widths', async ({ page }) => {
|
|
const ui = await imhotep(page)
|
|
await page.goto(await fixtureUrl('responsive'))
|
|
|
|
const viewports = [
|
|
{ width: 320, height: 568 },
|
|
{ width: 768, height: 1024 },
|
|
{ width: 1440, height: 900 },
|
|
]
|
|
|
|
for (const viewport of viewports) {
|
|
await ui.applyEnvironment({
|
|
viewport: { width: viewport.width, height: viewport.height },
|
|
})
|
|
|
|
await page.reload()
|
|
await waitForFixtureReady(page)
|
|
|
|
const sidebarData = await ui.extract('[data-testid="responsive-sidebar"]')
|
|
const contentData = await ui.extract('[data-testid="responsive-content"]')
|
|
|
|
const sidebarRect = (sidebarData as any[])[0].rect
|
|
const contentRect = (contentData as any[])[0].rect
|
|
|
|
// Invariant: both elements should be visible
|
|
expect(sidebarRect.width).toBeGreaterThan(0)
|
|
expect(sidebarRect.height).toBeGreaterThan(0)
|
|
expect(contentRect.width).toBeGreaterThan(0)
|
|
expect(contentRect.height).toBeGreaterThan(0)
|
|
|
|
// Invariant: content should be to the right of or below sidebar
|
|
const isRightOf = contentRect.x >= sidebarRect.x + sidebarRect.width
|
|
const isBelow = contentRect.y >= sidebarRect.y + sidebarRect.height
|
|
expect(isRightOf || isBelow).toBe(true)
|
|
}
|
|
})
|
|
|
|
test('container query responds to container width not viewport', async ({ page }) => {
|
|
const ui = await imhotep(page)
|
|
await page.goto(await fixtureUrl('responsive'))
|
|
await waitForFixtureReady(page)
|
|
|
|
const containerData = await ui.extract('[data-testid="cq-container"]')
|
|
const itemData = await ui.extract('[data-testid="cq-item"]')
|
|
|
|
const containerRect = (containerData as any[])[0].rect
|
|
const itemRect = (itemData as any[])[0].rect
|
|
|
|
// Container is max-width: 600px, but let's check the item responds to it
|
|
expect(containerRect.width).toBeGreaterThan(0)
|
|
expect(itemRect.width).toBeGreaterThan(0)
|
|
|
|
// Item should be within container
|
|
expect(itemRect.x).toBeGreaterThanOrEqual(containerRect.x)
|
|
expect(itemRect.x + itemRect.width).toBeLessThanOrEqual(containerRect.x + containerRect.width)
|
|
})
|
|
})
|