import { test, expect } from './fixtures'; // Uses the verification template, not the password reset one. Stored templates // live in admin_settings and are global per database, so two spec files editing // the same key race each other across Playwright's workers — which is exactly // what happened when this was first written against passwordReset, the key // email-templates.spec.ts already owns. Different key, no race. // // Each test still restores what it touched, so a later spec reads the built-in // copy rather than whatever this left behind. async function restore(request: import('@playwright/test').APIRequestContext, key: string) { await request.delete(`/api/admin/email-templates/${key}`); } const LABEL = 'Email verification'; test.describe.configure({ mode: 'serial' }); test.describe('Inserting placeholders from the chips', () => { test.afterEach(async ({ page }) => { await restore(page.request, 'verification'); }); test('inserts into the body at the caret, not at the end', async ({ admin, adminEmails }) => { await admin.open('Emails'); await adminEmails.openTemplate(LABEL); const body = adminEmails.body(LABEL); await body.fill('BEFORE AFTER'); // Caret between the two words. Appending would put the tag after AFTER, // which is the failure this test exists to catch. await body.click(); await body.press('Control+Home'); for (let i = 0; i < 'BEFORE '.length; i++) await body.press('ArrowRight'); await adminEmails.placeholderChip('greeting').click(); await expect(body).toHaveValue('BEFORE {{greeting}}AFTER'); }); test('leaves the caret after the tag, so typing continues from there', async ({ admin, adminEmails }) => { await admin.open('Emails'); await adminEmails.openTemplate(LABEL); const body = adminEmails.body(LABEL); await body.fill(''); await body.click(); await adminEmails.placeholderChip('greeting').click(); // No click in between: the field must still hold focus with the caret // placed just past what was inserted. await body.pressSequentially('!'); await expect(body).toHaveValue('{{greeting}}!'); }); test('replaces a selection rather than inserting alongside it', async ({ admin, adminEmails }) => { await admin.open('Emails'); await adminEmails.openTemplate(LABEL); const body = adminEmails.body(LABEL); await body.fill('REPLACE ME'); await body.click(); await body.press('Control+a'); await adminEmails.placeholderChip('verifyUrl').click(); await expect(body).toHaveValue('{{verifyUrl}}'); }); test('inserts into the subject when that was the field last used', async ({ admin, adminEmails }) => { await admin.open('Emails'); await adminEmails.openTemplate(LABEL); // Captured first: this template's default body already contains // {{greeting}}, so "the body does not contain the tag" would be false // before the click as well as after. Unchanged is the real claim. const bodyBefore = await adminEmails.body(LABEL).inputValue(); const subject = adminEmails.subject(LABEL); await subject.fill('Hi there'); await subject.click(); await subject.press('Control+Home'); for (let i = 0; i < 'Hi '.length; i++) await subject.press('ArrowRight'); await adminEmails.placeholderChip('greeting').click(); await expect(subject).toHaveValue('Hi {{greeting}} there'); await expect(adminEmails.body(LABEL)).toHaveValue(bodyBefore); }); test('is reachable and operable from the keyboard', async ({ page, admin, adminEmails }) => { await admin.open('Emails'); await adminEmails.openTemplate(LABEL); await adminEmails.body(LABEL).fill(''); await adminEmails.body(LABEL).click(); // Focused directly rather than tabbed to, since the number of tab stops // before it is a layout detail. What matters is that a button can take // focus at all and responds to a key — an antd Tag renders a span and // could do neither. const chip = adminEmails.placeholderChip('greeting'); await chip.focus(); await expect(chip).toBeFocused(); await page.keyboard.press('Enter'); await expect(adminEmails.body(LABEL)).toHaveValue('{{greeting}}'); }); });