AdminX: reroute settings-x to settings (#18360)

refs. https://github.com/TryGhost/Product/issues/3949

---------

Co-authored-by: Jono Mingard <reason.koan@gmail.com>
Co-authored-by: Daniel Lockyer <hi@daniellockyer.com>
This commit is contained in:
Peter Zimon 2023-10-09 09:12:46 +02:00 committed by GitHub
parent 741a00fe05
commit e68db848dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 3844 additions and 3971 deletions

View File

@ -77,8 +77,7 @@ jobs:
- name: Build Admin - name: Build Admin
if: env.ENVIRONMENT == 'browser-tests-local' if: env.ENVIRONMENT == 'browser-tests-local'
working-directory: ghost/admin run: yarn nx run ghost-admin:build:dev
run: yarn build:dev
- name: Run Playwright tests on a remote site - name: Run Playwright tests on a remote site
if: env.ENVIRONMENT == 'browser-tests-staging' if: env.ENVIRONMENT == 'browser-tests-staging'

View File

@ -26,8 +26,8 @@ const MainContent: React.FC = () => {
const {route, updateRoute, loadingModal} = useRouting(); const {route, updateRoute, loadingModal} = useRouting();
useEffect(() => { useEffect(() => {
if (!canAccessSettings(currentUser) && route !== `users/show/${currentUser.slug}`) { if (!canAccessSettings(currentUser) && route !== `staff/${currentUser.slug}`) {
updateRoute(`users/show/${currentUser.slug}`); updateRoute(`staff/${currentUser.slug}`);
} }
}, [currentUser, route, updateRoute]); }, [currentUser, route, updateRoute]);

View File

@ -82,7 +82,7 @@ const ClearIndicator: React.FC<ClearIndicatorProps<SelectOption, false>> = props
const Option: React.FC<OptionProps<SelectOption, false>> = ({children, ...optionProps}) => ( const Option: React.FC<OptionProps<SelectOption, false>> = ({children, ...optionProps}) => (
<components.Option {...optionProps}> <components.Option {...optionProps}>
<span data-testid="select-option">{children}</span> <span data-testid="select-option" data-value={optionProps.data.value}>{children}</span>
{optionProps.data.hint && <span className="block text-xs text-grey-700 dark:text-grey-300">{optionProps.data.hint}</span>} {optionProps.data.hint && <span className="block text-xs text-grey-700 dark:text-grey-300">{optionProps.data.hint}</span>}
</components.Option> </components.Option>
); );

View File

@ -97,13 +97,13 @@ export const getActorLinkTarget = (action: Action): InternalLink | ExternalLink
return; return;
} }
return {route: `integrations/show/${action.actor.id}`}; return {route: `integrations/${action.actor.id}`};
case 'user': case 'user':
if (!action.actor.slug) { if (!action.actor.slug) {
return; return;
} }
return {route: `users/show/${action.actor.slug}`}; return {route: `staff/${action.actor.slug}`};
} }
return; return;
@ -136,7 +136,7 @@ export const getLinkTarget = (action: Action): InternalLink | ExternalLink | und
return; return;
} }
return {route: `integrations/show/${action.resource.id}`}; return {route: `integrations/${action.resource.id}`};
case 'offer': case 'offer':
if (!action.resource || !action.resource.id) { if (!action.resource || !action.resource.id) {
return; return;
@ -164,7 +164,7 @@ export const getLinkTarget = (action: Action): InternalLink | ExternalLink | und
return; return;
} }
return {route: `users/show/${action.resource.slug}`}; return {route: `staff/${action.resource.slug}`};
} }
} }

View File

@ -15,9 +15,6 @@ const Settings: React.FC = () => {
<MembershipSettings /> <MembershipSettings />
<EmailSettings /> <EmailSettings />
<AdvancedSettings /> <AdvancedSettings />
<div className='mt-40 text-sm'>
<a className='text-green' href="/ghost/#/settings">Click here</a> to open the original Admin settings.
</div>
</div> </div>
</> </>
); );

View File

