mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-10 23:36:14 -05:00
Added test coverage over newsletter flows (#20672)
no ref - while reviewing the newsletter flows, it was apparent that we were missing test coverage Some of the tests in Portal are a bit redundant with tests added for child components, but it didn't seem worth removing them after getting them to work. There was a bug in our Portal fixture data that requires a few changes, as well as some small adjustments for making tests easier (testing-lib-react has `getByTestId` and simply a `querySelector` to use alternate test attributes).
This commit is contained in:
parent
c8df04de1b
commit
1f05a7890f
10 changed files with 261 additions and 10 deletions
|
@ -46,7 +46,7 @@ function NewsletterPrefSection({newsletter, subscribedNewsletters, setSubscribed
|
||||||
const [showUpdated, setShowUpdated] = useState(false);
|
const [showUpdated, setShowUpdated] = useState(false);
|
||||||
const [timeoutId, setTimeoutId] = useState(null);
|
const [timeoutId, setTimeoutId] = useState(null);
|
||||||
return (
|
return (
|
||||||
<section className='gh-portal-list-toggle-wrapper' data-test-toggle-wrapper>
|
<section className='gh-portal-list-toggle-wrapper' data-testid="toggle-wrapper">
|
||||||
<div className='gh-portal-list-detail'>
|
<div className='gh-portal-list-detail'>
|
||||||
<h3>{newsletter.name}</h3>
|
<h3>{newsletter.name}</h3>
|
||||||
<p>{newsletter?.description}</p>
|
<p>{newsletter?.description}</p>
|
||||||
|
@ -95,7 +95,7 @@ function CommentsSection({updateCommentNotifications, isCommentsEnabled, enableC
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className='gh-portal-list-toggle-wrapper' data-test-toggle-wrapper>
|
<section className='gh-portal-list-toggle-wrapper' data-testid="toggle-wrapper">
|
||||||
<div className='gh-portal-list-detail'>
|
<div className='gh-portal-list-detail'>
|
||||||
<h3>{t('Comments')}</h3>
|
<h3>{t('Comments')}</h3>
|
||||||
<p>{t('Get notified when someone replies to your comment')}</p>
|
<p>{t('Get notified when someone replies to your comment')}</p>
|
||||||
|
|
117
apps/portal/src/components/pages/AccountEmailPage.test.js
Normal file
117
apps/portal/src/components/pages/AccountEmailPage.test.js
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
import {getSiteData, getNewslettersData, getMemberData} from '../../utils/fixtures-generator';
|
||||||
|
import {render, fireEvent} from '../../utils/test-utils';
|
||||||
|
import AccountEmailPage from './AccountEmailPage';
|
||||||
|
|
||||||
|
const setup = (overrides) => {
|
||||||
|
const {mockOnActionFn, context, ...utils} = render(
|
||||||
|
<AccountEmailPage />,
|
||||||
|
{
|
||||||
|
overrideContext: {
|
||||||
|
...overrides
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const unsubscribeAllBtn = utils.getByText('Unsubscribe from all emails');
|
||||||
|
const closeBtn = utils.getByTestId('close-popup');
|
||||||
|
|
||||||
|
return {
|
||||||
|
unsubscribeAllBtn,
|
||||||
|
closeBtn,
|
||||||
|
mockOnActionFn,
|
||||||
|
context,
|
||||||
|
...utils
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Account Email Page', () => {
|
||||||
|
test('renders', () => {
|
||||||
|
const newsletterData = getNewslettersData({numOfNewsletters: 2});
|
||||||
|
const siteData = getSiteData({
|
||||||
|
newsletters: newsletterData,
|
||||||
|
member: getMemberData({newsletters: newsletterData})
|
||||||
|
});
|
||||||
|
const {unsubscribeAllBtn, getAllByTestId, getByText} = setup({site: siteData});
|
||||||
|
const unsubscribeBtns = getAllByTestId(`toggle-wrapper`);
|
||||||
|
expect(getByText('Email preferences')).toBeInTheDocument();
|
||||||
|
// one for each newsletter and one for comments
|
||||||
|
expect(unsubscribeBtns).toHaveLength(3);
|
||||||
|
expect(unsubscribeAllBtn).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can unsubscribe from all emails', async () => {
|
||||||
|
const newsletterData = getNewslettersData({numOfNewsletters: 2});
|
||||||
|
const siteData = getSiteData({
|
||||||
|
newsletters: newsletterData
|
||||||
|
});
|
||||||
|
const {mockOnActionFn, unsubscribeAllBtn, getAllByTestId} = setup({site: siteData, member: getMemberData({newsletters: newsletterData})});
|
||||||
|
let checkmarkContainers = getAllByTestId('checkmark-container');
|
||||||
|
// each newsletter should have the checked class (this is how we know they're enabled/subscribed to)
|
||||||
|
expect(checkmarkContainers[0]).toHaveClass('gh-portal-toggle-checked');
|
||||||
|
expect(checkmarkContainers[1]).toHaveClass('gh-portal-toggle-checked');
|
||||||
|
|
||||||
|
fireEvent.click(unsubscribeAllBtn);
|
||||||
|
expect(mockOnActionFn).toHaveBeenCalledTimes(2);
|
||||||
|
expect(mockOnActionFn).toHaveBeenCalledWith('showPopupNotification', {action: 'updated:success', message: 'Unsubscribed from all emails.'});
|
||||||
|
expect(mockOnActionFn).toHaveBeenLastCalledWith('updateNewsletterPreference', {newsletters: [], enableCommentNotifications: false});
|
||||||
|
|
||||||
|
checkmarkContainers = getAllByTestId('checkmark-container');
|
||||||
|
expect(checkmarkContainers).toHaveLength(3);
|
||||||
|
checkmarkContainers.forEach((newsletter) => {
|
||||||
|
// each newsletter htmlElement should not have the checked class
|
||||||
|
expect(newsletter).not.toHaveClass('gh-portal-toggle-checked');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('unsubscribe all is disabled when no newsletters are subscribed to', async () => {
|
||||||
|
const siteData = getSiteData({
|
||||||
|
newsletters: getNewslettersData({numOfNewsletters: 2})
|
||||||
|
});
|
||||||
|
const {unsubscribeAllBtn} = setup({site: siteData, member: getMemberData()});
|
||||||
|
expect(unsubscribeAllBtn).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can update newsletter preferences', async () => {
|
||||||
|
const newsletterData = getNewslettersData({numOfNewsletters: 2});
|
||||||
|
const siteData = getSiteData({
|
||||||
|
newsletters: newsletterData
|
||||||
|
});
|
||||||
|
const {mockOnActionFn, getAllByTestId} = setup({site: siteData, member: getMemberData({newsletters: newsletterData})});
|
||||||
|
let checkmarkContainers = getAllByTestId('checkmark-container');
|
||||||
|
// each newsletter should have the checked class (this is how we know they're enabled/subscribed to)
|
||||||
|
expect(checkmarkContainers[0]).toHaveClass('gh-portal-toggle-checked');
|
||||||
|
let subscriptionToggles = getAllByTestId('switch-input');
|
||||||
|
fireEvent.click(subscriptionToggles[0]);
|
||||||
|
expect(mockOnActionFn).toHaveBeenCalledWith('updateNewsletterPreference', {newsletters: [{id: newsletterData[1].id}]});
|
||||||
|
fireEvent.click(subscriptionToggles[0]);
|
||||||
|
expect(mockOnActionFn).toHaveBeenCalledWith('updateNewsletterPreference', {newsletters: [{id: newsletterData[1].id}, {id: newsletterData[0].id}]});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can update comment notifications', async () => {
|
||||||
|
const siteData = getSiteData();
|
||||||
|
const {mockOnActionFn, getAllByTestId} = setup({site: siteData, member: getMemberData()});
|
||||||
|
let subscriptionToggles = getAllByTestId('switch-input');
|
||||||
|
fireEvent.click(subscriptionToggles[0]);
|
||||||
|
expect(mockOnActionFn).toHaveBeenCalledWith('updateNewsletterPreference', {enableCommentNotifications: true});
|
||||||
|
fireEvent.click(subscriptionToggles[0]);
|
||||||
|
expect(mockOnActionFn).toHaveBeenCalledWith('updateNewsletterPreference', {enableCommentNotifications: false});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('displays help for members with email suppressions', async () => {
|
||||||
|
const newsletterData = getNewslettersData({numOfNewsletters: 2});
|
||||||
|
const siteData = getSiteData({
|
||||||
|
newsletters: newsletterData
|
||||||
|
});
|
||||||
|
const {getByText} = setup({site: siteData, member: getMemberData({newsletters: newsletterData, email_suppressions: {suppressed: false}})});
|
||||||
|
expect(getByText('Not receiving emails?')).toBeInTheDocument();
|
||||||
|
expect(getByText('Get help →')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('redirects to signin page if no member', async () => {
|
||||||
|
const newsletterData = getNewslettersData({numOfNewsletters: 2});
|
||||||
|
const siteData = getSiteData({
|
||||||
|
newsletters: newsletterData
|
||||||
|
});
|
||||||
|
const {mockOnActionFn} = setup({site: siteData, member: null});
|
||||||
|
expect(mockOnActionFn).toHaveBeenCalledWith('switchPage', {page: 'signin'});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,6 +1,7 @@
|
||||||
import {render, fireEvent} from '../../../utils/test-utils';
|
import {render, fireEvent} from '../../../utils/test-utils';
|
||||||
import AccountHomePage from './AccountHomePage';
|
import AccountHomePage from './AccountHomePage';
|
||||||
import {site} from '../../../utils/fixtures';
|
import {site} from '../../../utils/fixtures';
|
||||||
|
import {getSiteData} from '../../../utils/fixtures-generator';
|
||||||
|
|
||||||
const setup = (overrides) => {
|
const setup = (overrides) => {
|
||||||
const {mockOnActionFn, ...utils} = render(
|
const {mockOnActionFn, ...utils} = render(
|
||||||
|
@ -21,7 +22,8 @@ const setup = (overrides) => {
|
||||||
|
|
||||||
describe('Account Home Page', () => {
|
describe('Account Home Page', () => {
|
||||||
test('renders', () => {
|
test('renders', () => {
|
||||||
const {logoutBtn, utils} = setup();
|
const siteData = getSiteData({commentsEnabled: 'off'});
|
||||||
|
const {logoutBtn, utils} = setup({site: siteData});
|
||||||
expect(logoutBtn).toBeInTheDocument();
|
expect(logoutBtn).toBeInTheDocument();
|
||||||
expect(utils.queryByText('You\'re currently not receiving emails')).not.toBeInTheDocument();
|
expect(utils.queryByText('You\'re currently not receiving emails')).not.toBeInTheDocument();
|
||||||
expect(utils.queryByText('Email newsletter')).toBeInTheDocument();
|
expect(utils.queryByText('Email newsletter')).toBeInTheDocument();
|
||||||
|
|
|
@ -11,7 +11,7 @@ function NewsletterPrefSection({newsletter, subscribedNewsletters, setSubscribed
|
||||||
});
|
});
|
||||||
if (newsletter.paid) {
|
if (newsletter.paid) {
|
||||||
return (
|
return (
|
||||||
<section className='gh-portal-list-toggle-wrapper' data-test-toggle-wrapper>
|
<section className='gh-portal-list-toggle-wrapper' data-testid="toggle-wrapper">
|
||||||
<div className='gh-portal-list-detail gh-portal-list-big'>
|
<div className='gh-portal-list-detail gh-portal-list-big'>
|
||||||
<h3>{newsletter.name}</h3>
|
<h3>{newsletter.name}</h3>
|
||||||
<p>{newsletter.description}</p>
|
<p>{newsletter.description}</p>
|
||||||
|
@ -23,7 +23,7 @@ function NewsletterPrefSection({newsletter, subscribedNewsletters, setSubscribed
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<section className='gh-portal-list-toggle-wrapper' data-test-toggle-wrapper>
|
<section className='gh-portal-list-toggle-wrapper' data-testid="toggle-wrapper">
|
||||||
<div className='gh-portal-list-detail gh-portal-list-big'>
|
<div className='gh-portal-list-detail gh-portal-list-big'>
|
||||||
<h3>{newsletter.name}</h3>
|
<h3>{newsletter.name}</h3>
|
||||||
<p>{newsletter.description}</p>
|
<p>{newsletter.description}</p>
|
||||||
|
|
|
@ -160,7 +160,7 @@ describe('Newsletter Subscriptions', () => {
|
||||||
|
|
||||||
fireEvent.click(unsubscribeAllButton);
|
fireEvent.click(unsubscribeAllButton);
|
||||||
|
|
||||||
expect(ghostApi.member.update).toHaveBeenCalledWith({newsletters: []});
|
expect(ghostApi.member.update).toHaveBeenCalledWith({newsletters: [], enableCommentNotifications: false});
|
||||||
// Verify the local state shows the newsletter as unsubscribed
|
// Verify the local state shows the newsletter as unsubscribed
|
||||||
let newsletterToggles = within(popupIframeDocument).queryAllByTestId('checkmark-container');
|
let newsletterToggles = within(popupIframeDocument).queryAllByTestId('checkmark-container');
|
||||||
let newsletter1Toggle = newsletterToggles[0];
|
let newsletter1Toggle = newsletterToggles[0];
|
||||||
|
@ -254,4 +254,33 @@ describe('Newsletter Subscriptions', () => {
|
||||||
expect(newsletter2Toggle).toHaveClass('gh-portal-toggle-checked');
|
expect(newsletter2Toggle).toHaveClass('gh-portal-toggle-checked');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// describe('navigating straight to /portal/account/newsletters', () => {
|
||||||
|
// it('shows the newsletter management page when signed in', async () => {
|
||||||
|
// const {popupFrame, triggerButton, queryAllByText, popupIframeDocument} = await setup({
|
||||||
|
// site: FixtureSite.singleTier.onlyFreePlanWithoutStripe,
|
||||||
|
// member: FixtureMember.subbedToNewsletter,
|
||||||
|
// newsletters: Newsletters
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const manageSubscriptionsButton = within(popupIframeDocument).queryByRole('button', {name: 'Manage'});
|
||||||
|
// await userEvent.click(manageSubscriptionsButton);
|
||||||
|
|
||||||
|
// const newsletter1 = within(popupIframeDocument).queryAllByText('Newsletter 1');
|
||||||
|
// expect(newsletter1).toBeInTheDocument();
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('redirects to the sign in page when not signed in', async () => {
|
||||||
|
// const {popupFrame, queryByTitle, popupIframeDocument} = await setup({
|
||||||
|
// site: FixtureSite.singleTier.onlyFreePlanWithoutStripe,
|
||||||
|
// member: FixtureMember.subbedToNewsletter,
|
||||||
|
// newsletters: Newsletters
|
||||||
|
// }, true);
|
||||||
|
|
||||||
|
// // console.log(`popupFrame`, popupFrame);
|
||||||
|
// // console.log(`queryByTitle`, queryByTitle);
|
||||||
|
// // console.log(`popupIframeDocument`, popupIframeDocument);
|
||||||
|
|
||||||
|
// });
|
||||||
|
// });
|
||||||
});
|
});
|
||||||
|
|
|
@ -66,7 +66,7 @@ export function getSiteData({
|
||||||
portal_button_signup_text,
|
portal_button_signup_text,
|
||||||
portal_button_style,
|
portal_button_style,
|
||||||
members_support_address,
|
members_support_address,
|
||||||
comments_enabled: !!commentsEnabled,
|
comments_enabled: commentsEnabled !== 'off',
|
||||||
newsletters,
|
newsletters,
|
||||||
recommendations,
|
recommendations,
|
||||||
recommendations_enabled: !!recommendationsEnabled
|
recommendations_enabled: !!recommendationsEnabled
|
||||||
|
|
|
@ -110,7 +110,7 @@ const authMemberByUuid = async function authMemberByUuid(req, res, next) {
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new errors.UnauthorizedError({
|
throw new errors.UnauthorizedError({
|
||||||
messsage: tpl(messages.missingUuid)
|
message: tpl(messages.missingUuid)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,17 @@
|
||||||
{
|
{
|
||||||
"url": "http://localhost:2368",
|
"url": "http://localhost:2368",
|
||||||
|
"mail": {
|
||||||
|
"from": "test@example.com",
|
||||||
|
"transport": "SMTP",
|
||||||
|
"options": {
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"port": 1025,
|
||||||
|
"auth": {
|
||||||
|
"user": "user",
|
||||||
|
"pass": "unsecure"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"database": {
|
"database": {
|
||||||
"client": "sqlite3",
|
"client": "sqlite3",
|
||||||
"connection": {
|
"connection": {
|
||||||
|
|
|
@ -107,7 +107,7 @@ test.describe('Portal', () => {
|
||||||
await portalFrame.locator('[data-test-button="manage-newsletters"]').click();
|
await portalFrame.locator('[data-test-button="manage-newsletters"]').click();
|
||||||
|
|
||||||
// check amount of newsletterss
|
// check amount of newsletterss
|
||||||
const newsletters = await portalFrame.locator('[data-test-toggle-wrapper="true"]');
|
const newsletters = await portalFrame.locator('[data-testid="toggle-wrapper"]');
|
||||||
const count = await newsletters.count();
|
const count = await newsletters.count();
|
||||||
await expect(count).toEqual(2);
|
await expect(count).toEqual(2);
|
||||||
|
|
||||||
|
|
|
@ -192,4 +192,95 @@ describe('Members Service Middleware', function () {
|
||||||
res.redirect.firstCall.args[0].should.eql('/blah/?action=signin&success=true');
|
res.redirect.firstCall.args[0].should.eql('/blah/?action=signin&success=true');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('updateMemberNewsletters', function () {
|
||||||
|
// let oldMembersService;
|
||||||
|
let req;
|
||||||
|
let res;
|
||||||
|
|
||||||
|
before(function () {
|
||||||
|
models.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
req = {body: {newsletters: [], enable_comment_notifications: null}};
|
||||||
|
res = {writeHead: sinon.stub(), end: sinon.stub()};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
sinon.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns 400 if no member uuid is part of the request', async function () {
|
||||||
|
req.query = {};
|
||||||
|
|
||||||
|
// Call the middleware
|
||||||
|
await membersMiddleware.updateMemberNewsletters(req, res);
|
||||||
|
|
||||||
|
// Check behavior
|
||||||
|
res.writeHead.calledOnce.should.be.true();
|
||||||
|
res.writeHead.firstCall.args[0].should.eql(400);
|
||||||
|
res.end.calledOnce.should.be.true();
|
||||||
|
res.end.firstCall.args[0].should.eql('Invalid member uuid');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns 404 if member uuid is not found', async function () {
|
||||||
|
req.query = {uuid: 'test'};
|
||||||
|
sinon.stub(membersService, 'api').get(() => {
|
||||||
|
return {
|
||||||
|
members: {
|
||||||
|
get: sinon.stub().resolves()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Call the middleware
|
||||||
|
await membersMiddleware.updateMemberNewsletters(req, res);
|
||||||
|
|
||||||
|
// Check behavior
|
||||||
|
res.writeHead.calledOnce.should.be.true();
|
||||||
|
res.writeHead.firstCall.args[0].should.eql(404);
|
||||||
|
res.end.calledOnce.should.be.true();
|
||||||
|
res.end.firstCall.args[0].should.eql('Email address not found.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('attempts to update newsletters', async function () {
|
||||||
|
res.json = sinon.stub();
|
||||||
|
req.query = {uuid: 'test'};
|
||||||
|
const memberData = {
|
||||||
|
id: 'test',
|
||||||
|
email: 'test@email.com',
|
||||||
|
name: 'Test Name',
|
||||||
|
newsletters: [],
|
||||||
|
enable_comment_notifications: false,
|
||||||
|
status: 'free'
|
||||||
|
};
|
||||||
|
sinon.stub(membersService, 'api').get(() => {
|
||||||
|
return {
|
||||||
|
members: {
|
||||||
|
get: sinon.stub().resolves({id: 'test', email: 'test@email.com', get: () => 'test'}),
|
||||||
|
update: sinon.stub().resolves({
|
||||||
|
...memberData,
|
||||||
|
toJSON: () => JSON.stringify(memberData)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
await membersMiddleware.updateMemberNewsletters(req, res);
|
||||||
|
// the stubbing of the api is difficult to test with the current design, so we just check that the response is sent
|
||||||
|
res.json.calledOnce.should.be.true();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns 400 on error', async function () {
|
||||||
|
// use a malformed request to trigger an error
|
||||||
|
req = {};
|
||||||
|
await membersMiddleware.updateMemberNewsletters(req, res);
|
||||||
|
|
||||||
|
// Check behavior
|
||||||
|
res.writeHead.calledOnce.should.be.true();
|
||||||
|
res.writeHead.firstCall.args[0].should.eql(400);
|
||||||
|
res.end.calledOnce.should.be.true();
|
||||||
|
res.end.firstCall.args[0].should.eql('Failed to update newsletters');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
Loading…
Add table
Reference in a new issue