mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-10 09:53:48 +00:00
refactor: extract cuesheet permissions
This commit is contained in:
committed by
Carlos Valente
parent
5be504ce8f
commit
df588b8845
@@ -1,49 +1,19 @@
|
||||
import { memo, use, useEffect, useMemo } from 'react';
|
||||
import { useSessionStorage } from '@mantine/hooks';
|
||||
import { memo, use, useMemo } from 'react';
|
||||
|
||||
import EmptyPage from '../../common/components/state/EmptyPage';
|
||||
import { PresetContext } from '../../common/context/PresetContext';
|
||||
import useCustomFields from '../../common/hooks-query/useCustomFields';
|
||||
import { sessionScope } from '../../externals';
|
||||
import { AppMode, sessionKeys } from '../../ontimeConfig';
|
||||
|
||||
import CuesheetDnd from './cuesheet-dnd/CuesheetDnd';
|
||||
import { makeCuesheetColumns } from './cuesheet-table/cuesheet-table-elements/cuesheetColsFactory';
|
||||
import CuesheetTable from './cuesheet-table/CuesheetTable';
|
||||
import { useCuesheetPermissions } from './useTablePermissions';
|
||||
import { useApplyCuesheetPolicy } from './useApplyCuesheetPolicy';
|
||||
|
||||
export default memo(CuesheetTableWrapper);
|
||||
function CuesheetTableWrapper() {
|
||||
const { data: customFields, status: customFieldStatus } = useCustomFields();
|
||||
const setPermissions = useCuesheetPermissions((state) => state.setPermissions);
|
||||
const preset = use(PresetContext);
|
||||
|
||||
// set permissions based on preset
|
||||
useEffect(() => {
|
||||
if (preset) {
|
||||
const fullWrite = preset.options?.write === 'full';
|
||||
setPermissions({
|
||||
canChangeMode: preset.options?.write !== '-',
|
||||
canCreateEntries: fullWrite,
|
||||
canEditEntries: fullWrite,
|
||||
canFlag: fullWrite || Boolean(preset.options?.write.includes('flag')),
|
||||
canShare: false, // TODO: should be sessionScope === 'rw' when we have granular scopes
|
||||
});
|
||||
} else {
|
||||
setPermissions({
|
||||
canChangeMode: true,
|
||||
canCreateEntries: true,
|
||||
canEditEntries: true,
|
||||
canFlag: true,
|
||||
canShare: sessionScope === 'rw',
|
||||
});
|
||||
}
|
||||
}, [preset, setPermissions]);
|
||||
|
||||
const [cuesheetMode] = useSessionStorage({
|
||||
key: preset ? `${preset.alias}${sessionKeys.cuesheetMode}` : sessionKeys.cuesheetMode,
|
||||
defaultValue: preset ? AppMode.Run : AppMode.Edit,
|
||||
});
|
||||
const { cuesheetMode, setCuesheetMode } = useApplyCuesheetPolicy(preset);
|
||||
|
||||
const columns = useMemo(
|
||||
() => makeCuesheetColumns(customFields, cuesheetMode, preset),
|
||||
@@ -54,7 +24,16 @@ function CuesheetTableWrapper() {
|
||||
|
||||
return (
|
||||
<CuesheetDnd columns={columns}>
|
||||
{isLoading ? <EmptyPage text='Loading...' /> : <CuesheetTable columns={columns} cuesheetMode={cuesheetMode} />}
|
||||
{isLoading ? (
|
||||
<EmptyPage text='Loading...' />
|
||||
) : (
|
||||
<CuesheetTable
|
||||
columns={columns}
|
||||
cuesheetMode={cuesheetMode}
|
||||
tableRoot='cuesheet'
|
||||
setCuesheetMode={setCuesheetMode}
|
||||
/>
|
||||
)}
|
||||
</CuesheetDnd>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import { OntimeView, URLPreset } from 'ontime-types';
|
||||
|
||||
import { AppMode } from '../../../ontimeConfig';
|
||||
import { getCuesheetColumnAccessPolicy, getCuesheetPermissionsPolicy } from '../cuesheet.policies';
|
||||
|
||||
describe('getCuesheetPermissionsPolicy()', () => {
|
||||
test('returns full permissions when there is no preset', () => {
|
||||
expect(getCuesheetPermissionsPolicy(undefined, true)).toEqual({
|
||||
canChangeMode: true,
|
||||
canCreateEntries: true,
|
||||
canEditEntries: true,
|
||||
canFlag: true,
|
||||
canShare: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('returns run-only permissions when write is disabled', () => {
|
||||
const preset: URLPreset = {
|
||||
enabled: true,
|
||||
alias: 'cuesheet-read-only',
|
||||
target: OntimeView.Cuesheet,
|
||||
search: '',
|
||||
options: {
|
||||
read: 'full',
|
||||
write: '-',
|
||||
},
|
||||
};
|
||||
|
||||
expect(getCuesheetPermissionsPolicy(preset, true)).toEqual({
|
||||
canChangeMode: false,
|
||||
canCreateEntries: false,
|
||||
canEditEntries: false,
|
||||
canFlag: false,
|
||||
canShare: false,
|
||||
});
|
||||
});
|
||||
|
||||
test('allows flag changes when write includes flag only', () => {
|
||||
const preset: URLPreset = {
|
||||
enabled: true,
|
||||
alias: 'cuesheet-flag',
|
||||
target: OntimeView.Cuesheet,
|
||||
search: '',
|
||||
options: {
|
||||
read: 'full',
|
||||
write: 'flag',
|
||||
},
|
||||
};
|
||||
|
||||
expect(getCuesheetPermissionsPolicy(preset, true)).toEqual({
|
||||
canChangeMode: true,
|
||||
canCreateEntries: false,
|
||||
canEditEntries: false,
|
||||
canFlag: true,
|
||||
canShare: false,
|
||||
});
|
||||
});
|
||||
|
||||
test('defaults to full read and write when cuesheet options are absent', () => {
|
||||
const preset: URLPreset = {
|
||||
enabled: true,
|
||||
alias: 'cuesheet-default',
|
||||
target: OntimeView.Cuesheet,
|
||||
search: '',
|
||||
};
|
||||
|
||||
const policy = getCuesheetColumnAccessPolicy(preset, AppMode.Edit);
|
||||
|
||||
expect(policy.canRead('title')).toBe(true);
|
||||
expect(policy.canWrite('title')).toBe(true);
|
||||
});
|
||||
|
||||
test('column access honors granular permissions and mode', () => {
|
||||
const preset: URLPreset = {
|
||||
enabled: true,
|
||||
alias: 'cuesheet-granular',
|
||||
target: OntimeView.Cuesheet,
|
||||
search: '',
|
||||
options: {
|
||||
read: 'cue,title',
|
||||
write: 'title',
|
||||
},
|
||||
};
|
||||
|
||||
const editPolicy = getCuesheetColumnAccessPolicy(preset, AppMode.Edit);
|
||||
const runPolicy = getCuesheetColumnAccessPolicy(preset, AppMode.Run);
|
||||
|
||||
expect(editPolicy.canRead('cue')).toBe(true);
|
||||
expect(editPolicy.canRead('duration')).toBe(false);
|
||||
expect(editPolicy.canWrite('title')).toBe(true);
|
||||
expect(editPolicy.canWrite('cue')).toBe(false);
|
||||
expect(runPolicy.canWrite('title')).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -34,13 +34,24 @@ import { useColumnOrder, useColumnSizes, useColumnVisibility } from './useColumn
|
||||
|
||||
import style from './CuesheetTable.module.scss';
|
||||
|
||||
interface CuesheetTableProps {
|
||||
type CuesheetTableBaseProps = {
|
||||
columns: ColumnDef<ExtendedEntry>[];
|
||||
cuesheetMode: AppMode;
|
||||
tableRoot?: 'editor' | 'cuesheet';
|
||||
}
|
||||
};
|
||||
|
||||
export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cuesheet' }: CuesheetTableProps) {
|
||||
type EditorCuesheetTableProps = CuesheetTableBaseProps & {
|
||||
tableRoot: 'editor';
|
||||
setCuesheetMode?: undefined;
|
||||
};
|
||||
|
||||
type ViewCuesheetTableProps = CuesheetTableBaseProps & {
|
||||
tableRoot: 'cuesheet';
|
||||
setCuesheetMode: (mode: AppMode) => void;
|
||||
};
|
||||
|
||||
type CuesheetTableProps = EditorCuesheetTableProps | ViewCuesheetTableProps;
|
||||
|
||||
export default function CuesheetTable({ columns, cuesheetMode, tableRoot, setCuesheetMode }: CuesheetTableProps) {
|
||||
const { data, status } = useFlatRundownWithMetadata();
|
||||
const { updateEntry, updateTimer } = useEntryActionsContext();
|
||||
|
||||
@@ -206,17 +217,25 @@ export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cues
|
||||
return <EmptyPage text='Loading...' />;
|
||||
}
|
||||
|
||||
// control components need different implementations for handling permissions
|
||||
const TableRootSettings = tableRoot === 'editor' ? EditorTableSettings : CuesheetTableSettings;
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableRootSettings
|
||||
columns={allLeafColumns}
|
||||
handleResetResizing={resetColumnResizing}
|
||||
handleResetReordering={resetColumnOrder}
|
||||
handleClearToggles={setAllVisible}
|
||||
/>
|
||||
{tableRoot === 'editor' ? (
|
||||
<EditorTableSettings
|
||||
columns={allLeafColumns}
|
||||
handleResetResizing={resetColumnResizing}
|
||||
handleResetReordering={resetColumnOrder}
|
||||
handleClearToggles={setAllVisible}
|
||||
/>
|
||||
) : (
|
||||
<CuesheetTableSettings
|
||||
columns={allLeafColumns}
|
||||
cuesheetMode={cuesheetMode}
|
||||
setCuesheetMode={setCuesheetMode}
|
||||
handleResetResizing={resetColumnResizing}
|
||||
handleResetReordering={resetColumnOrder}
|
||||
handleClearToggles={setAllVisible}
|
||||
/>
|
||||
)}
|
||||
<TableVirtuoso
|
||||
ref={virtuosoRef}
|
||||
data={data}
|
||||
|
||||
+2
-9
@@ -7,6 +7,7 @@ import DelayIndicator from '../../../../common/components/delay-indicator/DelayI
|
||||
import type { ExtendedEntry } from '../../../../common/utils/rundownMetadata';
|
||||
import { formatDuration, formatTime } from '../../../../common/utils/time';
|
||||
import { AppMode } from '../../../../ontimeConfig';
|
||||
import { getCuesheetColumnAccessPolicy } from '../../cuesheet.policies';
|
||||
|
||||
import DurationInput from './DurationInput';
|
||||
import EditableImage from './EditableImage';
|
||||
@@ -257,15 +258,7 @@ export function makeCuesheetColumns(
|
||||
preset: URLPreset | undefined,
|
||||
): ColumnDef<ExtendedEntry>[] {
|
||||
const columnsDef: ColumnDef<ExtendedEntry>[] = [];
|
||||
const modeAllowsWrite = cuesheetMode === AppMode.Edit;
|
||||
const fullRead = preset ? preset.options?.read === 'full' : true;
|
||||
const fullWrite = preset ? preset.options?.write === 'full' : true;
|
||||
const canWriteKeys = preset?.options?.write ? new Set(preset.options.write.split(',')) : new Set<string>();
|
||||
const canReadKeys = preset?.options?.read ? new Set(preset.options.read.split(',')) : new Set<string>();
|
||||
|
||||
// helpers to check read/write for a given key
|
||||
const canRead = (key: string) => fullRead || canReadKeys.has(key);
|
||||
const canWrite = (key: string) => modeAllowsWrite && (fullWrite || canWriteKeys.has(key));
|
||||
const { canRead, canWrite } = getCuesheetColumnAccessPolicy(preset, cuesheetMode);
|
||||
|
||||
if (canRead('flag')) {
|
||||
columnsDef.push({
|
||||
|
||||
+6
-10
@@ -1,20 +1,18 @@
|
||||
import { ReactNode, use } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import { IoBookOutline, IoChevronDown, IoOptions } from 'react-icons/io5';
|
||||
import { Popover } from '@base-ui/react/popover';
|
||||
import { Toggle } from '@base-ui/react/toggle';
|
||||
import { ToggleGroup } from '@base-ui/react/toggle-group';
|
||||
import { Toolbar } from '@base-ui/react/toolbar';
|
||||
import { useSessionStorage } from '@mantine/hooks';
|
||||
import type { Column } from '@tanstack/react-table';
|
||||
|
||||
import Button from '../../../../common/components/buttons/Button';
|
||||
import Checkbox from '../../../../common/components/checkbox/Checkbox';
|
||||
import * as Editor from '../../../../common/components/editor-utils/EditorUtils';
|
||||
import PopoverContents from '../../../../common/components/popover/Popover';
|
||||
import { PresetContext } from '../../../../common/context/PresetContext';
|
||||
import type { ExtendedEntry } from '../../../../common/utils/rundownMetadata';
|
||||
import { cx } from '../../../../common/utils/styleUtils';
|
||||
import { AppMode, sessionKeys } from '../../../../ontimeConfig';
|
||||
import { AppMode } from '../../../../ontimeConfig';
|
||||
import { CuesheetOptions, usePersistedCuesheetOptions } from '../../cuesheet.options';
|
||||
import { useCuesheetPermissions } from '../../useTablePermissions';
|
||||
|
||||
@@ -24,6 +22,8 @@ import style from './CuesheetTableSettings.module.scss';
|
||||
|
||||
interface CuesheetTableSettingsProps {
|
||||
columns: Column<ExtendedEntry, unknown>[];
|
||||
cuesheetMode: AppMode;
|
||||
setCuesheetMode: (mode: AppMode) => void;
|
||||
handleResetResizing: () => void;
|
||||
handleResetReordering: () => void;
|
||||
handleClearToggles: () => void;
|
||||
@@ -42,20 +42,16 @@ export interface ColumnSettingsProps {
|
||||
|
||||
export default function CuesheetTableSettings({
|
||||
columns,
|
||||
cuesheetMode,
|
||||
setCuesheetMode,
|
||||
handleResetResizing,
|
||||
handleResetReordering,
|
||||
handleClearToggles,
|
||||
}: CuesheetTableSettingsProps) {
|
||||
const canChangeMode = useCuesheetPermissions((state) => state.canChangeMode);
|
||||
const canShare = useCuesheetPermissions((state) => state.canShare);
|
||||
const preset = use(PresetContext);
|
||||
const options = usePersistedCuesheetOptions();
|
||||
|
||||
const [cuesheetMode, setCuesheetMode] = useSessionStorage({
|
||||
key: preset ? `${preset.alias}${sessionKeys.cuesheetMode}` : sessionKeys.cuesheetMode,
|
||||
defaultValue: preset ? AppMode.Run : AppMode.Edit,
|
||||
});
|
||||
|
||||
const toggleCuesheetMode = (mode: AppMode[]) => {
|
||||
// we need to stop user from deselecting a mode
|
||||
const newValue = mode.at(0);
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { URLPreset } from 'ontime-types';
|
||||
|
||||
import { AppMode } from '../../ontimeConfig';
|
||||
|
||||
import type { CuesheetPermissions } from './useTablePermissions';
|
||||
|
||||
function getPermissionKeys(permission: string | undefined): Set<string> {
|
||||
return permission ? new Set(permission.split(',')) : new Set<string>();
|
||||
}
|
||||
|
||||
function getCuesheetPermissions(readPermission: string | undefined, writePermission: string | undefined) {
|
||||
const readKeys = getPermissionKeys(readPermission);
|
||||
const writeKeys = getPermissionKeys(writePermission);
|
||||
const fullRead = readPermission == null || readPermission === 'full';
|
||||
const fullWrite = writePermission == null || writePermission === 'full';
|
||||
|
||||
return {
|
||||
writePermission,
|
||||
readKeys,
|
||||
writeKeys,
|
||||
fullRead,
|
||||
fullWrite,
|
||||
};
|
||||
}
|
||||
|
||||
export function getCuesheetPermissionsPolicy(
|
||||
preset: URLPreset | undefined,
|
||||
canShareInSession: boolean,
|
||||
): CuesheetPermissions {
|
||||
if (!preset) {
|
||||
return {
|
||||
canChangeMode: true,
|
||||
canCreateEntries: true,
|
||||
canEditEntries: true,
|
||||
canFlag: true,
|
||||
canShare: canShareInSession,
|
||||
};
|
||||
}
|
||||
|
||||
const { writePermission, writeKeys, fullWrite } = getCuesheetPermissions(
|
||||
preset?.options?.read,
|
||||
preset?.options?.write,
|
||||
);
|
||||
|
||||
return {
|
||||
canChangeMode: writePermission !== '-',
|
||||
canCreateEntries: fullWrite,
|
||||
canEditEntries: fullWrite,
|
||||
canFlag: fullWrite || writeKeys.has('flag'),
|
||||
canShare: false, // TODO: should be sessionScope === 'rw' when we have granular scopes
|
||||
};
|
||||
}
|
||||
|
||||
export function getCuesheetColumnAccessPolicy(preset: URLPreset | undefined, cuesheetMode: AppMode) {
|
||||
const { readKeys, writeKeys, fullRead, fullWrite } = getCuesheetPermissions(
|
||||
preset?.options?.read,
|
||||
preset?.options?.write,
|
||||
);
|
||||
const modeAllowsWrite = cuesheetMode === AppMode.Edit;
|
||||
|
||||
return {
|
||||
canRead: (key: string) => fullRead || readKeys.has(key),
|
||||
canWrite: (key: string) => modeAllowsWrite && (fullWrite || writeKeys.has(key)),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useSessionStorage } from '@mantine/hooks';
|
||||
import { URLPreset } from 'ontime-types';
|
||||
|
||||
import { sessionScope } from '../../externals';
|
||||
import { AppMode, sessionKeys } from '../../ontimeConfig';
|
||||
|
||||
import { getCuesheetPermissionsPolicy } from './cuesheet.policies';
|
||||
import { useCuesheetPermissions } from './useTablePermissions';
|
||||
|
||||
/**
|
||||
* Applies cuesheet permissions to shared state and exposes the effective mode for the UI.
|
||||
*/
|
||||
export function useApplyCuesheetPolicy(preset: URLPreset | undefined): {
|
||||
cuesheetMode: AppMode;
|
||||
setCuesheetMode: (mode: AppMode) => void;
|
||||
} {
|
||||
const setPermissions = useCuesheetPermissions((state) => state.setPermissions);
|
||||
const canShareInSession = sessionScope === 'rw';
|
||||
const permissions = useMemo(
|
||||
() => getCuesheetPermissionsPolicy(preset, canShareInSession),
|
||||
[preset, canShareInSession],
|
||||
);
|
||||
|
||||
const [storedCuesheetMode, setStoredCuesheetMode] = useSessionStorage({
|
||||
key: preset ? `${preset.alias}${sessionKeys.cuesheetMode}` : sessionKeys.cuesheetMode,
|
||||
defaultValue: preset ? AppMode.Run : AppMode.Edit,
|
||||
});
|
||||
|
||||
const cuesheetMode = permissions.canChangeMode ? storedCuesheetMode : AppMode.Run;
|
||||
const setCuesheetMode = useCallback(
|
||||
(mode: AppMode) => {
|
||||
if (!permissions.canChangeMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
setStoredCuesheetMode(mode);
|
||||
},
|
||||
[permissions.canChangeMode, setStoredCuesheetMode],
|
||||
);
|
||||
|
||||
// Keep the shared permissions store aligned with the active preset policy.
|
||||
useEffect(() => {
|
||||
setPermissions(permissions);
|
||||
}, [permissions, setPermissions]);
|
||||
|
||||
// Force Run mode whenever the policy forbids mode switching.
|
||||
useEffect(() => {
|
||||
if (!permissions.canChangeMode) {
|
||||
setStoredCuesheetMode((mode) => (mode === AppMode.Run ? mode : AppMode.Run));
|
||||
}
|
||||
}, [permissions.canChangeMode, setStoredCuesheetMode]);
|
||||
|
||||
return { cuesheetMode, setCuesheetMode };
|
||||
}
|
||||
@@ -6,9 +6,11 @@ interface CuesheetPermissionsStore {
|
||||
canEditEntries: boolean;
|
||||
canFlag: boolean;
|
||||
canShare: boolean;
|
||||
setPermissions: (permissions: Omit<CuesheetPermissionsStore, 'setPermissions'>) => void;
|
||||
setPermissions: (permissions: CuesheetPermissions) => void;
|
||||
}
|
||||
|
||||
export type CuesheetPermissions = Omit<CuesheetPermissionsStore, 'setPermissions'>;
|
||||
|
||||
export const useCuesheetPermissions = create<CuesheetPermissionsStore>((set) => ({
|
||||
canChangeMode: false,
|
||||
canCreateEntries: false,
|
||||
|
||||
@@ -132,14 +132,16 @@ test.describe('Sharing from cuesheet', () => {
|
||||
await page.close();
|
||||
});
|
||||
|
||||
test('Sharing a link with readonly permissions', async ({ page }) => {
|
||||
test('Sharing a link with readonly permissions', async ({ page }, testInfo) => {
|
||||
const alias = `cuesheet-read-test-${testInfo.retry}-${Date.now()}`;
|
||||
|
||||
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.locator('input[name="alias"]').fill(alias);
|
||||
await page.getByText('Custom write').click();
|
||||
await page.getByText('Custom read').click();
|
||||
await page.getByTestId('lockNav').click();
|
||||
@@ -151,9 +153,16 @@ test.describe('Sharing from cuesheet', () => {
|
||||
await page.getByTestId('write-duration').click();
|
||||
await page.getByTestId('write-note').click();
|
||||
|
||||
// disable any custom write permissions if they exist
|
||||
const customWriteSwitches = page.locator('[data-testid^="write-custom-"]');
|
||||
const customWriteSwitchCount = await customWriteSwitches.count();
|
||||
for (let i = 0; i < customWriteSwitchCount; i++) {
|
||||
await customWriteSwitches.nth(i).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(`preset/${alias}`);
|
||||
await expect(page.getByTestId('copy-link')).toContainText('n=1');
|
||||
|
||||
// verify the preset
|
||||
@@ -177,14 +186,16 @@ test.describe('Sharing from cuesheet', () => {
|
||||
await expect(page.getByRole('cell', { name: 'Duration' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('Sharing a link with scoped read-write permissions', async ({ page }) => {
|
||||
test('Sharing a link with scoped read-write permissions', async ({ page }, testInfo) => {
|
||||
const alias = `cuesheet-scope-test-${testInfo.retry}-${Date.now()}`;
|
||||
|
||||
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.locator('input[name="alias"]').fill(alias);
|
||||
await page.getByText('Custom write').click();
|
||||
await page.getByText('Custom read').click();
|
||||
await page.getByTestId('lockNav').click();
|
||||
@@ -203,7 +214,7 @@ test.describe('Sharing from cuesheet', () => {
|
||||
|
||||
// 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(`preset/${alias}`);
|
||||
await expect(page.getByTestId('copy-link')).toContainText('n=1');
|
||||
|
||||
// verify the preset
|
||||
@@ -220,8 +231,9 @@ test.describe('Sharing from cuesheet', () => {
|
||||
|
||||
// Verify that the title is visible and editable
|
||||
await expect(page.getByTestId('cuesheet-event').getByRole('cell', { name: 'title' })).toBeVisible();
|
||||
await page.getByTestId('cuesheet-event').getByTestId('cuesheet-editor-title').click();
|
||||
await expect(page.getByTestId('cuesheet-event').getByTestId('cuesheet-editor-title').locator('input')).toBeVisible();
|
||||
const titleEditor = page.getByTestId('cuesheet-event').getByTestId('cuesheet-editor-title');
|
||||
await titleEditor.click();
|
||||
await expect(titleEditor).toBeEditable();
|
||||
|
||||
// other elements are not there
|
||||
await expect(page.getByRole('cell', { name: 'Duration' })).toBeHidden();
|
||||
|
||||
Reference in New Issue
Block a user