// ============================================================ // SETTINGS (React port) — Security sub-sections smoke tests. // // Mirrors the coverage of settings-faq-dictation.spec.js for the // Change Password / 2FA / Active Sessions sections, but against the // React tree at /app/settings (ported in commit 1 of the Settings // migration). The vanilla tree at / continues to be covered by // settings-faq-dictation.spec.js. // ============================================================ const { test, expect, E2E_BASE } = require('../fixtures'); async function openReactSettings(page) { await page.goto(E2E_BASE + '/app/settings'); // Page loads /api/auth/me first — wait for the form to hydrate. await page.waitForSelector('[data-testid="change-password-section"], [data-testid="2fa-section"], [data-testid="sessions-section"]', { timeout: 15000, }); } test.describe('React Settings — Security sections render for local-auth user', () => { test('change-password form has all three fields + submit button', async ({ authedPage: _, page }) => { await openReactSettings(page); await expect(page.locator('[data-testid="pw-current"]')).toBeVisible(); await expect(page.locator('[data-testid="pw-new"]')).toBeVisible(); await expect(page.locator('[data-testid="pw-confirm"]')).toBeVisible(); await expect(page.locator('[data-testid="btn-change-password"]')).toBeVisible(); }); test('2FA section shows status + at least one setup/disable button', async ({ authedPage: _, page }) => { await openReactSettings(page); await expect(page.locator('[data-testid="2fa-status"]')).toBeVisible(); const setupCount = await page.locator('[data-testid="btn-setup-2fa"]').count(); const disableCount = await page.locator('[data-testid="btn-disable-2fa"]').count(); expect(setupCount + disableCount).toBeGreaterThan(0); }); test('active sessions section lists the current session + revoke-all button', async ({ authedPage: _, page }) => { await openReactSettings(page); await expect(page.locator('[data-testid="sessions-section"]')).toBeVisible(); await expect(page.locator('[data-testid="btn-revoke-all-sessions"]')).toBeVisible(); // At least one session row should render (the current one). const rowCount = await page.locator('[data-testid^="session-row-"]').count(); expect(rowCount).toBeGreaterThan(0); }); test('change-password validation — mismatched confirm shows inline error', async ({ authedPage: _, page }) => { await openReactSettings(page); await page.fill('[data-testid="pw-current"]', 'whatever'); await page.fill('[data-testid="pw-new"]', 'newpassword123'); await page.fill('[data-testid="pw-confirm"]', 'different456'); await page.click('[data-testid="btn-change-password"]'); await expect(page.getByText('Passwords do not match')).toBeVisible(); }); test('revoke-all sessions triggers a styled confirm modal (not a native dialog)', async ({ authedPage: _, page }) => { // If the code ever regresses to window.confirm(), Playwright's dialog event // would fire and this test would hang / fail with a dialog warning. let nativeDialogFired = false; page.on('dialog', async (d) => { nativeDialogFired = true; await d.dismiss(); }); await openReactSettings(page); await page.click('[data-testid="btn-revoke-all-sessions"]'); // Styled modal must be visible with the cancel button from ConfirmModal. await expect(page.locator('[data-testid="confirm-modal-cancel"]')).toBeVisible(); await page.click('[data-testid="confirm-modal-cancel"]'); expect(nativeDialogFired).toBe(false); }); });