289 lines
12 KiB
JavaScript
289 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);
|
|
};
|
|
|
|
const ensureMailshotModalsClosed = async (page) => {
|
|
const acceptDialog = async (dialog) => {
|
|
await dialog.accept();
|
|
};
|
|
page.on('dialog', acceptDialog);
|
|
try {
|
|
const templateModal = page.locator('#ms-template-modal');
|
|
if (await templateModal.isVisible().catch(() => false)) {
|
|
await page.locator('#ms-template-close').click();
|
|
await expect(templateModal).toBeHidden();
|
|
}
|
|
|
|
const editorModal = page.locator('#ms-editor-modal');
|
|
if (await editorModal.isVisible().catch(() => false)) {
|
|
await page.locator('#ms-close-editor').click();
|
|
await expect(editorModal).toBeHidden();
|
|
}
|
|
} finally {
|
|
page.off('dialog', acceptDialog);
|
|
}
|
|
};
|
|
|
|
const openNewMailshotEditor = async (page) => {
|
|
await ensureMailshotModalsClosed(page);
|
|
await page.locator('#ms-open-new').click();
|
|
await expect(page.locator('#ms-editor-modal')).toBeVisible();
|
|
};
|
|
|
|
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 openNewMailshotEditor(page);
|
|
|
|
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 openNewMailshotEditor(page);
|
|
|
|
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 openNewMailshotEditor(page);
|
|
|
|
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 openNewMailshotEditor(page);
|
|
|
|
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-edit-message').click();
|
|
await expect(page.locator('#ms-template-modal')).toBeVisible();
|
|
await setJoditHtml(page, '');
|
|
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')).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('');
|
|
});
|