feat: cuesheet sharing

feat: locked presets

refactor: simplify locked param

refactor: create share links
This commit is contained in:
Carlos Valente
2025-07-22 05:45:20 +02:00
committed by Carlos Valente
parent 1695b4dc68
commit 6c6f5c2c0f
62 changed files with 1661 additions and 478 deletions
+12
View File
@@ -47,6 +47,18 @@ test.describe('test view navigation feature', () => {
page.locator('data-testid=timer-view');
await expect(page).toHaveURL('http://localhost:4001/timer');
});
test('not-found', async ({ page }) => {
await page.goto('http://localhost:4001/not-found');
await expect(page).toHaveTitle(/ontime/);
await expect(page.getByRole('heading', { name: 'Not found' })).toBeVisible();
await page.goto('http://localhost:4001/preset/not-found');
await expect(page).toHaveTitle(/ontime/);
await expect(page.getByRole('heading', { name: 'Not found' })).toBeVisible();
});
});
async function openNavigationMenu(page: Page) {
@@ -14,4 +14,8 @@ test('message control sends messages to screens', async ({ context }) => {
await featurePage.goto('http://localhost:4001/timer');
await featurePage.waitForLoadState('load', { timeout: 5000 });
await expect(featurePage.getByText('testing stage')).toBeVisible();
await editorPage.getByRole('button', { name: /toggle timer message/i }).click({ timeout: 5000 });
await expect(featurePage.getByText('TIME NOW')).toBeVisible();
});
+214 -18
View File
@@ -1,28 +1,224 @@
import { expect, test } from '@playwright/test';
test('URL preset feature, it should redirect to given URL', async ({ page }) => {
await page.goto('http://localhost:4001/editor');
const aliasName = 'testing';
const aliasUrl =
'www.getontime.no/team/timer/?hideTimerSeconds=true&showLeadingZeros=true&freezeOvertime=true&hidePhase=true&hideClock=true&hideCards=true&hideProgress=true&hideMessage=true&hideSecondary=true&hideLogo=true';
// open settings
await page.getByRole('button', { name: 'Toggle settings' }).click();
await page.getByRole('button', { name: 'URL Presets' }).click();
test.describe('URL Preset', () => {
test.beforeAll(async ({ browser }) => {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('http://localhost:4001/editor');
// create preset
await page.getByRole('heading', { name: 'URL presets New' }).getByRole('button').scrollIntoViewIfNeeded();
await page.getByRole('heading', { name: 'URL presets New' }).getByRole('button').click();
// Create the preset that will be used by other tests
await page.getByRole('button', { name: 'Toggle settings' }).click();
await page.getByRole('button', { name: 'URL Presets' }).click();
await page.locator('input[name="alias"]').click();
await page.locator('input[name="alias"]').fill('testing');
await page.getByRole('heading', { name: 'URL presets New' }).getByRole('button').scrollIntoViewIfNeeded();
await page.getByRole('heading', { name: 'URL presets New' }).getByRole('button').click();
await page.getByRole('textbox', { name: 'Paste URL' }).click();
await page.getByRole('textbox', { name: 'Paste URL' }).fill('www.getontime.no/team/countdown');
await page.getByRole('button', { name: 'Generate' }).click();
await page.locator('input[name="alias"]').click();
await page.locator('input[name="alias"]').fill(aliasName);
await page.getByRole('combobox').filter({ hasText: 'Countdown' });
await page.getByRole('textbox', { name: 'Paste URL' }).click();
await page.getByRole('textbox', { name: 'Paste URL' }).fill(aliasUrl);
await page.getByRole('button', { name: 'Generate' }).click();
await page.getByRole('button', { name: 'Save' }).click();
await page.getByRole('combobox').filter({ hasText: 'Timer' });
await page.getByRole('button', { name: 'Save' }).click();
// make sure preset works
await page.goto('http://localhost:4001/testing');
await expect(page.getByTestId('countdown-view')).toBeVisible();
await page.close();
});
test('Unwrapping a preset from a view', async ({ page }) => {
await page.goto('http://localhost:4001/timer');
// 1. the URL points to the view
expect(page.url().includes('hideTimerSeconds=true')).not.toBeTruthy();
expect(page.url().includes('alias=testing')).not.toBeTruthy();
// open settings
await page.getByRole('button', { name: 'Toggle settings' }).click();
await page
.locator('div')
.filter({ hasText: /^testingApply$/ })
.getByRole('button')
.click();
await expect(page.getByRole('button', { name: 'Applied' })).toBeVisible();
// 2. the URL contains the preset
expect(page.url().includes('hideTimerSeconds=true')).toBeTruthy();
expect(page.url().includes('alias=testing')).toBeTruthy();
});
test('Sharing a link to an unwrapped preset', async ({ page }) => {
await page.goto('http://localhost:4001/editor');
// open settings
await page.getByRole('button', { name: 'Toggle settings' }).click();
await page.getByRole('button', { name: 'Share link' }).click();
// select options
await page.getByRole('combobox').filter({ hasText: 'Timer' }).click();
await page.getByText('URL Preset: testing').click();
// create and verify link
await page.getByRole('button', { name: 'Create share link' }).click();
await expect(page.getByTestId('copy-link')).toContainText('testing');
await expect(page.getByTestId('copy-link')).not.toContainText('n=1');
// verify the preset
const generatedUrl = await page.getByTestId('copy-link').textContent();
await page.goto(generatedUrl);
// make sure preset works in mask mode
await expect(page.getByTestId('timer-view')).toBeVisible();
// the url unwraps the preset
expect(page.url().includes('hideTimerSeconds=true')).toBeTruthy();
expect(page.url().includes('alias=testing')).toBeTruthy();
expect(page.url().includes('/preset/testing')).not.toBeTruthy();
// the menus work
await expect(page.getByTestId('navigation__toggle-settings')).toBeVisible();
});
test('Sharing a link to a masked preset', async ({ page }) => {
await page.goto('http://localhost:4001/editor');
// open settings
await page.getByRole('button', { name: 'Toggle settings' }).click();
await page.getByRole('button', { name: 'Share link' }).click();
// select options
await page.getByRole('combobox').filter({ hasText: 'Timer' }).click();
await page.getByText('URL Preset: testing').click();
await page.locator('button[name="lockNav"]').click();
await page.locator('button[name="lockConfig"]').click();
// create and verify link
await page.getByRole('button', { name: 'Create share link' }).click();
await expect(page.getByTestId('copy-link')).toContainText('/preset/testing');
await expect(page.getByTestId('copy-link')).toContainText('n=1');
// verify the preset
const generatedUrl = await page.getByTestId('copy-link').textContent();
await page.goto(generatedUrl);
// make sure preset works in mask mode
await expect(page.getByTestId('timer-view')).toBeVisible();
// the url masks the preset
expect(page.url().includes('hideTimerSeconds=true')).not.toBeTruthy();
expect(page.url().includes('alias=testing')).not.toBeTruthy();
expect(page.url().includes('/preset/testing')).toBeTruthy();
});
});
test.describe('Sharing from cuesheet', () => {
test.beforeAll(async ({ browser }) => {
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('http://localhost:4001/editor');
// we create some elements to test with
await page.getByRole('button', { name: 'Clear all' }).click();
await page.getByRole('button', { name: 'Delete all' }).click();
await page.getByRole('button', { name: 'Create Event' }).click();
await page.getByTestId('entry-1').getByTestId('block__title').click();
await page.getByTestId('entry-1').getByTestId('block__title').fill('title 1');
await page.getByTestId('entry-1').getByTestId('block__title').press('Enter');
await page.close();
});
test('Sharing a link with readonly permissions', async ({ page }) => {
await page.goto('http://localhost:4001/cuesheet');
await expect(page.getByTestId('cuesheet')).toBeVisible();
await page.getByRole('button', { name: 'Share...' }).click();
// configure share for readonly
await page.getByRole('textbox').fill('cuesheet-read-test');
await page.getByText('Custom write').click();
await page.getByText('Custom read').click();
await page.locator('button[name="lockNav"]').click();
await page.getByTestId('write-flag').click();
await page.getByTestId('write-cue').click();
await page.getByTestId('write-title').click();
await page.getByTestId('write-timeStart').click();
await page.getByTestId('write-timeEnd').click();
await page.getByTestId('write-duration').click();
await page.getByTestId('write-note').click();
// create and verify link
await page.getByRole('button', { name: 'Create share link' }).click();
await expect(page.getByTestId('copy-link')).toContainText('preset/cuesheet-read-test');
await expect(page.getByTestId('copy-link')).toContainText('n=1');
// verify the preset
const generatedUrl = await page.getByTestId('copy-link').textContent();
await page.goto(generatedUrl);
// the menu is locked and we cant make shares
await expect(page.getByTestId('cuesheet')).toBeVisible();
await expect(page.getByTestId('navigation__toggle-settings')).toBeHidden();
await expect(page.getByRole('button', { name: 'Share...' })).toBeHidden();
// check that we are locked and cannot edit
await page.getByRole('button', { name: 'Edit' }).click();
// Verify that the title is visible but not editable
await expect(page.getByTestId('cuesheet-event').getByText('title 1')).toBeVisible();
await expect(page.getByTestId('cuesheet-event').locator('input')).toBeHidden();
// other elements are still there
await expect(page.getByRole('cell', { name: 'Duration' })).toBeVisible();
});
test('Sharing a link with scoped read-write permissions', async ({ page }) => {
await page.goto('http://localhost:4001/cuesheet');
await expect(page.getByTestId('cuesheet')).toBeVisible();
await page.getByRole('button', { name: 'Share...' }).click();
// configure share for readonly
await page.getByRole('textbox').fill('cuesheet-scope-test');
await page.getByText('Custom write').click();
await page.getByText('Custom read').click();
await page.locator('button[name="lockNav"]').click();
await page.getByTestId('write-flag').click();
await page.getByTestId('write-cue').click();
await page.getByTestId('write-timeStart').click();
await page.getByTestId('write-timeEnd').click();
await page.getByTestId('write-duration').click();
await page.getByTestId('write-note').click();
await page.getByTestId('read-flag').click();
await page.getByTestId('read-cue').click();
await page.getByTestId('read-timeStart').click();
await page.getByTestId('read-timeEnd').click();
await page.getByTestId('read-duration').click();
await page.getByTestId('read-note').click();
// create and verify link
await page.getByRole('button', { name: 'Create share link' }).click();
await expect(page.getByTestId('copy-link')).toContainText('preset/cuesheet-scope-test');
await expect(page.getByTestId('copy-link')).toContainText('n=1');
// verify the preset
const generatedUrl = await page.getByTestId('copy-link').textContent();
await page.goto(generatedUrl);
// the menu is locked and we cant make shares
await expect(page.getByTestId('cuesheet')).toBeVisible();
await expect(page.getByTestId('navigation__toggle-settings')).toBeHidden();
await expect(page.getByRole('button', { name: 'Share...' })).toBeHidden();
// check that we are locked and cannot edit
await page.getByRole('button', { name: 'Edit' }).click();
// Verify that the title is visible and editable
await expect(page.getByTestId('cuesheet-event').getByRole('cell', { name: 'title' })).toBeVisible();
// other elements are not there
await expect(page.getByRole('cell', { name: 'Duration' })).toBeHidden();
});
});
+2 -1
View File
@@ -8,7 +8,8 @@ test('View params configures timer view', async ({ page }) => {
await page.mouse.move(Math.random() * 100, Math.random() * 100);
await page.getByTestId('navigation__toggle-settings').click();
await page.locator('label').filter({ hasText: 'Hide Time NowHides the Time' }).locator('span').nth(2).click();
await page.getByRole('button', { name: 'Apply' }).click();
await page.getByTestId('apply-view-params').click();
await page.getByTestId('close-view-params').click();
await expect(page.getByText('TIME NOW', { exact: true })).not.toBeInViewport();
await expect(page).toHaveURL(/.*hideClock=true/);