diff options
Diffstat (limited to '')
| -rw-r--r-- | tests/math.spec.ts | 65 |
1 files changed, 65 insertions, 0 deletions
diff --git a/tests/math.spec.ts b/tests/math.spec.ts new file mode 100644 index 0000000..71b586d --- /dev/null +++ b/tests/math.spec.ts @@ -0,0 +1,65 @@ +import { test, expect } from '@playwright/test'; + +test('work equations typeset vector arrows and integral limits with accessible math', async ({ page }) => { + await page.goto('/#test-work'); + const equations = page.locator('#equations'); + await expect(equations.locator('.katex')).toHaveCount(2); + await expect(equations.locator('math')).toHaveCount(2); + await expect(equations.locator('math msubsup').first()).toBeAttached(); + await expect(equations.locator('math mover').first()).toBeAttached(); + await expect(page.locator('label[for="parameter-q0"] .katex')).toBeVisible(); + await expect(page.getByRole('slider', { name: 'Moving charge q₀', exact: true })).toBeVisible(); + await page.locator('#solution summary').click(); + await expect(page.locator('.math-step .katex')).toHaveCount(3); + await expect(page.locator('.katex-error')).toHaveCount(0); +}); + +test('every lesson summary and derivation renders without math parsing errors', async ({ page }) => { + const errors: string[] = []; + page.on('pageerror', error => errors.push(error.message)); + for (const id of ['test-work', 'test-conservation', 'test-central', 'test-assembly', 'test-uniform', 'test-dipole', 'field', 'flux', 'energy', 'potential', 'capacitance', 'dielectric', 'current', 'semiconductor']) { + await page.goto(`/#${id}`); + await expect(page.locator('#equations .katex').first()).toBeVisible(); + expect(await page.locator('#equations .katex').count()).toBe(await page.locator('#equations > p').count()); + if (id.startsWith('test-')) { + await page.locator('#solution details').evaluateAll(details => details.forEach(detail => detail.setAttribute('open', ''))); + expect(await page.locator('.math-step .katex').count()).toBe(await page.locator('.math-step').count()); + } + await expect(page.locator('.katex-error')).toHaveCount(0); + } + await page.goto('/#test-dipole'); + await expect(page.locator('#test-question .katex').first()).toBeVisible(); + await expect(page.locator('#explanation .katex').first()).toBeVisible(); + expect(errors).toEqual([]); +}); + +test('long formulas stay contained and usable on a narrow screen', async ({ page }) => { + await page.setViewportSize({ width: 390, height: 844 }); + await page.goto('/#test-assembly'); + await page.locator('#solution summary').first().click(); + await expect(page.locator('#equations .katex').first()).toBeVisible(); + expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true); + const equation = page.locator('#equations > p').first(); + const layout = await equation.evaluate(element => { + const style = getComputedStyle(element); + return { overflow: style.overflowX, width: element.getBoundingClientRect().width, available: element.parentElement!.getBoundingClientRect().width }; + }); + expect(layout.width).toBeLessThanOrEqual(layout.available); + expect(layout.overflow).toBe('auto'); +}); + +test('3D mathematical annotations track the scene and are removed when switching lessons', async ({ page }) => { + await page.goto('/#test-dipole'); + const labels = page.locator('#viewport .scene-math-label'); + await expect(labels.locator('math mfrac').first()).toBeAttached(); + await expect(labels.filter({ hasText: 'cos' }).first()).toBeVisible(); + const before = await labels.count(); + await page.getByRole('slider', { name: 'Final angle θᵦ', exact: true }).fill('180'); + await expect(labels).toHaveCount(before); + await page.getByRole('tab', { name: /Work & paths/ }).click(); + await expect(labels.filter({ hasText: 'cos' })).toHaveCount(0); + await expect(labels.locator('.katex').first()).toBeVisible(); + await expect(page.locator('#viewport .katex-error')).toHaveCount(0); + // Labels are an overlay; orbiting the underlying canvas remains possible. + expect(await page.locator('.scene-labels').evaluate(el => getComputedStyle(el).pointerEvents)).toBe('none'); +}); |