feca-mailshots-plugin/tests/e2e/specs/mailshots-editor.spec.mjs

247 lines
11 KiB
JavaScript

import { test, expect } from '@playwright/test';
import { adminPath, ensureDataSource, cleanupByNames } from './helpers.mjs';
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 setJoditMode = async (page, modeName) => {
await page.evaluate((nextModeName) => {
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');
}
const modeValue = nextModeName === 'wysiwyg'
? window.Jodit.MODE_WYSIWYG
: window.Jodit.MODE_SOURCE;
if (typeof jodit.setMode !== 'function') {
throw new Error('Jodit setMode is unavailable');
}
jodit.setMode(modeValue);
}, modeName);
};
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('mailshots editor: datasource-driven options + overlay editors + create', async ({ page, request }) => {
const uniq = `${Date.now()}_${Math.floor(Math.random() * 100000)}`;
const dsName = `e2e_ms_ds_${uniq}`;
const purpose = `e2e_mailshot_${uniq}`;
try {
await ensureDataSource(request, dsName, 'contacts');
await page.goto(adminPath('feca-mailshots-mailshots'));
await expect(page.locator('h1', { hasText: 'Mailshots' })).toBeVisible();
await openNewMailshotEditor(page);
await page.locator('#ms_purpose').fill(purpose);
await page.locator('#ms_ds').selectOption({ label: dsName });
await expect.poll(async () => await page.locator('#ms-token-controls button, #ms-token-controls select option').count()).toBeGreaterThan(0);
await expect.poll(async () => await page.locator('#ms_pdfname option').count()).toBeGreaterThan(1);
await expect.poll(async () => await page.locator('#ms_email_field option').count()).toBeGreaterThan(1);
const tokenChooser = page.locator('#ms-token-controls .feca-token-format-controls');
await expect(tokenChooser).toBeVisible();
await tokenChooser.locator('select').first().selectOption({ index: 1 });
await tokenChooser.locator('select').nth(1).selectOption({ label: 'Long Date' });
await tokenChooser.getByRole('button', { name: 'Insert' }).click();
await expect(page.locator('#ms_subject')).toHaveValue(/\{\{ [^}]+ \| date\('l j F Y'\) \}\}/);
await page.locator('#ms_subject').fill('Subject {{ contacts.id }}');
await page.locator('#ms-edit-message').click();
await expect(page.locator('#ms-template-modal')).toBeVisible();
await expect(page.locator('.jodit-container')).toBeVisible();
await setJoditHtml(page, '<style>.red-note{color:red;}</style><div class="red-note">Hello <strong>bold</strong> {{ contacts.id }}</div>');
await page.locator('#ms-template-save').click();
await expect(page.locator('#ms_message')).toHaveValue(/\.red-note\{color:red;\}/);
await expect(page.locator('#ms_message')).toHaveValue(/class="red-note"/);
await expect(page.locator('#ms_message')).toHaveValue(/<strong>bold<\/strong>/);
await page.locator('#ms-edit-pdf').click();
await expect(page.locator('#ms-template-modal')).toBeVisible();
await setJoditHtml(page, '<p>PDF {{ contacts.id }}</p>');
await page.locator('#ms-template-save').click();
await expect(page.locator('#ms_pdfa')).toHaveValue(/<p>PDF \{\{ contacts\.id \}\}<\/p>/);
await page.locator('#ms_email_field').evaluate((el) => {
const select = /** @type {HTMLSelectElement} */ (el);
const option = Array.from(select.options).find((o) => o.value && /email/i.test(o.value)) || Array.from(select.options).find((o) => o.value);
if (option) {
select.value = option.value;
select.dispatchEvent(new Event('change', { bubbles: true }));
}
});
await page.getByRole('button', { name: 'Save' }).first().click();
await expect(page.getByRole('cell', { name: purpose })).toBeVisible();
} finally {
try {
await cleanupByNames(request, { dataSourceNames: [dsName], mailshotPurposes: [purpose] });
} catch {
// best effort
}
}
});
test('mailshots editor: message save closes modal and persists on reopen', async ({ page }) => {
const html = '<style>.red-note{color:red;}</style>\n<div class="red-note">Hello <strong>bold</strong> {{ contacts.id }}</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(/<style>\.red-note\{color:red;\}<\/style>/);
await expect(page.locator('#ms_message')).toHaveValue(/<div class="red-note">Hello <strong>bold<\/strong> {{ contacts\.id }}<\/div>/);
await expect.poll(async () => {
const text = (await page.locator('#ms-message-meta').innerText()).trim();
const match = text.match(/^(\d+)\s+chars$/);
return match ? Number(match[1]) : 0;
}).toBeGreaterThan(0);
await page.locator('#ms-edit-message').click();
await expect(page.locator('#ms-template-modal')).toBeVisible();
const reopened = await page.evaluate(() => {
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.synchronizeValues === 'function') {
jodit.synchronizeValues();
}
if (typeof jodit.getEditorValue === 'function') {
return String(jodit.getEditorValue() || '');
}
return String(jodit.value || '');
});
expect(reopened).toContain('.red-note{color:red;}');
expect(reopened).toContain('class="red-note"');
expect(reopened).toContain('<strong>bold</strong>');
});
test('mailshots editor: save works in WYSIWYG mode with a single 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 setJoditMode(page, 'wysiwyg');
await setJoditHtml(page, '<p>WYSIWYG body {{ contacts_id }}</p>');
await page.locator('#ms-template-save').click();
await expect(page.locator('#ms-template-modal')).toBeHidden();
await expect(page.locator('#ms_message')).toHaveValue(/WYSIWYG body/);
});
test('mailshots editor: allows save without recipient field and shows warning', async ({ page, request }) => {
const uniq = `${Date.now()}_${Math.floor(Math.random() * 100000)}`;
const dsName = `e2e_ms_ds_norec_${uniq}`;
const purpose = `e2e_mailshot_norec_${uniq}`;
try {
await ensureDataSource(request, dsName, 'contacts');
await page.goto(adminPath('feca-mailshots-mailshots'));
await expect(page.locator('h1', { hasText: 'Mailshots' })).toBeVisible();
await openNewMailshotEditor(page);
await page.locator('#ms_purpose').fill(purpose);
await page.locator('#ms_ds').selectOption({ label: dsName });
await page.locator('#ms_subject').fill('No recipient email field');
await page.locator('#ms-edit-message').click();
await expect(page.locator('#ms-template-modal')).toBeVisible();
await setJoditHtml(page, '<p>PDF-only workflow {{ contacts.id }}</p>');
await page.locator('#ms-template-save').click();
await expect(page.locator('#ms-recipient-warning')).toBeVisible();
await expect(page.locator('#ms-recipient-warning')).toContainText('cannot be used to send a mailshot');
await page.getByRole('button', { name: 'Save' }).first().click();
await expect(page.getByRole('cell', { name: purpose })).toBeVisible();
} finally {
try {
await cleanupByNames(request, { dataSourceNames: [dsName], mailshotPurposes: [purpose] });
} catch {
// best effort
}
}
});