81 خطوط
4.1 KiB
TypeScript
81 خطوط
4.1 KiB
TypeScript
import { expect, test } from '@playwright/test'
|
|
|
|
async function login(page: import('@playwright/test').Page) {
|
|
await page.goto('http://127.0.0.1:5173/login')
|
|
await page.getByLabel(/ایمیل|Email/).fill('designer@microlearn.test')
|
|
await page.getByLabel(/رمز عبور|Password/).fill('password')
|
|
await page.getByRole('button', { name: /ورود|Sign in/ }).click()
|
|
await expect(page).toHaveURL(/\/app\/dashboard$/)
|
|
}
|
|
|
|
test('command palette traps focus and restores its trigger', async ({ page }) => {
|
|
await login(page)
|
|
const trigger = page.getByRole('button', { name: 'جستوجو و فرمان سریع' })
|
|
await trigger.focus(); await trigger.click()
|
|
const dialog = page.getByRole('dialog', { name: 'پالت فرمان' })
|
|
const input = dialog.getByPlaceholder('جستوجوی صفحه یا فرمان…')
|
|
await expect(input).toBeFocused()
|
|
await page.keyboard.press('Shift+Tab')
|
|
await expect(dialog.getByRole('button').last()).toBeFocused()
|
|
await page.keyboard.press('Tab')
|
|
await expect(input).toBeFocused()
|
|
await page.keyboard.press('Escape')
|
|
await expect(dialog).toHaveCount(0)
|
|
await expect(trigger).toBeFocused()
|
|
})
|
|
|
|
test('library tabs support arrows, Home, and End', async ({ page }) => {
|
|
await login(page)
|
|
await page.goto('http://127.0.0.1:5173/app/library')
|
|
const tabs = page.getByRole('tablist', { name: 'نوع فایل' })
|
|
const all = tabs.getByRole('tab', { name: 'همه' })
|
|
await all.focus(); await all.press('ArrowRight')
|
|
await expect(tabs.getByRole('tab', { name: 'تصاویر' })).toBeFocused()
|
|
await expect(tabs.getByRole('tab', { name: 'تصاویر' })).toHaveAttribute('aria-selected', 'true')
|
|
await page.keyboard.press('End')
|
|
await expect(tabs.getByRole('tab', { name: 'اسناد' })).toBeFocused()
|
|
await page.keyboard.press('Home')
|
|
await expect(all).toBeFocused()
|
|
})
|
|
|
|
test('semantic colors meet contrast targets and reduced motion is honored', async ({ page }) => {
|
|
await page.emulateMedia({ reducedMotion: 'reduce' })
|
|
await page.goto('http://127.0.0.1:5173/login')
|
|
for (const theme of ['light', 'dark'] as const) {
|
|
await page.evaluate(value => window.localStorage.setItem('microlearn.theme', value), theme)
|
|
await page.reload()
|
|
await expect(page.locator('html')).toHaveAttribute('data-theme', theme)
|
|
const result = await page.evaluate(() => {
|
|
const style = getComputedStyle(document.documentElement)
|
|
const rgb = (value: string) => {
|
|
const hex = value.trim().replace('#', '')
|
|
return [0, 2, 4].map(offset => Number.parseInt(hex.slice(offset, offset + 2), 16))
|
|
}
|
|
const luminance = (value: string) => {
|
|
const channels = rgb(value).map(channel => channel / 255).map(channel => channel <= .04045 ? channel / 12.92 : ((channel + .055) / 1.055) ** 2.4)
|
|
return .2126 * channels[0] + .7152 * channels[1] + .0722 * channels[2]
|
|
}
|
|
const contrast = (foreground: string, background: string) => {
|
|
const [lighter, darker] = [luminance(foreground), luminance(background)].sort((a, b) => b - a)
|
|
return (lighter + .05) / (darker + .05)
|
|
}
|
|
const token = (name: string) => style.getPropertyValue(name)
|
|
const probe = document.createElement('button')
|
|
probe.className = 'button'
|
|
document.body.append(probe)
|
|
const transition = getComputedStyle(probe).transitionDuration.split(',').map(value => value.trim().endsWith('ms') ? Number.parseFloat(value) / 1000 : Number.parseFloat(value))
|
|
probe.remove()
|
|
return {
|
|
text: contrast(token('--text'), token('--surface')),
|
|
muted: contrast(token('--text-muted'), token('--surface')),
|
|
focus: contrast(token('--focus'), token('--canvas')),
|
|
maxTransitionSeconds: Math.max(...transition),
|
|
}
|
|
})
|
|
expect(result.text, `${theme} primary text contrast`).toBeGreaterThanOrEqual(4.5)
|
|
expect(result.muted, `${theme} secondary text contrast`).toBeGreaterThanOrEqual(4.5)
|
|
expect(result.focus, `${theme} focus indicator contrast`).toBeGreaterThanOrEqual(3)
|
|
expect(result.maxTransitionSeconds, `${theme} reduced motion`).toBeLessThanOrEqual(.001)
|
|
}
|
|
})
|