Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions client-v3/e2e/tests/03-system-config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,50 @@ test('changing a setting enables the Submit and Reset buttons', async () => {
}
});

test('Security category is visible in the settings page', async () => {
await expect(page.locator('.card-header:has-text("Security")')).toBeVisible({ timeout: 5_000 });
});

test('Security category can be expanded to reveal JWT Token Lifetime', async () => {
// Security card is collapsed by default — click the header to expand
await page.locator('.card-header:has-text("Security")').click();
await expect(page.locator('#jwt_token_lifetime_hours-input')).toBeVisible({ timeout: 5_000 });
});

test('JWT Token Lifetime dropdown shows human-readable labels', async () => {
const select = page.locator('#jwt_token_lifetime_hours-input');
// The option text should be human-readable, not a raw number
await expect(select.locator('option').filter({ hasText: '1 day' })).toBeAttached({
timeout: 5_000,
});
await expect(select.locator('option').filter({ hasText: '1 week' })).toBeAttached();
await expect(select.locator('option').filter({ hasText: '1 month' })).toBeAttached();
});

test('can change JWT Token Lifetime and submit successfully', async () => {
const select = page.locator('#jwt_token_lifetime_hours-input');
// Change to 6 hours
await select.selectOption({ label: '6 hours' });
await expect(page.locator('button:has-text("Submit")')).toBeEnabled({ timeout: 3_000 });
await page.locator('button:has-text("Submit")').click();
// Use .v-toast__item to avoid strict-mode violation (.v-toast has two container elements)
await expect(page.locator('.v-toast__item:has-text("Saved settings")')).toBeVisible({
timeout: 5_000,
});
// Wait for Submit to re-disable — the WS SETTINGS_CHANGED broadcast resets editSettings to
// match the server, making hasChanges false. Selecting before this settles causes a race where
// the WS update overwrites our selection and Submit stays disabled.
await expect(page.locator('button:has-text("Submit")')).toBeDisabled({ timeout: 10_000 });
// Restore default (1 day = 24 hours)
await select.selectOption({ label: '1 day' });
await expect(page.locator('button:has-text("Submit")')).toBeEnabled({ timeout: 3_000 });
await page.locator('button:has-text("Submit")').click();
await page
.locator('.v-toast__item')
.waitFor({ state: 'detached', timeout: 10_000 })
.catch(() => {});
});

// ── System tab ────────────────────────────────────────────────────────────

test('switches to the System tab', async () => {
Expand Down
64 changes: 61 additions & 3 deletions client-v3/e2e/tests/14-user-settings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import {
ADMIN_PASSWORD,
loginAsAdmin,
waitForAppReady,
waitForModal,
confirmModal,
waitForModalClosed,
confirmDialog,
} from '../helpers.js';
import { registerRetryHooks } from '../db-snapshot.js';
Expand Down Expand Up @@ -36,9 +39,7 @@ test.afterAll(async () => {
// ── About ─────────────────────────────────────────────────────────────────

test('Settings page renders the About tab', async () => {
await expect(
page.locator('.nav-link:has-text("About"), button[role="tab"]:has-text("About")')
).toBeVisible();
await expect(page.locator('button[role="tab"]:has-text("About")')).toBeVisible();
});

// ── Settings ──────────────────────────────────────────────────────────────
Expand All @@ -64,12 +65,69 @@ test('changing a toggle enables the Submit button', async () => {

// ── Stage Direction Styles ────────────────────────────────────────────────

test('creates a stage direction style for override testing', async () => {
// spec-10 cleaned up after itself, so we need a fresh style here
await page.goto(`${UI_BASE}/show-config/script`);
await waitForAppReady(page);
await page.click('.nav-link:has-text("Stage Direction Styles")');
await expect(page.locator('button:has-text("New Style")')).toBeVisible({ timeout: 5_000 });
await page.click('button:has-text("New Style")');
await waitForModal(page, 'Add New Style');
await page.fill('.modal.show input[type="text"]', 'Action');
await confirmModal(page);
await waitForModalClosed(page);
await expect(page.locator('td:has-text("Action")')).toBeVisible();
await page.goto(`${UI_BASE}/me`);
await waitForAppReady(page);
});

test('switches to Stage Direction Styles tab', async () => {
await page.click('.nav-link:has-text("Stage Direction")');
// Use the component-specific table ID to avoid matching hidden tab-panel elements
await expect(page.locator('#stage-directions-table')).toBeVisible({ timeout: 5_000 });
});

test('"New Override" button is enabled when stage direction styles exist', async () => {
// Scope to thead to avoid matching the "New Override" button in the Cue Colour Preferences tab
// (BVN BTabs without lazy mounts all tab panels, so inactive tab content stays in the DOM)
await expect(
page.locator('#stage-directions-table thead button:has-text("New Override")')
).toBeEnabled();
});

test('clicking "New Override" opens the style selection modal', async () => {
await page.click('#stage-directions-table thead button:has-text("New Override")');
await waitForModal(page, 'Add New Override');
// OK is disabled until a style is selected
await expect(page.locator('.modal.show .modal-footer button.btn-primary')).toBeDisabled();
});

test('selecting a style and clicking OK opens the configuration modal', async () => {
await page.locator('.modal.show select').selectOption({ index: 1 });
await confirmModal(page);
// The config modal is identified by an input unique to it (both modals share the same title)
await expect(page.locator('#new-text-colour-input')).toBeVisible({ timeout: 5_000 });
});

test('submitting the configuration creates the override', async () => {
await confirmModal(page);
await waitForModalClosed(page);
await expect(page.locator('#stage-directions-table td:has-text("Action")')).toBeVisible({
timeout: 5_000,
});
});

test('deletes the stage direction override', async () => {
const row = page.locator('#stage-directions-table tr', {
has: page.locator('td:has-text("Action")'),
});
await row.locator('button:has-text("Delete")').click();
await confirmDialog(page);
await expect(page.locator('#stage-directions-table td:has-text("Action")')).not.toBeVisible({
timeout: 5_000,
});
});

// ── Change Password ───────────────────────────────────────────────────────

test('switches to Change Password tab', async () => {
Expand Down
Loading
Loading