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:
parent
06011bf513
commit
a413f2b54f
11 changed files with 404 additions and 15 deletions
|
@ -13,7 +13,7 @@ export interface ConfirmationModalProps {
|
|||
onCancel?: () => void;
|
||||
onOk?: (modal?: {
|
||||
remove: () => void;
|
||||
}) => void;
|
||||
}) => void | Promise<void>;
|
||||
customFooter?: boolean | React.ReactNode;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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‘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> = {};
|
||||
|
|
|
@ -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'>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) => ({
|
||||
|
|
180
apps/admin-x-settings/test/e2e/email/newsletters.test.ts
Normal file
180
apps/admin-x-settings/test/e2e/email/newsletters.test.ts
Normal 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'
|
||||
}]
|
||||
});
|
||||
});
|
||||
});
|
89
apps/admin-x-settings/test/e2e/membership/stripe.test.ts
Normal file
89
apps/admin-x-settings/test/e2e/membership/stripe.test.ts
Normal 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'
|
||||
}]
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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/`}]}
|
||||
};
|
||||
|
||||
|
|
90
apps/admin-x-settings/test/utils/responses/newsletters.json
Normal file
90
apps/admin-x-settings/test/utils/responses/newsletters.json
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue