import { type Page, expect, test } from '@playwright/test'; /** * The note field is used as the script source throughout: every event in the * test fixture has one, which keeps these tests independent of how custom field * keys happen to be spelled in the fixture. */ const teleprompterUrl = '/teleprompter?script=note'; function scroller(page: Page) { return page.getByTestId('teleprompter-scroller'); } function scrollTop(page: Page) { return scroller(page).evaluate((element) => element.scrollTop); } test.describe('teleprompter', () => { test('shows the script from the selected source', async ({ page }) => { await page.goto(teleprompterUrl); await expect(page.getByTestId('teleprompter-view')).toBeVisible(); await expect(scroller(page)).toBeVisible(); // the fixture uses cue style notes, the first event is Albania await expect(page.getByText('SF1.01', { exact: true })).toBeVisible(); }); test('asks for a script source when none is selected', async ({ page }) => { await page.goto('/teleprompter'); await expect(page.getByTestId('teleprompter-view')).toBeVisible(); await expect(page.getByText('Select which field holds the script in the view options')).toBeVisible(); }); test('space starts and stops the scroll', async ({ page }) => { await page.goto(teleprompterUrl); await expect(scroller(page)).toBeVisible(); expect(await scrollTop(page)).toBe(0); await page.keyboard.press('Space'); await expect.poll(() => scrollTop(page)).toBeGreaterThan(0); await page.keyboard.press('Space'); await page.waitForTimeout(200); const afterPause = await scrollTop(page); await page.waitForTimeout(500); expect(await scrollTop(page)).toBe(afterPause); }); test('space drives playback instead of opening the navigation menu', async ({ page }) => { // the navigation menu binds Space globally, the teleprompter claims it back await page.goto('/timer'); await expect(page.locator('data-testid=timer-view')).toBeVisible(); await page.keyboard.press('Space'); await expect(page.getByRole('dialog')).toBeVisible(); await page.goto(teleprompterUrl); await expect(scroller(page)).toBeVisible(); await page.keyboard.press('Space'); await expect(page.getByRole('dialog')).toHaveCount(0); // and the claim is released once the view goes away await page.goto('/timer'); await expect(page.locator('data-testid=timer-view')).toBeVisible(); await page.keyboard.press('Space'); await expect(page.getByRole('dialog')).toBeVisible(); }); test('arrow keys nudge and home rewinds', async ({ page }) => { await page.goto(teleprompterUrl); await expect(scroller(page)).toBeVisible(); await page.keyboard.press('ArrowDown'); await page.keyboard.press('ArrowDown'); await expect.poll(() => scrollTop(page)).toBeGreaterThan(0); // the rewind eases rather than snapping, so poll rather than guess a duration await page.keyboard.press('Home'); await expect.poll(() => scrollTop(page)).toBe(0); }); test('arrow keys change the speed', async ({ page }) => { await page.goto(teleprompterUrl); await expect(scroller(page)).toBeVisible(); const readout = page.getByTestId('teleprompter-speed'); const before = Number(await readout.innerText().then((text) => text.replace(/\D/g, ''))); await page.keyboard.press('ArrowRight'); const faster = Number(await readout.innerText().then((text) => text.replace(/\D/g, ''))); expect(faster).toBeGreaterThan(before); await page.keyboard.press('ArrowLeft'); const slower = Number(await readout.innerText().then((text) => text.replace(/\D/g, ''))); expect(slower).toBe(before); }); test('f mirrors the view for a beam splitter rig', async ({ page }) => { await page.goto(teleprompterUrl); await expect(scroller(page)).toBeVisible(); const view = page.getByTestId('teleprompter-view'); await page.keyboard.press('f'); // a horizontal flip is a negative x scale in the computed matrix const transform = await view.evaluate((element) => getComputedStyle(element).transform); expect(transform.startsWith('matrix(-1')).toBe(true); }); test('honours the flip and reading line params', async ({ page }) => { await page.goto('/teleprompter?script=note&flipV=true&readingLine=arrows'); const view = page.getByTestId('teleprompter-view'); const transform = await view.evaluate((element) => getComputedStyle(element).transform); // a vertical flip leaves x positive and makes the y scale negative expect(transform).toMatch(/^matrix\(1, 0, 0, -1/); }); });