@ -75,7 +75,7 @@ const Sidebar: React.FC = () => {
</div> </div>
<div className="no-scrollbar hidden pt-10 tablet:!visible tablet:!block tablet:h-[calc(100vh-5vmin-84px-64px)] tablet:w-[240px] tablet:overflow-y-auto" id='admin-x-settings-sidebar'> <div className="no-scrollbar hidden pt-10 tablet:!visible tablet:!block tablet:h-[calc(100vh-5vmin-84px-64px)] tablet:w-[240px] tablet:overflow-y-auto" id='admin-x-settings-sidebar'>
<SettingNavSection keywords={Object.values(generalSearchKeywords).flat()} title="General"> <SettingNavSection keywords={Object.values(generalSearchKeywords).flat()} title="General">
<SettingNavItem keywords={generalSearchKeywords.titleAndDescription} navid='title-and-description' title="Title & description" onClick={handleSectionClick} /> <SettingNavItem keywords={generalSearchKeywords.titleAndDescription} navid='general' title="Title & description" onClick={handleSectionClick} />
<SettingNavItem keywords={generalSearchKeywords.timeZone} navid='timezone' title="Timezone" onClick={handleSectionClick} /> <SettingNavItem keywords={generalSearchKeywords.timeZone} navid='timezone' title="Timezone" onClick={handleSectionClick} />
<SettingNavItem keywords={generalSearchKeywords.publicationLanguage} navid='publication-language' title="Publication language" onClick={handleSectionClick} /> <SettingNavItem keywords={generalSearchKeywords.publicationLanguage} navid='publication-language' title="Publication language" onClick={handleSectionClick} />
<SettingNavItem keywords={generalSearchKeywords.metadata} navid='metadata' title="Meta data" onClick={handleSectionClick} /> <SettingNavItem keywords={generalSearchKeywords.metadata} navid='metadata' title="Meta data" onClick={handleSectionClick} />
@ -83,7 +83,7 @@ const Sidebar: React.FC = () => {
<SettingNavItem keywords={generalSearchKeywords.facebook} navid='facebook' title="Facebook card" onClick={handleSectionClick} /> <SettingNavItem keywords={generalSearchKeywords.facebook} navid='facebook' title="Facebook card" onClick={handleSectionClick} />
<SettingNavItem keywords={generalSearchKeywords.socialAccounts} navid='social-accounts' title="Social accounts" onClick={handleSectionClick} /> <SettingNavItem keywords={generalSearchKeywords.socialAccounts} navid='social-accounts' title="Social accounts" onClick={handleSectionClick} />
<SettingNavItem keywords={generalSearchKeywords.lockSite} navid='locksite' title="Make this site private" onClick={handleSectionClick} /> <SettingNavItem keywords={generalSearchKeywords.lockSite} navid='locksite' title="Make this site private" onClick={handleSectionClick} />
<SettingNavItem keywords={generalSearchKeywords.users} navid='users' title="Staff" onClick={handleSectionClick} /> <SettingNavItem keywords={generalSearchKeywords.users} navid='staff' title="Staff" onClick={handleSectionClick} />
</SettingNavSection> </SettingNavSection>
<SettingNavSection keywords={Object.values(siteSearchKeywords).flat()} title="Site"> <SettingNavSection keywords={Object.values(siteSearchKeywords).flat()} title="Site">
@ -93,7 +93,7 @@ const Sidebar: React.FC = () => {
</SettingNavSection> </SettingNavSection>
<SettingNavSection keywords={Object.values(membershipSearchKeywords).flat()} title="Membership"> <SettingNavSection keywords={Object.values(membershipSearchKeywords).flat()} title="Membership">
<SettingNavItem keywords={membershipSearchKeywords.access} navid='access' title="Access" onClick={handleSectionClick} /> <SettingNavItem keywords={membershipSearchKeywords.access} navid='members' title="Access" onClick={handleSectionClick} />
<SettingNavItem keywords={membershipSearchKeywords.portal} navid='portal' title="Portal" onClick={handleSectionClick} /> <SettingNavItem keywords={membershipSearchKeywords.portal} navid='portal' title="Portal" onClick={handleSectionClick} />
<SettingNavItem keywords={membershipSearchKeywords.tiers} navid='tiers' title="Tiers" onClick={handleSectionClick} /> <SettingNavItem keywords={membershipSearchKeywords.tiers} navid='tiers' title="Tiers" onClick={handleSectionClick} />
{hasTipsAndDonations && <SettingNavItem keywords={membershipSearchKeywords.tips} navid='tips-or-donations' title="Tips or donations" onClick={handleSectionClick} />} {hasTipsAndDonations && <SettingNavItem keywords={membershipSearchKeywords.tips} navid='tips-or-donations' title="Tips or donations" onClick={handleSectionClick} />}

View File

@ -35,19 +35,19 @@ export type RoutingModalProps = {
} }
const modalPaths: {[key: string]: ModalName} = { const modalPaths: {[key: string]: ModalName} = {
'design/edit/themes': 'DesignAndThemeModal', 'design/change-theme': 'DesignAndThemeModal',
'design/edit': 'DesignAndThemeModal', 'design/edit': 'DesignAndThemeModal',
// this is a special route, because it can install a theme directly from the Ghost Marketplace // this is a special route, because it can install a theme directly from the Ghost Marketplace
'design/change-theme/install': 'DesignAndThemeModal', 'design/change-theme/install': 'DesignAndThemeModal',
'navigation/edit': 'NavigationModal', 'navigation/edit': 'NavigationModal',
'users/invite': 'InviteUserModal', 'staff/invite': 'InviteUserModal',
'users/show/:slug': 'UserDetailModal', 'staff/:slug': 'UserDetailModal',
'portal/edit': 'PortalModal', 'portal/edit': 'PortalModal',
'tiers/add': 'TierDetailModal', 'tiers/add': 'TierDetailModal',
'tiers/show/:id': 'TierDetailModal', 'tiers/:id': 'TierDetailModal',
'stripe-connect': 'StripeConnectModal', 'stripe-connect': 'StripeConnectModal',
'newsletters/add': 'AddNewsletterModal', 'newsletters/new': 'AddNewsletterModal',
'newsletters/show/:id': 'NewsletterDetailModal', 'newsletters/:id': 'NewsletterDetailModal',
'history/view': 'HistoryModal', 'history/view': 'HistoryModal',
'history/view/:user': 'HistoryModal', 'history/view/:user': 'HistoryModal',
'integrations/zapier': 'ZapierModal', 'integrations/zapier': 'ZapierModal',
@ -56,8 +56,8 @@ const modalPaths: {[key: string]: ModalName} = {
'integrations/unsplash': 'UnsplashModal', 'integrations/unsplash': 'UnsplashModal',
'integrations/firstpromoter': 'FirstpromoterModal', 'integrations/firstpromoter': 'FirstpromoterModal',
'integrations/pintura': 'PinturaModal', 'integrations/pintura': 'PinturaModal',
'integrations/add': 'AddIntegrationModal', 'integrations/new': 'AddIntegrationModal',
'integrations/show/:id': 'CustomIntegrationModal', 'integrations/:id': 'CustomIntegrationModal',
'recommendations/add': 'AddRecommendationModal', 'recommendations/add': 'AddRecommendationModal',
'recommendations/edit': 'EditRecommendationModal', 'recommendations/edit': 'EditRecommendationModal',
'announcement-bar/edit': 'AnnouncementBarModal', 'announcement-bar/edit': 'AnnouncementBarModal',
@ -68,7 +68,7 @@ function getHashPath(urlPath: string | undefined) {
if (!urlPath) { if (!urlPath) {
return null; return null;
} }
const regex = /\/settings-x\/(.*)/; const regex = /\/settings\/(.*)/;
const match = urlPath?.match(regex); const match = urlPath?.match(regex);
if (match) { if (match) {
@ -142,9 +142,9 @@ const RoutingProvider: React.FC<RouteProviderProps> = ({externalNavigate, childr
const newPath = options.route; const newPath = options.route;
if (newPath) { if (newPath) {
window.location.hash = `/settings-x/${newPath}`; window.location.hash = `/settings/${newPath}`;
} else { } else {
window.location.hash = `/settings-x`; window.location.hash = `/settings`;
} }
}, [externalNavigate]); }, [externalNavigate]);

View File

@ -158,7 +158,7 @@ const CustomIntegrations: React.FC<{integrations: Integration[]}> = ({integratio
<List borderTop={false}> <List borderTop={false}>
{integrations.map(integration => ( {integrations.map(integration => (
<IntegrationItem <IntegrationItem
action={() => updateRoute({route: `integrations/show/${integration.id}`})} action={() => updateRoute({route: `integrations/${integration.id}`})}
detail={integration.description || 'No description'} detail={integration.description || 'No description'}
icon={ icon={
integration.icon_image ? integration.icon_image ?
@ -218,7 +218,7 @@ const Integrations: React.FC<{ keywords: string[] }> = ({keywords}) => {
const buttons = ( const buttons = (
<Button className='hidden md:!visible md:!block' color='green' label='Add custom integration' link linkWithPadding onClick={() => { <Button className='hidden md:!visible md:!block' color='green' label='Add custom integration' link linkWithPadding onClick={() => {
updateRoute('integrations/add'); updateRoute('integrations/new');
setSelectedTab('custom'); setSelectedTab('custom');
}} /> }} />
); );
@ -234,7 +234,7 @@ const Integrations: React.FC<{ keywords: string[] }> = ({keywords}) => {
> >
<div className='flex justify-center rounded border border-green px-4 py-2 md:hidden'> <div className='flex justify-center rounded border border-green px-4 py-2 md:hidden'>
<Button color='green' label='Add custom integration' link onClick={() => { <Button color='green' label='Add custom integration' link onClick={() => {
updateRoute('integrations/add'); updateRoute('integrations/new');
setSelectedTab('custom'); setSelectedTab('custom');
}} /> }} />
</div> </div>

View File

@ -51,7 +51,7 @@ const AddIntegrationModal: React.FC<RoutingModalProps> = () => {
try { try {
const data = await createIntegration({name}); const data = await createIntegration({name});
modal.remove(); modal.remove();
updateRoute({route: `integrations/show/${data.integrations[0].id}`}); updateRoute({route: `integrations/${data.integrations[0].id}`});
} catch (e) { } catch (e) {
handleError(e); handleError(e);
} }

View File

@ -62,7 +62,7 @@ const AlphaFeatures: React.FC = () => {
<List titleSeparator={false}> <List titleSeparator={false}>
{features.map(feature => ( {features.map(feature => (
<LabItem <LabItem
action={<FeatureToggle flag={feature.flag} />} action={<FeatureToggle flag={feature.flag} label={feature.title} />}
detail={feature.description} detail={feature.description}
title={feature.title} /> title={feature.title} />
))} ))}

View File

@ -6,14 +6,14 @@ import {getSettingValue, useEditSettings} from '../../../../api/settings';
import {useGlobalData} from '../../../providers/GlobalDataProvider'; import {useGlobalData} from '../../../providers/GlobalDataProvider';
import {useQueryClient} from '@tanstack/react-query'; import {useQueryClient} from '@tanstack/react-query';
const FeatureToggle: React.FC<{ flag: string; }> = ({flag}) => { const FeatureToggle: React.FC<{ flag: string; label?: string; }> = ({label, flag}) => {
const {settings} = useGlobalData(); const {settings} = useGlobalData();
const labs = JSON.parse(getSettingValue<string>(settings, 'labs') || '{}'); const labs = JSON.parse(getSettingValue<string>(settings, 'labs') || '{}');
const {mutateAsync: editSettings} = useEditSettings(); const {mutateAsync: editSettings} = useEditSettings();
const client = useQueryClient(); const client = useQueryClient();
const handleError = useHandleError(); const handleError = useHandleError();
return <Toggle checked={labs[flag]} onChange={async () => { return <Toggle checked={labs[flag]} label={label} labelClasses='sr-only' onChange={async () => {
const newValue = !labs[flag]; const newValue = !labs[flag];
try { try {
await editSettings([{ await editSettings([{

View File

@ -10,7 +10,7 @@ import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
const Newsletters: React.FC<{ keywords: string[] }> = ({keywords}) => { const Newsletters: React.FC<{ keywords: string[] }> = ({keywords}) => {
const {updateRoute} = useRouting(); const {updateRoute} = useRouting();
const openNewsletterModal = () => { const openNewsletterModal = () => {
updateRoute('newsletters/add'); updateRoute('newsletters/new');
}; };
const [selectedTab, setSelectedTab] = useState('active-newsletters'); const [selectedTab, setSelectedTab] = useState('active-newsletters');
const {data: {newsletters, meta, isEnd} = {}, fetchNextPage} = useBrowseNewsletters(); const {data: {newsletters, meta, isEnd} = {}, fetchNextPage} = useBrowseNewsletters();

View File

@ -40,7 +40,7 @@ const AddNewsletterModal: React.FC<RoutingModalProps> = () => {
feedback_enabled: true feedback_enabled: true
}); });
updateRoute({route: `newsletters/show/${response.newsletters[0].id}`}); updateRoute({route: `newsletters/${response.newsletters[0].id}`});
}, },
onSaveError: handleError, onSaveError: handleError,
onValidate: () => { onValidate: () => {

View File

@ -16,7 +16,7 @@ const NewsletterItem: React.FC<{newsletter: Newsletter}> = ({newsletter}) => {
const {updateRoute} = useRouting(); const {updateRoute} = useRouting();
const showDetails = () => { const showDetails = () => {
updateRoute({route: `newsletters/show/${newsletter.id}`}); updateRoute({route: `newsletters/${newsletter.id}`});
}; };
return ( return (

View File

@ -130,7 +130,7 @@ const InviteUserModal = NiceModal.create(() => {
}); });
modal.remove(); modal.remove();
updateRoute('users'); updateRoute('staff');
} catch (e) { } catch (e) {
setSaveState('error'); setSaveState('error');
@ -175,7 +175,7 @@ const InviteUserModal = NiceModal.create(() => {
return ( return (
<Modal <Modal
afterClose={() => { afterClose={() => {
updateRoute('users'); updateRoute('staff');
}} }}
cancelLabel='' cancelLabel=''
okLabel={okLabel} okLabel={okLabel}

View File

@ -70,7 +70,7 @@ const TitleAndDescription: React.FC<{ keywords: string[] }> = ({keywords}) => {
description='The details used to identify your publication around the web' description='The details used to identify your publication around the web'
isEditing={isEditing} isEditing={isEditing}
keywords={keywords} keywords={keywords}
navid='title-and-description' navid='general'
saveState={saveState} saveState={saveState}
testId='title-and-description' testId='title-and-description'
title='Title & description' title='Title & description'

View File

@ -80,7 +80,7 @@ const UserDetailModalContent: React.FC<{user: User}> = ({user}) => {
const navigateOnClose = useCallback(() => { const navigateOnClose = useCallback(() => {
if (canAccessSettings(currentUser)) { if (canAccessSettings(currentUser)) {
updateRoute('users'); updateRoute('staff');
} else { } else {
updateRoute({isExternal: true, route: 'dashboard'}); updateRoute({isExternal: true, route: 'dashboard'});
} }

View File

@ -39,7 +39,7 @@ const Owner: React.FC<OwnerProps> = ({user}) => {
const showDetailModal = () => { const showDetailModal = () => {
if (hasAdminAccess(currentUser)) { if (hasAdminAccess(currentUser)) {
updateRoute({route: `users/show/${user.slug}`}); updateRoute({route: `staff/${user.slug}`});
} }
}; };
@ -63,7 +63,7 @@ const UsersList: React.FC<UsersListProps> = ({users, groupname}) => {
const {currentUser} = useGlobalData(); const {currentUser} = useGlobalData();
const showDetailModal = (user: User) => { const showDetailModal = (user: User) => {
updateRoute({route: `users/show/${user.slug}`}); updateRoute({route: `staff/${user.slug}`});
}; };
if (!users || !users.length) { if (!users || !users.length) {
@ -221,7 +221,7 @@ const Users: React.FC<{ keywords: string[], highlight?: boolean }> = ({keywords,
const {updateRoute} = useRouting(); const {updateRoute} = useRouting();
const showInviteModal = () => { const showInviteModal = () => {
updateRoute('users/invite'); updateRoute('staff/invite');
}; };
const buttons = ( const buttons = (
@ -265,7 +265,7 @@ const Users: React.FC<{ keywords: string[], highlight?: boolean }> = ({keywords,
customButtons={buttons} customButtons={buttons}
highlightOnModalClose={highlight} highlightOnModalClose={highlight}
keywords={keywords} keywords={keywords}
navid='users' navid='staff'
testId='users' testId='users'
title='Staff' title='Staff'
> >

View File

@ -182,7 +182,7 @@ const Access: React.FC<{ keywords: string[] }> = ({keywords}) => {
description='Set up default access options for subscription and posts' description='Set up default access options for subscription and posts'
isEditing={isEditing} isEditing={isEditing}
keywords={keywords} keywords={keywords}
navid='access' navid='members'
saveState={saveState} saveState={saveState}
testId='access' testId='access'
title='Access' title='Access'

View File

@ -17,7 +17,7 @@ const StripeConnectedButton: React.FC<{className?: string; onClick: () => void;}
className className
); );
return ( return (
<button className={className} type='button' onClick={onClick}> <button className={className} data-testid='stripe-connected' type='button' onClick={onClick}>
<span className="inline-flex h-2 w-2 rounded-full bg-green transition-all group-hover:bg-[#625BF6]"></span> <span className="inline-flex h-2 w-2 rounded-full bg-green transition-all group-hover:bg-[#625BF6]"></span>
<span className='ml-2'>Connected to Stripe</span> <span className='ml-2'>Connected to Stripe</span>
</button> </button>

View File

@ -103,6 +103,8 @@ const TipsOrDonations: React.FC<{ keywords: string[] }> = ({keywords}) => {
fullWidth={false} fullWidth={false}
options={currencySelectGroups()} options={currencySelectGroups()}
selectedOption={currencySelectGroups().flatMap(group => group.options).find(option => option.value === donationsCurrency)} selectedOption={currencySelectGroups().flatMap(group => group.options).find(option => option.value === donationsCurrency)}
title='Currency'
hideTitle
isSearchable isSearchable
onSelect={option => updateSetting('donations_currency', option?.value || 'USD')} onSelect={option => updateSetting('donations_currency', option?.value || 'USD')}
/> />

View File

@ -2,7 +2,7 @@ import Button from '../../../../admin-x-ds/global/Button';
import List from '../../../../admin-x-ds/global/List'; import List from '../../../../admin-x-ds/global/List';
import ListItem from '../../../../admin-x-ds/global/ListItem'; import ListItem from '../../../../admin-x-ds/global/ListItem';
import ModalPage from '../../../../admin-x-ds/global/modal/ModalPage'; import ModalPage from '../../../../admin-x-ds/global/modal/ModalPage';
import React, {useEffect, useState} from 'react'; import React, {useEffect, useId, useState} from 'react';
import Select from '../../../../admin-x-ds/global/form/Select'; import Select from '../../../../admin-x-ds/global/form/Select';
import TextField from '../../../../admin-x-ds/global/form/TextField'; import TextField from '../../../../admin-x-ds/global/form/TextField';
import {getHomepageUrl} from '../../../../api/site'; import {getHomepageUrl} from '../../../../api/site';
@ -15,6 +15,8 @@ interface PortalLinkPrefs {
} }
const PortalLink: React.FC<PortalLinkPrefs> = ({name, value}) => { const PortalLink: React.FC<PortalLinkPrefs> = ({name, value}) => {
const id = useId();
return ( return (
<ListItem <ListItem
action={<Button color='black' label='Copy' link onClick={(e) => { action={<Button color='black' label='Copy' link onClick={(e) => {
@ -29,8 +31,8 @@ const PortalLink: React.FC<PortalLinkPrefs> = ({name, value}) => {
separator separator
> >
<div className='flex w-full grow items-center gap-5 py-3'> <div className='flex w-full grow items-center gap-5 py-3'>
<span className='inline-block w-[240px] whitespace-nowrap'>{name}</span> <label className='inline-block w-[240px] whitespace-nowrap' htmlFor={id}>{name}</label>
<TextField className='border-b-500 grow bg-transparent p-1 text-grey-700' value={value} disabled unstyled /> <TextField className='border-b-500 grow bg-transparent p-1 text-grey-700' id={id} value={value} disabled unstyled />
</div> </div>
</ListItem> </ListItem>
); );

View File

@ -52,7 +52,8 @@ const TierDetailModalContent: React.FC<{tier?: Tier}> = ({tier}) => {
initialState: { initialState: {
...(tier || {}), ...(tier || {}),
trial_days: tier?.trial_days?.toString() || '', trial_days: tier?.trial_days?.toString() || '',
currency: tier?.currency || currencies[0].isoCode currency: tier?.currency || currencies[0].isoCode,
visibility: tier?.visibility || 'none'
}, },
onSave: async () => { onSave: async () => {
const {trial_days: trialDays, currency, ...rest} = formState; const {trial_days: trialDays, currency, ...rest} = formState;

View File

@ -27,9 +27,9 @@ const TierCard: React.FC<TierCardProps> = ({tier}) => {
const currencySymbol = currency ? getSymbol(currency) : '$'; const currencySymbol = currency ? getSymbol(currency) : '$';
return ( return (
<div className={cardContainerClasses} data-testid='tier-card'> <div className={cardContainerClasses} data-testid='tier-card' data-tier={tier.slug}>
<div className='w-full grow' onClick={() => { <div className='w-full grow' onClick={() => {
updateRoute({route: `tiers/show/${tier.id}`}); updateRoute({route: `tiers/${tier.id}`});
}}> }}>
<div className='text-[1.65rem] font-bold leading-tight tracking-tight text-pink'>{tier.name}</div> <div className='text-[1.65rem] font-bold leading-tight tracking-tight text-pink'>{tier.name}</div>
<div className='mt-2 flex items-baseline'> <div className='mt-2 flex items-baseline'>

View File

@ -8,7 +8,7 @@ const DesignAndThemeModal: React.FC<RoutingModalProps> = ({pathName}) => {
if (pathName === 'design/edit') { if (pathName === 'design/edit') {
return <DesignModal />; return <DesignModal />;
} else if (pathName === 'design/edit/themes') { } else if (pathName === 'design/change-theme') {
return <ChangeThemeModal />; return <ChangeThemeModal />;
} else if (pathName === 'design/change-theme/install') { } else if (pathName === 'design/change-theme/install') {
const url = window.location.href; const url = window.location.href;

View File

@ -66,7 +66,7 @@ const Sidebar: React.FC<{
<div className='w-full px-7'> <div className='w-full px-7'>
<button className='group flex w-full items-center justify-between text-sm font-medium opacity-80 transition-all hover:opacity-100' data-testid='change-theme' type='button' onClick={async () => { <button className='group flex w-full items-center justify-between text-sm font-medium opacity-80 transition-all hover:opacity-100' data-testid='change-theme' type='button' onClick={async () => {
await handleSave(); await handleSave();
updateRoute('design/edit/themes'); updateRoute('design/change-theme');
}}> }}>
<div className='text-left'> <div className='text-left'>
<div className='font-semibold'>Change theme</div> <div className='font-semibold'>Change theme</div>

View File

@ -1,5 +1,5 @@
import IframeBuffering from '../../../../utils/IframeBuffering'; import IframeBuffering from '../../../../utils/IframeBuffering';
import React, {memo} from 'react'; import React, {useCallback, useMemo} from 'react';
const getPreviewData = (announcementBackgroundColor?: string, announcementContent?: string, visibility?: string[]) => { const getPreviewData = (announcementBackgroundColor?: string, announcementContent?: string, visibility?: string[]) => {
const params = new URLSearchParams(); const params = new URLSearchParams();
@ -19,7 +19,10 @@ type AnnouncementBarSettings = {
}; };
const AnnouncementBarPreview: React.FC<AnnouncementBarSettings> = ({announcementBackgroundColor, announcementContent, url, visibility}) => { const AnnouncementBarPreview: React.FC<AnnouncementBarSettings> = ({announcementBackgroundColor, announcementContent, url, visibility}) => {
const injectContentIntoIframe = (iframe: HTMLIFrameElement) => { // Avoid re-rendering iframe if an equivalent array is initialised each render
const visibilityMemo = useMemo(() => visibility, [visibility?.join(',')]); // eslint-disable-line react-hooks/exhaustive-deps
const injectContentIntoIframe = useCallback((iframe: HTMLIFrameElement) => {
if (!url) { if (!url) {
return; return;
} }
@ -31,7 +34,7 @@ const AnnouncementBarPreview: React.FC<AnnouncementBarSettings> = ({announcement
'x-ghost-preview': getPreviewData( 'x-ghost-preview': getPreviewData(
announcementBackgroundColor, announcementBackgroundColor,
announcementContent, announcementContent,
visibility visibilityMemo
), ),
Accept: 'text/plain', Accept: 'text/plain',
mode: 'cors', mode: 'cors',
@ -63,7 +66,7 @@ const AnnouncementBarPreview: React.FC<AnnouncementBarSettings> = ({announcement
.catch(() => { .catch(() => {
// handle error in fetching data // handle error in fetching data
}); });
}; }, [announcementBackgroundColor, announcementContent, url, visibilityMemo]);
return ( return (
<IframeBuffering <IframeBuffering
@ -78,43 +81,4 @@ const AnnouncementBarPreview: React.FC<AnnouncementBarSettings> = ({announcement
); );
}; };
function arraysAreEqual(arr1: string[], arr2: string[]) { export default AnnouncementBarPreview;
if (!arr1 || !arr2) {
return arr1 === arr2;
} // handles null or undefined values
if (arr1.length !== arr2.length) {
return false;
}
for (let i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) {
return false;
}
}
return true;
}
export default memo(AnnouncementBarPreview, (prevProps, nextProps) => {
// Check if announcementBackgroundColor changed
if (prevProps.announcementBackgroundColor !== nextProps.announcementBackgroundColor) {
return false;
}
// Check if announcementContent changed
if (prevProps.announcementContent !== nextProps.announcementContent) {
return false;
}
// Check if url changed
if (prevProps.url !== nextProps.url) {
return false;
}
// Check if visibility array changed in size or content
if (!arraysAreEqual(prevProps.visibility || [], nextProps.visibility || [])) {
return false;
}
// If we've reached this point, all props are the same
return true;
});

View File

@ -89,9 +89,15 @@ const useSettingGroup = ({onValidate}: {onValidate?: () => ErrorMessages} = {}):
// function to update the local state // function to update the local state
const updateSetting = (key: string, value: SettingValue) => { const updateSetting = (key: string, value: SettingValue) => {
updateForm(state => state.map(setting => ( updateForm((state) => {
setting.key === key ? {...setting, value, dirty: true} : setting if (state.some(setting => setting.key === key)) {
))); return state.map(setting => (
setting.key === key ? {...setting, value, dirty: true} : setting
));
} else {
return [...state, {key, value, dirty: true}];
}
});
}; };
return { return {

View File

@ -593,5 +593,6 @@ add|ember-template-lint|no-action|2|45|2|45|55b33496610b2f4ef6b9fe62713f4b4ddee3
add|ember-template-lint|no-invalid-interactive|2|45|2|45|918fdec8490009c6197091e319d6ec52ee95b983|1695340800000|1705712400000|1710896400000|app/components/gh-fullscreen-modal.hbs add|ember-template-lint|no-invalid-interactive|2|45|2|45|918fdec8490009c6197091e319d6ec52ee95b983|1695340800000|1705712400000|1710896400000|app/components/gh-fullscreen-modal.hbs
remove|ember-template-lint|no-action|2|45|2|45|a9d29fae15800842d0e7b8f32b035ee22a23f4cb|1688342400000|1698714000000|1703898000000|app/components/gh-fullscreen-modal.hbs remove|ember-template-lint|no-action|2|45|2|45|a9d29fae15800842d0e7b8f32b035ee22a23f4cb|1688342400000|1698714000000|1703898000000|app/components/gh-fullscreen-modal.hbs
remove|ember-template-lint|no-invalid-interactive|2|45|2|45|b6693259da29d433264b59abc9a7c7ac846a4bf6|1688342400000|1698714000000|1703898000000|app/components/gh-fullscreen-modal.hbs remove|ember-template-lint|no-invalid-interactive|2|45|2|45|b6693259da29d433264b59abc9a7c7ac846a4bf6|1688342400000|1698714000000|1703898000000|app/components/gh-fullscreen-modal.hbs
remove|ember-template-lint|no-unknown-arguments-for-builtin-components|99|93|99|93|156670ca427c49c51f0a94f862b286ccc9466d92|1694649600000|1705021200000|1710205200000|app/components/gh-nav-menu/footer.hbs
add|ember-template-lint|no-action|213|50|213|50|693211baf08c011e77284b483c30e28ecaf63520|1696204800000|1706576400000|1711760400000|app/components/gh-post-settings-menu.hbs add|ember-template-lint|no-action|213|50|213|50|693211baf08c011e77284b483c30e28ecaf63520|1696204800000|1706576400000|1711760400000|app/components/gh-post-settings-menu.hbs
add|ember-template-lint|no-action|785|168|785|168|0145b67f0faef0aad141c6a4269c35c6ef8f0a47|1696204800000|1706576400000|1711760400000|app/components/gh-post-settings-menu.hbs add|ember-template-lint|no-action|785|168|785|168|0145b67f0faef0aad141c6a4269c35c6ef8f0a47|1696204800000|1706576400000|1711760400000|app/components/gh-post-settings-menu.hbs

View File

@ -9,14 +9,8 @@
<p class="gh-members-empty-secondary-cta">Have members already? <LinkTo @route="member.new">Add them manually</LinkTo> or <LinkTo @route="members.import">import from CSV</LinkTo></p> <p class="gh-members-empty-secondary-cta">Have members already? <LinkTo @route="member.new">Add them manually</LinkTo> or <LinkTo @route="members.import">import from CSV</LinkTo></p>
{{else}} {{else}}
<p>Memberships have been disabled. Adjust your Subscription Access settings to start adding members.</p> <p>Memberships have been disabled. Adjust your Subscription Access settings to start adding members.</p>
{{#if (feature "adminXSettings")}} <LinkTo @route="settings-x.settings-x" @model="access" class="gh-btn gh-btn-green">
<LinkTo @route="settings-x.settings-x" @model="access" class="gh-btn gh-btn-green"> <span>Membership settings</span>
<span>Membership settings</span> </LinkTo>
</LinkTo>
{{else}}
<LinkTo @route="settings.membership" class="gh-btn gh-btn-green">
<span>Membership settings</span>
</LinkTo>
{{/if}}
{{/if}} {{/if}}
</div> </div>

View File

@ -55,15 +55,9 @@
{{/if}} {{/if}}
<li> <li>
{{#if (feature "adminXSettings")}} <LinkTo @route="settings-x.settings-x" @model="staff/{{this.session.user.slug}}" class="dropdown-item" @role="menuitem" tabindex="-1" data-test-nav="user-profile">
<LinkTo @route="settings-x.settings-x" @model="users/show/{{this.session.user.slug}}" class="dropdown-item" @role="menuitem" tabindex="-1" data-test-nav="user-profile"> Your profile
Your profile </LinkTo>
</LinkTo>
{{else}}
<LinkTo @route="settings.staff.user" @model={{this.session.user.slug}} class="dropdown-item" @role="menuitem" tabindex="-1" data-test-nav="user-profile">
Your profile
</LinkTo>
{{/if}}
</li> </li>
{{#unless this.session.user.isContributor}} {{#unless this.session.user.isContributor}}
@ -106,11 +100,7 @@
</div> </div>
<div class="flex items-center pe-all"> <div class="flex items-center pe-all">
{{#if (or (gh-user-can-admin this.session.user) this.session.user.isEditor)}} {{#if (or (gh-user-can-admin this.session.user) this.session.user.isEditor)}}
{{#if (feature "adminXSettings")}} <LinkTo class="gh-nav-bottom-tabicon" @route="settings-x" @current-when={{this.isSettingsRoute}} data-test-nav="settings">{{svg-jar "settings"}}</LinkTo>
<LinkTo class="gh-nav-bottom-tabicon" @route="settings-x" @current-when={{this.isSettingsRoute}} data-test-nav="settings">{{svg-jar "settings"}}</LinkTo>
{{else}}
<LinkTo class="gh-nav-bottom-tabicon" @route="settings" @current-when={{this.isSettingsRoute}} data-test-nav="settings">{{svg-jar "settings"}}</LinkTo>
{{/if}}
{{/if}} {{/if}}
<div class="nightshift-toggle-container"> <div class="nightshift-toggle-container">
<div class="nightshift-toggle {{if this.feature.nightShift "on"}}" {{action (toggle "nightShift" this.feature)}}> <div class="nightshift-toggle {{if this.feature.nightShift "on"}}" {{action (toggle "nightShift" this.feature)}}>

View File

@ -64,7 +64,7 @@ export default class GhSearchInputComponent extends Component {
if (selected.searchable === 'Users') { if (selected.searchable === 'Users') {
let id = selected.id.replace('user.', ''); let id = selected.id.replace('user.', '');
this.router.transitionTo('settings.staff.user', id); this.router.transitionTo('settings-x.settings-x', `staff/${id}`);
} }
if (selected.searchable === 'Tags') { if (selected.searchable === 'Tags') {

View File

@ -53,41 +53,13 @@ Router.map(function () {
this.route('collection.new', {path: '/collections/new'}); this.route('collection.new', {path: '/collections/new'});
this.route('collection', {path: '/collections/:collection_slug'}); this.route('collection', {path: '/collections/:collection_slug'});
this.route('settings-x', function () { this.route('settings-x', {path: '/settings'}, function () {
this.route('settings-x', {path: '/*sub'}); this.route('settings-x', {path: '/*sub'});
}); });
this.route('settings');
this.route('settings.general', {path: '/settings/general'});
this.route('settings.membership', {path: '/settings/members'});
this.route('settings.code-injection', {path: '/settings/code-injection'});
this.route('settings.history', {path: '/settings/history'});
this.route('settings.analytics', {path: '/settings/analytics'});
this.route('settings.announcement-bar', {path: '/settings/announcement-bar'}, function () {});
// testing websockets // testing websockets
this.route('websockets'); this.route('websockets');
// redirect from old /settings/members-email to /settings/newsletters
this.route('settings.members-email', {path: '/settings/members-email'});
this.route('settings.newsletters', {path: '/settings/newsletters'}, function () {
this.route('new-newsletter', {path: 'new'});
this.route('edit-newsletter', {path: ':newsletter_id'});
});
this.route('settings.design', {path: '/settings/design'}, function () {
this.route('change-theme', function () {
this.route('view', {path: ':theme_name'});
this.route('install');
});
this.route('no-theme');
});
// redirect for old install route used by ghost.org/marketplace
this.route('settings.theme-install', {path: '/settings/theme/install'});
this.route('settings.staff', {path: '/settings/staff'}, function () {
this.route('user', {path: ':user_slug'});
});
this.route('explore', function () { this.route('explore', function () {
// actual Ember route, not rendered in iframe // actual Ember route, not rendered in iframe
this.route('connect'); this.route('connect');
@ -100,26 +72,6 @@ Router.map(function () {
}); });
}); });
this.route('settings.integrations', {path: '/settings/integrations'}, function () {
this.route('new');
});
this.route('settings.integration', {path: '/settings/integrations/:integration_id'}, function () {
this.route('webhooks.new', {path: 'webhooks/new'});
this.route('webhooks.edit', {path: 'webhooks/:webhook_id'});
});
this.route('settings.integrations.slack', {path: '/settings/integrations/slack'});
this.route('settings.integrations.amp', {path: '/settings/integrations/amp'});
this.route('settings.integrations.firstpromoter', {path: '/settings/integrations/firstpromoter'});
this.route('settings.integrations.pintura', {path: '/settings/integrations/pintura'});
this.route('settings.integrations.unsplash', {path: '/settings/integrations/unsplash'});
this.route('settings.integrations.zapier', {path: '/settings/integrations/zapier'});
this.route('settings.navigation', {path: '/settings/navigation'});
this.route('settings.labs', {path: '/settings/labs'}, function () {
this.route('import');
});
// this.route('settings.labs.import', {path: '/settings/labs/import'});
this.route('migrate'); this.route('migrate');
this.route('members', function () { this.route('members', function () {

View File

@ -7,14 +7,6 @@ export default class SettingsXRoute extends AuthenticatedRoute {
@service ui; @service ui;
@service modals; @service modals;
beforeModel() {
super.beforeModel(...arguments);
if (!this.feature.adminXSettings) {
return this.router.transitionTo('settings');
}
}
activate() { activate() {
super.activate(...arguments); super.activate(...arguments);
this.ui.set('isFullScreen', true); this.ui.set('isFullScreen', true);

View File

@ -4,6 +4,7 @@ import {inject as service} from '@ember/service';
// need this to be authenticated // need this to be authenticated
export default class WebsocketRoute extends AuthenticatedRoute { export default class WebsocketRoute extends AuthenticatedRoute {
@service session; @service session;
@service router;
beforeModel() { beforeModel() {
super.beforeModel(...arguments); super.beforeModel(...arguments);
@ -11,7 +12,7 @@ export default class WebsocketRoute extends AuthenticatedRoute {
const user = this.session.user; const user = this.session.user;
if (!user.isAdmin) { if (!user.isAdmin) {
return this.transitionTo('settings.staff.user', user); return this.router.transitionTo('settings-x.settings-x', `staff/${user.slug}`);
} }
} }
} }

View File

@ -30,15 +30,9 @@
<div class="gh-offers-list-cta missing-tiers"> <div class="gh-offers-list-cta missing-tiers">
{{svg-jar "discount-bubble" class="discount-bubble"}} {{svg-jar "discount-bubble" class="discount-bubble"}}
<h4>You must have an active tier to create offers</h4> <h4>You must have an active tier to create offers</h4>
{{#if (feature "adminXSettings")}} <LinkTo @route="settings-x.settings-x" @model="tiers" class="gh-btn gh-btn-green">
<LinkTo @route="settings-x.settings-x" @model="tiers" class="gh-btn gh-btn-green"> <span>Manage tiers</span>
<span>Manage tiers</span> </LinkTo>
</LinkTo>
{{else}}
<LinkTo @route="settings.membership" class="gh-btn gh-btn-green">
<span>Manage tiers</span>
</LinkTo>
{{/if}}
</div> </div>
{{else if (and this.offersExist this.filteredOffers.length)}} {{else if (and this.offersExist this.filteredOffers.length)}}
<table class="gh-list gh-offers-list" data-test-offers-list> <table class="gh-list gh-offers-list" data-test-offers-list>

View File

@ -16,7 +16,7 @@
<h6>Write your first post</h6> <h6>Write your first post</h6>
<p>Test out the editor and get a feel for creating content inside Ghost.</p> <p>Test out the editor and get a feel for creating content inside Ghost.</p>
</LinkTo> </LinkTo>
<LinkTo class="gh-done-green" @route="settings"> <LinkTo class="gh-done-green" @route="settings-x">
<span>{{svg-jar "paint-palette"}}</span> <span>{{svg-jar "paint-palette"}}</span>
<h6>Customize your site</h6> <h6>Customize your site</h6>
<p>Review your settings and tweak the design to make your site just right.</p> <p>Review your settings and tweak the design to make your site just right.</p>

View File

@ -61,7 +61,7 @@ describe('Acceptance: Authentication', function () {
})); }));
await authenticateSession(); await authenticateSession();
await visit('/settings/staff'); await visit('/members');
// running `visit(url)` inside windowProxy.replaceLocation breaks // running `visit(url)` inside windowProxy.replaceLocation breaks
// the async behaviour so we need to run `visit` here to simulate // the async behaviour so we need to run `visit` here to simulate
@ -82,7 +82,7 @@ describe('Acceptance: Authentication', function () {
})); }));
await authenticateSession(); await authenticateSession();
await visit('/settings/staff'); await visit('/members');
// running `visit(url)` inside windowProxy.replaceLocation breaks // running `visit(url)` inside windowProxy.replaceLocation breaks
// the async behaviour so we need to run `visit` here to simulate // the async behaviour so we need to run `visit` here to simulate

View File

@ -125,100 +125,101 @@ describe('Acceptance: Editor', function () {
}); });
describe('post settings menu', function () { describe('post settings menu', function () {
it('can set publish date', async function () { // TODO: Convert to E2E test
let [post1] = this.server.createList('post', 2, {authors: [author]}); // it('can set publish date', async function () {
let futureTime = moment().tz('Etc/UTC').add(10, 'minutes'); // let [post1] = this.server.createList('post', 2, {authors: [author]});
// let futureTime = moment().tz('Etc/UTC').add(10, 'minutes');
// sanity check // // sanity check
expect( // expect(
moment(post1.publishedAt).tz('Etc/UTC').format('YYYY-MM-DD HH:mm:ss'), // moment(post1.publishedAt).tz('Etc/UTC').format('YYYY-MM-DD HH:mm:ss'),
'initial publishedAt sanity check') // 'initial publishedAt sanity check')
.to.equal('2015-12-19 16:25:07'); // .to.equal('2015-12-19 16:25:07');
// post id 1 is a draft, checking for draft behaviour now // // post id 1 is a draft, checking for draft behaviour now
await visit('/editor/post/1'); // await visit('/editor/post/1');
// open post settings menu // // open post settings menu
await click('[data-test-psm-trigger]'); // await click('[data-test-psm-trigger]');
// should error, if the publish time is in the wrong format // // should error, if the publish time is in the wrong format
await fillIn('[data-test-date-time-picker-time-input]', 'foo'); // await fillIn('[data-test-date-time-picker-time-input]', 'foo');
await blur('[data-test-date-time-picker-time-input]'); // await blur('[data-test-date-time-picker-time-input]');
expect(find('[data-test-date-time-picker-error]').textContent.trim(), 'inline error response for invalid time') // expect(find('[data-test-date-time-picker-error]').textContent.trim(), 'inline error response for invalid time')
.to.equal('Must be in format: "15:00"'); // .to.equal('Must be in format: "15:00"');
// should error, if the publish time is in the future // // should error, if the publish time is in the future
// NOTE: date must be selected first, changing the time first will save // // NOTE: date must be selected first, changing the time first will save
// with the new time // // with the new time
await fillIn('[data-test-date-time-picker-datepicker] input', moment.tz('Etc/UTC').add(1, 'day').format('YYYY-MM-DD')); // await fillIn('[data-test-date-time-picker-datepicker] input', moment.tz('Etc/UTC').add(1, 'day').format('YYYY-MM-DD'));
await blur('[data-test-date-time-picker-datepicker] input'); // await blur('[data-test-date-time-picker-datepicker] input');
await fillIn('[data-test-date-time-picker-time-input]', futureTime.format('HH:mm')); // await fillIn('[data-test-date-time-picker-time-input]', futureTime.format('HH:mm'));
await blur('[data-test-date-time-picker-time-input]'); // await blur('[data-test-date-time-picker-time-input]');
expect(find('[data-test-date-time-picker-error]').textContent.trim(), 'inline error response for future time') // expect(find('[data-test-date-time-picker-error]').textContent.trim(), 'inline error response for future time')
.to.equal('Must be in the past'); // .to.equal('Must be in the past');
// closing the PSM will reset the invalid date/time // // closing the PSM will reset the invalid date/time
await click('[data-test-psm-trigger]'); // await click('[data-test-psm-trigger]');
await click('[data-test-psm-trigger]'); // await click('[data-test-psm-trigger]');
expect( // expect(
find('[data-test-date-time-picker-error]'), // find('[data-test-date-time-picker-error]'),
'date picker error after closing PSM' // 'date picker error after closing PSM'
).to.not.exist; // ).to.not.exist;
expect( // expect(
find('[data-test-date-time-picker-date-input]').value, // find('[data-test-date-time-picker-date-input]').value,
'PSM date value after closing with invalid date' // 'PSM date value after closing with invalid date'
).to.equal(moment(post1.publishedAt).tz('Etc/UTC').format('YYYY-MM-DD')); // ).to.equal(moment(post1.publishedAt).tz('Etc/UTC').format('YYYY-MM-DD'));
expect( // expect(
find('[data-test-date-time-picker-time-input]').value, // find('[data-test-date-time-picker-time-input]').value,
'PSM time value after closing with invalid date' // 'PSM time value after closing with invalid date'
).to.equal(moment(post1.publishedAt).tz('Etc/UTC').format('HH:mm')); // ).to.equal(moment(post1.publishedAt).tz('Etc/UTC').format('HH:mm'));
// saves the post with the new date // // saves the post with the new date
let validTime = moment('2017-04-09 12:00'); // let validTime = moment('2017-04-09 12:00');
await fillIn('[data-test-date-time-picker-time-input]', validTime.format('HH:mm')); // await fillIn('[data-test-date-time-picker-time-input]', validTime.format('HH:mm'));
await blur('[data-test-date-time-picker-time-input]'); // await blur('[data-test-date-time-picker-time-input]');
await datepickerSelect('[data-test-date-time-picker-datepicker]', validTime.toDate()); // await datepickerSelect('[data-test-date-time-picker-datepicker]', validTime.toDate());
expect(moment(post1.publishedAt).tz('Etc/UTC').format('YYYY-MM-DD HH:mm:ss')).to.equal('2017-04-09 12:00:00'); // expect(moment(post1.publishedAt).tz('Etc/UTC').format('YYYY-MM-DD HH:mm:ss')).to.equal('2017-04-09 12:00:00');
// go to settings to change the timezone // // go to settings to change the timezone
await visit('/settings/general'); // await visit('/settings/general');
await click('[data-test-toggle-timezone]'); // await click('[data-test-toggle-timezone]');
expect(currentURL(), 'currentURL for settings') // expect(currentURL(), 'currentURL for settings')
.to.equal('/settings/general'); // .to.equal('/settings/general');
expect(find('#timezone option:checked').textContent.trim(), 'default timezone') // expect(find('#timezone option:checked').textContent.trim(), 'default timezone')
.to.equal('(GMT) UTC'); // .to.equal('(GMT) UTC');
// select a new timezone // // select a new timezone
find('#timezone option[value="Pacific/Kwajalein"]').selected = true; // find('#timezone option[value="Pacific/Kwajalein"]').selected = true;
await triggerEvent('#timezone', 'change'); // await triggerEvent('#timezone', 'change');
// save the settings // // save the settings
await click('[data-test-button="save"]'); // await click('[data-test-button="save"]');
expect(find('#timezone option:checked').textContent.trim(), 'new timezone after saving') // expect(find('#timezone option:checked').textContent.trim(), 'new timezone after saving')
.to.equal('(GMT +12:00) International Date Line West'); // .to.equal('(GMT +12:00) International Date Line West');
// and now go back to the editor // // and now go back to the editor
await visit('/editor/post/1'); // await visit('/editor/post/1');
await click('[data-test-psm-trigger]'); // await click('[data-test-psm-trigger]');
expect( // expect(
find('[data-test-date-time-picker-date-input]').value, // find('[data-test-date-time-picker-date-input]').value,
'date after timezone change' // 'date after timezone change'
).to.equal('2017-04-10'); // ).to.equal('2017-04-10');
expect( // expect(
find('[data-test-date-time-picker-time-input]').value, // find('[data-test-date-time-picker-time-input]').value,
'time after timezone change' // 'time after timezone change'
).to.equal('00:00'); // ).to.equal('00:00');
}); // });
}); });
it.skip('handles validation errors when scheduling', async function () { it.skip('handles validation errors when scheduling', async function () {

View File

@ -431,39 +431,40 @@ describe('Acceptance: Publish flow', function () {
it('can publish'); it('can publish');
it('can schedule publish'); it('can schedule publish');
it('respects default recipient settings - usually nobody', async function () { // TODO: Update to E2E test
// switch to "usually nobody" setting // it('respects default recipient settings - usually nobody', async function () {
// - doing it this way so we're not testing potentially stale mocked setting keys/values // // switch to "usually nobody" setting
await loginAsRole('Administrator', this.server); // // - doing it this way so we're not testing potentially stale mocked setting keys/values
await visit('/settings/newsletters'); // await loginAsRole('Administrator', this.server);
await click('[data-test-toggle-membersemail]'); // await visit('/settings/newsletters');
await selectChoose('[data-test-select="default-recipients"]', 'Usually nobody'); // await click('[data-test-toggle-membersemail]');
await click('[data-test-button="save-members-settings"]'); // await selectChoose('[data-test-select="default-recipients"]', 'Usually nobody');
// await click('[data-test-button="save-members-settings"]');
const post = this.server.create('post', {status: 'draft'}); // const post = this.server.create('post', {status: 'draft'});
await visit(`/editor/post/${post.id}`); // await visit(`/editor/post/${post.id}`);
await click('[data-test-button="publish-flow"]'); // await click('[data-test-button="publish-flow"]');
expect( // expect(
find('[data-test-setting="publish-type"] [data-test-setting-title]'), 'publish type title' // find('[data-test-setting="publish-type"] [data-test-setting-title]'), 'publish type title'
).to.have.trimmed.rendered.text('Publish'); // ).to.have.trimmed.rendered.text('Publish');
expect( // expect(
find('[data-test-setting="email-recipients"] [data-test-setting-title]'), 'recipients title' // find('[data-test-setting="email-recipients"] [data-test-setting-title]'), 'recipients title'
).to.have.trimmed.rendered.text('Not sent as newsletter'); // ).to.have.trimmed.rendered.text('Not sent as newsletter');
await click('[data-test-setting="publish-type"] [data-test-setting-title]'); // await click('[data-test-setting="publish-type"] [data-test-setting-title]');
// email-related options are enabled // // email-related options are enabled
expect(find('[data-test-publish-type="publish+send"]')).to.not.have.attribute('disabled'); // expect(find('[data-test-publish-type="publish+send"]')).to.not.have.attribute('disabled');
expect(find('[data-test-publish-type="send"]')).to.not.have.attribute('disabled'); // expect(find('[data-test-publish-type="send"]')).to.not.have.attribute('disabled');
await click('[data-test-publish-type="publish+send"]'); // await click('[data-test-publish-type="publish+send"]');
expect( // expect(
find('[data-test-setting="email-recipients"] [data-test-setting-title]'), 'recipients title' // find('[data-test-setting="email-recipients"] [data-test-setting-title]'), 'recipients title'
).to.have.trimmed.rendered.text('All 7 subscribers'); // ).to.have.trimmed.rendered.text('All 7 subscribers');
}); // });
it('handles Mailgun not being set up', async function () { it('handles Mailgun not being set up', async function () {
disableMailgun(this.server); disableMailgun(this.server);

View File

@ -107,14 +107,14 @@ describe('Acceptance: Error Handling', function () {
}); });
it('handles ember-ajax HTML response', async function () { it('handles ember-ajax HTML response', async function () {
this.server.del('/themes/foo/', htmlErrorResponse); const tag = this.server.create('tag', {slug: 'test'});
await visit('/settings/design/change-theme'); this.server.del(`/tags/${tag.id}/`, htmlErrorResponse);
await click('[data-test-button="toggle-advanced"]'); await visit('/tags/test');
await click('[data-test-theme-id="foo"] [data-test-button="actions"]');
await click('[data-test-actions-for="foo"] [data-test-button="delete"]'); await click('[data-test-button="delete-tag"]');
await click('[data-test-modal="delete-theme"] [data-test-button="confirm"]'); await click('[data-test-modal="confirm-delete-tag"] [data-test-button="confirm"]');
expect(findAll('.gh-alert').length).to.equal(1); expect(findAll('.gh-alert').length).to.equal(1);
expect(find('.gh-alert').textContent).to.not.match(/html>/); expect(find('.gh-alert').textContent).to.not.match(/html>/);

View File

@ -1,130 +1,130 @@
import ctrlOrCmd from 'ghost-admin/utils/ctrl-or-cmd'; // import ctrlOrCmd from 'ghost-admin/utils/ctrl-or-cmd';
import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support'; // import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support';
import { // import {
beforeEach, // beforeEach,
describe, // describe,
it // it
} from 'mocha'; // } from 'mocha';
import {click, currentURL, find, findAll, triggerEvent} from '@ember/test-helpers'; // import {click, currentURL, find, findAll, triggerEvent} from '@ember/test-helpers';
import {expect} from 'chai'; // import {expect} from 'chai';
import {setupApplicationTest} from 'ember-mocha'; // import {setupApplicationTest} from 'ember-mocha';
import {setupMirage} from 'ember-cli-mirage/test-support'; // import {setupMirage} from 'ember-cli-mirage/test-support';
import {visit} from '../../helpers/visit'; // import {visit} from '../../helpers/visit';
describe('Acceptance: Settings - Integrations - AMP', function () { // describe('Acceptance: Settings - Integrations - AMP', function () {
let hooks = setupApplicationTest(); // let hooks = setupApplicationTest();
setupMirage(hooks); // setupMirage(hooks);
it('redirects to signin when not authenticated', async function () { // it('redirects to signin when not authenticated', async function () {
await invalidateSession(); // await invalidateSession();
await visit('/settings/integrations/amp'); // await visit('/settings/integrations/amp');
expect(currentURL(), 'currentURL').to.equal('/signin'); // expect(currentURL(), 'currentURL').to.equal('/signin');
}); // });
it('redirects to home page when authenticated as contributor', async function () { // it('redirects to home page when authenticated as contributor', async function () {
let role = this.server.create('role', {name: 'Contributor'}); // let role = this.server.create('role', {name: 'Contributor'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/integrations/amp'); // await visit('/settings/integrations/amp');
expect(currentURL(), 'currentURL').to.equal('/posts'); // expect(currentURL(), 'currentURL').to.equal('/posts');
}); // });
it('redirects to home page when authenticated as author', async function () { // it('redirects to home page when authenticated as author', async function () {
let role = this.server.create('role', {name: 'Author'}); // let role = this.server.create('role', {name: 'Author'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/integrations/amp'); // await visit('/settings/integrations/amp');
expect(currentURL(), 'currentURL').to.equal('/site'); // expect(currentURL(), 'currentURL').to.equal('/site');
}); // });
it('redirects to home page when authenticated as editor', async function () { // it('redirects to home page when authenticated as editor', async function () {
let role = this.server.create('role', {name: 'Editor'}); // let role = this.server.create('role', {name: 'Editor'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/integrations/amp'); // await visit('/settings/integrations/amp');
expect(currentURL(), 'currentURL').to.equal('/site'); // expect(currentURL(), 'currentURL').to.equal('/site');
}); // });
describe('when logged in', function () { // describe('when logged in', function () {
beforeEach(async function () { // beforeEach(async function () {
let role = this.server.create('role', {name: 'Administrator'}); // let role = this.server.create('role', {name: 'Administrator'});
this.server.create('user', {roles: [role]}); // this.server.create('user', {roles: [role]});
return await authenticateSession(); // return await authenticateSession();
}); // });
it('it enables or disables AMP properly and saves it', async function () { // it('it enables or disables AMP properly and saves it', async function () {
await visit('/settings/integrations/amp'); // await visit('/settings/integrations/amp');
// has correct url // // has correct url
expect(currentURL(), 'currentURL').to.equal('/settings/integrations/amp'); // expect(currentURL(), 'currentURL').to.equal('/settings/integrations/amp');
// AMP is disabled by default // // AMP is disabled by default
expect(find('[data-test-amp-checkbox]').checked, 'AMP checkbox').to.be.false; // expect(find('[data-test-amp-checkbox]').checked, 'AMP checkbox').to.be.false;
await click('[data-test-amp-checkbox]'); // await click('[data-test-amp-checkbox]');
expect(find('[data-test-amp-checkbox]').checked, 'AMP checkbox').to.be.true; // expect(find('[data-test-amp-checkbox]').checked, 'AMP checkbox').to.be.true;
await click('[data-test-save-button]'); // await click('[data-test-save-button]');
let [lastRequest] = this.server.pretender.handledRequests.slice(-1); // let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
let params = JSON.parse(lastRequest.requestBody); // let params = JSON.parse(lastRequest.requestBody);
expect(params.settings.findBy('key', 'amp').value).to.equal(true); // expect(params.settings.findBy('key', 'amp').value).to.equal(true);
// CMD-S shortcut works // // CMD-S shortcut works
await click('[data-test-amp-checkbox]'); // await click('[data-test-amp-checkbox]');
await triggerEvent('.gh-app', 'keydown', { // await triggerEvent('.gh-app', 'keydown', {
keyCode: 83, // s // keyCode: 83, // s
metaKey: ctrlOrCmd === 'command', // metaKey: ctrlOrCmd === 'command',
ctrlKey: ctrlOrCmd === 'ctrl' // ctrlKey: ctrlOrCmd === 'ctrl'
}); // });
// we've already saved in this test so there's no on-screen indication // // we've already saved in this test so there's no on-screen indication
// that we've had another save, check the request was fired instead // // that we've had another save, check the request was fired instead
let [newRequest] = this.server.pretender.handledRequests.slice(-1); // let [newRequest] = this.server.pretender.handledRequests.slice(-1);
params = JSON.parse(newRequest.requestBody); // params = JSON.parse(newRequest.requestBody);
expect(find('[data-test-amp-checkbox]').checked, 'AMP checkbox').to.be.false; // expect(find('[data-test-amp-checkbox]').checked, 'AMP checkbox').to.be.false;
expect(params.settings.findBy('key', 'amp').value).to.equal(false); // expect(params.settings.findBy('key', 'amp').value).to.equal(false);
}); // });
it('warns when leaving without saving', async function () { // it('warns when leaving without saving', async function () {
await visit('/settings/integrations/amp'); // await visit('/settings/integrations/amp');
// has correct url // // has correct url
expect(currentURL(), 'currentURL').to.equal('/settings/integrations/amp'); // expect(currentURL(), 'currentURL').to.equal('/settings/integrations/amp');
// AMP is disabled by default // // AMP is disabled by default
expect(find('[data-test-amp-checkbox]').checked, 'AMP checkbox default').to.be.false; // expect(find('[data-test-amp-checkbox]').checked, 'AMP checkbox default').to.be.false;
await click('[data-test-amp-checkbox]'); // await click('[data-test-amp-checkbox]');
expect(find('[data-test-amp-checkbox]').checked, 'AMP checkbox after click').to.be.true; // expect(find('[data-test-amp-checkbox]').checked, 'AMP checkbox after click').to.be.true;
await visit('/settings/staff'); // await visit('/settings/staff');
expect(findAll('[data-test-modal="unsaved-settings"]').length, 'unsaved changes modal exists').to.equal(1); // expect(findAll('[data-test-modal="unsaved-settings"]').length, 'unsaved changes modal exists').to.equal(1);
// Leave without saving // // Leave without saving
await click('[data-test-leave-button]'); // await click('[data-test-leave-button]');
expect(currentURL(), 'currentURL after leave without saving').to.equal('/settings/staff'); // expect(currentURL(), 'currentURL after leave without saving').to.equal('/settings/staff');
await visit('/settings/integrations/amp'); // await visit('/settings/integrations/amp');
expect(currentURL(), 'currentURL after return').to.equal('/settings/integrations/amp'); // expect(currentURL(), 'currentURL after return').to.equal('/settings/integrations/amp');
// settings were not saved // // settings were not saved
expect(find('[data-test-amp-checkbox]').checked, 'AMP checkbox').to.be.false; // expect(find('[data-test-amp-checkbox]').checked, 'AMP checkbox').to.be.false;
}); // });
}); // });
}); // });

View File

@ -1,80 +1,80 @@
import {authenticateSession} from 'ember-simple-auth/test-support'; // import {authenticateSession} from 'ember-simple-auth/test-support';
import {click, find} from '@ember/test-helpers'; // import {click, find} from '@ember/test-helpers';
import {expect} from 'chai'; // import {expect} from 'chai';
import {setupApplicationTest} from 'ember-mocha'; // import {setupApplicationTest} from 'ember-mocha';
import {setupMirage} from 'ember-cli-mirage/test-support'; // import {setupMirage} from 'ember-cli-mirage/test-support';
import {visit} from '../../helpers/visit'; // import {visit} from '../../helpers/visit';
describe('Acceptance: Settings - Analytics', function () { // describe('Acceptance: Settings - Analytics', function () {
const hooks = setupApplicationTest(); // const hooks = setupApplicationTest();
setupMirage(hooks); // setupMirage(hooks);
beforeEach(async function () { // beforeEach(async function () {
this.server.loadFixtures('configs', 'newsletters'); // this.server.loadFixtures('configs', 'newsletters');
const role = this.server.create('role', {name: 'Owner'}); // const role = this.server.create('role', {name: 'Owner'});
this.server.create('user', {roles: [role]}); // this.server.create('user', {roles: [role]});
return await authenticateSession(); // return await authenticateSession();
}); // });
it('can manage open rate tracking', async function () { // it('can manage open rate tracking', async function () {
this.server.db.settings.update({key: 'email_track_opens'}, {value: 'true'}); // this.server.db.settings.update({key: 'email_track_opens'}, {value: 'true'});
await visit('/settings/analytics'); // await visit('/settings/analytics');
expect(find('[data-test-checkbox="email-track-opens"]')).to.be.checked; // expect(find('[data-test-checkbox="email-track-opens"]')).to.be.checked;
await click('[data-test-label="email-track-opens"]'); // await click('[data-test-label="email-track-opens"]');
expect(find('[data-test-checkbox="email-track-opens"]')).to.not.be.checked; // expect(find('[data-test-checkbox="email-track-opens"]')).to.not.be.checked;
await click('[data-test-button="save-analytics-settings"]'); // await click('[data-test-button="save-analytics-settings"]');
expect(this.server.db.settings.findBy({key: 'email_track_opens'}).value).to.equal(false); // expect(this.server.db.settings.findBy({key: 'email_track_opens'}).value).to.equal(false);
}); // });
it('can manage click tracking', async function () { // it('can manage click tracking', async function () {
this.server.db.settings.update({key: 'email_track_clicks'}, {value: 'true'}); // this.server.db.settings.update({key: 'email_track_clicks'}, {value: 'true'});
await visit('/settings/analytics'); // await visit('/settings/analytics');
expect(find('[data-test-checkbox="email-track-clicks"]')).to.be.checked; // expect(find('[data-test-checkbox="email-track-clicks"]')).to.be.checked;
await click('[data-test-label="email-track-clicks"]'); // await click('[data-test-label="email-track-clicks"]');
expect(find('[data-test-checkbox="email-track-clicks"]')).to.not.be.checked; // expect(find('[data-test-checkbox="email-track-clicks"]')).to.not.be.checked;
await click('[data-test-button="save-analytics-settings"]'); // await click('[data-test-button="save-analytics-settings"]');
expect(this.server.db.settings.findBy({key: 'email_track_clicks'}).value).to.equal(false); // expect(this.server.db.settings.findBy({key: 'email_track_clicks'}).value).to.equal(false);
}); // });
it('can manage source tracking', async function () { // it('can manage source tracking', async function () {
this.server.db.settings.update({key: 'members_track_sources'}, {value: 'true'}); // this.server.db.settings.update({key: 'members_track_sources'}, {value: 'true'});
await visit('/settings/analytics'); // await visit('/settings/analytics');
expect(find('[data-test-checkbox="members-track-sources"]')).to.be.checked; // expect(find('[data-test-checkbox="members-track-sources"]')).to.be.checked;
await click('[data-test-label="members-track-sources"]'); // await click('[data-test-label="members-track-sources"]');
expect(find('[data-test-checkbox="members-track-sources"]')).to.not.be.checked; // expect(find('[data-test-checkbox="members-track-sources"]')).to.not.be.checked;
await click('[data-test-button="save-analytics-settings"]'); // await click('[data-test-button="save-analytics-settings"]');
expect(this.server.db.settings.findBy({key: 'members_track_sources'}).value).to.equal(false); // expect(this.server.db.settings.findBy({key: 'members_track_sources'}).value).to.equal(false);
}); // });
it('can manage outbound link tagging', async function () { // it('can manage outbound link tagging', async function () {
this.server.db.settings.update({key: 'outbound_link_tagging'}, {value: 'true'}); // this.server.db.settings.update({key: 'outbound_link_tagging'}, {value: 'true'});
await visit('/settings/analytics'); // await visit('/settings/analytics');
expect(find('[data-test-checkbox="outbound-link-tagging"]')).to.be.checked; // expect(find('[data-test-checkbox="outbound-link-tagging"]')).to.be.checked;
await click('[data-test-label="outbound-link-tagging"]'); // await click('[data-test-label="outbound-link-tagging"]');
expect(find('[data-test-checkbox="outbound-link-tagging"]')).to.not.be.checked; // expect(find('[data-test-checkbox="outbound-link-tagging"]')).to.not.be.checked;
await click('[data-test-button="save-analytics-settings"]'); // await click('[data-test-button="save-analytics-settings"]');
expect(this.server.db.settings.findBy({key: 'outbound_link_tagging'}).value).to.equal(false); // expect(this.server.db.settings.findBy({key: 'outbound_link_tagging'}).value).to.equal(false);
}); // });
}); // });

View File

@ -1,113 +1,113 @@
import ctrlOrCmd from 'ghost-admin/utils/ctrl-or-cmd'; // import ctrlOrCmd from 'ghost-admin/utils/ctrl-or-cmd';
import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support'; // import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support';
import { // import {
beforeEach, // beforeEach,
describe, // describe,
it // it
} from 'mocha'; // } from 'mocha';
import {click, currentURL, fillIn, find, findAll, triggerEvent} from '@ember/test-helpers'; // import {click, currentURL, fillIn, find, findAll, triggerEvent} from '@ember/test-helpers';
import {expect} from 'chai'; // import {expect} from 'chai';
import {setupApplicationTest} from 'ember-mocha'; // import {setupApplicationTest} from 'ember-mocha';
import {setupMirage} from 'ember-cli-mirage/test-support'; // import {setupMirage} from 'ember-cli-mirage/test-support';
import {visit} from '../../helpers/visit'; // import {visit} from '../../helpers/visit';
describe('Acceptance: Settings - Code-Injection', function () { // describe('Acceptance: Settings - Code-Injection', function () {
let hooks = setupApplicationTest(); // let hooks = setupApplicationTest();
setupMirage(hooks); // setupMirage(hooks);
it('redirects to signin when not authenticated', async function () { // it('redirects to signin when not authenticated', async function () {
await invalidateSession(); // await invalidateSession();
await visit('/settings/code-injection'); // await visit('/settings/code-injection');
expect(currentURL(), 'currentURL').to.equal('/signin'); // expect(currentURL(), 'currentURL').to.equal('/signin');
}); // });
it('redirects to home page when authenticated as contributor', async function () { // it('redirects to home page when authenticated as contributor', async function () {
let role = this.server.create('role', {name: 'Contributor'}); // let role = this.server.create('role', {name: 'Contributor'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/code-injection'); // await visit('/settings/code-injection');
expect(currentURL(), 'currentURL').to.equal('/posts'); // expect(currentURL(), 'currentURL').to.equal('/posts');
}); // });
it('redirects to staff page when authenticated as author', async function () { // it('redirects to staff page when authenticated as author', async function () {
let role = this.server.create('role', {name: 'Author'}); // let role = this.server.create('role', {name: 'Author'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/code-injection'); // await visit('/settings/code-injection');
expect(currentURL(), 'currentURL').to.equal('/site'); // expect(currentURL(), 'currentURL').to.equal('/site');
}); // });
it('redirects to home page when authenticated as editor', async function () { // it('redirects to home page when authenticated as editor', async function () {
let role = this.server.create('role', {name: 'Editor'}); // let role = this.server.create('role', {name: 'Editor'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/code-injection'); // await visit('/settings/code-injection');
expect(currentURL(), 'currentURL').to.equal('/site'); // expect(currentURL(), 'currentURL').to.equal('/site');
}); // });
describe('when logged in', function () { // describe('when logged in', function () {
beforeEach(async function () { // beforeEach(async function () {
let role = this.server.create('role', {name: 'Administrator'}); // let role = this.server.create('role', {name: 'Administrator'});
this.server.create('user', {roles: [role]}); // this.server.create('user', {roles: [role]});
return await authenticateSession(); // return await authenticateSession();
}); // });
it('it renders, loads and saves editors correctly', async function () { // it('it renders, loads and saves editors correctly', async function () {
await visit('/settings/code-injection'); // await visit('/settings/code-injection');
// has correct url // // has correct url
expect(currentURL(), 'currentURL').to.equal('/settings/code-injection'); // expect(currentURL(), 'currentURL').to.equal('/settings/code-injection');
// has correct page title // // has correct page title
expect(document.title, 'page title').to.equal('Settings - Code injection - Test Blog'); // expect(document.title, 'page title').to.equal('Settings - Code injection - Test Blog');
expect(find('[data-test-save-button]').textContent.trim(), 'save button text').to.equal('Save'); // expect(find('[data-test-save-button]').textContent.trim(), 'save button text').to.equal('Save');
expect(findAll('#ghost-head .CodeMirror').length, 'ghost head codemirror element').to.equal(1); // expect(findAll('#ghost-head .CodeMirror').length, 'ghost head codemirror element').to.equal(1);
expect(find('#ghost-head .CodeMirror'), 'ghost head editor theme').to.have.class('cm-s-xq-light'); // expect(find('#ghost-head .CodeMirror'), 'ghost head editor theme').to.have.class('cm-s-xq-light');
expect(findAll('#ghost-foot .CodeMirror').length, 'ghost head codemirror element').to.equal(1); // expect(findAll('#ghost-foot .CodeMirror').length, 'ghost head codemirror element').to.equal(1);
expect(find('#ghost-foot .CodeMirror'), 'ghost head editor theme').to.have.class('cm-s-xq-light'); // expect(find('#ghost-foot .CodeMirror'), 'ghost head editor theme').to.have.class('cm-s-xq-light');
await fillIn('#settings-code #ghost-head textarea', 'Test'); // await fillIn('#settings-code #ghost-head textarea', 'Test');
await click('[data-test-save-button]'); // await click('[data-test-save-button]');
let [lastRequest] = this.server.pretender.handledRequests.slice(-1); // let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
let params = JSON.parse(lastRequest.requestBody); // let params = JSON.parse(lastRequest.requestBody);
expect(params.settings.findBy('key', 'codeinjection_head').value).to.equal('Test'); // expect(params.settings.findBy('key', 'codeinjection_head').value).to.equal('Test');
// update should have been partial // // update should have been partial
expect(params.settings.findBy('key', 'navigation')).to.be.undefined; // expect(params.settings.findBy('key', 'navigation')).to.be.undefined;
expect(find('[data-test-save-button]').textContent.trim(), 'save button text').to.equal('Save'); // expect(find('[data-test-save-button]').textContent.trim(), 'save button text').to.equal('Save');
await fillIn('#settings-code #ghost-head textarea', ''); // await fillIn('#settings-code #ghost-head textarea', '');
// CMD-S shortcut works // // CMD-S shortcut works
await triggerEvent('.gh-app', 'keydown', { // await triggerEvent('.gh-app', 'keydown', {
keyCode: 83, // s // keyCode: 83, // s
metaKey: ctrlOrCmd === 'command', // metaKey: ctrlOrCmd === 'command',
ctrlKey: ctrlOrCmd === 'ctrl' // ctrlKey: ctrlOrCmd === 'ctrl'
}); // });
let [newRequest] = this.server.pretender.handledRequests.slice(-1); // let [newRequest] = this.server.pretender.handledRequests.slice(-1);
params = JSON.parse(newRequest.requestBody); // params = JSON.parse(newRequest.requestBody);
expect(params.settings.findBy('key', 'codeinjection_head').value).to.equal(''); // expect(params.settings.findBy('key', 'codeinjection_head').value).to.equal('');
expect(find('[data-test-save-button]').textContent.trim(), 'save button text').to.equal('Save'); // expect(find('[data-test-save-button]').textContent.trim(), 'save button text').to.equal('Save');
// Saving when no changed have been made should work // // Saving when no changed have been made should work
// (although no api request is expected) // // (although no api request is expected)
await click('[data-test-save-button]'); // await click('[data-test-save-button]');
expect(find('[data-test-save-button]').textContent.trim(), 'save button text').to.equal('Save'); // expect(find('[data-test-save-button]').textContent.trim(), 'save button text').to.equal('Save');
}); // });
}); // });
}); // });

View File

@ -1,159 +1,159 @@
import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support'; // import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support';
import {click, currentURL, fillIn, find, findAll} from '@ember/test-helpers'; // import {click, currentURL, fillIn, find, findAll} from '@ember/test-helpers';
import {expect} from 'chai'; // import {expect} from 'chai';
import {fileUpload} from '../../helpers/file-upload'; // import {fileUpload} from '../../helpers/file-upload';
import {setupApplicationTest} from 'ember-mocha'; // import {setupApplicationTest} from 'ember-mocha';
import {setupMirage} from 'ember-cli-mirage/test-support'; // import {setupMirage} from 'ember-cli-mirage/test-support';
import {visit} from '../../helpers/visit'; // import {visit} from '../../helpers/visit';
describe('Acceptance: Settings - Design', function () { // describe('Acceptance: Settings - Design', function () {
let hooks = setupApplicationTest(); // let hooks = setupApplicationTest();
setupMirage(hooks); // setupMirage(hooks);
beforeEach(async function () { // beforeEach(async function () {
let role = this.server.create('role', {name: 'Administrator'}); // let role = this.server.create('role', {name: 'Administrator'});
this.server.create('user', {roles: [role]}); // this.server.create('user', {roles: [role]});
this.server.loadFixtures('themes'); // this.server.loadFixtures('themes');
return await authenticateSession(); // return await authenticateSession();
}); // });
it('redirects to signin when not authenticated', async function () { // it('redirects to signin when not authenticated', async function () {
await invalidateSession(); // await invalidateSession();
await visit('/settings/general'); // await visit('/settings/general');
expect(currentURL(), 'currentURL').to.equal('/signin'); // expect(currentURL(), 'currentURL').to.equal('/signin');
}); // });
it('renders with no custom theme settings', async function () { // it('renders with no custom theme settings', async function () {
await visit('/settings'); // await visit('/settings');
await click('[data-test-nav="design"]'); // await click('[data-test-nav="design"]');
expect(currentURL(), 'currentURL').to.equal('/settings/design'); // expect(currentURL(), 'currentURL').to.equal('/settings/design');
expect(document.title, 'page title').to.equal('Settings - Design - Test Blog'); // expect(document.title, 'page title').to.equal('Settings - Design - Test Blog');
// side nav menu changes // // side nav menu changes
expect(find('[data-test-nav-menu="design"]'), 'design menu').to.exist; // expect(find('[data-test-nav-menu="design"]'), 'design menu').to.exist;
expect(find('[data-test-nav-menu="main"]'), 'main menu').to.not.exist; // expect(find('[data-test-nav-menu="main"]'), 'main menu').to.not.exist;
// side nav defaults to general group open // // side nav defaults to general group open
expect(find('[data-test-nav-toggle="general"]'), 'general toggle').to.exist; // expect(find('[data-test-nav-toggle="general"]'), 'general toggle').to.exist;
expect(find('[data-test-nav-group="general"]'), 'general form').to.exist; // expect(find('[data-test-nav-group="general"]'), 'general form').to.exist;
// no other side nav groups exist // // no other side nav groups exist
expect(findAll('[data-test-nav-toggle]'), 'no of group toggles').to.have.lengthOf(1); // expect(findAll('[data-test-nav-toggle]'), 'no of group toggles').to.have.lengthOf(1);
expect(findAll('[data-test-nav-group]'), 'no of groups open').to.have.lengthOf(1); // expect(findAll('[data-test-nav-group]'), 'no of groups open').to.have.lengthOf(1);
// current theme is shown in nav menu // // current theme is shown in nav menu
expect(find('[data-test-text="current-theme"]')).to.contain.text('source - v1.0'); // expect(find('[data-test-text="current-theme"]')).to.contain.text('source - v1.0');
// defaults to "home" desktop preview // // defaults to "home" desktop preview
expect(find('[data-test-button="desktop-preview"]')).to.have.class('gh-btn-group-selected'); // expect(find('[data-test-button="desktop-preview"]')).to.have.class('gh-btn-group-selected');
expect(find('[data-test-button="mobile-preview"]')).to.not.have.class('gh-btn-group-selected'); // expect(find('[data-test-button="mobile-preview"]')).to.not.have.class('gh-btn-group-selected');
}); // });
it('has unsaved-changes confirmation', async function () { // it('has unsaved-changes confirmation', async function () {
await visit('/settings/design'); // await visit('/settings/design');
await fillIn('[data-test-input="siteDescription"]', 'Changed'); // await fillIn('[data-test-input="siteDescription"]', 'Changed');
await click('[data-test-link="back-to-settings"]'); // await click('[data-test-link="back-to-settings"]');
expect(find('[data-test-modal="unsaved-settings"]')).to.exist; // expect(find('[data-test-modal="unsaved-settings"]')).to.exist;
await click('[data-test-modal="unsaved-settings"] [data-test-button="close"]'); // await click('[data-test-modal="unsaved-settings"] [data-test-button="close"]');
expect(currentURL()).to.equal('/settings/design'); // expect(currentURL()).to.equal('/settings/design');
await click('[data-test-link="back-to-settings"]'); // await click('[data-test-link="back-to-settings"]');
await click('[data-test-modal="unsaved-settings"] [data-test-leave-button]'); // await click('[data-test-modal="unsaved-settings"] [data-test-leave-button]');
expect(currentURL()).to.equal('/settings'); // expect(currentURL()).to.equal('/settings');
await click('[data-test-nav="design"]'); // await click('[data-test-nav="design"]');
expect(find('[data-test-input="siteDescription"]')).to.not.have.value('Changed'); // expect(find('[data-test-input="siteDescription"]')).to.not.have.value('Changed');
}); // });
it('renders with custom theme settings'); // it('renders with custom theme settings');
it('can install an official theme', async function () { // it('can install an official theme', async function () {
await visit('/settings/design'); // await visit('/settings/design');
await click('[data-test-nav="change-theme"]'); // await click('[data-test-nav="change-theme"]');
expect(currentURL(), 'currentURL').to.equal('/settings/design/change-theme'); // expect(currentURL(), 'currentURL').to.equal('/settings/design/change-theme');
await click('[data-test-theme-link="Journal"]'); // await click('[data-test-theme-link="Journal"]');
expect(currentURL(), 'currentURL').to.equal('/settings/design/change-theme/Journal'); // expect(currentURL(), 'currentURL').to.equal('/settings/design/change-theme/Journal');
await click('[data-test-button="install-theme"]'); // await click('[data-test-button="install-theme"]');
expect(find('[data-test-modal="install-theme"]'), 'install-theme modal').to.exist; // expect(find('[data-test-modal="install-theme"]'), 'install-theme modal').to.exist;
expect(find('[data-test-state="confirm"]'), 'confirm state').to.exist; // expect(find('[data-test-state="confirm"]'), 'confirm state').to.exist;
expect(findAll('[data-test-state]').length, 'state count').to.equal(1); // expect(findAll('[data-test-state]').length, 'state count').to.equal(1);
await click('[data-test-button="confirm-install"]'); // await click('[data-test-button="confirm-install"]');
expect(find('[data-test-state="installed-no-notes"]'), 'success state').to.exist; // expect(find('[data-test-state="installed-no-notes"]'), 'success state').to.exist;
expect(findAll('[data-test-state]').length, 'state count').to.equal(1); // expect(findAll('[data-test-state]').length, 'state count').to.equal(1);
// navigates back to design screen in background // // navigates back to design screen in background
expect(currentURL(), 'currentURL').to.equal('/settings/design'); // expect(currentURL(), 'currentURL').to.equal('/settings/design');
await click('[data-test-button="cancel"]'); // await click('[data-test-button="cancel"]');
expect(find('[data-test-modal="install-theme"]')).to.not.exist; // expect(find('[data-test-modal="install-theme"]')).to.not.exist;
// nav menu shows current theme // // nav menu shows current theme
expect(find('[data-test-text="current-theme"]')).to.contain.text('Journal - v0.1'); // expect(find('[data-test-text="current-theme"]')).to.contain.text('Journal - v0.1');
}); // });
it('can upload custom theme', async function () { // it('can upload custom theme', async function () {
this.server.post('/themes/upload/', function ({themes}) { // this.server.post('/themes/upload/', function ({themes}) {
const theme = themes.create({ // const theme = themes.create({
name: 'custom', // name: 'custom',
package: { // package: {
name: 'Custom', // name: 'Custom',
version: '1.0' // version: '1.0'
} // }
}); // });
return {themes: [theme]}; // return {themes: [theme]};
}); // });
await visit('/settings/design/change-theme'); // await visit('/settings/design/change-theme');
await click('[data-test-button="upload-theme"]'); // await click('[data-test-button="upload-theme"]');
expect(find('[data-test-modal="upload-theme"]'), 'upload-theme modal').to.exist; // expect(find('[data-test-modal="upload-theme"]'), 'upload-theme modal').to.exist;
await fileUpload('[data-test-modal="upload-theme"] input[type="file"]', ['test'], {name: 'valid-theme.zip', type: 'application/zip'}); // await fileUpload('[data-test-modal="upload-theme"] input[type="file"]', ['test'], {name: 'valid-theme.zip', type: 'application/zip'});
expect(find('[data-test-state="installed-no-notes"]'), 'success state').to.exist; // expect(find('[data-test-state="installed-no-notes"]'), 'success state').to.exist;
expect(currentURL(), 'url after upload').to.equal('/settings/design/change-theme'); // expect(currentURL(), 'url after upload').to.equal('/settings/design/change-theme');
await click('[data-test-button="activate"]'); // await click('[data-test-button="activate"]');
expect(currentURL(), 'url after activate').to.equal('/settings/design'); // expect(currentURL(), 'url after activate').to.equal('/settings/design');
expect(find('[data-test-modal="install-theme"]')).to.not.exist; // expect(find('[data-test-modal="install-theme"]')).to.not.exist;
expect(find('[data-test-text="current-theme"]')).to.contain.text('custom - v1.0'); // expect(find('[data-test-text="current-theme"]')).to.contain.text('custom - v1.0');
}); // });
it('can change between installed themes'); // it('can change between installed themes');
it('can delete installed theme'); // it('can delete installed theme');
describe('limits', function () { // describe('limits', function () {
it('displays upgrade notice when custom themes are not allowed', async function () { // it('displays upgrade notice when custom themes are not allowed', async function () {
this.server.loadFixtures('configs'); // this.server.loadFixtures('configs');
const config = this.server.db.configs.find(1); // const config = this.server.db.configs.find(1);
config.hostSettings = { // config.hostSettings = {
limits: { // limits: {
customThemes: { // customThemes: {
allowlist: ['source', 'casper', 'dawn', 'lyra'], // allowlist: ['source', 'casper', 'dawn', 'lyra'],
error: 'All our official built-in themes are available the Starter plan, if you upgrade to one of our higher tiers you will also be able to edit and upload custom themes for your site.' // error: 'All our official built-in themes are available the Starter plan, if you upgrade to one of our higher tiers you will also be able to edit and upload custom themes for your site.'
} // }
} // }
}; // };
this.server.db.configs.update(1, config); // this.server.db.configs.update(1, config);
await visit('/settings/design/change-theme'); // await visit('/settings/design/change-theme');
await click('[data-test-button="upload-theme"]'); // await click('[data-test-button="upload-theme"]');
expect(find('[data-test-modal="limits/custom-theme"]'), 'limits/custom-theme modal').to.exist; // expect(find('[data-test-modal="limits/custom-theme"]'), 'limits/custom-theme modal').to.exist;
}); // });
}); // });
}); // });

View File

@ -1,291 +1,291 @@
import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support'; // import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support';
import {beforeEach, describe, it} from 'mocha'; // import {beforeEach, describe, it} from 'mocha';
import {blur, click, currentURL, fillIn, find, findAll, focus, triggerEvent} from '@ember/test-helpers'; // import {blur, click, currentURL, fillIn, find, findAll, focus, triggerEvent} from '@ember/test-helpers';
import {expect} from 'chai'; // import {expect} from 'chai';
import {keyDown} from 'ember-keyboard/test-support/test-helpers'; // import {keyDown} from 'ember-keyboard/test-support/test-helpers';
import {setupApplicationTest} from 'ember-mocha'; // import {setupApplicationTest} from 'ember-mocha';
import {setupMirage} from 'ember-cli-mirage/test-support'; // import {setupMirage} from 'ember-cli-mirage/test-support';
import {visit} from '../../helpers/visit'; // import {visit} from '../../helpers/visit';
describe('Acceptance: Settings - General', function () { // describe('Acceptance: Settings - General', function () {
let hooks = setupApplicationTest(); // let hooks = setupApplicationTest();
setupMirage(hooks); // setupMirage(hooks);
it('redirects to signin when not authenticated', async function () { // it('redirects to signin when not authenticated', async function () {
await invalidateSession(); // await invalidateSession();
await visit('/settings/general'); // await visit('/settings/general');
expect(currentURL(), 'currentURL').to.equal('/signin'); // expect(currentURL(), 'currentURL').to.equal('/signin');
}); // });
it('redirects to home page when authenticated as contributor', async function () { // it('redirects to home page when authenticated as contributor', async function () {
let role = this.server.create('role', {name: 'Contributor'}); // let role = this.server.create('role', {name: 'Contributor'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/general'); // await visit('/settings/general');
expect(currentURL(), 'currentURL').to.equal('/posts'); // expect(currentURL(), 'currentURL').to.equal('/posts');
}); // });
it('redirects to home page when authenticated as author', async function () { // it('redirects to home page when authenticated as author', async function () {
let role = this.server.create('role', {name: 'Author'}); // let role = this.server.create('role', {name: 'Author'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/general'); // await visit('/settings/general');
expect(currentURL(), 'currentURL').to.equal('/site'); // expect(currentURL(), 'currentURL').to.equal('/site');
}); // });
it('redirects to home page when authenticated as editor', async function () { // it('redirects to home page when authenticated as editor', async function () {
let role = this.server.create('role', {name: 'Editor'}); // let role = this.server.create('role', {name: 'Editor'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/general'); // await visit('/settings/general');
expect(currentURL(), 'currentURL').to.equal('/site'); // expect(currentURL(), 'currentURL').to.equal('/site');
}); // });
describe('when logged in', function () { // describe('when logged in', function () {
beforeEach(async function () { // beforeEach(async function () {
let role = this.server.create('role', {name: 'Administrator'}); // let role = this.server.create('role', {name: 'Administrator'});
this.server.create('user', {roles: [role]}); // this.server.create('user', {roles: [role]});
return await authenticateSession(); // return await authenticateSession();
}); // });
it('it renders, handles image uploads', async function () { // it('it renders, handles image uploads', async function () {
await visit('/settings/general'); // await visit('/settings/general');
// has correct url // // has correct url
expect(currentURL(), 'currentURL').to.equal('/settings/general'); // expect(currentURL(), 'currentURL').to.equal('/settings/general');
// has correct page title // // has correct page title
expect(document.title, 'page title').to.equal('Settings - General - Test Blog'); // expect(document.title, 'page title').to.equal('Settings - General - Test Blog');
// highlights nav menu // // highlights nav menu
expect(find('[data-test-nav="settings"]'), 'highlights nav menu item') // expect(find('[data-test-nav="settings"]'), 'highlights nav menu item')
.to.have.class('active'); // .to.have.class('active');
expect( // expect(
find('[data-test-button="save"]').textContent.trim(), // find('[data-test-button="save"]').textContent.trim(),
'save button text' // 'save button text'
).to.equal('Save'); // ).to.equal('Save');
await click('[data-test-toggle-pub-info]'); // await click('[data-test-toggle-pub-info]');
await fillIn('[data-test-title-input]', 'New Blog Title'); // await fillIn('[data-test-title-input]', 'New Blog Title');
await click('[data-test-button="save"]'); // await click('[data-test-button="save"]');
expect(document.title, 'page title').to.equal('Settings - General - New Blog Title'); // expect(document.title, 'page title').to.equal('Settings - General - New Blog Title');
// CMD-S shortcut works // // CMD-S shortcut works
// -------------------------------------------------------------- // // // -------------------------------------------------------------- //
await fillIn('[data-test-title-input]', 'CMD-S Test'); // await fillIn('[data-test-title-input]', 'CMD-S Test');
await keyDown('cmd+s'); // await keyDown('cmd+s');
// we've already saved in this test so there's no on-screen indication // // we've already saved in this test so there's no on-screen indication
// that we've had another save, check the request was fired instead // // that we've had another save, check the request was fired instead
let [lastRequest] = this.server.pretender.handledRequests.slice(-1); // let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
let params = JSON.parse(lastRequest.requestBody); // let params = JSON.parse(lastRequest.requestBody);
expect(params.settings.findBy('key', 'title').value).to.equal('CMD-S Test'); // expect(params.settings.findBy('key', 'title').value).to.equal('CMD-S Test');
}); // });
it('renders timezone selector correctly', async function () { // it('renders timezone selector correctly', async function () {
await visit('/settings/general'); // await visit('/settings/general');
await click('[data-test-toggle-timezone]'); // await click('[data-test-toggle-timezone]');
expect(currentURL(), 'currentURL').to.equal('/settings/general'); // expect(currentURL(), 'currentURL').to.equal('/settings/general');
expect(findAll('#timezone option').length, 'available timezones').to.equal(66); // expect(findAll('#timezone option').length, 'available timezones').to.equal(66);
expect(find('#timezone option:checked').textContent.trim()).to.equal('(GMT) UTC'); // expect(find('#timezone option:checked').textContent.trim()).to.equal('(GMT) UTC');
find('#timezone option[value="Africa/Cairo"]').selected = true; // find('#timezone option[value="Africa/Cairo"]').selected = true;
await triggerEvent('#timezone', 'change'); // await triggerEvent('#timezone', 'change');
await click('[data-test-button="save"]'); // await click('[data-test-button="save"]');
expect(find('#timezone option:checked').textContent.trim()).to.equal('(GMT +2:00) Cairo, Egypt'); // expect(find('#timezone option:checked').textContent.trim()).to.equal('(GMT +2:00) Cairo, Egypt');
}); // });
it('handles private blog settings correctly', async function () { // it('handles private blog settings correctly', async function () {
await visit('/settings/general'); // await visit('/settings/general');
// handles private blog settings correctly // // handles private blog settings correctly
expect(find('[data-test-private-checkbox]').checked, 'isPrivate checkbox').to.be.false; // expect(find('[data-test-private-checkbox]').checked, 'isPrivate checkbox').to.be.false;
await click('[data-test-private-checkbox]'); // await click('[data-test-private-checkbox]');
expect(find('[data-test-private-checkbox]').checked, 'isPrivate checkbox').to.be.true; // expect(find('[data-test-private-checkbox]').checked, 'isPrivate checkbox').to.be.true;
expect(findAll('[data-test-password-input]').length, 'password input').to.equal(1); // expect(findAll('[data-test-password-input]').length, 'password input').to.equal(1);
expect(find('[data-test-password-input]').value, 'password default value').to.not.equal(''); // expect(find('[data-test-password-input]').value, 'password default value').to.not.equal('');
await fillIn('[data-test-password-input]', ''); // await fillIn('[data-test-password-input]', '');
await blur('[data-test-password-input]'); // await blur('[data-test-password-input]');
expect(find('[data-test-password-error]').textContent.trim(), 'empty password error') // expect(find('[data-test-password-error]').textContent.trim(), 'empty password error')
.to.equal('Password must be supplied'); // .to.equal('Password must be supplied');
await fillIn('[data-test-password-input]', 'asdfg'); // await fillIn('[data-test-password-input]', 'asdfg');
await blur('[data-test-password-input]'); // await blur('[data-test-password-input]');
expect(find('[data-test-password-error]').textContent.trim(), 'present password error') // expect(find('[data-test-password-error]').textContent.trim(), 'present password error')
.to.equal(''); // .to.equal('');
}); // });
it('handles social blog settings correctly', async function () { // it('handles social blog settings correctly', async function () {
let testSocialInput = async function (type, input, expectedValue, expectedError = '') { // let testSocialInput = async function (type, input, expectedValue, expectedError = '') {
await fillIn(`[data-test-${type}-input]`, input); // await fillIn(`[data-test-${type}-input]`, input);
await blur(`[data-test-${type}-input]`); // await blur(`[data-test-${type}-input]`);
expect( // expect(
find(`[data-test-${type}-input]`).value, // find(`[data-test-${type}-input]`).value,
`${type} value for ${input}` // `${type} value for ${input}`
).to.equal(expectedValue); // ).to.equal(expectedValue);
expect( // expect(
find(`[data-test-${type}-error]`).textContent.trim(), // find(`[data-test-${type}-error]`).textContent.trim(),
`${type} validation response for ${input}` // `${type} validation response for ${input}`
).to.equal(expectedError); // ).to.equal(expectedError);
expect( // expect(
find(`[data-test-${type}-input]`).closest('.form-group').classList.contains('error'), // find(`[data-test-${type}-input]`).closest('.form-group').classList.contains('error'),
`${type} input should be in error state with '${input}'` // `${type} input should be in error state with '${input}'`
).to.equal(!!expectedError); // ).to.equal(!!expectedError);
}; // };
let testFacebookValidation = async (...args) => testSocialInput('facebook', ...args); // let testFacebookValidation = async (...args) => testSocialInput('facebook', ...args);
let testTwitterValidation = async (...args) => testSocialInput('twitter', ...args); // let testTwitterValidation = async (...args) => testSocialInput('twitter', ...args);
await visit('/settings/general'); // await visit('/settings/general');
await click('[data-test-toggle-social]'); // await click('[data-test-toggle-social]');
// validates a facebook url correctly // // validates a facebook url correctly
// loads fixtures and performs transform // // loads fixtures and performs transform
expect(find('[data-test-facebook-input]').value, 'initial facebook value') // expect(find('[data-test-facebook-input]').value, 'initial facebook value')
.to.equal('https://www.facebook.com/test'); // .to.equal('https://www.facebook.com/test');
await focus('[data-test-facebook-input]'); // await focus('[data-test-facebook-input]');
await blur('[data-test-facebook-input]'); // await blur('[data-test-facebook-input]');
// regression test: we still have a value after the input is // // regression test: we still have a value after the input is
// focused and then blurred without any changes // // focused and then blurred without any changes
expect(find('[data-test-facebook-input]').value, 'facebook value after blur with no change') // expect(find('[data-test-facebook-input]').value, 'facebook value after blur with no change')
.to.equal('https://www.facebook.com/test'); // .to.equal('https://www.facebook.com/test');
await testFacebookValidation( // await testFacebookValidation(
'facebook.com/username', // 'facebook.com/username',
'https://www.facebook.com/username'); // 'https://www.facebook.com/username');
await testFacebookValidation( // await testFacebookValidation(
'testuser', // 'testuser',
'https://www.facebook.com/testuser'); // 'https://www.facebook.com/testuser');
await testFacebookValidation( // await testFacebookValidation(
'ab99', // 'ab99',
'https://www.facebook.com/ab99'); // 'https://www.facebook.com/ab99');
await testFacebookValidation( // await testFacebookValidation(
'page/ab99', // 'page/ab99',
'https://www.facebook.com/page/ab99'); // 'https://www.facebook.com/page/ab99');
await testFacebookValidation( // await testFacebookValidation(
'page/*(&*(%%))', // 'page/*(&*(%%))',
'https://www.facebook.com/page/*(&*(%%))'); // 'https://www.facebook.com/page/*(&*(%%))');
await testFacebookValidation( // await testFacebookValidation(
'facebook.com/pages/some-facebook-page/857469375913?ref=ts', // 'facebook.com/pages/some-facebook-page/857469375913?ref=ts',
'https://www.facebook.com/pages/some-facebook-page/857469375913?ref=ts'); // 'https://www.facebook.com/pages/some-facebook-page/857469375913?ref=ts');
await testFacebookValidation( // await testFacebookValidation(
'https://www.facebook.com/groups/savethecrowninn', // 'https://www.facebook.com/groups/savethecrowninn',
'https://www.facebook.com/groups/savethecrowninn'); // 'https://www.facebook.com/groups/savethecrowninn');
await testFacebookValidation( // await testFacebookValidation(
'http://github.com/username', // 'http://github.com/username',
'http://github.com/username', // 'http://github.com/username',
'The URL must be in a format like https://www.facebook.com/yourPage'); // 'The URL must be in a format like https://www.facebook.com/yourPage');
await testFacebookValidation( // await testFacebookValidation(
'http://github.com/pages/username', // 'http://github.com/pages/username',
'http://github.com/pages/username', // 'http://github.com/pages/username',
'The URL must be in a format like https://www.facebook.com/yourPage'); // 'The URL must be in a format like https://www.facebook.com/yourPage');
// validates a twitter url correctly // // validates a twitter url correctly
// loads fixtures and performs transform // // loads fixtures and performs transform
expect(find('[data-test-twitter-input]').value, 'initial twitter value') // expect(find('[data-test-twitter-input]').value, 'initial twitter value')
.to.equal('https://twitter.com/test'); // .to.equal('https://twitter.com/test');
await focus('[data-test-twitter-input]'); // await focus('[data-test-twitter-input]');
await blur('[data-test-twitter-input]'); // await blur('[data-test-twitter-input]');
// regression test: we still have a value after the input is // // regression test: we still have a value after the input is
// focused and then blurred without any changes // // focused and then blurred without any changes
expect(find('[data-test-twitter-input]').value, 'twitter value after blur with no change') // expect(find('[data-test-twitter-input]').value, 'twitter value after blur with no change')
.to.equal('https://twitter.com/test'); // .to.equal('https://twitter.com/test');
await testTwitterValidation( // await testTwitterValidation(
'twitter.com/username', // 'twitter.com/username',
'https://twitter.com/username'); // 'https://twitter.com/username');
await testTwitterValidation( // await testTwitterValidation(
'testuser', // 'testuser',
'https://twitter.com/testuser'); // 'https://twitter.com/testuser');
await testTwitterValidation( // await testTwitterValidation(
'http://github.com/username', // 'http://github.com/username',
'https://twitter.com/username'); // 'https://twitter.com/username');
await testTwitterValidation( // await testTwitterValidation(
'*(&*(%%))', // '*(&*(%%))',
'*(&*(%%))', // '*(&*(%%))',
'The URL must be in a format like https://twitter.com/yourUsername'); // 'The URL must be in a format like https://twitter.com/yourUsername');
await testTwitterValidation( // await testTwitterValidation(
'thisusernamehasmorethan15characters', // 'thisusernamehasmorethan15characters',
'thisusernamehasmorethan15characters', // 'thisusernamehasmorethan15characters',
'Your Username is not a valid Twitter Username'); // 'Your Username is not a valid Twitter Username');
}); // });
it('warns when leaving without saving', async function () { // it('warns when leaving without saving', async function () {
await visit('/settings/general'); // await visit('/settings/general');
expect( // expect(
find('[data-test-private-checkbox]').checked, // find('[data-test-private-checkbox]').checked,
'private blog checkbox' // 'private blog checkbox'
).to.be.false; // ).to.be.false;
await click('[data-test-toggle-pub-info]'); // await click('[data-test-toggle-pub-info]');
await fillIn('[data-test-title-input]', 'New Blog Title'); // await fillIn('[data-test-title-input]', 'New Blog Title');
await click('[data-test-private-checkbox]'); // await click('[data-test-private-checkbox]');
expect( // expect(
find('[data-test-private-checkbox]').checked, // find('[data-test-private-checkbox]').checked,
'private blog checkbox' // 'private blog checkbox'
).to.be.true; // ).to.be.true;
await visit('/settings/staff'); // await visit('/settings/staff');
expect(findAll('[data-test-modal="unsaved-settings"]').length, 'modal exists').to.equal(1); // expect(findAll('[data-test-modal="unsaved-settings"]').length, 'modal exists').to.equal(1);
// Leave without saving // // Leave without saving
await click('[data-test-leave-button]'); // await click('[data-test-leave-button]');
expect(currentURL(), 'currentURL').to.equal('/settings/staff'); // expect(currentURL(), 'currentURL').to.equal('/settings/staff');
await visit('/settings/general'); // await visit('/settings/general');
expect(currentURL(), 'currentURL').to.equal('/settings/general'); // expect(currentURL(), 'currentURL').to.equal('/settings/general');
// settings were not saved // // settings were not saved
expect( // expect(
find('[data-test-private-checkbox]').checked, // find('[data-test-private-checkbox]').checked,
'private blog checkbox' // 'private blog checkbox'
).to.be.false; // ).to.be.false;
expect( // expect(
find('[data-test-title-input]').textContent.trim(), // find('[data-test-title-input]').textContent.trim(),
'Blog title' // 'Blog title'
).to.equal(''); // ).to.equal('');
}); // });
}); // });
}); // });

View File

@ -1,512 +1,512 @@
import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support'; // import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support';
import {beforeEach, describe, it} from 'mocha'; // import {beforeEach, describe, it} from 'mocha';
import {blur, click, currentRouteName, currentURL, fillIn, find, findAll} from '@ember/test-helpers'; // import {blur, click, currentRouteName, currentURL, fillIn, find, findAll} from '@ember/test-helpers';
import {expect} from 'chai'; // import {expect} from 'chai';
import {setupApplicationTest} from 'ember-mocha'; // import {setupApplicationTest} from 'ember-mocha';
import {setupMirage} from 'ember-cli-mirage/test-support'; // import {setupMirage} from 'ember-cli-mirage/test-support';
import {visit} from '../../helpers/visit'; // import {visit} from '../../helpers/visit';
describe('Acceptance: Settings - Integrations - Custom', function () { // describe('Acceptance: Settings - Integrations - Custom', function () {
let hooks = setupApplicationTest(); // let hooks = setupApplicationTest();
setupMirage(hooks); // setupMirage(hooks);
describe('access permissions', function () { // describe('access permissions', function () {
beforeEach(function () { // beforeEach(function () {
this.server.create('integration', {name: 'Test'}); // this.server.create('integration', {name: 'Test'});
}); // });
it('redirects /integrations/ to signin when not authenticated', async function () { // it('redirects /integrations/ to signin when not authenticated', async function () {
await invalidateSession(); // await invalidateSession();
await visit('/settings/integrations'); // await visit('/settings/integrations');
expect(currentURL(), 'currentURL').to.equal('/signin'); // expect(currentURL(), 'currentURL').to.equal('/signin');
}); // });
it('redirects /integrations/ to home page when authenticated as contributor', async function () { // it('redirects /integrations/ to home page when authenticated as contributor', async function () {
let role = this.server.create('role', {name: 'Contributor'}); // let role = this.server.create('role', {name: 'Contributor'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/integrations'); // await visit('/settings/integrations');
expect(currentURL(), 'currentURL').to.equal('/posts'); // expect(currentURL(), 'currentURL').to.equal('/posts');
}); // });
it('redirects /integrations/ to home page when authenticated as author', async function () { // it('redirects /integrations/ to home page when authenticated as author', async function () {
let role = this.server.create('role', {name: 'Author'}); // let role = this.server.create('role', {name: 'Author'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/integrations'); // await visit('/settings/integrations');
expect(currentURL(), 'currentURL').to.equal('/site'); // expect(currentURL(), 'currentURL').to.equal('/site');
}); // });
it('redirects /integrations/ to home page when authenticated as editor', async function () { // it('redirects /integrations/ to home page when authenticated as editor', async function () {
let role = this.server.create('role', {name: 'Editor'}); // let role = this.server.create('role', {name: 'Editor'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/integrations/1'); // await visit('/settings/integrations/1');
expect(currentURL(), 'currentURL').to.equal('/site'); // expect(currentURL(), 'currentURL').to.equal('/site');
}); // });
it('redirects /integrations/:id/ to signin when not authenticated', async function () { // it('redirects /integrations/:id/ to signin when not authenticated', async function () {
await invalidateSession(); // await invalidateSession();
await visit('/settings/integrations/1'); // await visit('/settings/integrations/1');
expect(currentURL(), 'currentURL').to.equal('/signin'); // expect(currentURL(), 'currentURL').to.equal('/signin');
}); // });
it('redirects /integrations/:id/ to home page when authenticated as contributor', async function () { // it('redirects /integrations/:id/ to home page when authenticated as contributor', async function () {
let role = this.server.create('role', {name: 'Contributor'}); // let role = this.server.create('role', {name: 'Contributor'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/integrations/1'); // await visit('/settings/integrations/1');
expect(currentURL(), 'currentURL').to.equal('/posts'); // expect(currentURL(), 'currentURL').to.equal('/posts');
}); // });
it('redirects /integrations/:id/ to home page when authenticated as author', async function () { // it('redirects /integrations/:id/ to home page when authenticated as author', async function () {
let role = this.server.create('role', {name: 'Author'}); // let role = this.server.create('role', {name: 'Author'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/integrations/1'); // await visit('/settings/integrations/1');
expect(currentURL(), 'currentURL').to.equal('/site'); // expect(currentURL(), 'currentURL').to.equal('/site');
}); // });
it('redirects /integrations/:id/ to home page when authenticated as editor', async function () { // it('redirects /integrations/:id/ to home page when authenticated as editor', async function () {
let role = this.server.create('role', {name: 'Editor'}); // let role = this.server.create('role', {name: 'Editor'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/integrations/1'); // await visit('/settings/integrations/1');
expect(currentURL(), 'currentURL').to.equal('/site'); // expect(currentURL(), 'currentURL').to.equal('/site');
}); // });
}); // });
describe('navigation', function () { // describe('navigation', function () {
beforeEach(async function () { // beforeEach(async function () {
this.server.loadFixtures('settings'); // this.server.loadFixtures('settings');
let role = this.server.create('role', {name: 'Administrator'}); // let role = this.server.create('role', {name: 'Administrator'});
this.server.create('user', {roles: [role]}); // this.server.create('user', {roles: [role]});
return await authenticateSession(); // return await authenticateSession();
}); // });
it('renders defaults correctly', async function () { // it('renders defaults correctly', async function () {
await visit('/settings/integrations'); // await visit('/settings/integrations');
// slack is not configured in the fixtures // // slack is not configured in the fixtures
expect( // expect(
find('[data-test-app="slack"] [data-test-app-status]').textContent.trim(), // find('[data-test-app="slack"] [data-test-app-status]').textContent.trim(),
'slack app status' // 'slack app status'
).to.equal('Configure'); // ).to.equal('Configure');
// amp is disabled in the fixtures // // amp is disabled in the fixtures
expect( // expect(
find('[data-test-app="amp"] [data-test-app-status]').textContent.trim(), // find('[data-test-app="amp"] [data-test-app-status]').textContent.trim(),
'amp app status' // 'amp app status'
).to.equal('Configure'); // ).to.equal('Configure');
}); // });
it('renders AMP active state', async function () { // it('renders AMP active state', async function () {
this.server.db.settings.update({key: 'amp', value: true}); // this.server.db.settings.update({key: 'amp', value: true});
await visit('/settings/integrations'); // await visit('/settings/integrations');
// amp switches to active when enabled // // amp switches to active when enabled
expect( // expect(
find('[data-test-app="amp"] [data-test-app-status]').textContent.trim(), // find('[data-test-app="amp"] [data-test-app-status]').textContent.trim(),
'amp app status' // 'amp app status'
).to.equal('Active'); // ).to.equal('Active');
}); // });
it('it redirects to Slack when clicking on the grid', async function () { // it('it redirects to Slack when clicking on the grid', async function () {
await visit('/settings/integrations'); // await visit('/settings/integrations');
// has correct url // // has correct url
expect(currentURL(), 'currentURL').to.equal('/settings/integrations'); // expect(currentURL(), 'currentURL').to.equal('/settings/integrations');
await click('[data-test-link="slack"]'); // await click('[data-test-link="slack"]');
// has correct url // // has correct url
expect(currentURL(), 'currentURL').to.equal('/settings/integrations/slack'); // expect(currentURL(), 'currentURL').to.equal('/settings/integrations/slack');
}); // });
it('it redirects to AMP when clicking on the grid', async function () { // it('it redirects to AMP when clicking on the grid', async function () {
await visit('/settings/integrations'); // await visit('/settings/integrations');
// has correct url // // has correct url
expect(currentURL(), 'currentURL').to.equal('/settings/integrations'); // expect(currentURL(), 'currentURL').to.equal('/settings/integrations');
await click('[data-test-link="amp"]'); // await click('[data-test-link="amp"]');
// has correct url // // has correct url
expect(currentURL(), 'currentURL').to.equal('/settings/integrations/amp'); // expect(currentURL(), 'currentURL').to.equal('/settings/integrations/amp');
}); // });
it('it redirects to Unsplash when clicking on the grid', async function () { // it('it redirects to Unsplash when clicking on the grid', async function () {
await visit('/settings/integrations'); // await visit('/settings/integrations');
// has correct url // // has correct url
expect(currentURL(), 'currentURL').to.equal('/settings/integrations'); // expect(currentURL(), 'currentURL').to.equal('/settings/integrations');
await click('[data-test-link="unsplash"]'); // await click('[data-test-link="unsplash"]');
// has correct url // // has correct url
expect(currentURL(), 'currentURL').to.equal('/settings/integrations/unsplash'); // expect(currentURL(), 'currentURL').to.equal('/settings/integrations/unsplash');
}); // });
}); // });
describe('custom integrations', function () { // describe('custom integrations', function () {
beforeEach(async function () { // beforeEach(async function () {
this.server.loadFixtures('configs'); // this.server.loadFixtures('configs');
let config = this.server.schema.configs.first(); // let config = this.server.schema.configs.first();
config.update({ // config.update({
enableDeveloperExperiments: true // enableDeveloperExperiments: true
}); // });
let role = this.server.create('role', {name: 'Administrator'}); // let role = this.server.create('role', {name: 'Administrator'});
this.server.create('user', {roles: [role]}); // this.server.create('user', {roles: [role]});
return await authenticateSession(); // return await authenticateSession();
}); // });
it('handles 404', async function () { // it('handles 404', async function () {
await visit('/settings/integrations/1'); // await visit('/settings/integrations/1');
expect(currentRouteName()).to.equal('error404'); // expect(currentRouteName()).to.equal('error404');
}); // });
it('can add new integration', async function () { // it('can add new integration', async function () {
// sanity check // // sanity check
expect( // expect(
this.server.db.integrations.length, // this.server.db.integrations.length,
'number of integrations in db at start' // 'number of integrations in db at start'
).to.equal(0); // ).to.equal(0);
expect( // expect(
this.server.db.apiKeys.length, // this.server.db.apiKeys.length,
'number of apiKeys in db at start' // 'number of apiKeys in db at start'
).to.equal(0); // ).to.equal(0);
// blank slate // // blank slate
await visit('/settings/integrations'); // await visit('/settings/integrations');
expect( // expect(
find('[data-test-blank="custom-integrations"]'), // find('[data-test-blank="custom-integrations"]'),
'initial blank slate' // 'initial blank slate'
).to.exist; // ).to.exist;
// new integration modal opens/closes // // new integration modal opens/closes
await click('[data-test-button="new-integration"]'); // await click('[data-test-button="new-integration"]');
expect(currentURL(), 'url after clicking new').to.equal('/settings/integrations/new'); // expect(currentURL(), 'url after clicking new').to.equal('/settings/integrations/new');
expect(find('[data-test-modal="new-integration"]'), 'modal after clicking new').to.exist; // expect(find('[data-test-modal="new-integration"]'), 'modal after clicking new').to.exist;
await click('[data-test-button="cancel-new-integration"]'); // await click('[data-test-button="cancel-new-integration"]');
expect(find('[data-test-modal="new-integration"]'), 'modal after clicking cancel') // expect(find('[data-test-modal="new-integration"]'), 'modal after clicking cancel')
.to.not.exist; // .to.not.exist;
expect( // expect(
find('[data-test-blank="custom-integrations"]'), // find('[data-test-blank="custom-integrations"]'),
'blank slate after cancelled creation' // 'blank slate after cancelled creation'
).to.exist; // ).to.exist;
// new integration validations // // new integration validations
await click('[data-test-button="new-integration"]'); // await click('[data-test-button="new-integration"]');
await click('[data-test-button="create-integration"]'); // await click('[data-test-button="create-integration"]');
expect( // expect(
find('[data-test-error="new-integration-name"]').textContent, // find('[data-test-error="new-integration-name"]').textContent,
'name error after create with blank field' // 'name error after create with blank field'
).to.have.string('enter a name'); // ).to.have.string('enter a name');
await fillIn('[data-test-input="new-integration-name"]', 'Duplicate'); // await fillIn('[data-test-input="new-integration-name"]', 'Duplicate');
await click('[data-test-button="create-integration"]'); // await click('[data-test-button="create-integration"]');
expect( // expect(
find('[data-test-error="new-integration-name"]').textContent, // find('[data-test-error="new-integration-name"]').textContent,
'name error after create with duplicate name' // 'name error after create with duplicate name'
).to.have.string('already been used'); // ).to.have.string('already been used');
// successful creation // // successful creation
await fillIn('[data-test-input="new-integration-name"]', 'Test'); // await fillIn('[data-test-input="new-integration-name"]', 'Test');
expect( // expect(
find('[data-test-error="new-integration-name"]').textContent.trim(), // find('[data-test-error="new-integration-name"]').textContent.trim(),
'name error after typing in field' // 'name error after typing in field'
).to.be.empty; // ).to.be.empty;
await click('[data-test-button="create-integration"]'); // await click('[data-test-button="create-integration"]');
expect( // expect(
find('[data-test-modal="new-integration"]'), // find('[data-test-modal="new-integration"]'),
'modal after successful create' // 'modal after successful create'
).to.not.exist; // ).to.not.exist;
expect( // expect(
this.server.db.integrations.length, // this.server.db.integrations.length,
'number of integrations in db after create' // 'number of integrations in db after create'
).to.equal(1); // ).to.equal(1);
// mirage sanity check // // mirage sanity check
expect( // expect(
this.server.db.apiKeys.length, // this.server.db.apiKeys.length,
'number of api keys in db after create' // 'number of api keys in db after create'
).to.equal(2); // ).to.equal(2);
expect( // expect(
currentURL(), // currentURL(),
'url after integration creation' // 'url after integration creation'
).to.equal('/settings/integrations/1'); // ).to.equal('/settings/integrations/1');
// test navigation back to list then back to new integration // // test navigation back to list then back to new integration
await click('[data-test-link="integrations-back"]'); // await click('[data-test-link="integrations-back"]');
expect( // expect(
currentURL(), // currentURL(),
'url after clicking "Back"' // 'url after clicking "Back"'
).to.equal('/settings/integrations'); // ).to.equal('/settings/integrations');
expect( // expect(
find('[data-test-blank="custom-integrations"]'), // find('[data-test-blank="custom-integrations"]'),
'blank slate after creation' // 'blank slate after creation'
).to.not.exist; // ).to.not.exist;
expect( // expect(
findAll('[data-test-custom-integration]').length, // findAll('[data-test-custom-integration]').length,
'number of custom integrations after creation' // 'number of custom integrations after creation'
).to.equal(1); // ).to.equal(1);
await click(`[data-test-integration="1"]`); // await click(`[data-test-integration="1"]`);
expect( // expect(
currentURL(), // currentURL(),
'url after clicking integration in list' // 'url after clicking integration in list'
).to.equal('/settings/integrations/1'); // ).to.equal('/settings/integrations/1');
}); // });
it('can manage an integration', async function () { // it('can manage an integration', async function () {
this.server.create('integration'); // this.server.create('integration');
await visit('/settings/integrations/1'); // await visit('/settings/integrations/1');
expect( // expect(
currentURL(), // currentURL(),
'initial URL' // 'initial URL'
).to.equal('/settings/integrations/1'); // ).to.equal('/settings/integrations/1');
expect( // expect(
find('[data-test-screen-title]').textContent, // find('[data-test-screen-title]').textContent,
'screen title' // 'screen title'
).to.have.string('Integration 1'); // ).to.have.string('Integration 1');
// fields have expected values // // fields have expected values
// TODO: add test for logo // // TODO: add test for logo
expect( // expect(
find('[data-test-input="name"]').value, // find('[data-test-input="name"]').value,
'initial name value' // 'initial name value'
).to.equal('Integration 1'); // ).to.equal('Integration 1');
expect( // expect(
find('[data-test-input="description"]').value, // find('[data-test-input="description"]').value,
'initial description value' // 'initial description value'
).to.equal(''); // ).to.equal('');
expect( // expect(
find('[data-test-text="content-key"]'), // find('[data-test-text="content-key"]'),
'content key text' // 'content key text'
).to.have.trimmed.text('integration-1_content_key-12345'); // ).to.have.trimmed.text('integration-1_content_key-12345');
expect( // expect(
find('[data-test-text="admin-key"]'), // find('[data-test-text="admin-key"]'),
'admin key text' // 'admin key text'
).to.have.trimmed.text('integration-1_admin_key-12345'); // ).to.have.trimmed.text('integration-1_admin_key-12345');
expect( // expect(
find('[data-test-text="api-url"]'), // find('[data-test-text="api-url"]'),
'api url text' // 'api url text'
).to.have.trimmed.text(window.location.origin); // ).to.have.trimmed.text(window.location.origin);
// it can modify integration fields and has validation // // it can modify integration fields and has validation
expect( // expect(
find('[data-test-error="name"]').textContent.trim(), // find('[data-test-error="name"]').textContent.trim(),
'initial name error' // 'initial name error'
).to.be.empty; // ).to.be.empty;
await fillIn('[data-test-input="name"]', ''); // await fillIn('[data-test-input="name"]', '');
await await blur('[data-test-input="name"]'); // await await blur('[data-test-input="name"]');
expect( // expect(
find('[data-test-error="name"]').textContent, // find('[data-test-error="name"]').textContent,
'name validation for blank string' // 'name validation for blank string'
).to.have.string('enter a name'); // ).to.have.string('enter a name');
await click('[data-test-button="save"]'); // await click('[data-test-button="save"]');
expect( // expect(
this.server.schema.integrations.first().name, // this.server.schema.integrations.first().name,
'db integration name after failed save' // 'db integration name after failed save'
).to.equal('Integration 1'); // ).to.equal('Integration 1');
await fillIn('[data-test-input="name"]', 'Test Integration'); // await fillIn('[data-test-input="name"]', 'Test Integration');
await await blur('[data-test-input="name"]'); // await await blur('[data-test-input="name"]');
expect( // expect(
find('[data-test-error="name"]').textContent.trim(), // find('[data-test-error="name"]').textContent.trim(),
'name error after valid entry' // 'name error after valid entry'
).to.be.empty; // ).to.be.empty;
await fillIn('[data-test-input="description"]', 'Description for Test Integration'); // await fillIn('[data-test-input="description"]', 'Description for Test Integration');
await await blur('[data-test-input="description"]'); // await await blur('[data-test-input="description"]');
await click('[data-test-button="save"]'); // await click('[data-test-button="save"]');
// changes are reflected in the integrations list // // changes are reflected in the integrations list
await click('[data-test-link="integrations-back"]'); // await click('[data-test-link="integrations-back"]');
expect( // expect(
currentURL(), // currentURL(),
'url after saving and clicking "back"' // 'url after saving and clicking "back"'
).to.equal('/settings/integrations'); // ).to.equal('/settings/integrations');
expect( // expect(
find('[data-test-integration="1"] [data-test-text="name"]').textContent.trim(), // find('[data-test-integration="1"] [data-test-text="name"]').textContent.trim(),
'integration name after save' // 'integration name after save'
).to.equal('Test Integration'); // ).to.equal('Test Integration');
expect( // expect(
find('[data-test-integration="1"] [data-test-text="description"]').textContent.trim(), // find('[data-test-integration="1"] [data-test-text="description"]').textContent.trim(),
'integration description after save' // 'integration description after save'
).to.equal('Description for Test Integration'); // ).to.equal('Description for Test Integration');
await click('[data-test-integration="1"]'); // await click('[data-test-integration="1"]');
// warns of unsaved changes when leaving // // warns of unsaved changes when leaving
await fillIn('[data-test-input="name"]', 'Unsaved test'); // await fillIn('[data-test-input="name"]', 'Unsaved test');
await click('[data-test-link="integrations-back"]'); // await click('[data-test-link="integrations-back"]');
expect( // expect(
find('[data-test-modal="unsaved-settings"]'), // find('[data-test-modal="unsaved-settings"]'),
'modal shown when navigating with unsaved changes' // 'modal shown when navigating with unsaved changes'
).to.exist; // ).to.exist;
await click('[data-test-stay-button]'); // await click('[data-test-stay-button]');
expect( // expect(
find('[data-test-modal="unsaved-settings"]'), // find('[data-test-modal="unsaved-settings"]'),
'modal is closed after clicking "stay"' // 'modal is closed after clicking "stay"'
).to.not.exist; // ).to.not.exist;
expect( // expect(
currentURL(), // currentURL(),
'url after clicking "stay"' // 'url after clicking "stay"'
).to.equal('/settings/integrations/1'); // ).to.equal('/settings/integrations/1');
await click('[data-test-link="integrations-back"]'); // await click('[data-test-link="integrations-back"]');
await click('[data-test-leave-button]'); // await click('[data-test-leave-button]');
expect( // expect(
find('[data-test-modal="unsaved-settings"]'), // find('[data-test-modal="unsaved-settings"]'),
'modal is closed after clicking "leave"' // 'modal is closed after clicking "leave"'
).to.not.exist; // ).to.not.exist;
expect( // expect(
currentURL(), // currentURL(),
'url after clicking "leave"' // 'url after clicking "leave"'
).to.equal('/settings/integrations'); // ).to.equal('/settings/integrations');
expect( // expect(
find('[data-test-integration="1"] [data-test-text="name"]').textContent.trim(), // find('[data-test-integration="1"] [data-test-text="name"]').textContent.trim(),
'integration name after leaving unsaved changes' // 'integration name after leaving unsaved changes'
).to.equal('Test Integration'); // ).to.equal('Test Integration');
}); // });
it('can manage an integration\'s webhooks', async function () { // it('can manage an integration\'s webhooks', async function () {
this.server.create('integration'); // this.server.create('integration');
await visit('/settings/integrations/1'); // await visit('/settings/integrations/1');
expect(find('[data-test-webhooks-blank-slate]')).to.exist; // expect(find('[data-test-webhooks-blank-slate]')).to.exist;
// open new webhook modal // // open new webhook modal
await click('[data-test-link="add-webhook"]'); // await click('[data-test-link="add-webhook"]');
expect(find('[data-test-modal="webhook-form"]')).to.exist; // expect(find('[data-test-modal="webhook-form"]')).to.exist;
expect(find('[data-test-modal="webhook-form"] [data-test-text="title"]').textContent) // expect(find('[data-test-modal="webhook-form"] [data-test-text="title"]').textContent)
.to.have.string('New webhook'); // .to.have.string('New webhook');
// can cancel new webhook // // can cancel new webhook
await click('[data-test-button="cancel-webhook"]'); // await click('[data-test-button="cancel-webhook"]');
expect(find('[data-test-modal="webhook-form"]')).to.not.exist; // expect(find('[data-test-modal="webhook-form"]')).to.not.exist;
// create new webhook // // create new webhook
await click('[data-test-link="add-webhook"]'); // await click('[data-test-link="add-webhook"]');
await fillIn('[data-test-input="webhook-name"]', 'First webhook'); // await fillIn('[data-test-input="webhook-name"]', 'First webhook');
await fillIn('[data-test-select="webhook-event"]', 'site.changed'); // await fillIn('[data-test-select="webhook-event"]', 'site.changed');
await fillIn('[data-test-input="webhook-targetUrl"]', 'https://example.com/first-webhook'); // await fillIn('[data-test-input="webhook-targetUrl"]', 'https://example.com/first-webhook');
await click('[data-test-button="save-webhook"]'); // await click('[data-test-button="save-webhook"]');
// modal closed and 1 webhook listed with correct details // // modal closed and 1 webhook listed with correct details
expect(find('[data-test-modal="webhook-form"]')).to.not.exist; // expect(find('[data-test-modal="webhook-form"]')).to.not.exist;
expect(find('[data-test-webhook-row]')).to.exist; // expect(find('[data-test-webhook-row]')).to.exist;
let row = find('[data-test-webhook-row="1"]'); // let row = find('[data-test-webhook-row="1"]');
expect(row.querySelector('[data-test-text="name"]').textContent) // expect(row.querySelector('[data-test-text="name"]').textContent)
.to.have.string('First webhook'); // .to.have.string('First webhook');
expect(row.querySelector('[data-test-text="event"]').textContent) // expect(row.querySelector('[data-test-text="event"]').textContent)
.to.have.string('Site changed (rebuild)'); // .to.have.string('Site changed (rebuild)');
expect(row.querySelector('[data-test-text="targetUrl"]').textContent) // expect(row.querySelector('[data-test-text="targetUrl"]').textContent)
.to.have.string('https://example.com/first-webhook'); // .to.have.string('https://example.com/first-webhook');
expect(row.querySelector('[data-test-text="last-triggered"]').textContent) // expect(row.querySelector('[data-test-text="last-triggered"]').textContent)
.to.have.string('Not triggered'); // .to.have.string('Not triggered');
// click edit webhook link // // click edit webhook link
await click('[data-test-webhook-row="1"] [data-test-newsletter-menu-trigger]'); // await click('[data-test-webhook-row="1"] [data-test-newsletter-menu-trigger]');
await click('[data-test-link="edit-webhook"]'); // await click('[data-test-link="edit-webhook"]');
// modal appears and has correct title // // modal appears and has correct title
expect(find('[data-test-modal="webhook-form"]')).to.exist; // expect(find('[data-test-modal="webhook-form"]')).to.exist;
expect(find('[data-test-modal="webhook-form"] [data-test-text="title"]').textContent) // expect(find('[data-test-modal="webhook-form"] [data-test-text="title"]').textContent)
.to.have.string('Edit webhook'); // .to.have.string('Edit webhook');
}); // });
// test to ensure the `value=description` passed to `gh-text-input` is `readonly` // // test to ensure the `value=description` passed to `gh-text-input` is `readonly`
it('doesn\'t show unsaved changes modal after placing focus on description field', async function () { // it('doesn\'t show unsaved changes modal after placing focus on description field', async function () {
this.server.create('integration'); // this.server.create('integration');
await visit('/settings/integrations/1'); // await visit('/settings/integrations/1');
await click('[data-test-input="description"]'); // await click('[data-test-input="description"]');
await blur('[data-test-input="description"]'); // await blur('[data-test-input="description"]');
await click('[data-test-link="integrations-back"]'); // await click('[data-test-link="integrations-back"]');
expect( // expect(
find('[data-test-modal="unsaved-settings"]'), // find('[data-test-modal="unsaved-settings"]'),
'unsaved changes modal is not shown' // 'unsaved changes modal is not shown'
).to.not.exist; // ).to.not.exist;
expect(currentURL()).to.equal('/settings/integrations'); // expect(currentURL()).to.equal('/settings/integrations');
}); // });
it('can delete integration', async function () { // it('can delete integration', async function () {
this.server.create('integration'); // this.server.create('integration');
await visit('/settings/integrations/1'); // await visit('/settings/integrations/1');
await click('[data-test-button="delete-integration"]'); // await click('[data-test-button="delete-integration"]');
expect(find('[data-test-modal="delete-integration"]')).to.exist; // expect(find('[data-test-modal="delete-integration"]')).to.exist;
await click('[data-test-modal="delete-integration"] [data-test-button="confirm"]'); // await click('[data-test-modal="delete-integration"] [data-test-button="confirm"]');
expect(find('[data-test-modal="delete-integration"]')).to.not.exist; // expect(find('[data-test-modal="delete-integration"]')).to.not.exist;
expect(currentURL()).to.equal('/settings/integrations'); // expect(currentURL()).to.equal('/settings/integrations');
expect(find('[data-test-custom-integration]')).to.not.exist; // expect(find('[data-test-custom-integration]')).to.not.exist;
}); // });
}); // });
}); // });

View File

@ -1,339 +1,338 @@
import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support'; // import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support';
import {beforeEach, describe, it} from 'mocha'; // import {beforeEach, describe, it} from 'mocha';
import {click, currentURL, fillIn, find, findAll} from '@ember/test-helpers'; // import {click, currentURL, fillIn, find, findAll} from '@ember/test-helpers';
import {expect} from 'chai'; // import {expect} from 'chai';
import {fileUpload} from '../../helpers/file-upload'; // import {fileUpload} from '../../helpers/file-upload';
import {setupApplicationTest} from 'ember-mocha'; // import {setupApplicationTest} from 'ember-mocha';
import {setupMirage} from 'ember-cli-mirage/test-support'; // import {setupMirage} from 'ember-cli-mirage/test-support';
import {visit} from '../../helpers/visit'; // import {visit} from '../../helpers/visit';
describe('Acceptance: Settings - Labs', function () { // describe('Acceptance: Settings - Labs', function () {
let hooks = setupApplicationTest(); // let hooks = setupApplicationTest();
setupMirage(hooks); // setupMirage(hooks);
it('redirects to signin when not authenticated', async function () { // it('redirects to signin when not authenticated', async function () {
await invalidateSession(); // await invalidateSession();
await visit('/settings/labs'); // await visit('/settings/labs');
expect(currentURL(), 'currentURL').to.equal('/signin'); // expect(currentURL(), 'currentURL').to.equal('/signin');
}); // });
it('redirects to home page when authenticated as contributor', async function () { // it('redirects to home page when authenticated as contributor', async function () {
let role = this.server.create('role', {name: 'Contributor'}); // let role = this.server.create('role', {name: 'Contributor'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/labs'); // await visit('/settings/labs');
expect(currentURL(), 'currentURL').to.equal('/posts'); // expect(currentURL(), 'currentURL').to.equal('/posts');
}); // });
it('redirects to home page when authenticated as author', async function () { // it('redirects to home page when authenticated as author', async function () {
let role = this.server.create('role', {name: 'Author'}); // let role = this.server.create('role', {name: 'Author'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/labs'); // await visit('/settings/labs');
expect(currentURL(), 'currentURL').to.equal('/site'); // expect(currentURL(), 'currentURL').to.equal('/site');
}); // });
it('redirects to home page when authenticated as editor', async function () { // it('redirects to home page when authenticated as editor', async function () {
let role = this.server.create('role', {name: 'Editor'}); // let role = this.server.create('role', {name: 'Editor'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/labs'); // await visit('/settings/labs');
expect(currentURL(), 'currentURL').to.equal('/site'); // expect(currentURL(), 'currentURL').to.equal('/site');
}); // });
describe('when logged in', function () { // describe('when logged in', function () {
beforeEach(async function () { // beforeEach(async function () {
let role = this.server.create('role', {name: 'Administrator'}); // let role = this.server.create('role', {name: 'Administrator'});
this.server.create('user', {roles: [role]}); // this.server.create('user', {roles: [role]});
return await authenticateSession(); // return await authenticateSession();
}); // });
it('it renders', async function () { // it('it renders', async function () {
await visit('/settings/labs'); // await visit('/settings/labs');
// has correct url // // has correct url
expect(currentURL(), 'currentURL').to.equal('/settings/labs'); // expect(currentURL(), 'currentURL').to.equal('/settings/labs');
// has correct page title // // has correct page title
expect(document.title, 'page title').to.equal('Settings - Labs - Test Blog'); // expect(document.title, 'page title').to.equal('Settings - Labs - Test Blog');
// highlights nav menu // // highlights nav menu
expect(find('[data-test-nav="settings"]'), 'highlights nav menu item') // expect(find('[data-test-nav="settings"]'), 'highlights nav menu item')
.to.have.class('active'); // .to.have.class('active');
}); // });
it('can delete all content', async function () { // it('can delete all content', async function () {
await visit('/settings/labs'); // await visit('/settings/labs');
await click('[data-test-button="delete-all"]'); // await click('[data-test-button="delete-all"]');
const modal = '[data-test-modal="confirm-delete-all"]'; // const modal = '[data-test-modal="confirm-delete-all"]';
expect(find(modal)).to.exist; // expect(find(modal)).to.exist;
await click(`${modal} [data-test-button="confirm"]`); // await click(`${modal} [data-test-button="confirm"]`);
// API request is correct // // API request is correct
const [lastRequest] = this.server.pretender.handledRequests.slice(-1); // const [lastRequest] = this.server.pretender.handledRequests.slice(-1);
expect(lastRequest.url).to.equal('/ghost/api/admin/db/'); // expect(lastRequest.url).to.equal('/ghost/api/admin/db/');
expect(lastRequest.method).to.equal('DELETE'); // expect(lastRequest.method).to.equal('DELETE');
expect(find(modal)).to.not.exist; // expect(find(modal)).to.not.exist;
}); // });
it('can upload/download redirects', async function () { // it('can upload/download redirects', async function () {
await visit('/settings/labs'); // await visit('/settings/labs');
// successful upload // // successful upload
this.server.post('/redirects/upload/', {}, 200); // this.server.post('/redirects/upload/', {}, 200);
await fileUpload( // await fileUpload(
'[data-test-file-input="redirects"] input', // '[data-test-file-input="redirects"] input',
['test'], // ['test'],
{name: 'redirects.json', type: 'application/json'} // {name: 'redirects.json', type: 'application/json'}
); // );
// TODO: tests for the temporary success/failure state have been // // TODO: tests for the temporary success/failure state have been
// disabled because they were randomly failing // // disabled because they were randomly failing
// this should be half-way through button reset timeout // // this should be half-way through button reset timeout
// await timeout(50); // // await timeout(50);
// // //
// // shows success button // // // shows success button
// let buttons = findAll('[data-test-button="upload-redirects"]'); // // let buttons = findAll('[data-test-button="upload-redirects"]');
// expect(buttons.length, 'no of success buttons').to.equal(1); // // expect(buttons.length, 'no of success buttons').to.equal(1);
// expect( // // expect(
// buttons[0], // // buttons[0],
// 'success button is green' // // 'success button is green'
// ).to.have.class('gh-btn-green); // // ).to.have.class('gh-btn-green);
// expect( // // expect(
// button.textContent, // // button.textContent,
// 'success button text' // // 'success button text'
// ).to.have.string('Uploaded'); // // ).to.have.string('Uploaded');
// // //
// await wait(); // // await wait();
// returned to normal button // // returned to normal button
let buttons = findAll('[data-test-button="upload-redirects"]'); // let buttons = findAll('[data-test-button="upload-redirects"]');
expect(buttons.length, 'no of post-success buttons').to.equal(1); // expect(buttons.length, 'no of post-success buttons').to.equal(1);
expect( // expect(
buttons[0], // buttons[0],
'post-success button doesn\'t have success class' // 'post-success button doesn\'t have success class'
).to.not.have.class('gh-btn-green'); // ).to.not.have.class('gh-btn-green');
expect( // expect(
buttons[0].textContent, // buttons[0].textContent,
'post-success button text' // 'post-success button text'
).to.have.string('Upload redirects'); // ).to.have.string('Upload redirects');
// failed upload // // failed upload
this.server.post('/redirects/upload/', { // this.server.post('/redirects/upload/', {
errors: [{ // errors: [{
type: 'BadRequestError', // type: 'BadRequestError',
message: 'Test failure message' // message: 'Test failure message'
}] // }]
}, 400); // }, 400);
await fileUpload( // await fileUpload(
'[data-test-file-input="redirects"] input', // '[data-test-file-input="redirects"] input',
['test'], // ['test'],
{name: 'redirects-bad.json', type: 'application/json'} // {name: 'redirects-bad.json', type: 'application/json'}
); // );
// TODO: tests for the temporary success/failure state have been // // TODO: tests for the temporary success/failure state have been
// disabled because they were randomly failing // // disabled because they were randomly failing
// this should be half-way through button reset timeout // // this should be half-way through button reset timeout
// await timeout(50); // // await timeout(50);
// // //
// shows failure button // // shows failure button
// buttons = findAll('[data-test-button="upload-redirects"]'); // // buttons = findAll('[data-test-button="upload-redirects"]');
// expect(buttons.length, 'no of failure buttons').to.equal(1); // // expect(buttons.length, 'no of failure buttons').to.equal(1);
// expect( // // expect(
// buttons[0], // // buttons[0],
// 'failure button is red' // // 'failure button is red'
// ).to.have.class('gh-btn-red); // // ).to.have.class('gh-btn-red);
// expect( // // expect(
// buttons[0].textContent, // // buttons[0].textContent,
// 'failure button text' // // 'failure button text'
// ).to.have.string('Upload Failed'); // // ).to.have.string('Upload Failed');
// // //
// await wait(); // // await wait();
// shows error message // // shows error message
expect( // expect(
find('[data-test-error="redirects"]').textContent.trim(), // find('[data-test-error="redirects"]').textContent.trim(),
'upload error text' // 'upload error text'
).to.have.string('Test failure message'); // ).to.have.string('Test failure message');
// returned to normal button // // returned to normal button
buttons = findAll('[data-test-button="upload-redirects"]'); // buttons = findAll('[data-test-button="upload-redirects"]');
expect(buttons.length, 'no of post-failure buttons').to.equal(1); // expect(buttons.length, 'no of post-failure buttons').to.equal(1);
expect( // expect(
buttons[0], // buttons[0],
'post-failure button doesn\'t have failure class' // 'post-failure button doesn\'t have failure class'
).to.not.have.class('gh-btn-red'); // ).to.not.have.class('gh-btn-red');
expect( // expect(
buttons[0].textContent, // buttons[0].textContent,
'post-failure button text' // 'post-failure button text'
).to.have.string('Upload redirects'); // ).to.have.string('Upload redirects');
// successful upload clears error // // successful upload clears error
this.server.post('/redirects/upload/', {}, 200); // this.server.post('/redirects/upload/', {}, 200);
await fileUpload( // await fileUpload(
'[data-test-file-input="redirects"] input', // '[data-test-file-input="redirects"] input',
['test'], // ['test'],
{name: 'redirects-bad.json', type: 'application/json'} // {name: 'redirects-bad.json', type: 'application/json'}
); // );
expect(find('[data-test-error="redirects"]')).to.not.exist; // expect(find('[data-test-error="redirects"]')).to.not.exist;
// can download redirects.json // // can download redirects.json
await click('[data-test-link="download-redirects"]'); // await click('[data-test-link="download-redirects"]');
let iframe = document.querySelector('#iframeDownload'); // let iframe = document.querySelector('#iframeDownload');
expect(iframe.getAttribute('src')).to.have.string('/redirects/download/'); // expect(iframe.getAttribute('src')).to.have.string('/redirects/download/');
}); // });
it('can upload/download routes.yaml', async function () { // it('can upload/download routes.yaml', async function () {
await visit('/settings/labs'); // await visit('/settings/labs');
// successful upload // // successful upload
this.server.post('/settings/routes/yaml/', {}, 200); // this.server.post('/settings/routes/yaml/', {}, 200);
await fileUpload( // await fileUpload(
'[data-test-file-input="routes"] input', // '[data-test-file-input="routes"] input',
['test'], // ['test'],
{name: 'routes.yaml', type: 'application/x-yaml'} // {name: 'routes.yaml', type: 'application/x-yaml'}
); // );
// TODO: tests for the temporary success/failure state have been // // TODO: tests for the temporary success/failure state have been
// disabled because they were randomly failing // // disabled because they were randomly failing
// this should be half-way through button reset timeout // // this should be half-way through button reset timeout
// await timeout(50); // // await timeout(50);
// // //
// // shows success button // // // shows success button
// let button = find('[data-test-button="upload-routes"]'); // // let button = find('[data-test-button="upload-routes"]');
// expect(button.length, 'no of success buttons').to.equal(1); // // expect(button.length, 'no of success buttons').to.equal(1);
// expect( // // expect(
// button.hasClass('gh-btn-green'), // // button.hasClass('gh-btn-green'),
// 'success button is green' // // 'success button is green'
// ).to.be.true; // // ).to.be.true;
// expect( // // expect(
// button.text().trim(), // // button.text().trim(),
// 'success button text' // // 'success button text'
// ).to.have.string('Uploaded'); // // ).to.have.string('Uploaded');
// // //
// await wait(); // // await wait();
// returned to normal button // // returned to normal button
let buttons = findAll('[data-test-button="upload-routes"]'); // let buttons = findAll('[data-test-button="upload-routes"]');
expect(buttons.length, 'no of post-success buttons').to.equal(1); // expect(buttons.length, 'no of post-success buttons').to.equal(1);
expect( // expect(
buttons[0], // buttons[0],
'routes post-success button doesn\'t have success class' // 'routes post-success button doesn\'t have success class'
).to.not.have.class('gh-btn-green'); // ).to.not.have.class('gh-btn-green');
expect( // expect(
buttons[0].textContent, // buttons[0].textContent,
'routes post-success button text' // 'routes post-success button text'
).to.have.string('Upload routes YAML'); // ).to.have.string('Upload routes YAML');
// failed upload // // failed upload
this.server.post('/settings/routes/yaml/', { // this.server.post('/settings/routes/yaml/', {
errors: [{ // errors: [{
type: 'BadRequestError', // type: 'BadRequestError',
message: 'Test failure message' // message: 'Test failure message'
}] // }]
}, 400); // }, 400);
await fileUpload( // await fileUpload(
'[data-test-file-input="routes"] input', // '[data-test-file-input="routes"] input',
['test'], // ['test'],
{name: 'routes-bad.yaml', type: 'application/x-yaml'} // {name: 'routes-bad.yaml', type: 'application/x-yaml'}
); // );
// TODO: tests for the temporary success/failure state have been // // TODO: tests for the temporary success/failure state have been
// disabled because they were randomly failing // // disabled because they were randomly failing
// this should be half-way through button reset timeout // // this should be half-way through button reset timeout
// await timeout(50); // // await timeout(50);
// // //
// shows failure button // // shows failure button
// button = find('[data-test-button="upload-routes"]'); // // button = find('[data-test-button="upload-routes"]');
// expect(button.length, 'no of failure buttons').to.equal(1); // // expect(button.length, 'no of failure buttons').to.equal(1);
// expect( // // expect(
// button.hasClass('gh-btn-red'), // // button.hasClass('gh-btn-red'),
// 'failure button is red' // // 'failure button is red'
// ).to.be.true; // // ).to.be.true;
// expect( // // expect(
// button.text().trim(), // // button.text().trim(),
// 'failure button text' // // 'failure button text'
// ).to.have.string('Upload Failed'); // // ).to.have.string('Upload Failed');
// // //
// await wait(); // // await wait();
// shows error message // // shows error message
expect( // expect(
find('[data-test-error="routes"]').textContent, // find('[data-test-error="routes"]').textContent,
'routes upload error text' // 'routes upload error text'
).to.have.string('Test failure message'); // ).to.have.string('Test failure message');
// returned to normal button // // returned to normal button
buttons = findAll('[data-test-button="upload-routes"]'); // buttons = findAll('[data-test-button="upload-routes"]');
expect(buttons.length, 'no of post-failure buttons').to.equal(1); // expect(buttons.length, 'no of post-failure buttons').to.equal(1);
expect( // expect(
buttons[0], // buttons[0],
'routes post-failure button doesn\'t have failure class' // 'routes post-failure button doesn\'t have failure class'
).to.not.have.class('gh-btn-red'); // ).to.not.have.class('gh-btn-red');
expect( // expect(
buttons[0].textContent, // buttons[0].textContent,
'routes post-failure button text' // 'routes post-failure button text'
).to.have.string('Upload routes YAML'); // ).to.have.string('Upload routes YAML');
// successful upload clears error // // successful upload clears error
this.server.post('/settings/routes/yaml/', {}, 200); // this.server.post('/settings/routes/yaml/', {}, 200);
await fileUpload( // await fileUpload(
'[data-test-file-input="routes"] input', // '[data-test-file-input="routes"] input',
['test'], // ['test'],
{name: 'routes-good.yaml', type: 'application/x-yaml'} // {name: 'routes-good.yaml', type: 'application/x-yaml'}
); // );
expect(find('[data-test-error="routes"]')).to.not.exist; // expect(find('[data-test-error="routes"]')).to.not.exist;
// can download redirects.json // // can download redirects.json
await click('[data-test-link="download-routes"]'); // await click('[data-test-link="download-routes"]');
let iframe = document.querySelector('#iframeDownload'); // let iframe = document.querySelector('#iframeDownload');
expect(iframe.getAttribute('src')).to.have.string('/settings/routes/yaml/'); // expect(iframe.getAttribute('src')).to.have.string('/settings/routes/yaml/');
}); // });
});
describe('When logged in as Owner', function () { // describe('When logged in as Owner', function () {
beforeEach(async function () { // beforeEach(async function () {
let role = this.server.create('role', {name: 'Owner'}); // let role = this.server.create('role', {name: 'Owner'});
this.server.create('user', {roles: [role]}); // this.server.create('user', {roles: [role]});
return await authenticateSession(); // return await authenticateSession();
}); // });
it.skip('sets the mailgunBaseUrl to the default', async function () { // it.skip('sets the mailgunBaseUrl to the default', async function () {
await visit('/settings/members'); // await visit('/settings/members');
await fillIn('[data-test-mailgun-api-key-input]', 'i_am_an_api_key'); // await fillIn('[data-test-mailgun-api-key-input]', 'i_am_an_api_key');
await fillIn('[data-test-mailgun-domain-input]', 'https://domain.tld'); // await fillIn('[data-test-mailgun-domain-input]', 'https://domain.tld');
await click('[data-test-button="save-members-settings"]'); // await click('[data-test-button="save-members-settings"]');
let [lastRequest] = this.server.pretender.handledRequests.slice(-1); // let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
let params = JSON.parse(lastRequest.requestBody); // let params = JSON.parse(lastRequest.requestBody);
expect(params.settings.findBy('key', 'mailgun_base_url').value).not.to.equal(null); // expect(params.settings.findBy('key', 'mailgun_base_url').value).not.to.equal(null);
}); // });
}); // });
}); // });

View File

@ -1,292 +1,292 @@
import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support'; // import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support';
import {blur, click, currentURL, fillIn, find, findAll} from '@ember/test-helpers'; // import {blur, click, currentURL, fillIn, find, findAll} from '@ember/test-helpers';
import {expect} from 'chai'; // import {expect} from 'chai';
import {setupApplicationTest} from 'ember-mocha'; // import {setupApplicationTest} from 'ember-mocha';
import {setupMirage} from 'ember-cli-mirage/test-support'; // import {setupMirage} from 'ember-cli-mirage/test-support';
import {visit} from '../../helpers/visit'; // import {visit} from '../../helpers/visit';
describe('Acceptance: Settings - Membership', function () { // describe('Acceptance: Settings - Membership', function () {
const hooks = setupApplicationTest(); // const hooks = setupApplicationTest();
setupMirage(hooks); // setupMirage(hooks);
beforeEach(function () { // beforeEach(function () {
}); // });
beforeEach(async function () { // beforeEach(async function () {
this.server.loadFixtures('configs'); // this.server.loadFixtures('configs');
this.server.loadFixtures('tiers'); // this.server.loadFixtures('tiers');
this.server.db.configs.update(1, {blogUrl: 'http://localhost:2368'}); // this.server.db.configs.update(1, {blogUrl: 'http://localhost:2368'});
const role = this.server.create('role', {name: 'Owner'}); // const role = this.server.create('role', {name: 'Owner'});
this.server.create('user', {roles: [role]}); // this.server.create('user', {roles: [role]});
return await authenticateSession(); // return await authenticateSession();
}); // });
describe('permissions', function () { // describe('permissions', function () {
let visitAs; // let visitAs;
before(function () { // before(function () {
visitAs = async (roleName) => { // visitAs = async (roleName) => {
const role = this.server.create('role', {name: roleName}); // const role = this.server.create('role', {name: roleName});
this.server.create('user', {roles: [role]}); // this.server.create('user', {roles: [role]});
await authenticateSession(); // await authenticateSession();
await visit('/settings/members'); // await visit('/settings/members');
}; // };
}); // });
beforeEach(async function () { // beforeEach(async function () {
this.server.db.users.remove(); // this.server.db.users.remove();
await invalidateSession(); // await invalidateSession();
}); // });
it('allows Owners', async function () { // it('allows Owners', async function () {
await visitAs('Owner'); // await visitAs('Owner');
expect(currentURL()).to.equal('/settings/members'); // expect(currentURL()).to.equal('/settings/members');
}); // });
it('allows Administrators', async function () { // it('allows Administrators', async function () {
await visitAs('Administrator'); // await visitAs('Administrator');
expect(currentURL()).to.equal('/settings/members'); // expect(currentURL()).to.equal('/settings/members');
}); // });
it('disallows Editors', async function () { // it('disallows Editors', async function () {
await visitAs('Editor'); // await visitAs('Editor');
expect(currentURL()).to.not.equal('/settings/members'); // expect(currentURL()).to.not.equal('/settings/members');
}); // });
it('disallows Authors', async function () { // it('disallows Authors', async function () {
await visitAs('Author'); // await visitAs('Author');
expect(currentURL()).to.not.equal('/settings/members'); // expect(currentURL()).to.not.equal('/settings/members');
}); // });
it('disallows Contributors', async function () { // it('disallows Contributors', async function () {
await visitAs('Contributor'); // await visitAs('Contributor');
expect(currentURL()).to.not.equal('/settings/members'); // expect(currentURL()).to.not.equal('/settings/members');
}); // });
}); // });
it('can change subscription access', async function () { // it('can change subscription access', async function () {
await visit('/settings/members'); // await visit('/settings/members');
expect(this.server.db.settings.findBy({key: 'members_signup_access'}).value).to.equal('all'); // expect(this.server.db.settings.findBy({key: 'members_signup_access'}).value).to.equal('all');
expect(find('[data-test-members-subscription-option="all"]'), 'initial selection is "all"').to.exist; // expect(find('[data-test-members-subscription-option="all"]'), 'initial selection is "all"').to.exist;
expect(find('[data-test-iframe="portal-preview"]'), 'initial preview src matches "all"') // expect(find('[data-test-iframe="portal-preview"]'), 'initial preview src matches "all"')
.to.have.attribute('src').match(/membersSignupAccess=all/); // .to.have.attribute('src').match(/membersSignupAccess=all/);
// open dropdown // // open dropdown
await click('[data-test-members-subscription-option="all"]'); // await click('[data-test-members-subscription-option="all"]');
// all settings exist in dropdown // // all settings exist in dropdown
expect(find('.ember-power-select-options [data-test-members-subscription-option="all"]'), 'all option').to.exist; // expect(find('.ember-power-select-options [data-test-members-subscription-option="all"]'), 'all option').to.exist;
expect(find('.ember-power-select-options [data-test-members-subscription-option="invite"]'), 'invite option').to.exist; // expect(find('.ember-power-select-options [data-test-members-subscription-option="invite"]'), 'invite option').to.exist;
expect(find('.ember-power-select-options [data-test-members-subscription-option="none"]'), 'none option').to.exist; // expect(find('.ember-power-select-options [data-test-members-subscription-option="none"]'), 'none option').to.exist;
// switch to invite // // switch to invite
await click('.ember-power-select-options [data-test-members-subscription-option="invite"]'); // await click('.ember-power-select-options [data-test-members-subscription-option="invite"]');
expect(find('.ember-power-select-options'), 'dropdown closes').to.not.exist; // expect(find('.ember-power-select-options'), 'dropdown closes').to.not.exist;
expect(find('[data-test-members-subscription-option="invite"]'), 'invite option shown after selected').to.exist; // expect(find('[data-test-members-subscription-option="invite"]'), 'invite option shown after selected').to.exist;
expect(find('[data-test-iframe="portal-preview"]')) // expect(find('[data-test-iframe="portal-preview"]'))
.to.have.attribute('src').match(/membersSignupAccess=invite/); // .to.have.attribute('src').match(/membersSignupAccess=invite/);
await click('[data-test-button="save-settings"]'); // await click('[data-test-button="save-settings"]');
expect(this.server.db.settings.findBy({key: 'members_signup_access'}).value).to.equal('invite'); // expect(this.server.db.settings.findBy({key: 'members_signup_access'}).value).to.equal('invite');
// switch to nobody // // switch to nobody
await click('[data-test-members-subscription-option="invite"]'); // await click('[data-test-members-subscription-option="invite"]');
await click('.ember-power-select-options [data-test-members-subscription-option="none"]'); // await click('.ember-power-select-options [data-test-members-subscription-option="none"]');
expect(find('.ember-power-select-options'), 'dropdown closes').to.not.exist; // expect(find('.ember-power-select-options'), 'dropdown closes').to.not.exist;
expect(find('[data-test-members-subscription-option="none"]'), 'none option shown after selected').to.exist; // expect(find('[data-test-members-subscription-option="none"]'), 'none option shown after selected').to.exist;
expect(find('[data-test-iframe="portal-preview"]')).to.not.exist; // expect(find('[data-test-iframe="portal-preview"]')).to.not.exist;
expect(find('[data-test-portal-preview-disabled]')).to.exist; // expect(find('[data-test-portal-preview-disabled]')).to.exist;
expect(find('[data-test-default-post-access] .ember-basic-dropdown-trigger')).to.have.attribute('aria-disabled', 'true'); // expect(find('[data-test-default-post-access] .ember-basic-dropdown-trigger')).to.have.attribute('aria-disabled', 'true');
await click('[data-test-button="save-settings"]'); // await click('[data-test-button="save-settings"]');
expect(this.server.db.settings.findBy({key: 'members_signup_access'}).value).to.equal('none'); // expect(this.server.db.settings.findBy({key: 'members_signup_access'}).value).to.equal('none');
// automatically saves when switching back off nobody // // automatically saves when switching back off nobody
await click('[data-test-members-subscription-option="none"]'); // await click('[data-test-members-subscription-option="none"]');
await click('.ember-power-select-options [data-test-members-subscription-option="invite"]'); // await click('.ember-power-select-options [data-test-members-subscription-option="invite"]');
expect(this.server.db.settings.findBy({key: 'members_signup_access'}).value).to.equal('invite'); // expect(this.server.db.settings.findBy({key: 'members_signup_access'}).value).to.equal('invite');
}); // });
it('can change default post access', async function () { // it('can change default post access', async function () {
await visit('/settings/members'); // await visit('/settings/members');
// fixtures match what we expect // // fixtures match what we expect
expect(this.server.db.settings.findBy({key: 'default_content_visibility'}).value).to.equal('public'); // expect(this.server.db.settings.findBy({key: 'default_content_visibility'}).value).to.equal('public');
expect(this.server.db.settings.findBy({key: 'default_content_visibility_tiers'}).value).to.equal('[]'); // expect(this.server.db.settings.findBy({key: 'default_content_visibility_tiers'}).value).to.equal('[]');
expect(find('[data-test-default-post-access-option="public"]'), 'initial selection is "public"').to.exist; // expect(find('[data-test-default-post-access-option="public"]'), 'initial selection is "public"').to.exist;
expect(find('[data-test-default-post-access-tiers]')).to.not.exist; // expect(find('[data-test-default-post-access-tiers]')).to.not.exist;
// open dropdown // // open dropdown
await click('[data-test-default-post-access-option="public"]'); // await click('[data-test-default-post-access-option="public"]');
// all settings exist in dropdown // // all settings exist in dropdown
expect(find('.ember-power-select-options [data-test-default-post-access-option="public"]'), 'public option').to.exist; // expect(find('.ember-power-select-options [data-test-default-post-access-option="public"]'), 'public option').to.exist;
expect(find('.ember-power-select-options [data-test-default-post-access-option="members"]'), 'members-only option').to.exist; // expect(find('.ember-power-select-options [data-test-default-post-access-option="members"]'), 'members-only option').to.exist;
expect(find('.ember-power-select-options [data-test-default-post-access-option="paid"]'), 'paid-only option').to.exist; // expect(find('.ember-power-select-options [data-test-default-post-access-option="paid"]'), 'paid-only option').to.exist;
expect(find('.ember-power-select-options [data-test-default-post-access-option="tiers"]'), 'specific tiers option').to.exist; // expect(find('.ember-power-select-options [data-test-default-post-access-option="tiers"]'), 'specific tiers option').to.exist;
// switch to members only // // switch to members only
await click('.ember-power-select-options [data-test-default-post-access-option="members"]'); // await click('.ember-power-select-options [data-test-default-post-access-option="members"]');
await click('[data-test-button="save-settings"]'); // await click('[data-test-button="save-settings"]');
expect(this.server.db.settings.findBy({key: 'default_content_visibility'}).value).to.equal('members'); // expect(this.server.db.settings.findBy({key: 'default_content_visibility'}).value).to.equal('members');
expect(this.server.db.settings.findBy({key: 'default_content_visibility_tiers'}).value).to.equal('[]'); // expect(this.server.db.settings.findBy({key: 'default_content_visibility_tiers'}).value).to.equal('[]');
expect(find('[data-test-default-post-access-option="members"]'), 'post-members selection is "members"').to.exist; // expect(find('[data-test-default-post-access-option="members"]'), 'post-members selection is "members"').to.exist;
expect(find('[data-test-default-post-access-tiers]')).to.not.exist; // expect(find('[data-test-default-post-access-tiers]')).to.not.exist;
// can switch to specific tiers // // can switch to specific tiers
await click('[data-test-default-post-access-option="members"]'); // await click('[data-test-default-post-access-option="members"]');
await click('.ember-power-select-options [data-test-default-post-access-option="tiers"]'); // await click('.ember-power-select-options [data-test-default-post-access-option="tiers"]');
// tiers input is shown // // tiers input is shown
expect(find('[data-test-default-post-access-tiers]')).to.exist; // expect(find('[data-test-default-post-access-tiers]')).to.exist;
// open tiers dropdown // // open tiers dropdown
await click('[data-test-default-post-access-tiers] .ember-basic-dropdown-trigger'); // await click('[data-test-default-post-access-tiers] .ember-basic-dropdown-trigger');
// paid tiers are available in tiers input // // paid tiers are available in tiers input
expect(find('[data-test-default-post-access-tiers] [data-test-visibility-segment-option="Default Tier"]')).to.exist; // expect(find('[data-test-default-post-access-tiers] [data-test-visibility-segment-option="Default Tier"]')).to.exist;
// select tier // // select tier
await click('[data-test-default-post-access-tiers] [data-test-visibility-segment-option="Default Tier"]'); // await click('[data-test-default-post-access-tiers] [data-test-visibility-segment-option="Default Tier"]');
// save // // save
await click('[data-test-button="save-settings"]'); // await click('[data-test-button="save-settings"]');
expect(this.server.db.settings.findBy({key: 'default_content_visibility'}).value).to.equal('tiers'); // expect(this.server.db.settings.findBy({key: 'default_content_visibility'}).value).to.equal('tiers');
expect(this.server.db.settings.findBy({key: 'default_content_visibility_tiers'}).value).to.equal('["2"]'); // expect(this.server.db.settings.findBy({key: 'default_content_visibility_tiers'}).value).to.equal('["2"]');
// switch back to non-tiers option // // switch back to non-tiers option
await click('[data-test-default-post-access-option="tiers"]'); // await click('[data-test-default-post-access-option="tiers"]');
await click('.ember-power-select-options [data-test-default-post-access-option="paid"]'); // await click('.ember-power-select-options [data-test-default-post-access-option="paid"]');
expect(find('[data-test-default-post-access-tiers]')).to.not.exist; // expect(find('[data-test-default-post-access-tiers]')).to.not.exist;
await click('[data-test-button="save-settings"]'); // await click('[data-test-button="save-settings"]');
expect(this.server.db.settings.findBy({key: 'default_content_visibility'}).value).to.equal('paid'); // expect(this.server.db.settings.findBy({key: 'default_content_visibility'}).value).to.equal('paid');
expect(this.server.db.settings.findBy({key: 'default_content_visibility_tiers'}).value).to.equal('["2"]'); // expect(this.server.db.settings.findBy({key: 'default_content_visibility_tiers'}).value).to.equal('["2"]');
}); // });
it('can manage free tier', async function () { // it('can manage free tier', async function () {
await visit('/settings/members'); // await visit('/settings/members');
await click('[data-test-button="toggle-free-settings"]'); // await click('[data-test-button="toggle-free-settings"]');
expect(find('[data-test-free-settings-expanded]'), 'expanded free settings').to.exist; // expect(find('[data-test-free-settings-expanded]'), 'expanded free settings').to.exist;
// we aren't viewing the non-labs-flag input // // we aren't viewing the non-labs-flag input
expect(find('[data-test-input="old-free-welcome-page"]')).to.not.exist; // expect(find('[data-test-input="old-free-welcome-page"]')).to.not.exist;
// it can set free signup welcome page // // it can set free signup welcome page
// initial value // // initial value
expect(find('[data-test-input="free-welcome-page"]')).to.exist; // expect(find('[data-test-input="free-welcome-page"]')).to.exist;
expect(find('[data-test-input="free-welcome-page"]')).to.have.value(''); // expect(find('[data-test-input="free-welcome-page"]')).to.have.value('');
// saving // // saving
await fillIn('[data-test-input="free-welcome-page"]', 'not a url'); // await fillIn('[data-test-input="free-welcome-page"]', 'not a url');
await blur('[data-test-input="free-welcome-page"]'); // await blur('[data-test-input="free-welcome-page"]');
await click('[data-test-button="save-settings"]'); // await click('[data-test-button="save-settings"]');
expect(this.server.db.tiers.findBy({slug: 'free'}).welcomePageUrl) // expect(this.server.db.tiers.findBy({slug: 'free'}).welcomePageUrl)
.to.equal('/not%20a%20url'); // .to.equal('/not%20a%20url');
// re-rendering will insert full URL in welcome page input // // re-rendering will insert full URL in welcome page input
await visit('/settings'); // await visit('/settings');
await visit('/settings/members'); // await visit('/settings/members');
expect(find('[data-test-input="free-welcome-page"]')).to.exist; // expect(find('[data-test-input="free-welcome-page"]')).to.exist;
expect(find('[data-test-input="free-welcome-page"]')) // expect(find('[data-test-input="free-welcome-page"]'))
.to.have.value('http://localhost:2368/not%20a%20url'); // .to.have.value('http://localhost:2368/not%20a%20url');
// it can manage free tier description and benefits // // it can manage free tier description and benefits
// initial free tier details are as expected // // initial free tier details are as expected
expect(find('[data-test-tier-card="free"]')).to.exist; // expect(find('[data-test-tier-card="free"]')).to.exist;
expect(find('[data-test-tier-card="free"] [data-test-name]')).to.contain.text('Free'); // expect(find('[data-test-tier-card="free"] [data-test-name]')).to.contain.text('Free');
expect(find('[data-test-tier-card="free"] [data-test-description]')).to.contain.text('No description'); // expect(find('[data-test-tier-card="free"] [data-test-description]')).to.contain.text('No description');
expect(find('[data-test-tier-card="free"] [data-test-benefits]')).to.contain.text('No benefits'); // expect(find('[data-test-tier-card="free"] [data-test-benefits]')).to.contain.text('No benefits');
expect(find('[data-test-tier-card="free"] [data-test-free-price]')).to.exist; // expect(find('[data-test-tier-card="free"] [data-test-free-price]')).to.exist;
// open modal // // open modal
await click('[data-test-tier-card="free"] [data-test-button="edit-tier"]'); // await click('[data-test-tier-card="free"] [data-test-button="edit-tier"]');
// initial modal state is as expected // // initial modal state is as expected
const modal = '[data-test-modal="edit-tier"]'; // const modal = '[data-test-modal="edit-tier"]';
expect(find(modal)).to.exist; // expect(find(modal)).to.exist;
expect(find(`${modal} [data-test-input="tier-name"]`)).to.not.exist; // expect(find(`${modal} [data-test-input="tier-name"]`)).to.not.exist;
expect(find(`${modal} [data-test-input="tier-description"]`)).to.not.exist; // expect(find(`${modal} [data-test-input="tier-description"]`)).to.not.exist;
expect(find(`${modal} [data-test-input="free-tier-description"]`)).to.exist; // expect(find(`${modal} [data-test-input="free-tier-description"]`)).to.exist;
expect(find(`${modal} [data-test-input="free-tier-description"]`)).to.have.value(''); // expect(find(`${modal} [data-test-input="free-tier-description"]`)).to.have.value('');
expect(find(`${modal} [data-test-formgroup="prices"]`)).to.not.exist; // expect(find(`${modal} [data-test-formgroup="prices"]`)).to.not.exist;
expect(find(`${modal} [data-test-benefit-item="new"]`)).to.exist; // expect(find(`${modal} [data-test-benefit-item="new"]`)).to.exist;
expect(findAll(`${modal} [data-test-benefit-item]`).length).to.equal(1); // expect(findAll(`${modal} [data-test-benefit-item]`).length).to.equal(1);
expect(find(`${modal} [data-test-tierpreview-title]`)).to.contain.text('Free Membership Preview'); // expect(find(`${modal} [data-test-tierpreview-title]`)).to.contain.text('Free Membership Preview');
expect(find(`${modal} [data-test-tierpreview-description]`)).to.contain.text('Free preview of'); // expect(find(`${modal} [data-test-tierpreview-description]`)).to.contain.text('Free preview of');
expect(find(`${modal} [data-test-tierpreview-benefits]`)).to.contain.text('Access to all public posts'); // expect(find(`${modal} [data-test-tierpreview-benefits]`)).to.contain.text('Access to all public posts');
expect(find(`${modal} [data-test-tierpreview-price]`).textContent).to.match(/\$\s+0/); // expect(find(`${modal} [data-test-tierpreview-price]`).textContent).to.match(/\$\s+0/);
// can change description // // can change description
await fillIn(`${modal} [data-test-input="free-tier-description"]`, 'Test description'); // await fillIn(`${modal} [data-test-input="free-tier-description"]`, 'Test description');
expect(find(`${modal} [data-test-tierpreview-description]`)).to.contain.text('Test description'); // expect(find(`${modal} [data-test-tierpreview-description]`)).to.contain.text('Test description');
// can manage benefits // // can manage benefits
const newBenefit = `${modal} [data-test-benefit-item="new"]`; // const newBenefit = `${modal} [data-test-benefit-item="new"]`;
await fillIn(`${newBenefit} [data-test-input="benefit-label"]`, 'First benefit'); // await fillIn(`${newBenefit} [data-test-input="benefit-label"]`, 'First benefit');
await click(`${newBenefit} [data-test-button="add-benefit"]`); // await click(`${newBenefit} [data-test-button="add-benefit"]`);
expect(find(`${modal} [data-test-tierpreview-benefits]`)).to.contain.text('First benefit'); // expect(find(`${modal} [data-test-tierpreview-benefits]`)).to.contain.text('First benefit');
expect(find(`${modal} [data-test-benefit-item="0"]`)).to.exist; // expect(find(`${modal} [data-test-benefit-item="0"]`)).to.exist;
expect(find(`${modal} [data-test-benefit-item="new"]`)).to.exist; // expect(find(`${modal} [data-test-benefit-item="new"]`)).to.exist;
await click(`${newBenefit} [data-test-button="add-benefit"]`); // await click(`${newBenefit} [data-test-button="add-benefit"]`);
expect(find(`${newBenefit}`)).to.contain.text('Please enter a benefit'); // expect(find(`${newBenefit}`)).to.contain.text('Please enter a benefit');
await fillIn(`${newBenefit} [data-test-input="benefit-label"]`, 'Second benefit'); // await fillIn(`${newBenefit} [data-test-input="benefit-label"]`, 'Second benefit');
await click(`${newBenefit} [data-test-button="add-benefit"]`); // await click(`${newBenefit} [data-test-button="add-benefit"]`);
expect(find(`${modal} [data-test-tierpreview-benefits]`)).to.contain.text('Second benefit'); // expect(find(`${modal} [data-test-tierpreview-benefits]`)).to.contain.text('Second benefit');
expect(findAll(`${modal} [data-test-tierpreview-benefits] div`).length).to.equal(4); // expect(findAll(`${modal} [data-test-tierpreview-benefits] div`).length).to.equal(4);
await click(`${modal} [data-test-benefit-item="0"] [data-test-button="delete-benefit"]`); // await click(`${modal} [data-test-benefit-item="0"] [data-test-button="delete-benefit"]`);
expect(find(`${modal} [data-test-tierpreview-benefits]`)).to.not.contain.text('First benefit'); // expect(find(`${modal} [data-test-tierpreview-benefits]`)).to.not.contain.text('First benefit');
expect(findAll(`${modal} [data-test-tierpreview-benefits] div`).length).to.equal(2); // expect(findAll(`${modal} [data-test-tierpreview-benefits] div`).length).to.equal(2);
// Add a new benefit that we will later rename to an empty name // // Add a new benefit that we will later rename to an empty name
await fillIn(`${newBenefit} [data-test-input="benefit-label"]`, 'Third benefit'); // await fillIn(`${newBenefit} [data-test-input="benefit-label"]`, 'Third benefit');
await click(`${newBenefit} [data-test-button="add-benefit"]`); // await click(`${newBenefit} [data-test-button="add-benefit"]`);
expect(find(`${modal} [data-test-tierpreview-benefits]`)).to.contain.text('Third benefit'); // expect(find(`${modal} [data-test-tierpreview-benefits]`)).to.contain.text('Third benefit');
expect(findAll(`${modal} [data-test-tierpreview-benefits] div`).length).to.equal(4); // expect(findAll(`${modal} [data-test-tierpreview-benefits] div`).length).to.equal(4);
// Clear the second benefit's name (it should get removed after saving) // // Clear the second benefit's name (it should get removed after saving)
const secondBenefitItem = `${modal} [data-test-benefit-item="1"]`; // const secondBenefitItem = `${modal} [data-test-benefit-item="1"]`;
await fillIn(`${secondBenefitItem} [data-test-input="benefit-label"]`, ''); // await fillIn(`${secondBenefitItem} [data-test-input="benefit-label"]`, '');
await click('[data-test-button="save-tier"]'); // await click('[data-test-button="save-tier"]');
expect(find(`${modal}`)).to.not.exist; // expect(find(`${modal}`)).to.not.exist;
expect(find('[data-test-tier-card="free"] [data-test-name]')).to.contain.text('Free'); // expect(find('[data-test-tier-card="free"] [data-test-name]')).to.contain.text('Free');
expect(find('[data-test-tier-card="free"] [data-test-description]')).to.contain.text('Test description'); // expect(find('[data-test-tier-card="free"] [data-test-description]')).to.contain.text('Test description');
expect(find('[data-test-tier-card="free"] [data-test-benefits]')).to.contain.text('Benefits (1)'); // expect(find('[data-test-tier-card="free"] [data-test-benefits]')).to.contain.text('Benefits (1)');
expect(find('[data-test-tier-card="free"] [data-test-benefits] li:nth-of-type(1)')).to.contain.text('Second benefit'); // expect(find('[data-test-tier-card="free"] [data-test-benefits] li:nth-of-type(1)')).to.contain.text('Second benefit');
expect(findAll(`[data-test-tier-card="free"] [data-test-benefits] li`).length).to.equal(1); // expect(findAll(`[data-test-tier-card="free"] [data-test-benefits] li`).length).to.equal(1);
const freeTier = this.server.db.tiers.findBy({slug: 'free'}); // const freeTier = this.server.db.tiers.findBy({slug: 'free'});
expect(freeTier.description).to.equal('Test description'); // expect(freeTier.description).to.equal('Test description');
expect(freeTier.welcomePageUrl).to.equal('/not%20a%20url'); // expect(freeTier.welcomePageUrl).to.equal('/not%20a%20url');
expect(freeTier.benefits.length).to.equal(1); // expect(freeTier.benefits.length).to.equal(1);
expect(freeTier.benefits[0]).to.equal('Second benefit'); // expect(freeTier.benefits[0]).to.equal('Second benefit');
}); // });
}); // });

View File

@ -1,252 +1,252 @@
import ctrlOrCmd from 'ghost-admin/utils/ctrl-or-cmd'; // import ctrlOrCmd from 'ghost-admin/utils/ctrl-or-cmd';
import {authenticateSession} from 'ember-simple-auth/test-support'; // import {authenticateSession} from 'ember-simple-auth/test-support';
import {beforeEach, describe, it} from 'mocha'; // import {beforeEach, describe, it} from 'mocha';
import {blur, click, currentRouteName, currentURL, fillIn, find, findAll, triggerEvent, typeIn} from '@ember/test-helpers'; // import {blur, click, currentRouteName, currentURL, fillIn, find, findAll, triggerEvent, typeIn} from '@ember/test-helpers';
import {expect} from 'chai'; // import {expect} from 'chai';
import {setupApplicationTest} from 'ember-mocha'; // import {setupApplicationTest} from 'ember-mocha';
import {setupMirage} from 'ember-cli-mirage/test-support'; // import {setupMirage} from 'ember-cli-mirage/test-support';
import {visit} from '../../helpers/visit'; // import {visit} from '../../helpers/visit';
// simulate jQuery's `:visible` pseudo-selector // // simulate jQuery's `:visible` pseudo-selector
function withText(elements) { // function withText(elements) {
return Array.from(elements).filter(elem => elem.textContent.trim() !== ''); // return Array.from(elements).filter(elem => elem.textContent.trim() !== '');
} // }
describe('Acceptance: Settings - Navigation', function () { // describe('Acceptance: Settings - Navigation', function () {
let hooks = setupApplicationTest(); // let hooks = setupApplicationTest();
setupMirage(hooks); // setupMirage(hooks);
describe('when logged in', function () { // describe('when logged in', function () {
beforeEach(async function () { // beforeEach(async function () {
let role = this.server.create('role', {name: 'Administrator'}); // let role = this.server.create('role', {name: 'Administrator'});
this.server.create('user', {roles: [role]}); // this.server.create('user', {roles: [role]});
await authenticateSession(); // await authenticateSession();
}); // });
it('can visit /settings/navigation', async function () { // it('can visit /settings/navigation', async function () {
await visit('/settings/navigation'); // await visit('/settings/navigation');
expect(currentRouteName()).to.equal('settings.navigation'); // expect(currentRouteName()).to.equal('settings.navigation');
expect(find('[data-test-save-button]').textContent.trim(), 'save button text').to.equal('Save'); // expect(find('[data-test-save-button]').textContent.trim(), 'save button text').to.equal('Save');
// fixtures contain two nav items, check for four rows as we // // fixtures contain two nav items, check for four rows as we
// should have one extra that's blank for each navigation section // // should have one extra that's blank for each navigation section
expect( // expect(
findAll('[data-test-navitem]').length, // findAll('[data-test-navitem]').length,
'navigation items count' // 'navigation items count'
).to.equal(4); // ).to.equal(4);
}); // });
it('saves navigation settings', async function () { // it('saves navigation settings', async function () {
await visit('/settings/navigation'); // await visit('/settings/navigation');
await fillIn('#settings-navigation [data-test-navitem="0"] [data-test-input="label"]', 'Test'); // await fillIn('#settings-navigation [data-test-navitem="0"] [data-test-input="label"]', 'Test');
await typeIn('#settings-navigation [data-test-navitem="0"] [data-test-input="url"]', '/test'); // await typeIn('#settings-navigation [data-test-navitem="0"] [data-test-input="url"]', '/test');
await click('[data-test-save-button]'); // await click('[data-test-save-button]');
let [navSetting] = this.server.db.settings.where({key: 'navigation'}); // let [navSetting] = this.server.db.settings.where({key: 'navigation'});
expect(navSetting.value).to.equal('[{"label":"Test","url":"/test/"},{"label":"About","url":"/about"}]'); // expect(navSetting.value).to.equal('[{"label":"Test","url":"/test/"},{"label":"About","url":"/about"}]');
// don't test against .error directly as it will pick up failed // // don't test against .error directly as it will pick up failed
// tests "pre.error" elements // // tests "pre.error" elements
expect(findAll('span.error').length, 'error messages count').to.equal(0); // expect(findAll('span.error').length, 'error messages count').to.equal(0);
expect(findAll('.gh-alert').length, 'alerts count').to.equal(0); // expect(findAll('.gh-alert').length, 'alerts count').to.equal(0);
expect(withText(findAll('[data-test-error]')).length, 'validation errors count') // expect(withText(findAll('[data-test-error]')).length, 'validation errors count')
.to.equal(0); // .to.equal(0);
}); // });
it('validates new item correctly on save', async function () { // it('validates new item correctly on save', async function () {
await visit('/settings/navigation'); // await visit('/settings/navigation');
await click('[data-test-save-button]'); // await click('[data-test-save-button]');
expect( // expect(
findAll('#settings-navigation [data-test-navitem]').length, // findAll('#settings-navigation [data-test-navitem]').length,
'number of nav items after saving with blank new item' // 'number of nav items after saving with blank new item'
).to.equal(3); // ).to.equal(3);
await fillIn('#settings-navigation [data-test-navitem="new"] [data-test-input="label"]', 'Test'); // await fillIn('#settings-navigation [data-test-navitem="new"] [data-test-input="label"]', 'Test');
await fillIn('#settings-navigation [data-test-navitem="new"] [data-test-input="url"]', ''); // await fillIn('#settings-navigation [data-test-navitem="new"] [data-test-input="url"]', '');
await typeIn('#settings-navigation [data-test-navitem="new"] [data-test-input="url"]', 'http://invalid domain/'); // await typeIn('#settings-navigation [data-test-navitem="new"] [data-test-input="url"]', 'http://invalid domain/');
await click('[data-test-save-button]'); // await click('[data-test-save-button]');
expect( // expect(
findAll('#settings-navigation [data-test-navitem]').length, // findAll('#settings-navigation [data-test-navitem]').length,
'number of nav items after saving with invalid new item' // 'number of nav items after saving with invalid new item'
).to.equal(3); // ).to.equal(3);
expect( // expect(
withText(findAll('#settings-navigation [data-test-navitem="new"] [data-test-error]')).length, // withText(findAll('#settings-navigation [data-test-navitem="new"] [data-test-error]')).length,
'number of invalid fields in new item' // 'number of invalid fields in new item'
).to.equal(1); // ).to.equal(1);
}); // });
it('clears unsaved settings when navigating away but warns with a confirmation dialog', async function () { // it('clears unsaved settings when navigating away but warns with a confirmation dialog', async function () {
await visit('/settings/navigation'); // await visit('/settings/navigation');
await fillIn('[data-test-navitem="0"] [data-test-input="label"]', 'Test'); // await fillIn('[data-test-navitem="0"] [data-test-input="label"]', 'Test');
await blur('[data-test-navitem="0"] [data-test-input="label"]'); // await blur('[data-test-navitem="0"] [data-test-input="label"]');
expect(find('[data-test-navitem="0"] [data-test-input="label"]').value).to.equal('Test'); // expect(find('[data-test-navitem="0"] [data-test-input="label"]').value).to.equal('Test');
await visit('/settings/code-injection'); // await visit('/settings/code-injection');
expect(findAll('[data-test-modal="unsaved-settings"]').length, 'modal exists').to.equal(1); // expect(findAll('[data-test-modal="unsaved-settings"]').length, 'modal exists').to.equal(1);
// Leave without saving // // Leave without saving
await click('[data-test-leave-button]'), 'leave without saving'; // await click('[data-test-leave-button]'), 'leave without saving';
expect(currentURL(), 'currentURL').to.equal('/settings/code-injection'); // expect(currentURL(), 'currentURL').to.equal('/settings/code-injection');
await visit('/settings/navigation'); // await visit('/settings/navigation');
expect(find('[data-test-navitem="0"] [data-test-input="label"]').value).to.equal('Home'); // expect(find('[data-test-navitem="0"] [data-test-input="label"]').value).to.equal('Home');
}); // });
it('can add and remove items', async function () { // it('can add and remove items', async function () {
await visit('/settings/navigation'); // await visit('/settings/navigation');
await click('#settings-navigation .gh-blognav-add'); // await click('#settings-navigation .gh-blognav-add');
expect( // expect(
find('[data-test-navitem="new"] [data-test-error="label"]').textContent.trim(), // find('[data-test-navitem="new"] [data-test-error="label"]').textContent.trim(),
'blank label has validation error' // 'blank label has validation error'
).to.not.be.empty; // ).to.not.be.empty;
await fillIn('[data-test-navitem="new"] [data-test-input="label"]', ''); // await fillIn('[data-test-navitem="new"] [data-test-input="label"]', '');
await typeIn('[data-test-navitem="new"] [data-test-input="label"]', 'New'); // await typeIn('[data-test-navitem="new"] [data-test-input="label"]', 'New');
expect( // expect(
find('[data-test-navitem="new"] [data-test-error="label"]').textContent.trim(), // find('[data-test-navitem="new"] [data-test-error="label"]').textContent.trim(),
'label validation is visible after typing' // 'label validation is visible after typing'
).to.be.empty; // ).to.be.empty;
await fillIn('[data-test-navitem="new"] [data-test-input="url"]', ''); // await fillIn('[data-test-navitem="new"] [data-test-input="url"]', '');
await typeIn('[data-test-navitem="new"] [data-test-input="url"]', '/new'); // await typeIn('[data-test-navitem="new"] [data-test-input="url"]', '/new');
await blur('[data-test-navitem="new"] [data-test-input="url"]'); // await blur('[data-test-navitem="new"] [data-test-input="url"]');
expect( // expect(
find('[data-test-navitem="new"] [data-test-error="url"]').textContent.trim(), // find('[data-test-navitem="new"] [data-test-error="url"]').textContent.trim(),
'url validation is visible after typing' // 'url validation is visible after typing'
).to.be.empty; // ).to.be.empty;
expect( // expect(
find('[data-test-navitem="new"] [data-test-input="url"]').value // find('[data-test-navitem="new"] [data-test-input="url"]').value
).to.equal(`${window.location.origin}/new/`); // ).to.equal(`${window.location.origin}/new/`);
await click('.gh-blognav-add'); // await click('.gh-blognav-add');
expect( // expect(
findAll('#settings-navigation [data-test-navitem]').length, // findAll('#settings-navigation [data-test-navitem]').length,
'number of nav items after successful add' // 'number of nav items after successful add'
).to.equal(4); // ).to.equal(4);
expect( // expect(
find('#settings-navigation [data-test-navitem="new"] [data-test-input="label"]').value, // find('#settings-navigation [data-test-navitem="new"] [data-test-input="label"]').value,
'new item label value after successful add' // 'new item label value after successful add'
).to.be.empty; // ).to.be.empty;
expect( // expect(
find('#settings-navigation [data-test-navitem="new"] [data-test-input="url"]').value, // find('#settings-navigation [data-test-navitem="new"] [data-test-input="url"]').value,
'new item url value after successful add' // 'new item url value after successful add'
).to.equal(`${window.location.origin}/`); // ).to.equal(`${window.location.origin}/`);
expect( // expect(
withText(findAll('[data-test-navitem] [data-test-error]')).length, // withText(findAll('[data-test-navitem] [data-test-error]')).length,
'number or validation errors shown after successful add' // 'number or validation errors shown after successful add'
).to.equal(0); // ).to.equal(0);
await click('#settings-navigation [data-test-navitem="0"] .gh-blognav-delete'); // await click('#settings-navigation [data-test-navitem="0"] .gh-blognav-delete');
expect( // expect(
findAll('#settings-navigation [data-test-navitem]').length, // findAll('#settings-navigation [data-test-navitem]').length,
'number of nav items after successful remove' // 'number of nav items after successful remove'
).to.equal(3); // ).to.equal(3);
// CMD-S shortcut works // // CMD-S shortcut works
await triggerEvent('.gh-app', 'keydown', { // await triggerEvent('.gh-app', 'keydown', {
keyCode: 83, // s // keyCode: 83, // s
metaKey: ctrlOrCmd === 'command', // metaKey: ctrlOrCmd === 'command',
ctrlKey: ctrlOrCmd === 'ctrl' // ctrlKey: ctrlOrCmd === 'ctrl'
}); // });
let [navSetting] = this.server.db.settings.where({key: 'navigation'}); // let [navSetting] = this.server.db.settings.where({key: 'navigation'});
expect(navSetting.value).to.equal('[{"label":"About","url":"/about"},{"label":"New","url":"/new/"}]'); // expect(navSetting.value).to.equal('[{"label":"About","url":"/about"},{"label":"New","url":"/new/"}]');
}); // });
it('can also add and remove items from seconday nav', async function () { // it('can also add and remove items from seconday nav', async function () {
await visit('/settings/navigation'); // await visit('/settings/navigation');
await click('#secondary-navigation .gh-blognav-add'); // await click('#secondary-navigation .gh-blognav-add');
expect( // expect(
find('#secondary-navigation [data-test-navitem="new"] [data-test-error="label"]').textContent.trim(), // find('#secondary-navigation [data-test-navitem="new"] [data-test-error="label"]').textContent.trim(),
'blank label has validation error' // 'blank label has validation error'
).to.not.be.empty; // ).to.not.be.empty;
await fillIn('#secondary-navigation [data-test-navitem="new"] [data-test-input="label"]', ''); // await fillIn('#secondary-navigation [data-test-navitem="new"] [data-test-input="label"]', '');
await typeIn('#secondary-navigation [data-test-navitem="new"] [data-test-input="label"]', 'Foo'); // await typeIn('#secondary-navigation [data-test-navitem="new"] [data-test-input="label"]', 'Foo');
expect( // expect(
find('#secondary-navigation [data-test-navitem="new"] [data-test-error="label"]').textContent.trim(), // find('#secondary-navigation [data-test-navitem="new"] [data-test-error="label"]').textContent.trim(),
'label validation is visible after typing' // 'label validation is visible after typing'
).to.be.empty; // ).to.be.empty;
await fillIn('#secondary-navigation [data-test-navitem="new"] [data-test-input="url"]', ''); // await fillIn('#secondary-navigation [data-test-navitem="new"] [data-test-input="url"]', '');
await typeIn('#secondary-navigation [data-test-navitem="new"] [data-test-input="url"]', '/bar'); // await typeIn('#secondary-navigation [data-test-navitem="new"] [data-test-input="url"]', '/bar');
await blur('#secondary-navigation [data-test-navitem="new"] [data-test-input="url"]'); // await blur('#secondary-navigation [data-test-navitem="new"] [data-test-input="url"]');
expect( // expect(
find('#secondary-navigation [data-test-navitem="new"] [data-test-error="url"]').textContent.trim(), // find('#secondary-navigation [data-test-navitem="new"] [data-test-error="url"]').textContent.trim(),
'url validation is visible after typing' // 'url validation is visible after typing'
).to.be.empty; // ).to.be.empty;
expect( // expect(
find('#secondary-navigation [data-test-navitem="new"] [data-test-input="url"]').value // find('#secondary-navigation [data-test-navitem="new"] [data-test-input="url"]').value
).to.equal(`${window.location.origin}/bar/`); // ).to.equal(`${window.location.origin}/bar/`);
await click('[data-test-save-button]'); // await click('[data-test-save-button]');
expect( // expect(
findAll('#secondary-navigation [data-test-navitem]').length, // findAll('#secondary-navigation [data-test-navitem]').length,
'number of nav items after successful add' // 'number of nav items after successful add'
).to.equal(2); // ).to.equal(2);
expect( // expect(
find('#secondary-navigation [data-test-navitem="new"] [data-test-input="label"]').value, // find('#secondary-navigation [data-test-navitem="new"] [data-test-input="label"]').value,
'new item label value after successful add' // 'new item label value after successful add'
).to.be.empty; // ).to.be.empty;
expect( // expect(
find('#secondary-navigation [data-test-navitem="new"] [data-test-input="url"]').value, // find('#secondary-navigation [data-test-navitem="new"] [data-test-input="url"]').value,
'new item url value after successful add' // 'new item url value after successful add'
).to.equal(`${window.location.origin}/`); // ).to.equal(`${window.location.origin}/`);
expect( // expect(
withText(findAll('#secondary-navigation [data-test-navitem] [data-test-error]')).length, // withText(findAll('#secondary-navigation [data-test-navitem] [data-test-error]')).length,
'number or validation errors shown after successful add' // 'number or validation errors shown after successful add'
).to.equal(0); // ).to.equal(0);
let [navSetting] = this.server.db.settings.where({key: 'secondary_navigation'}); // let [navSetting] = this.server.db.settings.where({key: 'secondary_navigation'});
expect(navSetting.value).to.equal('[{"label":"Foo","url":"/bar/"}]'); // expect(navSetting.value).to.equal('[{"label":"Foo","url":"/bar/"}]');
await click('#secondary-navigation [data-test-navitem="0"] .gh-blognav-delete'); // await click('#secondary-navigation [data-test-navitem="0"] .gh-blognav-delete');
expect( // expect(
findAll('#secondary-navigation [data-test-navitem]').length, // findAll('#secondary-navigation [data-test-navitem]').length,
'number of nav items after successful remove' // 'number of nav items after successful remove'
).to.equal(1); // ).to.equal(1);
// CMD-S shortcut works // // CMD-S shortcut works
await triggerEvent('.gh-app', 'keydown', { // await triggerEvent('.gh-app', 'keydown', {
keyCode: 83, // s // keyCode: 83, // s
metaKey: ctrlOrCmd === 'command', // metaKey: ctrlOrCmd === 'command',
ctrlKey: ctrlOrCmd === 'ctrl' // ctrlKey: ctrlOrCmd === 'ctrl'
}); // });
[navSetting] = this.server.db.settings.where({key: 'secondary_navigation'}); // [navSetting] = this.server.db.settings.where({key: 'secondary_navigation'});
expect(navSetting.value).to.equal('[]'); // expect(navSetting.value).to.equal('[]');
}); // });
}); // });
}); // });

File diff suppressed because it is too large Load Diff

View File

@ -1,151 +1,151 @@
import ctrlOrCmd from 'ghost-admin/utils/ctrl-or-cmd'; // import ctrlOrCmd from 'ghost-admin/utils/ctrl-or-cmd';
import {Response} from 'miragejs'; // import {Response} from 'miragejs';
import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support'; // import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support';
import {beforeEach, describe, it} from 'mocha'; // import {beforeEach, describe, it} from 'mocha';
import {blur, click, currentURL, fillIn, find, findAll, triggerEvent} from '@ember/test-helpers'; // import {blur, click, currentURL, fillIn, find, findAll, triggerEvent} from '@ember/test-helpers';
import {expect} from 'chai'; // import {expect} from 'chai';
import {setupApplicationTest} from 'ember-mocha'; // import {setupApplicationTest} from 'ember-mocha';
import {setupMirage} from 'ember-cli-mirage/test-support'; // import {setupMirage} from 'ember-cli-mirage/test-support';
import {visit} from '../../helpers/visit'; // import {visit} from '../../helpers/visit';
describe('Acceptance: Settings - Integrations - Slack', function () { // describe('Acceptance: Settings - Integrations - Slack', function () {
let hooks = setupApplicationTest(); // let hooks = setupApplicationTest();
setupMirage(hooks); // setupMirage(hooks);
it('redirects to signin when not authenticated', async function () { // it('redirects to signin when not authenticated', async function () {
await invalidateSession(); // await invalidateSession();
await visit('/settings/integrations/slack'); // await visit('/settings/integrations/slack');
expect(currentURL(), 'currentURL').to.equal('/signin'); // expect(currentURL(), 'currentURL').to.equal('/signin');
}); // });
it('redirects to home page when authenticated as contributor', async function () { // it('redirects to home page when authenticated as contributor', async function () {
let role = this.server.create('role', {name: 'Contributor'}); // let role = this.server.create('role', {name: 'Contributor'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/integrations/slack'); // await visit('/settings/integrations/slack');
expect(currentURL(), 'currentURL').to.equal('/posts'); // expect(currentURL(), 'currentURL').to.equal('/posts');
}); // });
it('redirects to home page when authenticated as author', async function () { // it('redirects to home page when authenticated as author', async function () {
let role = this.server.create('role', {name: 'Author'}); // let role = this.server.create('role', {name: 'Author'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/integrations/slack'); // await visit('/settings/integrations/slack');
expect(currentURL(), 'currentURL').to.equal('/site'); // expect(currentURL(), 'currentURL').to.equal('/site');
}); // });
it('redirects to home page when authenticated as editor', async function () { // it('redirects to home page when authenticated as editor', async function () {
let role = this.server.create('role', {name: 'Editor'}); // let role = this.server.create('role', {name: 'Editor'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/integrations/slack'); // await visit('/settings/integrations/slack');
expect(currentURL(), 'currentURL').to.equal('/site'); // expect(currentURL(), 'currentURL').to.equal('/site');
}); // });
describe('when logged in', function () { // describe('when logged in', function () {
beforeEach(async function () { // beforeEach(async function () {
let role = this.server.create('role', {name: 'Administrator'}); // let role = this.server.create('role', {name: 'Administrator'});
this.server.create('user', {roles: [role]}); // this.server.create('user', {roles: [role]});
return await authenticateSession(); // return await authenticateSession();
}); // });
it('it validates and saves slack settings properly', async function () { // it('it validates and saves slack settings properly', async function () {
await visit('/settings/integrations/slack'); // await visit('/settings/integrations/slack');
// has correct url // // has correct url
expect(currentURL(), 'currentURL').to.equal('/settings/integrations/slack'); // expect(currentURL(), 'currentURL').to.equal('/settings/integrations/slack');
await fillIn('[data-test-slack-url-input]', 'notacorrecturl'); // await fillIn('[data-test-slack-url-input]', 'notacorrecturl');
await click('[data-test-save-button]'); // await click('[data-test-save-button]');
expect(find('[data-test-error="slack-url"]').textContent.trim(), 'inline validation response') // expect(find('[data-test-error="slack-url"]').textContent.trim(), 'inline validation response')
.to.equal('The URL must be in a format like https://hooks.slack.com/services/<your personal key>'); // .to.equal('The URL must be in a format like https://hooks.slack.com/services/<your personal key>');
// CMD-S shortcut works // // CMD-S shortcut works
await fillIn('[data-test-slack-url-input]', 'https://hooks.slack.com/services/1275958430'); // await fillIn('[data-test-slack-url-input]', 'https://hooks.slack.com/services/1275958430');
await fillIn('[data-test-slack-username-input]', 'SlackBot'); // await fillIn('[data-test-slack-username-input]', 'SlackBot');
await triggerEvent('.gh-app', 'keydown', { // await triggerEvent('.gh-app', 'keydown', {
keyCode: 83, // s // keyCode: 83, // s
metaKey: ctrlOrCmd === 'command', // metaKey: ctrlOrCmd === 'command',
ctrlKey: ctrlOrCmd === 'ctrl' // ctrlKey: ctrlOrCmd === 'ctrl'
}); // });
let [newRequest] = this.server.pretender.handledRequests.slice(-1); // let [newRequest] = this.server.pretender.handledRequests.slice(-1);
let params = JSON.parse(newRequest.requestBody); // let params = JSON.parse(newRequest.requestBody);
let urlResult = params.settings.findBy('key', 'slack_url').value; // let urlResult = params.settings.findBy('key', 'slack_url').value;
let usernameResult = params.settings.findBy('key', 'slack_username').value; // let usernameResult = params.settings.findBy('key', 'slack_username').value;
expect(urlResult).to.equal('https://hooks.slack.com/services/1275958430'); // expect(urlResult).to.equal('https://hooks.slack.com/services/1275958430');
expect(usernameResult).to.equal('SlackBot'); // expect(usernameResult).to.equal('SlackBot');
expect(find('[data-test-error="slack-url"]'), 'inline validation response') // expect(find('[data-test-error="slack-url"]'), 'inline validation response')
.to.not.exist; // .to.not.exist;
await fillIn('[data-test-slack-url-input]', 'https://hooks.slack.com/services/1275958430'); // await fillIn('[data-test-slack-url-input]', 'https://hooks.slack.com/services/1275958430');
await click('[data-test-send-notification-button]'); // await click('[data-test-send-notification-button]');
expect(findAll('.gh-notification').length, 'number of notifications').to.equal(1); // expect(findAll('.gh-notification').length, 'number of notifications').to.equal(1);
expect(find('[data-test-error="slack-url"]'), 'inline validation response') // expect(find('[data-test-error="slack-url"]'), 'inline validation response')
.to.not.exist; // .to.not.exist;
// modify model data or there will be no api call // // modify model data or there will be no api call
await fillIn('[data-test-slack-url-input]', 'https://hooks.slack.com/services/1275958431'); // await fillIn('[data-test-slack-url-input]', 'https://hooks.slack.com/services/1275958431');
this.server.put('/settings/', function () { // this.server.put('/settings/', function () {
return new Response(422, {}, { // return new Response(422, {}, {
errors: [ // errors: [
{ // {
type: 'ValidationError', // type: 'ValidationError',
message: 'Test error' // message: 'Test error'
} // }
] // ]
}); // });
}); // });
await click('.gh-notification .gh-notification-close'); // await click('.gh-notification .gh-notification-close');
await click('[data-test-send-notification-button]'); // await click('[data-test-send-notification-button]');
// we shouldn't try to send the test request if the save fails // // we shouldn't try to send the test request if the save fails
let [lastRequest] = this.server.pretender.handledRequests.slice(-1); // let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
expect(lastRequest.url).to.not.match(/\/slack\/test/); // expect(lastRequest.url).to.not.match(/\/slack\/test/);
expect(findAll('.gh-notification').length, 'check slack notification after api validation error').to.equal(0); // expect(findAll('.gh-notification').length, 'check slack notification after api validation error').to.equal(0);
}); // });
it('warns when leaving without saving', async function () { // it('warns when leaving without saving', async function () {
await visit('/settings/integrations/slack'); // await visit('/settings/integrations/slack');
// has correct url // // has correct url
expect(currentURL(), 'currentURL').to.equal('/settings/integrations/slack'); // expect(currentURL(), 'currentURL').to.equal('/settings/integrations/slack');
await fillIn('[data-test-slack-url-input]', 'https://hooks.slack.com/services/1275958430'); // await fillIn('[data-test-slack-url-input]', 'https://hooks.slack.com/services/1275958430');
await blur('[data-test-slack-url-input]'); // await blur('[data-test-slack-url-input]');
await visit('/settings'); // await visit('/settings');
expect(findAll('[data-test-modal="unsaved-settings"]').length, 'modal exists').to.equal(1); // expect(findAll('[data-test-modal="unsaved-settings"]').length, 'modal exists').to.equal(1);
// Leave without saving // // Leave without saving
await click('[data-test-modal="unsaved-settings"] [data-test-leave-button]'); // await click('[data-test-modal="unsaved-settings"] [data-test-leave-button]');
expect(currentURL(), 'currentURL').to.equal('/settings'); // expect(currentURL(), 'currentURL').to.equal('/settings');
await visit('/settings/integrations/slack'); // await visit('/settings/integrations/slack');
expect(currentURL(), 'currentURL').to.equal('/settings/integrations/slack'); // expect(currentURL(), 'currentURL').to.equal('/settings/integrations/slack');
// settings were not saved // // settings were not saved
expect( // expect(
find('[data-test-slack-url-input]').textContent.trim(), // find('[data-test-slack-url-input]').textContent.trim(),
'Slack Webhook URL' // 'Slack Webhook URL'
).to.equal(''); // ).to.equal('');
}); // });
}); // });
}); // });

View File

@ -18,24 +18,24 @@ describe('Acceptance: Tags', function () {
expect(currentURL()).to.equal('/signin'); expect(currentURL()).to.equal('/signin');
}); });
it('redirects to staff page when authenticated as contributor', async function () { it('redirects to posts page when authenticated as contributor', async function () {
let role = this.server.create('role', {name: 'Contributor'}); let role = this.server.create('role', {name: 'Contributor'});
this.server.create('user', {roles: [role], slug: 'test-user'}); this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); await authenticateSession();
await visit('/settings/design'); await visit('/tags');
expect(currentURL(), 'currentURL').to.equal('/settings/staff/test-user'); expect(currentURL(), 'currentURL').to.equal('/posts');
}); });
it('redirects to staff page when authenticated as author', async function () { it('redirects to site page when authenticated as author', async function () {
let role = this.server.create('role', {name: 'Author'}); let role = this.server.create('role', {name: 'Author'});
this.server.create('user', {roles: [role], slug: 'test-user'}); this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); await authenticateSession();
await visit('/settings/design'); await visit('/tags');
expect(currentURL(), 'currentURL').to.equal('/settings/staff/test-user'); expect(currentURL(), 'currentURL').to.equal('/site');
}); });
describe('when logged in', function () { describe('when logged in', function () {

View File

@ -1,132 +1,132 @@
import ctrlOrCmd from 'ghost-admin/utils/ctrl-or-cmd'; // import ctrlOrCmd from 'ghost-admin/utils/ctrl-or-cmd';
import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support'; // import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support';
import { // import {
beforeEach, // beforeEach,
describe, // describe,
it // it
} from 'mocha'; // } from 'mocha';
import {click, currentURL, find, findAll, triggerEvent} from '@ember/test-helpers'; // import {click, currentURL, find, findAll, triggerEvent} from '@ember/test-helpers';
import {expect} from 'chai'; // import {expect} from 'chai';
import {setupApplicationTest} from 'ember-mocha'; // import {setupApplicationTest} from 'ember-mocha';
import {setupMirage} from 'ember-cli-mirage/test-support'; // import {setupMirage} from 'ember-cli-mirage/test-support';
import {visit} from '../../helpers/visit'; // import {visit} from '../../helpers/visit';
describe('Acceptance: Settings - Integrations - Unsplash', function () { // describe('Acceptance: Settings - Integrations - Unsplash', function () {
let hooks = setupApplicationTest(); // let hooks = setupApplicationTest();
setupMirage(hooks); // setupMirage(hooks);
it('redirects to signin when not authenticated', async function () { // it('redirects to signin when not authenticated', async function () {
await invalidateSession(); // await invalidateSession();
await visit('/settings/integrations/unsplash'); // await visit('/settings/integrations/unsplash');
expect(currentURL(), 'currentURL').to.equal('/signin'); // expect(currentURL(), 'currentURL').to.equal('/signin');
}); // });
it('redirects to home page when authenticated as contributor', async function () { // it('redirects to home page when authenticated as contributor', async function () {
let role = this.server.create('role', {name: 'Contributor'}); // let role = this.server.create('role', {name: 'Contributor'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/integrations/unsplash'); // await visit('/settings/integrations/unsplash');
expect(currentURL(), 'currentURL').to.equal('/posts'); // expect(currentURL(), 'currentURL').to.equal('/posts');
}); // });
it('redirects to home page when authenticated as author', async function () { // it('redirects to home page when authenticated as author', async function () {
let role = this.server.create('role', {name: 'Author'}); // let role = this.server.create('role', {name: 'Author'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/integrations/unsplash'); // await visit('/settings/integrations/unsplash');
expect(currentURL(), 'currentURL').to.equal('/site'); // expect(currentURL(), 'currentURL').to.equal('/site');
}); // });
it('redirects to home page when authenticated as editor', async function () { // it('redirects to home page when authenticated as editor', async function () {
let role = this.server.create('role', {name: 'Editor'}); // let role = this.server.create('role', {name: 'Editor'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/integrations/unsplash'); // await visit('/settings/integrations/unsplash');
expect(currentURL(), 'currentURL').to.equal('/site'); // expect(currentURL(), 'currentURL').to.equal('/site');
}); // });
describe('when logged in', function () { // describe('when logged in', function () {
beforeEach(async function () { // beforeEach(async function () {
let role = this.server.create('role', {name: 'Administrator'}); // let role = this.server.create('role', {name: 'Administrator'});
this.server.create('user', {roles: [role]}); // this.server.create('user', {roles: [role]});
return await authenticateSession(); // return await authenticateSession();
}); // });
it('it can activate/deactivate', async function () { // it('it can activate/deactivate', async function () {
await visit('/settings/integrations/unsplash'); // await visit('/settings/integrations/unsplash');
// has correct url // // has correct url
expect(currentURL(), 'currentURL').to.equal('/settings/integrations/unsplash'); // expect(currentURL(), 'currentURL').to.equal('/settings/integrations/unsplash');
// it's enabled by default when settings is empty // // it's enabled by default when settings is empty
expect(find('[data-test-unsplash-checkbox]').checked, 'checked by default').to.be.true; // expect(find('[data-test-unsplash-checkbox]').checked, 'checked by default').to.be.true;
await click('[data-test-unsplash-checkbox]'); // await click('[data-test-unsplash-checkbox]');
expect(find('[data-test-unsplash-checkbox]').checked, 'unsplash checkbox').to.be.false; // expect(find('[data-test-unsplash-checkbox]').checked, 'unsplash checkbox').to.be.false;
// trigger a save // // trigger a save
await click('[data-test-save-button]'); // await click('[data-test-save-button]');
// server should now have an unsplash setting // // server should now have an unsplash setting
let [lastRequest] = this.server.pretender.handledRequests.slice(-1); // let [lastRequest] = this.server.pretender.handledRequests.slice(-1);
let params = JSON.parse(lastRequest.requestBody); // let params = JSON.parse(lastRequest.requestBody);
expect(params.settings.findBy('key', 'unsplash').value).to.equal(false); // expect(params.settings.findBy('key', 'unsplash').value).to.equal(false);
// save via CMD-S shortcut // // save via CMD-S shortcut
await click('[data-test-unsplash-checkbox]'); // await click('[data-test-unsplash-checkbox]');
await triggerEvent('.gh-app', 'keydown', { // await triggerEvent('.gh-app', 'keydown', {
keyCode: 83, // s // keyCode: 83, // s
metaKey: ctrlOrCmd === 'command', // metaKey: ctrlOrCmd === 'command',
ctrlKey: ctrlOrCmd === 'ctrl' // ctrlKey: ctrlOrCmd === 'ctrl'
}); // });
// we've already saved in this test so there's no on-screen indication // // we've already saved in this test so there's no on-screen indication
// that we've had another save, check the request was fired instead // // that we've had another save, check the request was fired instead
let [newRequest] = this.server.pretender.handledRequests.slice(-1); // let [newRequest] = this.server.pretender.handledRequests.slice(-1);
params = JSON.parse(newRequest.requestBody); // params = JSON.parse(newRequest.requestBody);
expect(find('[data-test-unsplash-checkbox]').checked, 'AMP checkbox').to.be.true; // expect(find('[data-test-unsplash-checkbox]').checked, 'AMP checkbox').to.be.true;
expect(params.settings.findBy('key', 'unsplash').value).to.equal(true); // expect(params.settings.findBy('key', 'unsplash').value).to.equal(true);
}); // });
it('warns when leaving without saving', async function () { // it('warns when leaving without saving', async function () {
await visit('/settings/integrations/unsplash'); // await visit('/settings/integrations/unsplash');
// has correct url // // has correct url
expect(currentURL(), 'currentURL').to.equal('/settings/integrations/unsplash'); // expect(currentURL(), 'currentURL').to.equal('/settings/integrations/unsplash');
// AMP is enabled by default // // AMP is enabled by default
expect(find('[data-test-unsplash-checkbox]').checked, 'AMP checkbox default').to.be.true; // expect(find('[data-test-unsplash-checkbox]').checked, 'AMP checkbox default').to.be.true;
await click('[data-test-unsplash-checkbox]'); // await click('[data-test-unsplash-checkbox]');
expect(find('[data-test-unsplash-checkbox]').checked, 'Unsplash checkbox').to.be.false; // expect(find('[data-test-unsplash-checkbox]').checked, 'Unsplash checkbox').to.be.false;
await visit('/settings/labs'); // await visit('/settings/labs');
expect(findAll('[data-test-modal="unsaved-settings"]').length, 'unsaved changes modal exists').to.equal(1); // expect(findAll('[data-test-modal="unsaved-settings"]').length, 'unsaved changes modal exists').to.equal(1);
// Leave without saving // // Leave without saving
await click('[data-test-modal="unsaved-settings"] [data-test-leave-button]'); // await click('[data-test-modal="unsaved-settings"] [data-test-leave-button]');
expect(currentURL(), 'currentURL after leave without saving').to.equal('/settings/labs'); // expect(currentURL(), 'currentURL after leave without saving').to.equal('/settings/labs');
await visit('/settings/integrations/unsplash'); // await visit('/settings/integrations/unsplash');
expect(currentURL(), 'currentURL').to.equal('/settings/integrations/unsplash'); // expect(currentURL(), 'currentURL').to.equal('/settings/integrations/unsplash');
// settings were not saved // // settings were not saved
expect(find('[data-test-unsplash-checkbox]').checked, 'Unsplash checkbox').to.be.true; // expect(find('[data-test-unsplash-checkbox]').checked, 'Unsplash checkbox').to.be.true;
}); // });
}); // });
}); // });

View File

@ -1,69 +1,69 @@
import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support'; // import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support';
import { // import {
beforeEach, // beforeEach,
describe, // describe,
it // it
} from 'mocha'; // } from 'mocha';
import {currentURL} from '@ember/test-helpers'; // import {currentURL} from '@ember/test-helpers';
import {expect} from 'chai'; // import {expect} from 'chai';
import {setupApplicationTest} from 'ember-mocha'; // import {setupApplicationTest} from 'ember-mocha';
import {setupMirage} from 'ember-cli-mirage/test-support'; // import {setupMirage} from 'ember-cli-mirage/test-support';
import {visit} from '../../helpers/visit'; // import {visit} from '../../helpers/visit';
describe('Acceptance: Settings - Integrations - Zapier', function () { // describe('Acceptance: Settings - Integrations - Zapier', function () {
let hooks = setupApplicationTest(); // let hooks = setupApplicationTest();
setupMirage(hooks); // setupMirage(hooks);
it('redirects to signin when not authenticated', async function () { // it('redirects to signin when not authenticated', async function () {
await invalidateSession(); // await invalidateSession();
await visit('/settings/integrations/zapier'); // await visit('/settings/integrations/zapier');
expect(currentURL(), 'currentURL').to.equal('/signin'); // expect(currentURL(), 'currentURL').to.equal('/signin');
}); // });
it('redirects to home page when authenticated as contributor', async function () { // it('redirects to home page when authenticated as contributor', async function () {
let role = this.server.create('role', {name: 'Contributor'}); // let role = this.server.create('role', {name: 'Contributor'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/integrations/zapier'); // await visit('/settings/integrations/zapier');
expect(currentURL(), 'currentURL').to.equal('/posts'); // expect(currentURL(), 'currentURL').to.equal('/posts');
}); // });
it('redirects to home page when authenticated as author', async function () { // it('redirects to home page when authenticated as author', async function () {
let role = this.server.create('role', {name: 'Author'}); // let role = this.server.create('role', {name: 'Author'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/integrations/zapier'); // await visit('/settings/integrations/zapier');
expect(currentURL(), 'currentURL').to.equal('/site'); // expect(currentURL(), 'currentURL').to.equal('/site');
}); // });
it('redirects to home page when authenticated as editor', async function () { // it('redirects to home page when authenticated as editor', async function () {
let role = this.server.create('role', {name: 'Editor'}); // let role = this.server.create('role', {name: 'Editor'});
this.server.create('user', {roles: [role], slug: 'test-user'}); // this.server.create('user', {roles: [role], slug: 'test-user'});
await authenticateSession(); // await authenticateSession();
await visit('/settings/integrations/zapier'); // await visit('/settings/integrations/zapier');
expect(currentURL(), 'currentURL').to.equal('/site'); // expect(currentURL(), 'currentURL').to.equal('/site');
}); // });
describe('when logged in', function () { // describe('when logged in', function () {
beforeEach(async function () { // beforeEach(async function () {
let role = this.server.create('role', {name: 'Administrator'}); // let role = this.server.create('role', {name: 'Administrator'});
this.server.create('user', {roles: [role]}); // this.server.create('user', {roles: [role]});
return await authenticateSession(); // return await authenticateSession();
}); // });
it('it loads', async function () { // it('it loads', async function () {
await visit('/settings/integrations/zapier'); // await visit('/settings/integrations/zapier');
// has correct url // // has correct url
expect(currentURL(), 'currentURL').to.equal('/settings/integrations/zapier'); // expect(currentURL(), 'currentURL').to.equal('/settings/integrations/zapier');
}); // });
}); // });
}); // });

File diff suppressed because it is too large Load Diff

View File

@ -6,40 +6,38 @@ test.describe('Announcement Bar Settings', () => {
await goToAnnouncementBarSettings(page); await goToAnnouncementBarSettings(page);
await test.step('Bar should be hidden', async () => { await test.step('Bar should be hidden', async () => {
const htmlFrame = await getPreviewFrame(page); const htmlFrame = getPreviewFrame(page);
await expect(await htmlFrame.locator('#announcement-bar-root')).toHaveCount(0); await expect(await htmlFrame.locator('#announcement-bar-root')).toHaveCount(0);
}); });
}); });
test('Show/hide bar if visibility checked/unchecked and text filled', async ({page}) => { test('Show/hide bar if visibility checked/unchecked and text filled', async ({page}) => {
await page.goto('/ghost'); await page.goto('/ghost');
await goToAnnouncementBarSettings(page); const modal = await goToAnnouncementBarSettings(page);
await test.step('Check free members', async () => { await test.step('Check free members', async () => {
const freeMembersCheckbox = await page.getByTestId('announcement-bar-free-member-input'); const freeMembersCheckbox = modal.getByLabel('Free members');
await expect(await freeMembersCheckbox.isChecked()).toBeFalsy(); await expect(freeMembersCheckbox).not.toBeChecked();
await page.getByTestId('announcement-bar-free-member-label').click(); await freeMembersCheckbox.check();
await expect(await freeMembersCheckbox.isChecked()).toBeTruthy();
}); });
await test.step('Fill announcement text', async () => { await test.step('Fill announcement text', async () => {
await page.locator('.koenig-react-editor').click(); await modal.locator('.koenig-react-editor').click();
await expect(await page.locator('[contenteditable="true"]')).toBeVisible({timeout: 30000}); // add timeout as lexical module loading can take time await expect(await modal.locator('[contenteditable="true"]')).toBeVisible({timeout: 30000}); // add timeout as lexical module loading can take time
await page.keyboard.type('Announcement text'); await page.keyboard.type('Announcement text');
await page.getByTestId('announcement-bar-title').click(); await modal.getByText('Announcement').first().click(); // defocus the editor
}); });
const htmlFrame = await getPreviewFrame(page); const htmlFrame = getPreviewFrame(page);
await test.step('Announcement bar should be visible', async () => { await test.step('Announcement bar should be visible', async () => {
await expect(await htmlFrame.getByText('Announcement text')).toBeVisible(); await expect(await htmlFrame.getByText('Announcement text')).toBeVisible();
}); });
await test.step('Disable free members', async () => { await test.step('Disable free members', async () => {
const freeMembersCheckbox = await page.getByTestId('announcement-bar-free-member-input'); const freeMembersCheckbox = modal.getByLabel('Free members');
await expect(await freeMembersCheckbox.isChecked()).toBeTruthy(); await expect(freeMembersCheckbox).toBeChecked();
await page.getByTestId('announcement-bar-free-member-label').click(); await freeMembersCheckbox.uncheck();
await expect(await freeMembersCheckbox.isChecked()).toBeFalsy(); await modal.locator('.koenig-react-editor').click();
await page.locator('.koenig-react-editor').click();
}); });
await test.step('Announcement bar should be hidden', async () => { await test.step('Announcement bar should be hidden', async () => {
@ -49,13 +47,15 @@ test.describe('Announcement Bar Settings', () => {
}); });
async function goToAnnouncementBarSettings(page) { async function goToAnnouncementBarSettings(page) {
return await test.step('Navigate to the announcement bar settings', async () => { await test.step('Navigate to the announcement bar settings', async () => {
await page.locator('[data-test-nav="settings"]').click(); await page.locator('[data-test-nav="settings"]').click();
await page.locator('[data-test-nav="announcement-bar"]').click(); await page.getByTestId('announcement-bar').getByRole('button', {name: 'Customize'}).click();
await expect(await page.getByTestId('announcement-bar-title')).toBeVisible(); // Wait for the preview to load
await getPreviewFrame(page).locator('body *:visible').first().waitFor();
}); });
return page.getByTestId('announcement-bar-modal');
} }
async function getPreviewFrame(page) { function getPreviewFrame(page) {
return page.frameLocator('[data-testid="iframe-html"]:visible'); return page.frameLocator('[data-testid="announcement-bar-preview-iframe"] > iframe[data-visible=true]');
} }

View File

@ -11,10 +11,11 @@ test.describe('Membership Settings', () => {
// Open Portal settings // Open Portal settings
await page.goto('/ghost'); await page.goto('/ghost');
await page.locator('.gh-nav a[href="#/settings/"]').click(); await page.locator('.gh-nav a[href="#/settings/"]').click();
await page.locator('.gh-setting-group').filter({hasText: 'Membership'}).click(); await page.getByTestId('portal').getByRole('button', {name: 'Customize'}).click();
await page.locator('[data-test-toggle="portal-settings"]').click();
const modal = page.getByTestId('portal-modal');
// Check free tier toggle is visible // Check free tier toggle is visible
await expect(page.locator('label').filter({hasText: 'Free'}).first()).toBeVisible(); await expect(modal.locator('label').filter({hasText: 'Free'}).first()).toBeVisible();
// Reconnect Stripe for other tests // Reconnect Stripe for other tests
const stripeToken = await generateStripeIntegrationToken(); const stripeToken = await generateStripeIntegrationToken();

View File

@ -2,20 +2,24 @@ const {expect, test} = require('@playwright/test');
test.describe('Portal Settings', () => { test.describe('Portal Settings', () => {
test.describe('Links', () => { test.describe('Links', () => {
test('can open portal on default page', async ({page}) => { const openPortalLinks = async (page) => {
await page.goto('/ghost'); await page.goto('/ghost');
// Navigate to the member settings
await page.locator('[data-test-nav="settings"]').click(); await page.locator('[data-test-nav="settings"]').click();
await page.locator('[data-test-nav="members-membership"]').click();
// open portal settings await page.getByTestId('portal').getByRole('button', {name: 'Customize'}).click();
await page.locator('[data-test-toggle="portal-settings"]').click();
// open links preview page const modal = page.getByTestId('portal-modal');
await page.locator('[data-test-select="page-selector"]').first().selectOption('links');
await modal.getByRole('tab', {name: 'Links'}).click();
return modal;
};
test('can open portal on default page', async ({page}) => {
const modal = await openPortalLinks(page);
// fetch portal default url from input // fetch portal default url from input
const portalUrl = await page.locator('[data-test-input="portal-link-default"]').inputValue(); const portalUrl = await modal.getByLabel('Default').inputValue();
await page.goto(portalUrl); await page.goto(portalUrl);
const portalFrame = page.locator('[data-testid="portal-popup-frame"]'); const portalFrame = page.locator('[data-testid="portal-popup-frame"]');
@ -25,19 +29,10 @@ test.describe('Portal Settings', () => {
}); });
test('can open portal on signin page', async ({page}) => { test('can open portal on signin page', async ({page}) => {
await page.goto('/ghost'); const modal = await openPortalLinks(page);
// Navigate to the member settings
await page.locator('[data-test-nav="settings"]').click();
await page.locator('[data-test-nav="members-membership"]').click();
// open portal settings
await page.locator('[data-test-toggle="portal-settings"]').click();
// open links preview page
await page.locator('[data-test-select="page-selector"]').first().selectOption('links');
// fetch portal signin url from input // fetch portal signin url from input
const portalUrl = await page.locator('[data-test-input="portal-link-signin"]').inputValue(); const portalUrl = await modal.getByLabel('Sign in').inputValue();
await page.goto(portalUrl); await page.goto(portalUrl);
const portalFrame = page.locator('[data-testid="portal-popup-frame"]'); const portalFrame = page.locator('[data-testid="portal-popup-frame"]');
@ -50,19 +45,10 @@ test.describe('Portal Settings', () => {
}); });
test('can open portal on signup page', async ({page}) => { test('can open portal on signup page', async ({page}) => {
await page.goto('/ghost'); const modal = await openPortalLinks(page);
// Navigate to the member settings
await page.locator('[data-test-nav="settings"]').click();
await page.locator('[data-test-nav="members-membership"]').click();
// open portal settings
await page.locator('[data-test-toggle="portal-settings"]').click();
// open links preview page
await page.locator('[data-test-select="page-selector"]').first().selectOption('links');
// fetch portal signup url from input // fetch portal signup url from input
const portalUrl = await page.locator('[data-test-input="portal-link-signup"]').inputValue(); const portalUrl = await modal.getByLabel('Sign up').inputValue();
await page.goto(portalUrl); await page.goto(portalUrl);
const portalFrame = page.locator('[data-testid="portal-popup-frame"]'); const portalFrame = page.locator('[data-testid="portal-popup-frame"]');
@ -75,19 +61,10 @@ test.describe('Portal Settings', () => {
}); });
test('can open portal directly on monthly signup', async ({page}) => { test('can open portal directly on monthly signup', async ({page}) => {
await page.goto('/ghost'); const modal = await openPortalLinks(page);
// Navigate to the member settings
await page.locator('[data-test-nav="settings"]').click();
await page.locator('[data-test-nav="members-membership"]').click();
// open portal settings
await page.locator('[data-test-toggle="portal-settings"]').click();
// open links preview page
await page.locator('[data-test-select="page-selector"]').first().selectOption('links');
// fetch and go to portal directly monthly signup url // fetch and go to portal directly monthly signup url
const portalUrl = await page.locator('[data-test-input="portal-tier-link-monthly"]').inputValue(); const portalUrl = await modal.getByLabel('Signup / Monthly').inputValue();
await page.goto(portalUrl); await page.goto(portalUrl);
// expect stripe checkout to have opeened // expect stripe checkout to have opeened
@ -95,19 +72,10 @@ test.describe('Portal Settings', () => {
}); });
test('can open portal directly on yearly signup', async ({page}) => { test('can open portal directly on yearly signup', async ({page}) => {
await page.goto('/ghost'); const modal = await openPortalLinks(page);
// Navigate to the member settings
await page.locator('[data-test-nav="settings"]').click();
await page.locator('[data-test-nav="members-membership"]').click();
// open portal settings // fetch and go to portal directly yearly signup url
await page.locator('[data-test-toggle="portal-settings"]').click(); const portalUrl = await modal.getByLabel('Signup / Yearly').inputValue();
// open links preview page
await page.locator('[data-test-select="page-selector"]').first().selectOption('links');
// fetch and go to portal directly monthly signup url
const portalUrl = await page.locator('[data-test-input="portal-tier-link-yearly"]').inputValue();
await page.goto(portalUrl); await page.goto(portalUrl);
// expect stripe checkout to have opeened // expect stripe checkout to have opeened

View File

@ -7,17 +7,17 @@ test.describe('Site Settings', () => {
await page.goto('/ghost'); await page.goto('/ghost');
await page.locator('[data-test-nav="settings"]').click(); await page.locator('[data-test-nav="settings"]').click();
await page.locator('[data-test-nav="general"]').click();
// @NOTE: needs a data-test selector const section = page.getByTestId('locksite');
await page.locator('label.switch span').click();
await section.getByRole('button', {name: 'Edit'}).click();
await section.getByLabel(/Enable password protection/).check();
await section.getByLabel('Site password').fill('password');
// save changes // save changes
await page.locator('[data-test-button="save"]').click(); await section.getByRole('button', {name: 'Save'}).click();
await page.getByRole('button', {name: 'Saved'}).waitFor({ await expect(section.getByLabel('Site password')).toHaveCount(0);
state: 'visible',
timeout: 1000
});
// copy site password // copy site password
//const passwordInput = await page.locator('[data-test-password-input]'); //const passwordInput = await page.locator('[data-test-password-input]');
@ -40,20 +40,20 @@ test.describe('Site Settings', () => {
// await frontendPage.waitForSelector('.site-title'); // await frontendPage.waitForSelector('.site-title');
// await expect(frontendPage.locator('.site-title')).toHaveText('The Local Test'); // await expect(frontendPage.locator('.site-title')).toHaveText('The Local Test');
// // set private mode in admin "off" // set private mode in admin "off"
// @NOTE: needs a data-test selector await section.getByRole('button', {name: 'Edit'}).click();
await page.locator('label.switch span').click();
await section.getByLabel(/Enable password protection/).uncheck();
// save changes // save changes
await page.locator('[data-test-button="save"]').click(); await section.getByRole('button', {name: 'Save'}).click();
await page.getByRole('button', {name: 'Saved'}).waitFor({ await expect(section.getByLabel('Site password')).toHaveCount(0);
state: 'visible',
timeout: 1000
});
// check the site is publicly accessible // check the site is publicly accessible
await frontendPage.goto('/'); await expect(async () => {
await expect(frontendPage.locator('.gh-navigation-brand')).toHaveText('The Local Test'); await frontendPage.goto('/');
await expect(frontendPage.locator('.gh-navigation-brand')).toHaveText('The Local Test');
}).toPass();
}); });
}); });
}); });

View File

@ -2,22 +2,18 @@ const {expect, test} = require('@playwright/test');
const {createPostDraft, createTier, disconnectStripe, generateStripeIntegrationToken, setupStripe} = require('../utils'); const {createPostDraft, createTier, disconnectStripe, generateStripeIntegrationToken, setupStripe} = require('../utils');
const changeSubscriptionAccess = async (page, access) => { const changeSubscriptionAccess = async (page, access) => {
// Go to settings page
await page.locator('[data-test-nav="settings"]').click(); await page.locator('[data-test-nav="settings"]').click();
// Go to members settings page const section = page.getByTestId('access');
await page.locator('[data-test-nav="members-membership"]').click(); await section.getByRole('button', {name: 'Edit'}).click();
// Change subscription access const select = section.getByTestId('subscription-access-select');
await page.locator('[data-test-members-subscription-access] [data-test-members-subscription-option]').click(); await select.click();
await page.locator(`[data-test-members-subscription-option="${access}"]`).click(); await page.locator(`[data-testid="select-option"][data-value="${access}"]`).click();
// Save settings // Save settings
await page.locator('[data-test-button="save-settings"]').click(); await section.getByRole('button', {name: 'Save'}).click();
await page.getByRole('button', {name: 'Saved'}).waitFor({ await expect(select).not.toBeVisible();
state: 'visible',
timeout: 1000
});
}; };
const checkPortalScriptLoaded = async (page, loaded = true) => { const checkPortalScriptLoaded = async (page, loaded = true) => {

View File

@ -1,16 +1,16 @@
const {expect, test} = require('@playwright/test'); const {expect, test} = require('@playwright/test');
const {createTier, createOffer, getUniqueName, getSlug, goToMembershipPage, getTierCardById} = require('../utils'); const {createTier, createOffer, getUniqueName, getSlug, goToMembershipPage, openTierModal} = require('../utils');
test.describe('Admin', () => { test.describe('Admin', () => {
test.describe('Tiers', () => { test.describe('Tiers', () => {
test('Default tier should be $5mo / $50yr', async ({page}) => { test('Default tier should be $5mo / $50yr', async ({page}) => {
const defaultTierId = 'default-product'; const defaultTier = 'default-product';
await goToMembershipPage(page); await goToMembershipPage(page);
const tierCard = await getTierCardById(page, {id: defaultTierId}); const tierModal = await openTierModal(page, {slug: defaultTier});
await test.step('Default tier should be $5mo / $50yr', async () => { await test.step('Default tier should be $5mo / $50yr', async () => {
await expect(tierCard.locator('[data-test-amount-monthly-price]')).toHaveText('5'); await expect(tierModal.getByLabel('Monthly price')).toHaveValue('5');
await expect(tierCard.locator('[data-test-amount-yearly-price]')).toHaveText('50'); await expect(tierModal.getByLabel('Yearly price')).toHaveValue('50');
}); });
}); });
@ -50,18 +50,20 @@ test.describe('Admin', () => {
await goToMembershipPage(page); await goToMembershipPage(page);
await test.step('Created tier should be in Portal settings and not selected', async () => { await test.step('Created tier should be in Portal settings and not selected', async () => {
await page.locator('[data-test-toggle="portal-settings"]').click(); await page.getByTestId('portal').getByRole('button', {name: 'Customize'}).click();
// Wait until the list of tiers available at signup is visible
await page.waitForSelector('[data-test-tiers-at-signup]'); const portalSettings = page.getByTestId('portal-modal');
await page.locator(`[data-test-settings-tier-label="${tierName}"]`);
expect(await page.locator(`[data-test-settings-tier-input="${tierName}"]`).isChecked()).toBeFalsy(); await portalSettings.getByLabel(tierName).first().waitFor();
await expect(portalSettings.getByLabel(tierName).first()).not.toBeChecked();
}); });
}); });
test('Can update Tier', async ({page}) => { test('Can update Tier', async ({page}) => {
await page.goto('/ghost'); await page.goto('/ghost');
const tierName = getUniqueName('New Test Tier'); const tierName = getUniqueName('New Test Tier');
const tierId = getSlug(tierName); const slug = getSlug(tierName);
const updatedTierName = getUniqueName('Updated Test Tier Name'); const updatedTierName = getUniqueName('Updated Test Tier Name');
const updatedMonthlyPrice = '66'; const updatedMonthlyPrice = '66';
const updatedYearlyPrice = '666'; const updatedYearlyPrice = '666';
@ -73,18 +75,14 @@ test.describe('Admin', () => {
}); });
await goToMembershipPage(page); await goToMembershipPage(page);
const tierCard = await getTierCardById(page, {id: tierId}); const tierModal = await openTierModal(page, {slug});
await test.step('Open modal and edit tier information', async () => { await test.step('Open modal and edit tier information', async () => {
await tierCard.locator('[data-test-button="tiers-actions"]').click(); await tierModal.getByLabel('Name').fill(updatedTierName);
await tierCard.locator('[data-test-button="edit-tier"]').click(); await tierModal.getByLabel('Description').fill(updatedDescription);
const modal = page.locator('[data-test-modal="edit-tier"]'); await tierModal.getByLabel('Monthly price').fill(updatedMonthlyPrice);
await tierModal.getByLabel('Yearly price').fill(updatedYearlyPrice);
await modal.locator('[data-test-input="tier-name"]').first().fill(updatedTierName); await tierModal.getByRole('button', {name: 'Save & close'}).click();
await modal.locator('[data-test-input="tier-description"]').first().fill(updatedDescription);
await modal.locator('[data-test-input="monthly-price"]').fill(updatedMonthlyPrice);
await modal.locator('[data-test-input="yearly-price"]').fill(updatedYearlyPrice);
await page.locator('[data-test-button="save-tier"]').click();
}); });
const portalFrame = await test.step('Go to website and open portal', async () => { const portalFrame = await test.step('Go to website and open portal', async () => {
@ -111,15 +109,16 @@ test.describe('Admin', () => {
await test.step('Check monthly price', async () => { await test.step('Check monthly price', async () => {
await portalFrame.locator('[data-test-button="switch-monthly"]').click(); await portalFrame.locator('[data-test-button="switch-monthly"]').click();
await expect(await portalTierCard.getByText('/month')).toBeVisible(); await expect(portalTierCard.getByText('/month')).toBeVisible();
await expect(portalTierCard.locator('.amount').first()).toHaveText(updatedMonthlyPrice); await expect(portalTierCard.locator('.amount').first()).toHaveText(updatedMonthlyPrice);
}); });
}); });
// TODO: Add something more useful to this, e.g. checking in the portal
test('Can archive and unarchive a Tier', async ({page}) => { test('Can archive and unarchive a Tier', async ({page}) => {
await page.goto('/ghost'); await page.goto('/ghost');
const tierName = getUniqueName('Archive Tier'); const tierName = getUniqueName('Archive Tier');
const tierId = getSlug(tierName); const slug = getSlug(tierName);
await createTier(page, { await createTier(page, {
name: tierName, name: tierName,
monthlyPrice: 5, monthlyPrice: 5,
@ -127,50 +126,56 @@ test.describe('Admin', () => {
}); });
await goToMembershipPage(page); await goToMembershipPage(page);
const tierCard = await getTierCardById(page, {id: tierId});
await test.step('Archive tier', async () => { await test.step('Archive tier', async () => {
await tierCard.locator('[data-test-button="tiers-actions"]').click(); const tierModal = await openTierModal(page, {slug});
await tierCard.locator('[data-test-button="archive-tier"]').click(); await tierModal.getByRole('button', {name: 'Archive tier'}).click();
const modal = page.locator('[data-test-modal="archive-tier"]'); await page.getByTestId('confirmation-modal').getByRole('button', {name: 'Archive'}).click();
await modal.locator('[data-test-button="archive-tier"]').click(); await tierModal.getByRole('button', {name: 'Save & close'}).click();
}); });
await test.step('Archived tier should not be available in active tiers', async () => { await test.step('Archived tier should not be available in active tiers', async () => {
await expect(page.locator(`[data-test-tier-card="${tierId}"]`)).toBeHidden(); await expect(page.locator(`[data-testid="tier-card"][data-tier="${slug}"]`)).toBeHidden();
}); });
await test.step('Archived tier should be available in archived tiers', async () => { await test.step('Archived tier should be available in archived tiers', async () => {
const tiersSelect = await page.locator('[data-test-select-tiers-list]'); await page.getByTestId('tiers').getByRole('tab', {name: 'Archived'}).click();
await tiersSelect.click(); await expect(page.locator(`[data-testid="tier-card"][data-tier="${slug}"]`)).toBeVisible();
await page.getByRole('option', {name: 'Archived'}).click();
await expect(page.locator(`[data-test-tier-card="${tierId}"]`)).toBeVisible();
}); });
await test.step('Archived tier should not be available in portal settings', async () => { await test.step('Archived tier should not be available in portal settings', async () => {
await expect(page.locator(`[data-test-settings-tier-label="${tierName}"]`)).toBeHidden(); await page.getByTestId('portal').getByRole('button', {name: 'Customize'}).click();
const portalSettings = page.getByTestId('portal-modal');
await portalSettings.locator('input[type=checkbox]').first().waitFor();
await expect(portalSettings.getByLabel(tierName).first()).toBeHidden();
await portalSettings.getByRole('button', {name: 'Close'}).click();
}); });
await test.step('Unarchive tier', async () => { await test.step('Unarchive tier', async () => {
await tierCard.locator('[data-test-button="tiers-actions"]').click(); const tierModal = await openTierModal(page, {slug});
await tierCard.locator('[data-test-button="unarchive-tier"]').click(); await tierModal.getByRole('button', {name: 'Reactivate tier'}).click();
const modal = page.locator('[data-test-modal="unarchive-tier"]'); await page.getByTestId('confirmation-modal').getByRole('button', {name: 'Reactivate'}).click();
await modal.locator('[data-test-button="unarchive-tier"]').click(); await tierModal.getByRole('button', {name: 'Save & close'}).click();
}); });
await test.step('Unarchived tier should be available in active tiers', async () => { await test.step('Unarchived tier should be available in active tiers', async () => {
await expect(page.locator(`[data-test-tier-card="${tierId}"]`)).toBeVisible(); await page.getByTestId('tiers').getByRole('tab', {name: 'Active'}).click();
}); await expect(page.locator(`[data-testid="tier-card"][data-tier="${slug}"]`)).toBeVisible();
await test.step('Open Portal settings', async () => {
await page.locator('[data-test-toggle="portal-settings"]').click();
// Wait until the list of tiers available at signup is visible
await page.waitForSelector('[data-test-tiers-at-signup]');
}); });
await test.step('Unarchived tier should be available in portal settings', async () => { await test.step('Unarchived tier should be available in portal settings', async () => {
await page.locator(`[data-test-settings-tier-label="${tierName}"]`); await page.getByTestId('portal').getByRole('button', {name: 'Customize'}).click();
expect(await page.locator(`[data-test-settings-tier-input="${tierName}"]`).isChecked()).toBeFalsy();
const portalSettings = page.getByTestId('portal-modal');
await portalSettings.locator('input[type=checkbox]').first().waitFor();
await expect(portalSettings.getByLabel(tierName).first()).toBeVisible();
await portalSettings.getByRole('button', {name: 'Close'}).click();
}); });
}); });
}); });

View File

@ -40,11 +40,17 @@ test.describe('Portal', () => {
}); });
test('Can donate with a fixed amount set and different currency', async ({page}) => { test('Can donate with a fixed amount set and different currency', async ({page}) => {
await page.goto('/ghost/#/settings/members'); await page.goto('/ghost/#/settings');
await page.getByTestId('expand-tips-and-donations').click();
await page.getByTestId('tips-and-donations-amount').fill('98'); const section = page.getByTestId('tips-or-donations');
await page.locator('#gh-tips-and-donations-currency').selectOption('EUR');
await page.locator('[data-test-button="save-settings"]').click(); await section.getByRole('button', {name: 'Edit'}).click();
await section.getByLabel('Suggested amount').fill('98');
const select = section.getByLabel('Currency');
await select.click();
await page.locator(`[data-testid="select-option"][data-value="EUR"]`).click();
await section.getByRole('button', {name: 'Save'}).click();
await expect(select).not.toBeVisible();
// go to website and open portal // go to website and open portal
await page.goto('/#/portal/support'); await page.goto('/#/portal/support');

View File

@ -1,26 +1,29 @@
const {expect, test} = require('@playwright/test'); const {expect, test} = require('@playwright/test');
const {createMember, impersonateMember} = require('../utils'); const {createMember, impersonateMember} = require('../utils');
/**
* @param {import('@playwright/test').Page} page
*/
const addNewsletter = async (page) => { const addNewsletter = async (page) => {
// go to email settings
await page.goto('/ghost'); await page.goto('/ghost');
await page.locator('[data-test-nav="settings"]').click(); await page.locator('[data-test-nav="settings"]').click();
await page.locator('[data-test-nav="members-email"]').click();
// create newsletter // create newsletter
await page.locator('[data-test-button="add-newsletter"]').click(); const section = page.getByTestId('newsletters');
await page.locator('[data-test-newsletter-title-input]').click(); await section.getByRole('button', {name: 'Add newsletter'}).click();
await page.locator('[data-test-newsletter-title-input]').fill('One more newsletter');
await page.locator('[data-test-button="save-newsletter"]').click(); const modal = page.getByTestId('add-newsletter-modal');
await modal.getByLabel('Name').fill('One more newsletter');
await modal.getByRole('button', {name: 'Create'}).click();
// check that newsletter was added // check that newsletter was added
await page.waitForSelector('[data-test-newsletter="one-more-newsletter"]'); await section.locator('*', {hasText: 'One more newsletter'}).first().waitFor();
}; };
test.describe('Portal', () => { test.describe('Portal', () => {
test.describe('Member actions', () => { test.describe('Member actions', () => {
test.describe.configure({retries: 1}); test.describe.configure({retries: 1});
test('can log out', async ({page}) => { test('can log out', async ({page}) => {
// create a new free member // create a new free member
await createMember(page, { await createMember(page, {

View File

@ -74,54 +74,49 @@ const setupGhost = async (page) => {
const disconnectStripe = async (page) => { const disconnectStripe = async (page) => {
await deleteAllMembers(page); await deleteAllMembers(page);
await page.locator('.gh-nav a[href="#/settings/"]').click(); await page.locator('.gh-nav a[href="#/settings/"]').click();
await page.locator('.gh-setting-group').filter({hasText: 'Membership'}).click(); await page.getByTestId('tiers').waitFor();
if (await page.isVisible('.gh-btn-stripe-status.connected')) { if (await page.isVisible('[data-testid="stripe-connected"]')) {
// Disconnect if already connected await page.getByTestId('stripe-connected').first().click();
await page.locator('.gh-btn-stripe-status.connected').click(); await page.getByTestId('stripe-modal').getByRole('button', {name: 'Disconnect'}).click();
await page.locator('.modal-content .gh-btn-stripe-disconnect').first().click(); await page.getByTestId('confirmation-modal').getByRole('button', {name: 'Disconnect'}).click();
await page
.locator('.modal-content')
.filter({hasText: 'Are you sure you want to disconnect?'})
.first()
.getByRole('button', {name: 'Disconnect'})
.click();
} }
}; };
const setupStripe = async (page, stripConnectIntegrationToken) => { const setupStripe = async (page, stripConnectIntegrationToken) => {
await deleteAllMembers(page); await deleteAllMembers(page);
await page.locator('.gh-nav a[href="#/settings/"]').click(); await page.locator('.gh-nav a[href="#/settings/"]').click();
await page.locator('.gh-setting-group').filter({hasText: 'Membership'}).click(); await page.getByTestId('tiers').waitFor();
if (await page.isVisible('.gh-btn-stripe-status.connected')) { if (await page.isVisible('[data-testid="stripe-connected"]')) {
// Disconnect if already connected // Disconnect if already connected
await page.locator('.gh-btn-stripe-status.connected').click(); await page.getByTestId('stripe-connected').first().click();
await page.locator('.modal-content .gh-btn-stripe-disconnect').first().click(); await page.getByTestId('stripe-modal').getByRole('button', {name: 'Disconnect'}).click();
await page await page.getByTestId('confirmation-modal').getByRole('button', {name: 'Disconnect'}).click();
.locator('.modal-content')
.filter({hasText: 'Are you sure you want to disconnect?'})
.first()
.getByRole('button', {name: 'Disconnect'})
.click();
} else { } else {
await page.locator('.gh-setting-members-tierscontainer .stripe-connect').click(); await page.getByRole('button', {name: 'Connect with Stripe'}).click();
} }
await page.getByPlaceholder('Paste your secure key here').first().fill(stripConnectIntegrationToken); const modal = page.getByTestId('stripe-modal');
await page.getByRole('button', {name: 'Save Stripe settings'}).click(); await modal.getByRole('button', {name: /I have a Stripe account/}).click();
await modal.getByPlaceholder('Paste your secure key here').first().fill(stripConnectIntegrationToken);
await modal.getByRole('button', {name: 'Save Stripe settings'}).click();
// We need to wait for the saving to succeed // We need to wait for the saving to succeed
await expect(page.locator('[data-test-button="stripe-disconnect"]')).toBeVisible(); await expect(modal.getByRole('button', {name: 'Disconnect'})).toBeVisible();
await page.locator('[data-test-button="close-stripe-connect"]').click(); await modal.getByRole('button', {name: 'Close'}).click();
await page.getByRole('button', {name: '← Done'}).click();
}; };
// Setup Mailgun with fake data, for Ghost Admin to allow bulk sending // Setup Mailgun with fake data, for Ghost Admin to allow bulk sending
const setupMailgun = async (page) => { const setupMailgun = async (page) => {
await page.locator('.gh-nav a[href="#/settings/"]').click(); await page.locator('.gh-nav a[href="#/settings/"]').click();
await page.locator('.gh-setting-group').filter({hasText: 'Email newsletter'}).click(); const section = page.getByTestId('mailgun');
await page.locator('.gh-expandable-block').filter({hasText: 'Mailgun configuration'}).getByRole('button', {name: 'Expand'}).click();
await page.locator('[data-test-mailgun-domain-input]').fill('api.testgun.com'); await section.getByRole('button', {name: 'Edit'}).click();
await page.locator('[data-test-mailgun-api-key-input]').fill('Not an API key'); await section.getByLabel('Mailgun domain').fill('api.testgun.com');
await page.locator('[data-test-button="save-members-settings"]').click(); await section.getByLabel('Mailgun private API key').fill('Not an API key');
await page.waitForSelector('[data-test-button="save-members-settings"] [data-test-task-button-state="success"]'); await section.getByRole('button', {name: 'Save'}).click();
await section.getByText('Mailgun is set up').waitFor();
await page.getByRole('button', {name: '← Done'}).click();
}; };
/** /**
@ -130,10 +125,15 @@ const setupMailgun = async (page) => {
*/ */
const enableLabs = async (page) => { const enableLabs = async (page) => {
await page.locator('.gh-nav a[href="#/settings/"]').click(); await page.locator('.gh-nav a[href="#/settings/"]').click();
await page.locator('.gh-setting-group').filter({hasText: 'Labs'}).click();
const alphaList = page.locator('.gh-main-section').filter({hasText: 'Alpha Features'}); const section = page.getByTestId('labs');
await alphaList.locator('label[for="labs-webmentions"]').click(); await section.getByRole('button', {name: 'Open'}).click();
await alphaList.locator('label[for="labs-tipsAndDonations"]').click();
await section.getByRole('tab', {name: 'Alpha features'}).click();
await section.getByLabel('Webmentions').click();
await section.getByLabel('Tips & donations').click();
await page.getByRole('button', {name: '← Done'}).click();
}; };
/** /**
@ -213,62 +213,59 @@ const impersonateMember = async (page) => {
* @param {number} [tier.trialDays] * @param {number} [tier.trialDays]
*/ */
const createTier = async (page, {name, monthlyPrice, yearlyPrice, trialDays}, enableInPortal = true) => { const createTier = async (page, {name, monthlyPrice, yearlyPrice, trialDays}, enableInPortal = true) => {
await test.step('Create a tier', async () => {
// Navigate to the member settings // Navigate to the member settings
await page.locator('[data-test-nav="settings"]').click(); await page.locator('[data-test-nav="settings"]').click();
await page.locator('[data-test-nav="members-membership"]').click();
// Tiers request can take time, so waiting until there is no connections before interacting with them // Tiers request can take time, so waiting until there is no connections before interacting with them
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
// Expand the premium tier list // Archive if already exists
await page.locator('[data-test-toggle-pub-info]').click(); while (await page.getByTestId('tier-card').filter({hasText: name}).first().isVisible()) {
await page.getByTestId('tier-card').filter({hasText: name}).first().click();
// Archive if already exists await page.getByTestId('tier-detail-modal').getByRole('button', {name: 'Archive tier'}).click();
while (await page.locator('.gh-tier-card').filter({hasText: name}).first().isVisible()) { await page.getByTestId('confirmation-modal').getByRole('button', {name: 'Archive'}).click();
const tierCard = page.locator('.gh-tier-card').filter({hasText: name}).first(); await page.getByTestId('tier-detail-modal').getByRole('button', {name: 'Reactivate tier'}).waitFor();
await tierCard.locator('.gh-tier-card-actions-button').click(); await page.getByTestId('tier-detail-modal').getByRole('button', {name: 'Save & close'}).click();
await tierCard.getByRole('button', {name: 'Archive'}).click();
await page.locator('.modal-content').getByRole('button', {name: 'Archive'}).click();
await page.locator('.modal-content').waitFor({state: 'detached', timeout: 1000});
}
if (!await page.locator('.gh-btn-add-tier').isVisible()) {
await page.locator('[data-test-toggle-pub-info]').click();
}
// Add the tier
await page.locator('.gh-btn-add-tier').click();
const modal = page.locator('.modal-content');
await modal.locator('input#name').first().fill(name);
await modal.locator('#monthlyPrice').fill(`${monthlyPrice}`);
await modal.locator('#yearlyPrice').fill(`${yearlyPrice}`);
if (trialDays) {
await modal.locator('[data-test-toggle="free-trial"]').click();
await modal.locator('#trial').fill(`${trialDays}`);
}
await modal.getByRole('button', {name: 'Add tier'}).click();
await page.waitForSelector('.modal-content input#name', {state: 'detached'});
// Close the premium tier list
await page.locator('[data-test-toggle-pub-info]').click();
// Enable the tier in portal
if (enableInPortal) {
await page.getByRole('button', {name: 'Customize Portal'}).click();
const portalSettings = page.locator('.modal-content').filter({hasText: 'Portal settings'});
if (!await portalSettings.locator('label').filter({hasText: name}).locator('input').first().isChecked()) {
await portalSettings.locator('label').filter({hasText: name}).locator('span').first().click();
} }
if (!await portalSettings.locator('label').filter({hasText: 'Monthly'}).locator('input').first().isChecked()) {
await portalSettings.locator('label').filter({hasText: 'Monthly'}).locator('span').first().click();
}
if (!await portalSettings.locator('label').filter({hasText: 'Yearly'}).locator('input').first().isChecked()) {
await portalSettings.locator('label').filter({hasText: 'Yearly'}).locator('span').first().click();
}
await portalSettings.getByRole('button', {name: 'Save and close'}).click();
await page.waitForSelector('.gh-portal-settings', {state: 'detached'});
}
// Navigate back to the dashboard // Add the tier
await page.goto('/ghost'); await page.getByTestId('tiers').getByRole('button', {name: 'Add tier'}).click();
const modal = page.getByTestId('tier-detail-modal');
await modal.getByLabel('Name').fill(name);
await modal.getByLabel('Monthly price').fill(`${monthlyPrice}`);
await modal.getByLabel('Yearly price').fill(`${yearlyPrice}`);
if (trialDays) {
await modal.getByLabel('Add a free trial').check();
await modal.getByLabel('Trial days').fill(`${trialDays}`);
}
await modal.getByRole('button', {name: 'Save & close'}).click();
await page.locator('[data-testid="tier-card"]:visible').filter({hasText: name}).waitFor();
// Enable the tier in portal
if (enableInPortal) {
await page.getByTestId('portal').getByRole('button', {name: 'Customize'}).click();
const portalSettings = page.getByTestId('portal-modal');
if (!await portalSettings.getByLabel(name).first().isChecked()) {
await portalSettings.getByLabel(name).first().check();
}
if (!await portalSettings.getByLabel('Monthly').first().isChecked()) {
await portalSettings.getByLabel('Monthly').first().check();
}
if (!await portalSettings.getByLabel('Yearly').first().isChecked()) {
await portalSettings.getByLabel('Yearly').first().check();
}
await portalSettings.getByRole('button', {name: 'Save'}).click();
await page.waitForLoadState('networkidle');
await portalSettings.getByRole('button', {name: 'Close'}).click();
}
// Navigate back to the dashboard
await page.goto('/ghost');
});
}; };
/** /**
@ -453,7 +450,6 @@ const goToMembershipPage = async (page) => {
return await test.step('Open Membership settings', async () => { return await test.step('Open Membership settings', async () => {
await page.goto('/ghost'); await page.goto('/ghost');
await page.locator('[data-test-nav="settings"]').click(); await page.locator('[data-test-nav="settings"]').click();
await page.locator('[data-test-nav="members-membership"]').click();
// Tiers request can take time, so waiting until there is no connections before interacting with UI // Tiers request can take time, so waiting until there is no connections before interacting with UI
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
}); });
@ -463,14 +459,13 @@ const goToMembershipPage = async (page) => {
* Get tier card from membership page * Get tier card from membership page
* @param {import('@playwright/test').Page} page * @param {import('@playwright/test').Page} page
* @param {Object} options * @param {Object} options
* @param {String} [options.id] * @param {String} [options.slug]
*/ */
const getTierCardById = async (page, {id}) => { const openTierModal = async (page, {slug}) => {
return await test.step('Expand the premium tier list and find the tier', async () => { return await test.step('Open the tier modal', async () => {
await page.locator('[data-test-toggle-pub-info]').click(); await page.getByTestId('tiers').locator(`[data-testid="tier-card"][data-tier="${slug}"]`).click();
await page.waitForSelector(`[data-test-tier-card="${id}"]`);
return page.locator(`[data-test-tier-card="${id}"]`); return page.getByTestId('tier-detail-modal');
}); });
}; };
@ -522,5 +517,5 @@ module.exports = {
completeStripeSubscription, completeStripeSubscription,
impersonateMember, impersonateMember,
goToMembershipPage, goToMembershipPage,
getTierCardById openTierModal
}; };

View File

@ -2,7 +2,10 @@ let uniqueId = 0;
const getUniqueName = (prefix = 'id') => { const getUniqueName = (prefix = 'id') => {
uniqueId += 1; uniqueId += 1;
return `${prefix} ${uniqueId}`; // uniqueId is incremented to avoid conflicts when running tests in parallel,
// while Date.now() is used to avoid conflicts when running tests locally in
// the same session (e.g. using the Playwright UI)
return `${prefix} ${Date.now()}${uniqueId}`;
}; };
const getSlug = str => str.toLowerCase().split(' ').join('-'); const getSlug = str => str.toLowerCase().split(' ').join('-');