feca-mailshots-plugin/tests/e2e/specs/modal-regressions.spec.mjs

261 lines
12 KiB
JavaScript

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) => `<p>Line ${i + 1}</p>`).join('');
const html = `<style>body{font-family:Cambria, serif;}</style><div class="regression-one-click">One click save marker</div>${longBody}<div id="tail-marker">Tail marker</div>`;
await page.goto(adminPath('feca-mailshots-mailshots'));
await expect(page.locator('h1', { hasText: 'Mailshots' })).toBeVisible();
await page.locator('#ms-open-new').click();
await expect(page.locator('#ms-editor-modal')).toBeVisible();
await page.locator('#ms-edit-message').click();
await expect(page.locator('#ms-template-modal')).toBeVisible();
await setJoditHtml(page, html);
await page.locator('#ms-template-save').click();
await expect(page.locator('#ms-template-modal')).toBeHidden();
await expect(page.locator('#ms_message')).toHaveValue(/regression-one-click/);
await expect(page.locator('#ms_message')).toHaveValue(/tail-marker/);
await expect(page.locator('#ms-message-snippet')).toContainText('One click save marker');
});
test('regression: message editor single-save with invoicing template (scroll case)', async ({ page }) => {
await page.goto(adminPath('feca-mailshots-mailshots'));
await expect(page.locator('h1', { hasText: 'Mailshots' })).toBeVisible();
await page.locator('#ms-open-new').click();
await expect(page.locator('#ms-editor-modal')).toBeVisible();
await page.locator('#ms-edit-message').click();
await expect(page.locator('#ms-template-modal')).toBeVisible();
await setJoditHtml(page, invoicingHtml);
await page.evaluate(() => {
const host = /** @type {HTMLTextAreaElement|null} */ (document.getElementById('ms-template-jodit'));
if (host) {
host.scrollTop = host.scrollHeight;
host.focus();
}
const modal = document.getElementById('ms-template-modal');
if (!modal) { return; }
const areas = modal.querySelectorAll('textarea');
areas.forEach((ta) => {
const el = /** @type {HTMLTextAreaElement} */ (ta);
el.scrollTop = el.scrollHeight;
el.focus();
});
});
await page.locator('#ms-template-save').click();
await expect(page.locator('#ms-template-modal')).toBeHidden();
await expect(page.locator('#ms_message')).toHaveValue(/invoice-header/);
await expect(page.locator('#ms_message')).toHaveValue(/Registered Charity No\. 293020/);
});
test('regression: message editor manual source edit saves on first click', async ({ page }) => {
await page.goto(adminPath('feca-mailshots-mailshots'));
await expect(page.locator('h1', { hasText: 'Mailshots' })).toBeVisible();
await page.locator('#ms-open-new').click();
await expect(page.locator('#ms-editor-modal')).toBeVisible();
await page.locator('#ms-edit-message').click();
await expect(page.locator('#ms-template-modal')).toBeVisible();
await setJoditHtml(page, invoicingHtml);
await page.evaluate(() => {
const modal = document.getElementById('ms-template-modal');
if (!modal) { return; }
const sourceAreas = Array.from(modal.querySelectorAll('textarea'))
.filter((el) => el.offsetParent !== null);
const source = sourceAreas[sourceAreas.length - 1] || null;
if (!source) { return; }
source.scrollTop = source.scrollHeight;
source.focus();
source.setSelectionRange(source.value.length, source.value.length);
source.value = `${source.value}\n<!-- manual-edit-marker -->`;
source.dispatchEvent(new Event('input', { bubbles: true }));
source.dispatchEvent(new Event('change', { bubbles: true }));
});
await page.locator('#ms-template-save').click();
await expect(page.locator('#ms-template-modal')).toBeHidden();
await expect(page.locator('#ms_message')).toHaveValue(/manual-edit-marker/);
await expect(page.locator('#ms_message')).toHaveValue(/invoice-header/);
});
test('regression: mailshot validation failure keeps entered values in modal', async ({ page }) => {
await page.goto(adminPath('feca-mailshots-mailshots'));
await expect(page.locator('h1', { hasText: 'Mailshots' })).toBeVisible();
await page.locator('#ms-open-new').click();
await expect(page.locator('#ms-editor-modal')).toBeVisible();
await page.locator('#ms_purpose').fill('validation draft mailshot');
await page.locator('#ms_subject').fill('');
await page.locator('#ms-edit-message').click();
await expect(page.locator('#ms-template-modal')).toBeVisible();
await setJoditHtml(page, '<p>Draft body should survive validation failure</p>');
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: '<p>Original message</p>',
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('');
});