0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-04-08 02:52:39 -05:00

Added tests for Stripe and Newsletter settings (#17598)

refs https://github.com/TryGhost/Product/issues/3608,
https://github.com/TryGhost/Product/issues/3601
This commit is contained in:
Jono M 2023-08-07 12:44:30 +01:00 committed by GitHub
parent 06011bf513
commit a413f2b54f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 404 additions and 15 deletions

View file

@ -13,7 +13,7 @@ export interface ConfirmationModalProps {
onCancel?: () => void;
onOk?: (modal?: {
remove: () => void;
}) => void;
}) => void | Promise<void>;
customFooter?: boolean | React.ReactNode;
}

View file

@ -13,7 +13,10 @@ const StripeButton: React.FC<StripeButtonProps | ButtonProps> = ({label, ...prop
);
if (!label) {
label = <svg fill='none' height='16' width='132' xmlns='http://www.w3.org/2000/svg'><g clipPath='url(#clip0_1443_3527)' fill='#fff'><path d='M2 7.6c0-2.3 1.3-3.9 3-3.9 1.3 0 2.2.8 2.5 2l1.8-.6C8.7 3.2 7.2 2 5 2 2.1 2 0 4.3 0 7.6s2.1 5.6 5 5.6c2.2 0 3.7-1.2 4.3-3.1l-1.8-.6c-.3 1.3-1.2 2-2.5 2-1.8 0-3-1.6-3-3.9zm15.6 1.5C17.6 6.7 16 5 13.8 5 11.6 5 10 6.6 10 9.1c0 2.5 1.6 4.1 3.8 4.1 2.3 0 3.8-1.7 3.8-4.1zm-5.7 0c0-1.6.8-2.6 2-2.6s2 1 2 2.6-.8 2.6-2 2.6-2-1-2-2.6zM19 13h1.8V8.4c0-1.1.8-1.7 1.6-1.7 1 0 1.4.7 1.4 1.7V13h1.8V7.8c0-1.7-1-2.8-2.6-2.8-1 0-1.7.5-2.2 1v-.8H19V13zm8.5 0h1.8V8.4c0-1.1.8-1.7 1.6-1.7 1 0 1.4.7 1.4 1.7V13h1.8V7.8c0-1.7-1-2.8-2.6-2.8-1 0-1.7.5-2.2 1v-.8h-1.8V13zm11.8.2c1.6 0 2.8-.8 3.4-2.2l-1.5-.6c-.2.8-.9 1.3-1.8 1.3-1.2 0-2-.8-2.1-2.2h5.5v-.6c0-2.2-1.2-3.9-3.5-3.9-2.2 0-3.8 1.8-3.8 4.1 0 2.4 1.5 4.1 3.8 4.1zm-.1-6.7c1.1 0 1.7.8 1.7 1.6h-3.6c.2-1.1 1-1.6 1.9-1.6zm6.2 2.6c0-1.6.8-2.5 2.1-2.5 1 0 1.5.6 1.8 1.5l1.5-.8c-.4-1.3-1.6-2.2-3.3-2.2-2.2 0-3.9 1.7-3.9 4.1 0 2.4 1.6 4.1 3.9 4.1 1.7 0 2.9-1 3.3-2.3l-1.5-.8c-.2.9-.8 1.5-1.8 1.5-1.3-.1-2.1-1-2.1-2.6zM52 11c0 1.6.8 2.1 2.3 2.1.5 0 .9 0 1.2-.1v-1.5h-.7c-.6 0-1.1-.1-1.1-.8V6.6h1.7V5.1h-1.7V2.8h-1.8v2.3h-1.1v1.5h1.1L52 11zm11.7-5.9l-1.4 5.2-1.4-5.2h-1.8l2.4 7.9h1.6l1.4-5.2 1.4 5.2h1.6l2.4-7.9h-1.8l-1.5 5.2-1.4-5.2h-1.5zm7.1-1h1.8V2.3h-1.9l.1 1.8zm1.8 1h-1.8V13h1.8V5.1zm2 5.9c0 1.6.8 2.1 2.3 2.1.5 0 .9 0 1.2-.1v-1.5h-.7c-.6 0-1.1-.1-1.1-.8V6.6H78V5.1h-1.7V2.8h-1.7v2.3h-1.1v1.5h1.1V11zm4.9 2h1.8V8.4c0-1.1.8-1.7 1.6-1.7 1 0 1.4.7 1.4 1.7V13h1.8V7.8c0-1.7-1-2.8-2.6-2.8-1 0-1.7.5-2.2 1V2.3h-1.8V13zM117.1 15.4v-3c.4.3.9.7 1.9.7 1.9 0 3.6-1.5 3.6-4.9 0-3.1-1.7-4.8-3.6-4.8-1 0-1.7.5-2.1.9l-.1-.6h-2.3V16l2.6-.6zm1.2-9.7c1 0 1.6 1.1 1.6 2.5 0 1.5-.6 2.5-1.6 2.5-.6 0-1-.2-1.3-.5v-4c.4-.2.7-.5 1.3-.5zM127.701 13.2c1.3 0 2.2-.3 3-.7v-2.2c-.7.4-1.5.6-2.6.6s-1.9-.4-2.1-1.7h5.2v-1c0-2.8-1.3-4.9-3.8-4.9s-4.1 2.2-4.1 4.9c0 3.3 1.8 5 4.4 5zm-.3-7.7c.6 0 1.3.5 1.3 1.7h-2.8c.1-1.1.8-1.7 1.5-1.7zM107.6 6.5c.6-.9 1.7-.7 2-.6V3.4c-.4-.1-1.6-.4-2.1.8l-.2-.8H105v9.4h2.6V6.5zM95.602 10.2c0 .5-.4.6-1 .6-.9 0-2-.4-2.9-.9v2.6c1 .4 2 .6 2.9.6 2.2 0 3.7-1.1 3.7-3 0-3.2-4-2.6-4-3.8 0-.4.3-.6.9-.6.8 0 1.8.2 2.6.7V3.8c-.9-.4-1.7-.5-2.6-.5-2.1 0-3.6 1.1-3.6 3 0 3.1 4 2.6 4 3.9zM102.001 13.2c.8 0 1.5-.2 1.9-.4v-2.2c-.3.1-2 .6-2-1V5.8h2V3.5h-2V1.2l-2.6.6v8.6c0 1.6 1.1 2.8 2.7 2.8zM110.5.6v2.1l2.7-.5V0l-2.7.6zM113.1 3.5h-2.6v9.3h2.6V3.5z'/></g><defs><clipPath id='clip0_1443_3527'><path d='M0 0h132v16H0z' fill='#fff'/></clipPath></defs></svg>;
label = <>
<span className="sr-only">Connect with Stripe</span>
<svg fill='none' height='16' width='132' xmlns='http://www.w3.org/2000/svg'><g clipPath='url(#clip0_1443_3527)' fill='#fff'><path d='M2 7.6c0-2.3 1.3-3.9 3-3.9 1.3 0 2.2.8 2.5 2l1.8-.6C8.7 3.2 7.2 2 5 2 2.1 2 0 4.3 0 7.6s2.1 5.6 5 5.6c2.2 0 3.7-1.2 4.3-3.1l-1.8-.6c-.3 1.3-1.2 2-2.5 2-1.8 0-3-1.6-3-3.9zm15.6 1.5C17.6 6.7 16 5 13.8 5 11.6 5 10 6.6 10 9.1c0 2.5 1.6 4.1 3.8 4.1 2.3 0 3.8-1.7 3.8-4.1zm-5.7 0c0-1.6.8-2.6 2-2.6s2 1 2 2.6-.8 2.6-2 2.6-2-1-2-2.6zM19 13h1.8V8.4c0-1.1.8-1.7 1.6-1.7 1 0 1.4.7 1.4 1.7V13h1.8V7.8c0-1.7-1-2.8-2.6-2.8-1 0-1.7.5-2.2 1v-.8H19V13zm8.5 0h1.8V8.4c0-1.1.8-1.7 1.6-1.7 1 0 1.4.7 1.4 1.7V13h1.8V7.8c0-1.7-1-2.8-2.6-2.8-1 0-1.7.5-2.2 1v-.8h-1.8V13zm11.8.2c1.6 0 2.8-.8 3.4-2.2l-1.5-.6c-.2.8-.9 1.3-1.8 1.3-1.2 0-2-.8-2.1-2.2h5.5v-.6c0-2.2-1.2-3.9-3.5-3.9-2.2 0-3.8 1.8-3.8 4.1 0 2.4 1.5 4.1 3.8 4.1zm-.1-6.7c1.1 0 1.7.8 1.7 1.6h-3.6c.2-1.1 1-1.6 1.9-1.6zm6.2 2.6c0-1.6.8-2.5 2.1-2.5 1 0 1.5.6 1.8 1.5l1.5-.8c-.4-1.3-1.6-2.2-3.3-2.2-2.2 0-3.9 1.7-3.9 4.1 0 2.4 1.6 4.1 3.9 4.1 1.7 0 2.9-1 3.3-2.3l-1.5-.8c-.2.9-.8 1.5-1.8 1.5-1.3-.1-2.1-1-2.1-2.6zM52 11c0 1.6.8 2.1 2.3 2.1.5 0 .9 0 1.2-.1v-1.5h-.7c-.6 0-1.1-.1-1.1-.8V6.6h1.7V5.1h-1.7V2.8h-1.8v2.3h-1.1v1.5h1.1L52 11zm11.7-5.9l-1.4 5.2-1.4-5.2h-1.8l2.4 7.9h1.6l1.4-5.2 1.4 5.2h1.6l2.4-7.9h-1.8l-1.5 5.2-1.4-5.2h-1.5zm7.1-1h1.8V2.3h-1.9l.1 1.8zm1.8 1h-1.8V13h1.8V5.1zm2 5.9c0 1.6.8 2.1 2.3 2.1.5 0 .9 0 1.2-.1v-1.5h-.7c-.6 0-1.1-.1-1.1-.8V6.6H78V5.1h-1.7V2.8h-1.7v2.3h-1.1v1.5h1.1V11zm4.9 2h1.8V8.4c0-1.1.8-1.7 1.6-1.7 1 0 1.4.7 1.4 1.7V13h1.8V7.8c0-1.7-1-2.8-2.6-2.8-1 0-1.7.5-2.2 1V2.3h-1.8V13zM117.1 15.4v-3c.4.3.9.7 1.9.7 1.9 0 3.6-1.5 3.6-4.9 0-3.1-1.7-4.8-3.6-4.8-1 0-1.7.5-2.1.9l-.1-.6h-2.3V16l2.6-.6zm1.2-9.7c1 0 1.6 1.1 1.6 2.5 0 1.5-.6 2.5-1.6 2.5-.6 0-1-.2-1.3-.5v-4c.4-.2.7-.5 1.3-.5zM127.701 13.2c1.3 0 2.2-.3 3-.7v-2.2c-.7.4-1.5.6-2.6.6s-1.9-.4-2.1-1.7h5.2v-1c0-2.8-1.3-4.9-3.8-4.9s-4.1 2.2-4.1 4.9c0 3.3 1.8 5 4.4 5zm-.3-7.7c.6 0 1.3.5 1.3 1.7h-2.8c.1-1.1.8-1.7 1.5-1.7zM107.6 6.5c.6-.9 1.7-.7 2-.6V3.4c-.4-.1-1.6-.4-2.1.8l-.2-.8H105v9.4h2.6V6.5zM95.602 10.2c0 .5-.4.6-1 .6-.9 0-2-.4-2.9-.9v2.6c1 .4 2 .6 2.9.6 2.2 0 3.7-1.1 3.7-3 0-3.2-4-2.6-4-3.8 0-.4.3-.6.9-.6.8 0 1.8.2 2.6.7V3.8c-.9-.4-1.7-.5-2.6-.5-2.1 0-3.6 1.1-3.6 3 0 3.1 4 2.6 4 3.9zM102.001 13.2c.8 0 1.5-.2 1.9-.4v-2.2c-.3.1-2 .6-2-1V5.8h2V3.5h-2V1.2l-2.6.6v8.6c0 1.6 1.1 2.8 2.7 2.8zM110.5.6v2.1l2.7-.5V0l-2.7.6zM113.1 3.5h-2.6v9.3h2.6V3.5z'/></g><defs><clipPath id='clip0_1443_3527'><path d='M0 0h132v16H0z' fill='#fff'/></clipPath></defs></svg>
</>;
}
return (

View file

@ -1,4 +1,5 @@
import ButtonGroup from '../../../../admin-x-ds/global/ButtonGroup';
import ConfirmationModal from '../../../../admin-x-ds/global/modal/ConfirmationModal';
import Form from '../../../../admin-x-ds/global/form/Form';
import Heading from '../../../../admin-x-ds/global/Heading';
import Hint from '../../../../admin-x-ds/global/Hint';
@ -278,13 +279,32 @@ const Sidebar: React.FC<{
const NewsletterDetailModal: React.FC<NewsletterDetailModalProps> = ({newsletter}) => {
const modal = useModal();
const {siteData} = useGlobalData();
const {mutateAsync: editNewsletter} = useEditNewsletter();
const {formState, updateForm, handleSave, validate, errors, clearError} = useForm({
initialState: newsletter,
onSave: async () => {
await editNewsletter(formState);
modal.remove();
const {newsletters, meta} = await editNewsletter(formState);
if (meta?.sent_email_verification) {
NiceModal.show(ConfirmationModal, {
title: 'Confirm newsletter email address',
prompt: <>
We&lsquo;ve sent a confirmation email to <strong>{formState.sender_email}</strong>.
Until the address has been verified newsletters will be sent from the
{newsletters[0].sender_email ? ' previous' : ' default'} email address
({fullEmailAddress(newsletters[0].sender_email || 'noreply', siteData)}).
</>,
cancelLabel: '',
onOk: (confirmModal) => {
confirmModal?.remove();
modal.remove();
}
});
} else {
modal.remove();
}
},
onValidate: () => {
const newErrors: Record<string, string> = {};

View file

@ -14,11 +14,11 @@ interface NewslettersListProps {
newsletters: Newsletter[]
}
const NewsletterItem: React.FC<{newsletter: Newsletter}> = ({newsletter}) => {
const NewsletterItem: React.FC<{newsletter: Newsletter, onlyOne: boolean}> = ({newsletter, onlyOne}) => {
const {mutateAsync: editNewsletter} = useEditNewsletter();
const action = newsletter.status === 'active' ? (
<Button color='green' label='Archive' link onClick={() => {
<Button color='green' disabled={onlyOne} label='Archive' link onClick={() => {
NiceModal.show(ConfirmationModal, {
title: 'Archive newsletter',
prompt: <>
@ -79,7 +79,7 @@ const NewsletterItem: React.FC<{newsletter: Newsletter}> = ({newsletter}) => {
const NewslettersList: React.FC<NewslettersListProps> = ({newsletters}) => {
if (newsletters.length) {
return <Table>
{newsletters.map(newsletter => <NewsletterItem key={newsletter.id} newsletter={newsletter} />)}
{newsletters.map(newsletter => <NewsletterItem key={newsletter.id} newsletter={newsletter} onlyOne={newsletters.length === 1} />)}
</Table>;
} else {
return <NoValueLabel icon='mail-block'>

View file

@ -186,7 +186,7 @@ const Connected: React.FC<{onClose?: () => void}> = ({onClose}) => {
<section>
<div className='flex items-center justify-between'>
<Button disabled={isFetchingMembers} icon='link-broken' label='Disconnect' link onClick={openDisconnectStripeModal} />
<Button icon='close' size='sm' link onClick={onClose} />
<Button icon='close' label='Close' size='sm' hideLabel link onClick={onClose} />
</div>
<div className='my-20 flex flex-col items-center'>
<div className='relative h-20 w-[200px]'>
@ -290,6 +290,7 @@ const StripeConnectModal: React.FC = () => {
cancelLabel=''
footer={<></>}
size={stripeConnectAccountId ? 740 : 520}
testId='stripe-modal'
title=''
>
{contents}

View file

@ -58,14 +58,14 @@ const useForm = <State>({initialState, onSave, onValidate}: {
// function to save the changed settings via API
const handleSave = async () => {
if (saveState !== 'unsaved') {
return true;
}
if (!validate()) {
return false;
}
if (saveState !== 'unsaved') {
return true;
}
setSaveState('saving');
try {
await onSave();

View file

@ -19,7 +19,7 @@ export const useAddNewsletter = createMutation<NewslettersResponseType, Partial<
path: () => '/newsletters/',
// eslint-disable-next-line @typescript-eslint/no-unused-vars
body: ({opt_in_existing: _, ...newsletter}) => ({newsletters: [newsletter]}),
searchParams: payload => ({opt_in_existing: payload.opt_in_existing.toString(), include: 'count.active_members,count.posts', limit: 'all'}),
searchParams: payload => ({opt_in_existing: payload.opt_in_existing.toString(), include: 'count.active_members,count.posts'}),
updateQueries: {
dataType,
update: (newData, currentData) => ({
@ -29,11 +29,15 @@ export const useAddNewsletter = createMutation<NewslettersResponseType, Partial<
}
});
export const useEditNewsletter = createMutation<NewslettersResponseType, Newsletter>({
export interface NewslettersEditResponseType extends NewslettersResponseType {
meta?: Meta & {sent_email_verification: string[]}
}
export const useEditNewsletter = createMutation<NewslettersEditResponseType, Newsletter>({
method: 'PUT',
path: newsletter => `/newsletters/${newsletter.id}/`,
body: newsletter => ({newsletters: [newsletter]}),
defaultSearchParams: {include: 'count.active_members,count.posts', limit: 'all'},
defaultSearchParams: {include: 'count.active_members,count.posts'},
updateQueries: {
dataType,
update: (newData, currentData) => ({

View file

@ -0,0 +1,180 @@
import {expect, test} from '@playwright/test';
import {globalDataRequests, mockApi, responseFixtures} from '../../utils/e2e';
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=all', 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')).toHaveText(/One or more fields have errors/);
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=all', 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 & close'}).click();
await expect(page.getByTestId('toast')).toHaveText(/One or more fields have errors/);
await expect(modal).toHaveText(/Please enter a name/);
await modal.getByPlaceholder('Weekly Roundup').fill('Updated newsletter');
await modal.getByRole('tab', {name: 'Design'}).click();
await modal.getByLabel('Body style').selectOption({value: 'sans_serif'});
await modal.getByRole('button', {name: 'Save & close'}).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('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=all', 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('test@test.com');
await modal.getByRole('button', {name: 'Save & close'}).click();
await expect(page.getByTestId('confirmation-modal')).toHaveCount(1);
await expect(page.getByTestId('confirmation-modal')).toHaveText(/Confirm newsletter email address/);
});
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=all', 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: 'Activate'}).click();
await page.getByTestId('confirmation-modal').getByRole('button', {name: 'Reactivate'}).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=all', 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: 'Archive'}).click();
await page.getByTestId('confirmation-modal').getByRole('button', {name: 'Archive'}).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'
}]
});
});
});

View file

@ -0,0 +1,89 @@
import {expect, test} from '@playwright/test';
import {globalDataRequests, mockApi, responseFixtures, updatedSettingsResponse} from '../../utils/e2e';
test.describe('Stripe settings', async () => {
test('Supports the Stripe Connect flow', async ({page}) => {
const {lastApiRequests} = await mockApi({page, requests: {
...globalDataRequests,
editSettings: {method: 'PUT', path: '/settings/', response: updatedSettingsResponse([
{key: 'stripe_connect_account_name', value: 'Dummy'},
{key: 'stripe_connect_livemode', value: false},
{key: 'stripe_connect_account_id', value: 'acct_123'},
{key: 'stripe_connect_publishable_key', value: 'pk_test_123'},
{key: 'stripe_connect_secret_key', value: 'sk_test_123'}
])}
}});
await page.goto('/');
const section = page.getByTestId('tiers');
await section.getByRole('button', {name: 'Connect with Stripe'}).click();
const modal = page.getByTestId('stripe-modal');
await modal.getByRole('button', {name: /I have a Stripe account/}).click();
await expect(modal.locator('a', {hasText: 'Connect with Stripe'})).toHaveAttribute('href', '/ghost/api/admin/members/stripe_connect?mode=live');
await modal.getByLabel('Test mode').check();
await expect(modal.locator('a', {hasText: 'Connect with Stripe'})).toHaveAttribute('href', '/ghost/api/admin/members/stripe_connect?mode=test');
await modal.getByPlaceholder('Paste your secure key here').fill('token_test');
await modal.getByRole('button', {name: 'Save Stripe settings'}).click();
await expect(modal.getByText('You are connected with Stripe!')).toHaveCount(1);
await modal.getByRole('button', {name: 'Close'}).click();
await expect(modal).toHaveCount(0);
await expect(section.getByText('Connected to Stripe')).toHaveCount(1);
// We actually do two settings update requests here, this just checks the last one
expect(lastApiRequests.editSettings?.body).toEqual({
settings: [{
key: 'portal_plans',
value: '["free","monthly","yearly"]'
}]
});
});
test('Supports updating Stripe Direct settings', async ({page}) => {
const {lastApiRequests} = await mockApi({page, requests: {
...globalDataRequests,
browseConfig: {method: 'GET', path: '/config/', response: {
config: {
...responseFixtures.config,
stripeDirect: true
}
}},
editSettings: {method: 'PUT', path: '/settings/', response: updatedSettingsResponse([
{key: 'stripe_publishable_key', value: 'pk_test_123'},
{key: 'stripe_secret_key', value: 'sk_test_123'}
])}
}});
await page.goto('/');
const section = page.getByTestId('tiers');
await section.getByRole('button', {name: 'Connect with Stripe'}).click();
const modal = page.getByTestId('stripe-modal');
await modal.getByLabel('Publishable key').fill('pk_test_123');
await modal.getByLabel('Secure key').fill('sk_test_123');
await modal.getByRole('button', {name: 'Save Stripe settings'}).click();
await expect(modal).toHaveCount(0);
await expect(section.getByText('Connected to Stripe')).toHaveCount(1);
expect(lastApiRequests.editSettings?.body).toEqual({
settings: [{
key: 'stripe_secret_key',
value: 'sk_test_123'
}, {
key: 'stripe_publishable_key',
value: 'pk_test_123'
}]
});
});
});

View file

@ -2,6 +2,7 @@ import {ConfigResponseType} from '../../src/utils/api/config';
import {CustomThemeSettingsResponseType} from '../../src/utils/api/customThemeSettings';
import {InvitesResponseType} from '../../src/utils/api/invites';
import {LabelsResponseType} from '../../src/utils/api/labels';
import {NewslettersResponseType} from '../../src/utils/api/newsletters';
import {OffersResponseType} from '../../src/utils/api/offers';
import {Page} from '@playwright/test';
import {RolesResponseType} from '../../src/utils/api/roles';
@ -40,6 +41,7 @@ export const responseFixtures = {
labels: JSON.parse(readFileSync(`${__dirname}/responses/labels.json`).toString()) as LabelsResponseType,
offers: JSON.parse(readFileSync(`${__dirname}/responses/offers.json`).toString()) as OffersResponseType,
themes: JSON.parse(readFileSync(`${__dirname}/responses/themes.json`).toString()) as ThemesResponseType,
newsletters: JSON.parse(readFileSync(`${__dirname}/responses/newsletters.json`).toString()) as NewslettersResponseType,
latestPost: {posts: [{id: '1', url: `${siteFixture.site.url}/test-post/`}]}
};

View file

@ -0,0 +1,90 @@
{
"newsletters": [
{
"id": "645453f4d254799990dd0e23",
"uuid": "f8436434-6eff-4737-9d50-ac055273f60b",
"name": "Awesome newsletter",
"description": null,
"feedback_enabled": true,
"slug": "default-newsletter",
"sender_name": "Sender",
"sender_email": null,
"sender_reply_to": "support",
"status": "active",
"visibility": "members",
"subscribe_on_signup": true,
"sort_order": 0,
"header_image": null,
"show_header_icon": true,
"show_header_title": true,
"title_font_category": "serif",
"title_alignment": "center",
"show_feature_image": true,
"body_font_category": "serif",
"footer_content": "",
"show_badge": false,
"show_header_name": true,
"show_post_title_section": true,
"show_comment_cta": true,
"show_subscription_details": false,
"show_latest_posts": false,
"background_color": "light",
"border_color": null,
"title_color": null,
"created_at": "2023-05-05T00:55:16.000Z",
"updated_at": "2023-08-04T11:38:55.000Z",
"count": {
"posts": 0,
"active_members": 0
}
},
{
"id": "64cbd8487078e646579e3103",
"uuid": "a9fb9efd-6a71-4497-accd-47de5729c4ad",
"name": "Average newsletter",
"description": null,
"feedback_enabled": true,
"slug": "average-newsletter",
"sender_name": null,
"sender_email": null,
"sender_reply_to": "newsletter",
"status": "archived",
"visibility": "members",
"subscribe_on_signup": true,
"sort_order": 1,
"header_image": null,
"show_header_icon": true,
"show_header_title": true,
"title_font_category": "sans_serif",
"title_alignment": "center",
"show_feature_image": true,
"body_font_category": "sans_serif",
"footer_content": null,
"show_badge": true,
"show_header_name": true,
"show_post_title_section": true,
"show_comment_cta": true,
"show_subscription_details": false,
"show_latest_posts": false,
"background_color": "light",
"border_color": null,
"title_color": null,
"created_at": "2023-08-03T16:39:36.000Z",
"updated_at": "2023-08-03T17:05:37.000Z",
"count": {
"posts": 0,
"active_members": 0
}
}
],
"meta": {
"pagination": {
"page": 1,
"limit": "all",
"pages": 1,
"total": 2,
"next": null,
"prev": null
}
}
}