a5dcae80e7
fixes PROD-202
481 lines
23 KiB
TypeScript
481 lines
23 KiB
TypeScript
import {NewslettersResponseType} from '@tryghost/admin-x-framework/api/newsletters';
|
|
import {chooseOptionInSelect, limitRequests, mockApi, responseFixtures} from '@tryghost/admin-x-framework/test/acceptance';
|
|
import {expect, test} from '@playwright/test';
|
|
import {globalDataRequests} from '../../utils/acceptance';
|
|
|
|
test.describe('Newsletter settings', async () => {
|
|
test('Supports creating a new newsletter', async ({page}) => {
|
|
const {lastApiRequests} = await mockApi({page, requests: {
|
|
...globalDataRequests,
|
|
browseNewsletters: {method: 'GET', path: '/newsletters/?include=count.active_members%2Ccount.posts&limit=50', response: responseFixtures.newsletters},
|
|
addNewsletter: {method: 'POST', path: '/newsletters/?opt_in_existing=true&include=count.active_members%2Ccount.posts', response: {newsletters: [{
|
|
id: 'new-newsletter',
|
|
name: 'New newsletter',
|
|
description: null,
|
|
count: {
|
|
active_members: 0,
|
|
posts: 0
|
|
}
|
|
}]}}
|
|
}});
|
|
|
|
await page.goto('/');
|
|
|
|
const section = page.getByTestId('newsletters');
|
|
|
|
await section.getByRole('button', {name: 'Add newsletter'}).click();
|
|
|
|
const modal = page.getByTestId('add-newsletter-modal');
|
|
await modal.getByRole('button', {name: 'Create'}).click();
|
|
|
|
await expect(page.getByTestId('toast-error')).toHaveText(/Can't save newsletter/);
|
|
await expect(modal).toHaveText(/Please enter a name/);
|
|
|
|
// Shouldn't be necessary, but without these Playwright doesn't click Create the second time for some reason
|
|
await modal.getByRole('button', {name: 'Cancel'}).click();
|
|
await section.getByRole('button', {name: 'Add newsletter'}).click();
|
|
|
|
await modal.getByLabel('Name').fill('New newsletter');
|
|
await modal.getByRole('button', {name: 'Create'}).click();
|
|
|
|
await expect(page.getByTestId('newsletter-modal')).toHaveCount(1);
|
|
|
|
expect(lastApiRequests.addNewsletter?.body).toMatchObject({
|
|
newsletters: [{
|
|
name: 'New newsletter'
|
|
}]
|
|
});
|
|
});
|
|
|
|
test('Supports updating a newsletter', async ({page}) => {
|
|
const {lastApiRequests} = await mockApi({page, requests: {
|
|
...globalDataRequests,
|
|
browseNewsletters: {method: 'GET', path: '/newsletters/?include=count.active_members%2Ccount.posts&limit=50', response: responseFixtures.newsletters},
|
|
editNewsletter: {method: 'PUT', path: `/newsletters/${responseFixtures.newsletters.newsletters[0].id}/?include=count.active_members%2Ccount.posts`, response: {
|
|
newsletters: [{
|
|
...responseFixtures.newsletters.newsletters[0],
|
|
name: 'Updated newsletter',
|
|
body_font_category: 'sans_serif'
|
|
}]
|
|
}}
|
|
}});
|
|
|
|
await page.goto('/');
|
|
|
|
const section = page.getByTestId('newsletters');
|
|
|
|
await section.getByText('Awesome newsletter').click();
|
|
|
|
const modal = page.getByTestId('newsletter-modal');
|
|
|
|
await modal.getByPlaceholder('Weekly Roundup').fill('');
|
|
await modal.getByRole('button', {name: 'Save'}).click();
|
|
|
|
await expect(page.getByTestId('toast-error')).toHaveText(/Can't save newsletter/);
|
|
await expect(modal).toHaveText(/Please enter a name/);
|
|
|
|
await modal.getByPlaceholder('Weekly Roundup').fill('Updated newsletter');
|
|
|
|
await modal.getByRole('tab', {name: 'Design'}).click();
|
|
await chooseOptionInSelect(modal.getByTestId('body-font-select'), 'Clean sans-serif');
|
|
|
|
await modal.getByRole('button', {name: 'Save'}).click();
|
|
|
|
await expect(section.getByText('Updated newsletter')).toHaveCount(1);
|
|
|
|
expect(lastApiRequests.editNewsletter?.body).toMatchObject({
|
|
newsletters: [{
|
|
id: responseFixtures.newsletters.newsletters[0].id,
|
|
name: 'Updated newsletter',
|
|
body_font_category: 'sans_serif'
|
|
}]
|
|
});
|
|
});
|
|
|
|
test.describe('Email addresses', async () => {
|
|
test.describe('For self-hosters', async () => {
|
|
test('Displays a prompt when email verification is required', async ({page}) => {
|
|
await mockApi({page, requests: {
|
|
...globalDataRequests,
|
|
browseNewsletters: {method: 'GET', path: '/newsletters/?include=count.active_members%2Ccount.posts&limit=50', response: responseFixtures.newsletters},
|
|
editNewsletter: {method: 'PUT', path: `/newsletters/${responseFixtures.newsletters.newsletters[0].id}/?include=count.active_members%2Ccount.posts`, response: {
|
|
newsletters: [responseFixtures.newsletters.newsletters[0]],
|
|
meta: {
|
|
sent_email_verification: ['sender_email']
|
|
}
|
|
}}
|
|
}});
|
|
|
|
await page.goto('/');
|
|
|
|
const section = page.getByTestId('newsletters');
|
|
|
|
await section.getByText('Awesome newsletter').click();
|
|
|
|
const modal = page.getByTestId('newsletter-modal');
|
|
|
|
await modal.getByLabel('Sender email').fill('not-an-email');
|
|
await modal.getByRole('button', {name: 'Save'}).click();
|
|
|
|
await expect(page.getByTestId('toast-error')).toHaveText(/Can't save newsletter/);
|
|
await expect(modal).toHaveText(/Invalid email/);
|
|
|
|
await modal.getByLabel('Sender email').fill('test@test.com');
|
|
await modal.getByRole('button', {name: 'Save'}).click();
|
|
|
|
await expect(page.getByTestId('confirmation-modal')).toHaveCount(1);
|
|
await expect(page.getByTestId('confirmation-modal')).toHaveText(/Confirm newsletter email address/);
|
|
await expect(page.getByTestId('confirmation-modal')).toHaveText(/default email address \(default@example.com\)/);
|
|
});
|
|
|
|
test('Displays the current email when changing sender address', async ({page}) => {
|
|
const response = {
|
|
...responseFixtures.newsletters,
|
|
newsletters: [{
|
|
...responseFixtures.newsletters.newsletters[0],
|
|
sender_email: 'current@test.com'
|
|
}]
|
|
} satisfies NewslettersResponseType;
|
|
|
|
await mockApi({page, requests: {
|
|
...globalDataRequests,
|
|
browseNewsletters: {method: 'GET', path: '/newsletters/?include=count.active_members%2Ccount.posts&limit=50', response},
|
|
editNewsletter: {method: 'PUT', path: `/newsletters/${responseFixtures.newsletters.newsletters[0].id}/?include=count.active_members%2Ccount.posts`, response: {
|
|
newsletters: response.newsletters,
|
|
meta: {
|
|
sent_email_verification: ['sender_email']
|
|
}
|
|
}}
|
|
}});
|
|
|
|
await page.goto('/');
|
|
|
|
const section = page.getByTestId('newsletters');
|
|
|
|
await section.getByText('Awesome newsletter').click();
|
|
|
|
const modal = page.getByTestId('newsletter-modal');
|
|
|
|
await modal.getByLabel('Sender email').fill('not-an-email');
|
|
await modal.getByRole('button', {name: 'Save'}).click();
|
|
|
|
await expect(page.getByTestId('toast-error')).toHaveText(/Can't save newsletter/);
|
|
await expect(modal).toHaveText(/Invalid email/);
|
|
|
|
await modal.getByLabel('Sender email').fill('test@test.com');
|
|
await modal.getByRole('button', {name: 'Save'}).click();
|
|
|
|
await expect(page.getByTestId('confirmation-modal')).toHaveCount(1);
|
|
await expect(page.getByTestId('confirmation-modal')).toHaveText(/Confirm newsletter email address/);
|
|
await expect(page.getByTestId('confirmation-modal')).toHaveText(/previous email address \(current@test.com\)/);
|
|
});
|
|
});
|
|
|
|
test.describe('For Ghost (Pro) users without custom domain', () => {
|
|
test('Does not allow the Sender email address to be edited', async ({page}) => {
|
|
await mockApi({page, requests: {
|
|
...globalDataRequests,
|
|
browseNewsletters: {method: 'GET', path: '/newsletters/?include=count.active_members%2Ccount.posts&limit=50', response: responseFixtures.newsletters},
|
|
browseConfig: {
|
|
...globalDataRequests.browseConfig,
|
|
response: {
|
|
config: {
|
|
...responseFixtures.config.config,
|
|
hostSettings: {
|
|
managedEmail: {
|
|
enabled: true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}});
|
|
|
|
await page.goto('/');
|
|
const section = page.getByTestId('newsletters');
|
|
await section.getByText('Awesome newsletter').click();
|
|
const modal = page.getByTestId('newsletter-modal');
|
|
const senderEmailField = modal.getByLabel('Sender email');
|
|
|
|
// Test that there is no input field near "Sender email"
|
|
const parentElementLocator = senderEmailField.locator('xpath=..');
|
|
const inputElementsNearby = await parentElementLocator.locator('input').count();
|
|
|
|
expect(inputElementsNearby).toBe(0);
|
|
});
|
|
|
|
test('Allow full customisation of the reply-to address', async ({page}) => {
|
|
await mockApi({page, requests: {
|
|
...globalDataRequests,
|
|
browseNewsletters: {method: 'GET', path: '/newsletters/?include=count.active_members%2Ccount.posts&limit=50', response: responseFixtures.newsletters},
|
|
editNewsletter: {method: 'PUT', path: `/newsletters/${responseFixtures.newsletters.newsletters[0].id}/?include=count.active_members%2Ccount.posts`, response: {
|
|
newsletters: [responseFixtures.newsletters.newsletters[0]],
|
|
meta: {
|
|
sent_email_verification: ['sender_reply_to']
|
|
}
|
|
}},
|
|
browseConfig: {
|
|
...globalDataRequests.browseConfig,
|
|
response: {
|
|
config: {
|
|
...responseFixtures.config.config,
|
|
hostSettings: {
|
|
managedEmail: {
|
|
enabled: true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}});
|
|
|
|
await page.goto('/');
|
|
const section = page.getByTestId('newsletters');
|
|
await section.getByText('Awesome newsletter').click();
|
|
const modal = page.getByTestId('newsletter-modal');
|
|
const replyToEmail = modal.getByLabel('Reply-to email');
|
|
|
|
await replyToEmail.fill('not-an-email');
|
|
await modal.getByRole('button', {name: 'Save'}).click();
|
|
|
|
await expect(page.getByTestId('toast-error')).toHaveText(/Can't save newsletter/);
|
|
await expect(modal).toHaveText(/Invalid email/);
|
|
|
|
await replyToEmail.fill('test@test.com');
|
|
await modal.getByRole('button', {name: 'Save'}).click();
|
|
|
|
await expect(page.getByTestId('confirmation-modal')).toHaveCount(1);
|
|
await expect(page.getByTestId('confirmation-modal')).toHaveText(/Confirm reply-to address/);
|
|
await expect(page.getByTestId('confirmation-modal')).toHaveText(/sent a confirmation email to test@test.com/);
|
|
await expect(page.getByTestId('confirmation-modal')).toHaveText(/replies will continue to go to support@example.com/);
|
|
});
|
|
});
|
|
|
|
test.describe('For Ghost (Pro) users with custom sending domain', () => {
|
|
test('Allow sender address to be changed partially (username but not domain name)', async ({page}) => {
|
|
await mockApi({page, requests: {
|
|
...globalDataRequests,
|
|
browseNewsletters: {method: 'GET', path: '/newsletters/?include=count.active_members%2Ccount.posts&limit=50', response: responseFixtures.newsletters},
|
|
editNewsletter: {method: 'PUT', path: `/newsletters/${responseFixtures.newsletters.newsletters[0].id}/?include=count.active_members%2Ccount.posts`, response: {
|
|
newsletters: [responseFixtures.newsletters.newsletters[0]],
|
|
meta: {
|
|
sent_email_verification: []
|
|
}
|
|
}},
|
|
browseConfig: {
|
|
...globalDataRequests.browseConfig,
|
|
response: {
|
|
config: {
|
|
...responseFixtures.config.config,
|
|
hostSettings: {
|
|
managedEmail: {
|
|
enabled: true,
|
|
sendingDomain: 'customdomain.com'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}});
|
|
|
|
await page.goto('/');
|
|
const section = page.getByTestId('newsletters');
|
|
await section.getByText('Awesome newsletter').click();
|
|
const modal = page.getByTestId('newsletter-modal');
|
|
const senderEmail = modal.getByLabel('Sender email');
|
|
|
|
// The sender email field should keep the username part of the email address
|
|
await senderEmail.fill('harry@potter.com');
|
|
expect(await senderEmail.inputValue()).toBe('harry');
|
|
|
|
// The new username is saved without a confirmation popup
|
|
await modal.getByRole('button', {name: 'Save'}).click();
|
|
await expect(page.getByTestId('confirmation-modal')).toHaveCount(0);
|
|
});
|
|
|
|
test('Allow full customisation of the reply-to address, with verification', async ({page}) => {
|
|
await mockApi({page, requests: {
|
|
...globalDataRequests,
|
|
browseNewsletters: {method: 'GET', path: '/newsletters/?include=count.active_members%2Ccount.posts&limit=50', response: responseFixtures.newsletters},
|
|
editNewsletter: {method: 'PUT', path: `/newsletters/${responseFixtures.newsletters.newsletters[0].id}/?include=count.active_members%2Ccount.posts`, response: {
|
|
newsletters: [responseFixtures.newsletters.newsletters[0]],
|
|
meta: {
|
|
sent_email_verification: ['sender_reply_to']
|
|
}
|
|
}},
|
|
browseConfig: {
|
|
...globalDataRequests.browseConfig,
|
|
response: {
|
|
config: {
|
|
...responseFixtures.config.config,
|
|
hostSettings: {
|
|
managedEmail: {
|
|
enabled: true,
|
|
sendingDomain: 'customdomain.com'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}});
|
|
|
|
await page.goto('/');
|
|
const section = page.getByTestId('newsletters');
|
|
await section.getByText('Awesome newsletter').click();
|
|
const modal = page.getByTestId('newsletter-modal');
|
|
const replyToEmail = modal.getByLabel('Reply-to email');
|
|
|
|
// Full flexibility for the reply-to address
|
|
await replyToEmail.fill('hermione@granger.com');
|
|
expect(await replyToEmail.inputValue()).toBe('hermione@granger.com');
|
|
|
|
// There is a verification popup for the new reply-to address
|
|
await modal.getByRole('button', {name: 'Save'}).click();
|
|
await expect(page.getByTestId('confirmation-modal')).toHaveCount(1);
|
|
await expect(page.getByTestId('confirmation-modal')).toHaveText(/Confirm reply-to address/);
|
|
await expect(page.getByTestId('confirmation-modal')).toHaveText(/sent a confirmation email to hermione@granger.com/);
|
|
await expect(page.getByTestId('confirmation-modal')).toHaveText(/replies will continue to go to support@example.com/);
|
|
});
|
|
});
|
|
});
|
|
|
|
test('Supports archiving newsletters', async ({page}) => {
|
|
const activate = await mockApi({page, requests: {
|
|
...globalDataRequests,
|
|
browseNewsletters: {method: 'GET', path: '/newsletters/?include=count.active_members%2Ccount.posts&limit=50', response: responseFixtures.newsletters},
|
|
editNewsletter: {method: 'PUT', path: `/newsletters/${responseFixtures.newsletters.newsletters[1].id}/?include=count.active_members%2Ccount.posts`, response: {
|
|
newsletters: [{
|
|
...responseFixtures.newsletters.newsletters[1],
|
|
status: 'active'
|
|
}]
|
|
}}
|
|
}});
|
|
|
|
await page.goto('/');
|
|
|
|
const section = page.getByTestId('newsletters');
|
|
|
|
await section.getByRole('tab', {name: 'Archived'}).click();
|
|
|
|
await section.getByText('Average newsletter').hover();
|
|
await section.getByRole('button', {name: 'Edit'}).click();
|
|
|
|
const archivedNewsletterModal = page.getByTestId('newsletter-modal');
|
|
await archivedNewsletterModal.getByRole('button', {name: 'Reactivate newsletter'}).click();
|
|
await page.getByTestId('confirmation-modal').getByRole('button', {name: 'Reactivate'}).click();
|
|
await archivedNewsletterModal.getByRole('button', {name: 'Close'}).click();
|
|
|
|
await section.getByRole('tab', {name: 'Active'}).click();
|
|
|
|
await expect(section.getByText('Awesome newsletter')).toHaveCount(1);
|
|
await expect(section.getByText('Average newsletter')).toHaveCount(1);
|
|
|
|
expect(activate.lastApiRequests.editNewsletter?.body).toMatchObject({
|
|
newsletters: [{
|
|
id: responseFixtures.newsletters.newsletters[1].id,
|
|
status: 'active'
|
|
}]
|
|
});
|
|
|
|
const archive = await mockApi({page, requests: {
|
|
...globalDataRequests,
|
|
browseNewsletters: {method: 'GET', path: '/newsletters/?include=count.active_members%2Ccount.posts&limit=50', response: responseFixtures.newsletters},
|
|
editNewsletter: {method: 'PUT', path: `/newsletters/${responseFixtures.newsletters.newsletters[0].id}/?include=count.active_members%2Ccount.posts`, response: {
|
|
newsletters: [{
|
|
...responseFixtures.newsletters.newsletters[0],
|
|
status: 'archived'
|
|
}]
|
|
}}
|
|
}});
|
|
|
|
await section.getByText('Awesome newsletter').hover();
|
|
await section.getByRole('button', {name: 'Edit'}).click();
|
|
|
|
const activeNewsletterModal = page.getByTestId('newsletter-modal');
|
|
await activeNewsletterModal.getByRole('button', {name: 'Archive newsletter'}).click();
|
|
await page.getByTestId('confirmation-modal').getByRole('button', {name: 'Archive'}).click();
|
|
await activeNewsletterModal.getByRole('button', {name: 'Close'}).click();
|
|
|
|
await section.getByRole('tab', {name: 'Archived'}).click();
|
|
|
|
await expect(section.getByText('Awesome newsletter')).toHaveCount(1);
|
|
|
|
expect(archive.lastApiRequests.editNewsletter?.body).toMatchObject({
|
|
newsletters: [{
|
|
id: responseFixtures.newsletters.newsletters[0].id,
|
|
status: 'archived'
|
|
}]
|
|
});
|
|
});
|
|
|
|
test('Limits the number of newsletters', async ({page}) => {
|
|
await mockApi({page, requests: {
|
|
...globalDataRequests,
|
|
...limitRequests,
|
|
browseConfig: {
|
|
...globalDataRequests.browseConfig,
|
|
response: {
|
|
config: {
|
|
...responseFixtures.config.config,
|
|
hostSettings: {
|
|
limits: {
|
|
newsletters: {
|
|
max: 1,
|
|
error: 'Your plan supports up to {{max}} newsletters. Please upgrade to add more.'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
browseNewsletters: {method: 'GET', path: '/newsletters/?include=count.active_members%2Ccount.posts&limit=50', response: responseFixtures.newsletters}
|
|
}});
|
|
|
|
await page.goto('/');
|
|
|
|
const section = page.getByTestId('newsletters');
|
|
|
|
await section.getByRole('button', {name: 'Add newsletter'}).click();
|
|
|
|
await expect(page.getByTestId('limit-modal')).toHaveText(/Your plan supports up to 1 newsletters/);
|
|
|
|
await page.getByTestId('limit-modal').getByRole('button', {name: 'Cancel'}).click();
|
|
|
|
await section.getByRole('tab', {name: 'Archived'}).click();
|
|
|
|
await section.getByText('Average newsletter').hover();
|
|
await section.getByRole('button', {name: 'Edit'}).click();
|
|
|
|
const newsletterModal = page.getByTestId('newsletter-modal');
|
|
await newsletterModal.getByRole('button', {name: 'Reactivate newsletter'}).click();
|
|
|
|
await expect(page.getByTestId('limit-modal')).toHaveText(/Your plan supports up to 1 newsletters/);
|
|
});
|
|
|
|
test('Warns when leaving without saving', async ({page}) => {
|
|
const {lastApiRequests} = await mockApi({page, requests: {
|
|
...globalDataRequests,
|
|
browseNewsletters: {method: 'GET', path: '/newsletters/?include=count.active_members%2Ccount.posts&limit=50', response: responseFixtures.newsletters},
|
|
editNewsletter: {method: 'PUT', path: `/newsletters/${responseFixtures.newsletters.newsletters[1].id}/?include=count.active_members%2Ccount.posts`, response: responseFixtures.newsletters}
|
|
}});
|
|
|
|
await page.goto('/');
|
|
|
|
const section = page.getByTestId('newsletters');
|
|
await section.getByText('Awesome newsletter').click();
|
|
|
|
const modal = page.getByTestId('newsletter-modal');
|
|
|
|
await modal.getByPlaceholder('Weekly Roundup').fill('New title');
|
|
|
|
await modal.getByRole('button', {name: 'Close'}).click();
|
|
|
|
await expect(page.getByTestId('confirmation-modal')).toHaveText(/leave/i);
|
|
|
|
await page.getByTestId('confirmation-modal').getByRole('button', {name: 'Leave'}).click();
|
|
|
|
await expect(modal).toBeHidden();
|
|
expect(lastApiRequests.editNewsletter).toBeUndefined();
|
|
});
|
|
});
|