import { test, expect } from '@playwright/test'; import { adminPath } from './helpers.mjs'; import { ensureDataSource, ensureMailshot, cleanupByNames } from './helpers.mjs'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const invoicingHtml = fs.readFileSync(path.resolve(__dirname, '../../../samples/invoicing.html'), 'utf8'); const setJoditHtml = async (page, html) => { await page.evaluate((nextHtml) => { const host = /** @type {any} */ (document.getElementById('ms-template-jodit')); const instances = (window.Jodit && window.Jodit.instances) ? window.Jodit.instances : null; let jodit = host && host.jodit ? host.jodit : null; if (!jodit && instances) { jodit = instances['ms-template-jodit'] || instances.ms_template_jodit || null; } if (!jodit && instances && typeof instances === 'object') { const vals = Array.isArray(instances) ? instances : Object.values(instances); jodit = vals.find((x) => x && x.container && x.container.isConnected) || null; } if (!jodit) { throw new Error('Jodit instance not available'); } if (typeof jodit.setEditorValue === 'function') { jodit.setEditorValue(nextHtml); } else { jodit.value = nextHtml; } if (typeof jodit.synchronizeValues === 'function') { jodit.synchronizeValues(); } }, html); }; test('regression: mailshot message modal saves and closes on first click', async ({ page }) => { const longBody = Array.from({ length: 120 }, (_, i) => `
Line ${i + 1}
`).join(''); const html = `Draft body should survive validation failure
'); await page.locator('#ms-template-save').click(); await expect(page.locator('#ms-template-modal')).toBeHidden(); await page.locator('#mailshot-editor button[type="submit"]').click(); await expect(page.locator('#ms-editor-modal')).toBeVisible(); await expect(page.locator('#mailshot-editor')).toContainText('Please fix the following:'); await expect(page.locator('#mailshot-editor')).toContainText('Subject is required.'); await expect(page.locator('.wrap')).not.toContainText('Mailshot action failed.'); await expect(page.locator('#ms_purpose')).toHaveValue('validation draft mailshot'); await expect(page.locator('#ms_message')).toHaveValue(/Draft body should survive validation failure/); }); test('regression: editing existing mailshot allows empty message for pdf-only workflow', async ({ page, request }) => { const uniq = `${Date.now()}_${Math.floor(Math.random() * 100000)}`; const dsName = `e2e_ms_edit_ds_${uniq}`; const purpose = `e2e_mailshot_edit_${uniq}`; const editedPurpose = `${purpose}_edited`; let mailshotId = ''; try { await ensureDataSource(request, dsName, 'contacts'); const saved = await ensureMailshot(request, { Purpose: purpose, DataSource: dsName, CC: '', BCC: '', Subject: 'Original subject', Message: 'Original message
', PDFAttachment: '', AttachmentNames: '[]', PDFFilenameDerivedFrom: '', RecipientEmailField: 'contacts.contact_email_1', ReplyTo: '' }); mailshotId = String(saved.id || ''); await page.goto(`${adminPath('feca-mailshots-mailshots')}&edit_id=${encodeURIComponent(mailshotId)}`); await expect(page.locator('#ms-editor-modal')).toBeVisible(); await page.locator('#ms_purpose').fill(editedPurpose); await page.locator('#ms_subject').fill('Edited subject'); await page.locator('#ms_message').fill(''); await page.locator('#mailshot-editor button[type="submit"]').click(); await expect(page.locator('#ms-editor-modal')).toBeHidden(); await expect(page.getByRole('cell', { name: editedPurpose })).toBeVisible(); } finally { try { await cleanupByNames(request, { dataSourceNames: [dsName], mailshotPurposes: [purpose, editedPurpose] }); } catch { // best effort } } }); test('regression: attachments new button opens modal editor', async ({ page }) => { await page.goto(adminPath('feca-mailshots-attachments')); await expect(page.locator('h1', { hasText: 'Attachments' })).toBeVisible(); await page.locator('#att-open-new').click(); await expect(page.locator('#att-editor-modal')).toBeVisible(); await expect(page.locator('#att-editor-form')).toBeVisible(); await expect(page.locator('#att-editor-id')).toHaveValue(''); }); test('regression: attachments upload populates filename and failed save preserves data', async ({ page }) => { await page.goto(adminPath('feca-mailshots-attachments')); await expect(page.locator('h1', { hasText: 'Attachments' })).toBeVisible(); await page.locator('#att-open-new').click(); await expect(page.locator('#att-editor-modal')).toBeVisible(); await page.locator('#att_name').fill('draft_attachment'); await page.setInputFiles('#att_file_upload', { name: 'sample-attachment.txt', mimeType: 'text/plain', buffer: Buffer.from('attachment regression sample', 'utf8') }); await expect(page.locator('#att_file_name')).toHaveValue('sample-attachment.txt'); await page.locator('#att_name').fill(''); await page.getByRole('button', { name: 'Create Attachment' }).click(); await expect(page.locator('#att-editor-modal')).toBeVisible(); await expect(page.locator('#att-editor-form')).toContainText('Please fix the following:'); await expect(page.locator('#att-editor-form')).toContainText('name is required.'); await expect(page.locator('#att_file_name')).toHaveValue('sample-attachment.txt'); await expect(page.locator('#att_base64')).not.toHaveValue(''); }); test('regression: pdf assets new button opens modal editor', async ({ page }) => { await page.goto(adminPath('feca-mailshots-pdf-assets')); await expect(page.locator('h1', { hasText: 'PDF Assets' })).toBeVisible(); await page.locator('#pdf-open-new').click(); await expect(page.locator('#pdf-editor-modal')).toBeVisible(); await expect(page.locator('#pdf-editor-form')).toBeVisible(); await expect(page.locator('#pdf-editor-id')).toHaveValue(''); }); test('regression: pdf assets failed save keeps modal values and shows inline errors', async ({ page }) => { await page.goto(adminPath('feca-mailshots-pdf-assets')); await expect(page.locator('h1', { hasText: 'PDF Assets' })).toBeVisible(); await page.locator('#pdf-open-new').click(); await expect(page.locator('#pdf-editor-modal')).toBeVisible(); await page.locator('#pdf_name').fill('draft_pdf_asset'); await page.locator('#pdf_width').fill(''); await page.locator('#pdf_height').fill(''); await page.locator('#pdf_file_name').fill(''); await page.setInputFiles('#pdf_file_upload', { name: 'sample-asset.png', mimeType: 'image/png', buffer: Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00]) }); await expect(page.locator('#pdf_file_name')).toHaveValue('sample-asset.png'); await page.locator('#pdf_file_name').fill(''); await page.getByRole('button', { name: 'Create PDF Asset' }).click(); await expect(page.locator('#pdf-editor-modal')).toBeVisible(); await expect(page.locator('#pdf-editor-form')).toContainText('Please fix the following:'); await expect(page.locator('#pdf-editor-form')).toContainText('file_name is required.'); await expect(page.locator('#pdf-editor-form')).toContainText('width_mm and height_mm must be > 0.'); await expect(page.locator('#pdf_name')).toHaveValue('draft_pdf_asset'); await expect(page.locator('#pdf_base64')).not.toHaveValue(''); });