mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-10 23:36:14 -05:00
Fixed low-hanging fruit in AdminX (#18061)
refs https://github.com/TryGhost/Product/issues/3832 - Fixed code editor horizontal scroll - Fixed toggle setting initial value in theme settings - Prevented locking site without a password - Updated to hide Pintura settings when config is set - Updated mailgun settings to show only if not in server config - Updated Pintura settings to hide banner when in server config - Fixed body font category not updating in newsletter preview - Fixed external images not loading when hosted - Added character count to user bio - Updated to scroll up when searching - Fixed code editor not working with null value - Improved form error messages
This commit is contained in:
parent
f566729ed6
commit
07f1ae06c8
25 changed files with 91 additions and 49 deletions
|
@ -1,7 +1,7 @@
|
|||
import CodeMirror, {ReactCodeMirrorProps, ReactCodeMirrorRef} from '@uiw/react-codemirror';
|
||||
import Heading from '../Heading';
|
||||
import Hint from '../Hint';
|
||||
import React, {forwardRef, useEffect, useId} from 'react';
|
||||
import React, {forwardRef, useEffect, useId, useRef, useState} from 'react';
|
||||
import clsx from 'clsx';
|
||||
import {EditorView} from '@codemirror/view';
|
||||
import {Extension} from '@codemirror/state';
|
||||
|
@ -40,26 +40,35 @@ const CodeEditorView = forwardRef<ReactCodeMirrorRef, CodeEditorProps>(function
|
|||
...props
|
||||
}, ref) {
|
||||
const id = useId();
|
||||
const sizeRef = useRef<HTMLDivElement>(null);
|
||||
const [width, setWidth] = useState(100);
|
||||
const [resolvedExtensions, setResolvedExtensions] = React.useState<Extension[] | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all(extensions).then(setResolvedExtensions);
|
||||
}, [extensions]);
|
||||
|
||||
useEffect(() => {
|
||||
const resizeObserver = new ResizeObserver(([entry]) => {
|
||||
setWidth(entry.contentRect.width);
|
||||
});
|
||||
|
||||
resizeObserver.observe(sizeRef.current!);
|
||||
|
||||
return () => resizeObserver.disconnect();
|
||||
}, []);
|
||||
|
||||
let styles = clsx(
|
||||
'peer order-2 overflow-hidden rounded-sm border',
|
||||
'peer order-2 w-full max-w-full overflow-hidden rounded-sm border',
|
||||
clearBg ? 'bg-transparent' : 'bg-grey-75',
|
||||
error ? 'border-red' : 'border-grey-500 hover:border-grey-700 focus:border-grey-800',
|
||||
title && 'mt-2',
|
||||
height === 'full' && 'h-full'
|
||||
);
|
||||
|
||||
if (!resolvedExtensions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={height === 'full' ? 'h-full' : ''}>
|
||||
return <>
|
||||
<div ref={sizeRef} />
|
||||
{resolvedExtensions && <div className={height === 'full' ? 'h-full' : ''} style={{width}}>
|
||||
<CodeMirror
|
||||
ref={ref}
|
||||
className={styles}
|
||||
|
@ -72,8 +81,8 @@ const CodeEditorView = forwardRef<ReactCodeMirrorRef, CodeEditorProps>(function
|
|||
/>
|
||||
{title && <Heading className={'order-1 !text-grey-700 peer-focus:!text-black'} htmlFor={id} useLabelTag={true}>{title}</Heading>}
|
||||
{hint && <Hint className='order-3' color={error ? 'red' : ''}>{hint}</Hint>}
|
||||
</div>
|
||||
);
|
||||
</div>}
|
||||
</>;
|
||||
});
|
||||
|
||||
export default CodeEditorView;
|
||||
|
|
|
@ -10,7 +10,7 @@ export type User = {
|
|||
email: string;
|
||||
profile_image: string;
|
||||
cover_image: string|null;
|
||||
bio: string;
|
||||
bio: string|null;
|
||||
website: string;
|
||||
location: string;
|
||||
facebook: string;
|
||||
|
|
|
@ -13,7 +13,7 @@ const Sidebar: React.FC = () => {
|
|||
const {filter, setFilter} = useSearch();
|
||||
const {updateRoute} = useRouting();
|
||||
|
||||
const {settings} = useGlobalData();
|
||||
const {settings, config} = useGlobalData();
|
||||
const [newslettersEnabled] = getSettingValues(settings, ['editor_default_email_recipients']) as [string];
|
||||
|
||||
const handleSectionClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
|
@ -24,11 +24,19 @@ const Sidebar: React.FC = () => {
|
|||
const hasTipsAndDonations = useFeatureFlag('tipsAndDonations');
|
||||
const hasRecommendations = useFeatureFlag('recommendations');
|
||||
|
||||
const updateSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFilter(e.target.value);
|
||||
|
||||
if (e.target.value) {
|
||||
document.getElementById('admin-x-root')?.scrollTo({top: 0, left: 0});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='tablet:h-[calc(100vh-5vmin-84px)] tablet:w-[240px] tablet:overflow-y-scroll'>
|
||||
<div className='relative mb-10 md:pt-4 tablet:pt-[32px]'>
|
||||
<Icon className='absolute top-2 md:top-6 tablet:top-10' colorClass='text-grey-500' name='magnifying-glass' size='sm' />
|
||||
<TextField autoComplete="off" className='border-b border-grey-500 bg-transparent px-3 py-1.5 pl-[24px] text-sm dark:text-white' placeholder="Search" title="Search" value={filter} hideTitle unstyled onChange={e => setFilter(e.target.value)} />
|
||||
<TextField autoComplete="off" className='border-b border-grey-500 bg-transparent px-3 py-1.5 pl-[24px] text-sm dark:text-white' placeholder="Search" title="Search" value={filter} hideTitle unstyled onChange={updateSearch} />
|
||||
</div>
|
||||
<div className="hidden tablet:!visible tablet:!block">
|
||||
<SettingNavSection title="General">
|
||||
|
@ -66,7 +74,7 @@ const Sidebar: React.FC = () => {
|
|||
<>
|
||||
<SettingNavItem navid='newsletters' title="Newsletters" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='default-recipients' title="Default recipients" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='mailgun' title="Mailgun settings" onClick={handleSectionClick} />
|
||||
{!config.mailgunIsConfigured && <SettingNavItem navid='mailgun' title="Mailgun settings" onClick={handleSectionClick} />}
|
||||
</>
|
||||
)}
|
||||
</SettingNavSection>
|
||||
|
|
|
@ -32,14 +32,14 @@ const CodeInjection: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
|||
const headerProps = {
|
||||
extensions: [html],
|
||||
hint: 'Code here will be injected into the {{ghost_head}} tag on every page of the site',
|
||||
value: headerContent,
|
||||
value: headerContent || '',
|
||||
onChange: (value: string) => updateSetting('codeinjection_head', value)
|
||||
};
|
||||
|
||||
const footerProps = {
|
||||
extensions: [html],
|
||||
hint: 'Code here will be injected into the {{ghost_foot}} tag on every page of the site',
|
||||
value: footerContent,
|
||||
value: footerContent || '',
|
||||
onChange: (value: string) => updateSetting('codeinjection_foot', value)
|
||||
};
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ const CustomIntegrationModalContent: React.FC<{integration: Integration}> = ({in
|
|||
} else {
|
||||
showToast({
|
||||
type: 'pageError',
|
||||
message: 'Can\'t save integration! One or more fields have errors, please doublecheck you filled all mandatory fields'
|
||||
message: 'Can\'t save integration, please double check that you\'ve filled in all mandatory fields.'
|
||||
});
|
||||
}
|
||||
}}
|
||||
|
|
|
@ -22,7 +22,7 @@ const PinturaModal = NiceModal.create(() => {
|
|||
css: false
|
||||
});
|
||||
|
||||
const {settings} = useGlobalData();
|
||||
const {settings, config} = useGlobalData();
|
||||
const [pinturaEnabled] = getSettingValues<boolean>(settings, ['pintura']);
|
||||
const {mutateAsync: editSettings} = useEditSettings();
|
||||
const {mutateAsync: uploadFile} = useUploadFile();
|
||||
|
@ -99,7 +99,7 @@ const PinturaModal = NiceModal.create(() => {
|
|||
title='Pintura'
|
||||
/>
|
||||
<div className='mt-7'>
|
||||
<div className='mb-7 flex flex-col items-stretch justify-between gap-4 rounded-sm bg-grey-75 p-4 dark:bg-grey-950 md:flex-row md:p-7'>
|
||||
{!config.pintura && <div className='mb-7 flex flex-col items-stretch justify-between gap-4 rounded-sm bg-grey-75 p-4 dark:bg-grey-950 md:flex-row md:p-7'>
|
||||
<div className='md:basis-1/2'>
|
||||
<p className='mb-4 font-bold'>Add advanced image editing to Ghost, with Pintura</p>
|
||||
<p className='mb-4 text-sm'>Pintura is a powerful JavaScript image editor that allows you to crop, rotate, annotate and modify images directly inside Ghost.</p>
|
||||
|
@ -109,7 +109,7 @@ const PinturaModal = NiceModal.create(() => {
|
|||
<img alt='Pintura screenshot' src={pinturaScreenshot} />
|
||||
<a className='-mb-1 text-sm font-bold text-green' href="https://pqina.nl/pintura/?ref=ghost.org" rel="noopener noreferrer" target="_blank">Find out more →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
<Form marginBottom={false} title='Pintura configuration' grouped>
|
||||
<Toggle
|
||||
|
@ -121,7 +121,7 @@ const PinturaModal = NiceModal.create(() => {
|
|||
setEnabled(e.target.checked);
|
||||
}}
|
||||
/>
|
||||
{enabled && (
|
||||
{enabled && !config.pintura && (
|
||||
<>
|
||||
<div className='flex flex-col justify-between gap-1 md:flex-row md:items-center'>
|
||||
<div>
|
||||
|
|
|
@ -43,7 +43,7 @@ const SlackModal = NiceModal.create(() => {
|
|||
} else {
|
||||
showToast({
|
||||
type: 'pageError',
|
||||
message: 'Can\'t save Slack settings! One or more fields have errors, please doublecheck you filled all mandatory fields'
|
||||
message: 'Can\'t save Slack settings, please double check that you\'ve filled in all mandatory fields.'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -66,7 +66,7 @@ const SlackModal = NiceModal.create(() => {
|
|||
} else {
|
||||
showToast({
|
||||
type: 'pageError',
|
||||
message: 'Can\'t save Slack settings! One or more fields have errors, please doublecheck you filled all mandatory fields'
|
||||
message: 'Can\'t save Slack settings, please double check that you\'ve filled in all mandatory fields.'
|
||||
});
|
||||
}
|
||||
}}
|
||||
|
|
|
@ -67,7 +67,7 @@ const WebhookModal: React.FC<WebhookModalProps> = ({webhook, integrationId}) =>
|
|||
} else {
|
||||
showToast({
|
||||
type: 'pageError',
|
||||
message: 'Can\'t save webhook! One or more fields have errors, please doublecheck you filled all mandatory fields'
|
||||
message: 'Can\'t save webhook, please double check that you\'ve filled in all mandatory fields.'
|
||||
});
|
||||
}
|
||||
}}
|
||||
|
|
|
@ -10,7 +10,7 @@ import useRouting from '../../../../hooks/useRouting';
|
|||
import {ReactComponent as ArrowRightIcon} from '../../../../admin-x-ds/assets/icons/arrow-right.svg';
|
||||
import {ReactComponent as Icon} from '../../../../assets/icons/zapier.svg';
|
||||
import {ReactComponent as Logo} from '../../../../assets/images/zapier-logo.svg';
|
||||
import {getGhostPaths} from '../../../../utils/helpers';
|
||||
import {getGhostPaths, resolveAsset} from '../../../../utils/helpers';
|
||||
import {useBrowseIntegrations} from '../../../../api/integrations';
|
||||
import {useEffect, useState} from 'react';
|
||||
import {useGlobalData} from '../../../providers/GlobalDataProvider';
|
||||
|
@ -102,9 +102,9 @@ const ZapierModal = NiceModal.create(() => {
|
|||
title={
|
||||
<div className='flex flex-col gap-4 md:flex-row md:items-center'>
|
||||
<div className='flex shrink-0 flex-nowrap items-center gap-2'>
|
||||
<img className='h-8 w-8 object-contain dark:invert' role='presentation' src={`${adminRoot}${template.ghostImage}`} />
|
||||
<img className='h-8 w-8 object-contain dark:invert' role='presentation' src={resolveAsset(template.ghostImage, adminRoot)} />
|
||||
<ArrowRightIcon className='h-3 w-3' />
|
||||
<img className='h-8 w-8 object-contain' role='presentation' src={`${adminRoot}${template.appImage}`} />
|
||||
<img className='h-8 w-8 object-contain' role='presentation' src={resolveAsset(template.appImage, adminRoot)} />
|
||||
</div>
|
||||
<span className='text-sm'>{template.title}</span>
|
||||
</div>
|
||||
|
|
|
@ -15,7 +15,7 @@ const searchKeywords = {
|
|||
};
|
||||
|
||||
const EmailSettings: React.FC = () => {
|
||||
const {settings} = useGlobalData();
|
||||
const {settings, config} = useGlobalData();
|
||||
const [newslettersEnabled] = getSettingValues(settings, ['editor_default_email_recipients']) as [string];
|
||||
|
||||
return (
|
||||
|
@ -25,7 +25,7 @@ const EmailSettings: React.FC = () => {
|
|||
<>
|
||||
<Newsletters keywords={searchKeywords.newsletters} />
|
||||
<DefaultRecipients keywords={searchKeywords.defaultRecipients} />
|
||||
<MailGun keywords={searchKeywords.mailgun} />
|
||||
{!config.mailgunIsConfigured && <MailGun keywords={searchKeywords.mailgun} />}
|
||||
</>
|
||||
)}
|
||||
</SettingSection>
|
||||
|
|
|
@ -83,7 +83,7 @@ const AddNewsletterModal: React.FC<AddNewsletterModalProps> = () => {
|
|||
} else {
|
||||
showToast({
|
||||
type: 'pageError',
|
||||
message: 'Can\'t save newsletter! One or more fields have errors, please doublecheck you filled all mandatory fields'
|
||||
message: 'Can\'t save newsletter, please double check that you\'ve filled in all mandatory fields.'
|
||||
});
|
||||
}
|
||||
}}
|
||||
|
|
|
@ -425,7 +425,7 @@ const NewsletterDetailModalContent: React.FC<{newsletter: Newsletter}> = ({newsl
|
|||
} else {
|
||||
showToast({
|
||||
type: 'pageError',
|
||||
message: 'Can\'t save newsletter! One or more fields have errors, please doublecheck you filled all mandatory fields'
|
||||
message: 'Can\'t save newsletter, please double check that you\'ve filled in all mandatory fields.'
|
||||
});
|
||||
}
|
||||
}}
|
||||
|
|
|
@ -93,6 +93,7 @@ const NewsletterPreview: React.FC<{newsletter: Newsletter}> = ({newsletter}) =>
|
|||
|
||||
return <NewsletterPreviewContent
|
||||
authorPlaceholder={currentUser.name || currentUser.email}
|
||||
bodyFontCategory={newsletter.body_font_category}
|
||||
footerContent={newsletter.footer_content}
|
||||
headerIcon={newsletter.show_header_icon ? icon : undefined}
|
||||
headerImage={newsletter.header_image}
|
||||
|
|
|
@ -16,8 +16,20 @@ const LockSite: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
|||
handleSave,
|
||||
handleCancel,
|
||||
updateSetting,
|
||||
handleEditingChange
|
||||
} = useSettingGroup();
|
||||
handleEditingChange,
|
||||
errors,
|
||||
clearError
|
||||
} = useSettingGroup({
|
||||
onValidate: () => {
|
||||
if (passwordEnabled && !password) {
|
||||
return {
|
||||
password: 'Password must be supplied'
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
});
|
||||
|
||||
const [passwordEnabled, password] = getSettingValues(localSettings, ['is_private', 'password']) as [boolean, string];
|
||||
|
||||
|
@ -65,12 +77,14 @@ const LockSite: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
|||
/>
|
||||
{passwordEnabled &&
|
||||
<TextField
|
||||
hint={hint}
|
||||
error={!!errors.password}
|
||||
hint={errors.password || hint}
|
||||
placeholder="Enter password"
|
||||
title="Site password"
|
||||
value={password}
|
||||
hideTitle
|
||||
onChange={handlePasswordChange}
|
||||
onKeyDown={() => clearError('password')}
|
||||
/>
|
||||
}
|
||||
</SettingGroupContent>
|
||||
|
|
|
@ -11,6 +11,7 @@ import Radio from '../../../admin-x-ds/global/form/Radio';
|
|||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
|
||||
import SettingGroupContent from '../../../admin-x-ds/settings/SettingGroupContent';
|
||||
import TextArea from '../../../admin-x-ds/global/form/TextArea';
|
||||
import TextField from '../../../admin-x-ds/global/form/TextField';
|
||||
import Toggle from '../../../admin-x-ds/global/form/Toggle';
|
||||
import useFeatureFlag from '../../../hooks/useFeatureFlag';
|
||||
|
@ -190,10 +191,10 @@ const DetailsInputs: React.FC<UserDetailProps> = ({errors, validators, user, set
|
|||
setUserData?.({...user, twitter: e.target.value});
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
hint="Recommended: 200 characters."
|
||||
<TextArea
|
||||
hint={<>Recommended: 200 characters. You‘ve used <span className='font-bold'>{user.bio?.length || 0}</span></>}
|
||||
title="Bio"
|
||||
value={user.bio}
|
||||
value={user.bio || ''}
|
||||
onChange={(e) => {
|
||||
setUserData?.({...user, bio: e.target.value});
|
||||
}}
|
||||
|
@ -674,7 +675,7 @@ const UserDetailModalContent: React.FC<{user: User}> = ({user}) => {
|
|||
if (error) {
|
||||
showToast({
|
||||
type: 'pageError',
|
||||
message: 'Can\'t save user! One or more fields have errors, please doublecheck you filled all mandatory fields'
|
||||
message: 'Can\'t save user, please double check that you\'ve filled in all mandatory fields.'
|
||||
});
|
||||
setSaveState('');
|
||||
return;
|
||||
|
|
|
@ -121,7 +121,7 @@ const TierDetailModalContent: React.FC<{tier?: Tier}> = ({tier}) => {
|
|||
if (Object.values(validators).filter(validator => validator()).length) {
|
||||
showToast({
|
||||
type: 'pageError',
|
||||
message: 'Can\'t save tier! One or more fields have errors, please doublecheck you filled all mandatory fields'
|
||||
message: 'Can\'t save tier, please double check that you\'ve filled in all mandatory fields.'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ const ThemeSetting: React.FC<{
|
|||
case 'boolean':
|
||||
return (
|
||||
<Toggle
|
||||
checked={setting.value}
|
||||
direction="rtl"
|
||||
hint={setting.description}
|
||||
label={humanizeSettingKey(setting.key)}
|
||||
|
|
|
@ -135,7 +135,7 @@ const AddRecommendationModal: React.FC<AddRecommendationModalProps> = ({recommen
|
|||
} else {
|
||||
showToast({
|
||||
type: 'pageError',
|
||||
message: 'One or more fields have errors, please doublecheck you filled all mandatory fields'
|
||||
message: 'One or more fields have errors, please double check that you\'ve filled in all mandatory fields.'
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
@ -101,7 +101,7 @@ const AddRecommendationModalConfirm: React.FC<AddRecommendationModalProps> = ({r
|
|||
} else {
|
||||
showToast({
|
||||
type: 'pageError',
|
||||
message: 'One or more fields have errors, please doublecheck you filled all mandatory fields'
|
||||
message: 'One or more fields have errors, please double check that you\'ve filled in all mandatory fields.'
|
||||
});
|
||||
}
|
||||
}}
|
||||
|
|
|
@ -70,7 +70,7 @@ const EditRecommendationModalConfirm: React.FC<AddRecommendationModalProps> = ({
|
|||
} else {
|
||||
showToast({
|
||||
type: 'pageError',
|
||||
message: 'One or more fields have errors, please doublecheck you filled all mandatory fields'
|
||||
message: 'One or more fields have errors, please double check that you\'ve filled in all mandatory fields.'
|
||||
});
|
||||
}
|
||||
}}
|
||||
|
|
|
@ -3,7 +3,7 @@ import MarketplaceBgImage from '../../../../assets/images/footer-marketplace-bg.
|
|||
import ModalPage from '../../../../admin-x-ds/global/modal/ModalPage';
|
||||
import React from 'react';
|
||||
import {OfficialTheme, useOfficialThemes} from '../../../providers/ServiceProvider';
|
||||
import {getGhostPaths} from '../../../../utils/helpers';
|
||||
import {getGhostPaths, resolveAsset} from '../../../../utils/helpers';
|
||||
|
||||
const OfficialThemes: React.FC<{
|
||||
onSelectTheme?: (theme: OfficialTheme) => void;
|
||||
|
@ -26,7 +26,7 @@ const OfficialThemes: React.FC<{
|
|||
<img
|
||||
alt={`${theme.name} Theme`}
|
||||
className='h-full w-full object-contain'
|
||||
src={`${adminRoot}${theme.image}`}
|
||||
src={resolveAsset(theme.image, adminRoot)}
|
||||
/>
|
||||
</div>
|
||||
<div className='mt-3'>
|
||||
|
|
|
@ -105,7 +105,7 @@ const useSettingGroup = ({onValidate}: {onValidate?: () => ErrorMessages} = {}):
|
|||
} else {
|
||||
showToast({
|
||||
type: 'pageError',
|
||||
message: 'Can\'t save settings! One or more fields have errors, please doublecheck you filled all mandatory fields'
|
||||
message: 'Can\'t save settings! One or more fields have errors, please double check that you\'ve filled in all mandatory fields.'
|
||||
});
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -14,6 +14,14 @@ export function getGhostPaths(): IGhostPaths {
|
|||
return {subdir, adminRoot, assetRoot, apiRoot};
|
||||
}
|
||||
|
||||
export function resolveAsset(assetPath: string, relativeTo: string) {
|
||||
if (assetPath.match(/^(?:[a-z]+:)?\/\//i)) {
|
||||
return assetPath;
|
||||
}
|
||||
|
||||
return `${relativeTo}${assetPath}`;
|
||||
}
|
||||
|
||||
export function getLocalTime(timeZone: string) {
|
||||
const date = new Date();
|
||||
const options = {timeZone: timeZone};
|
||||
|
|
|
@ -26,7 +26,7 @@ test.describe('Newsletter settings', async () => {
|
|||
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(page.getByTestId('toast')).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
|
||||
|
@ -69,7 +69,7 @@ test.describe('Newsletter settings', async () => {
|
|||
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(page.getByTestId('toast')).toHaveText(/Can't save newsletter/);
|
||||
await expect(modal).toHaveText(/Please enter a name/);
|
||||
|
||||
await modal.getByPlaceholder('Weekly Roundup').fill('Updated newsletter');
|
||||
|
|
|
@ -26,7 +26,7 @@ test.describe('Tier settings', async () => {
|
|||
|
||||
await modal.getByRole('button', {name: 'Save & close'}).click();
|
||||
|
||||
await expect(page.getByTestId('toast')).toHaveText(/One or more fields have errors/);
|
||||
await expect(page.getByTestId('toast')).toHaveText(/Can't save tier/);
|
||||
await expect(modal).toHaveText(/You must specify a name/);
|
||||
await expect(modal).toHaveText(/Amount must be at least \$1/);
|
||||
|
||||
|
@ -104,7 +104,7 @@ test.describe('Tier settings', async () => {
|
|||
await modal.getByLabel('Name').fill('');
|
||||
await modal.getByRole('button', {name: 'Save & close'}).click();
|
||||
|
||||
await expect(page.getByTestId('toast')).toHaveText(/One or more fields have errors/);
|
||||
await expect(page.getByTestId('toast')).toHaveText(/Can't save tier/);
|
||||
await expect(modal).toHaveText(/You must specify a name/);
|
||||
|
||||
await modal.getByLabel('Name').fill('Supporter updated');
|
||||
|
|
Loading…
Add table
Reference in a new issue