diff --git a/services/web/frontend/js/features/settings/components/linking-section.tsx b/services/web/frontend/js/features/settings/components/linking-section.tsx index 3c69351da6..7807e0d726 100644 --- a/services/web/frontend/js/features/settings/components/linking-section.tsx +++ b/services/web/frontend/js/features/settings/components/linking-section.tsx @@ -1,17 +1,25 @@ +import { useState } from 'react' import { useTranslation } from 'react-i18next' import importOverleafModules from '../../../../macros/import-overleaf-module.macro' import { useSSOContext, SSOSubscription } from '../context/sso-context' import { SSOLinkingWidget } from './linking/sso-widget' - -const integrationLinkingWidgets = importOverleafModules( - 'integrationLinkingWidgets' -) -const referenceLinkingWidgets = importOverleafModules('referenceLinkingWidgets') +import getMeta from '../../../utils/meta' function LinkingSection() { const { t } = useTranslation() const { subscriptions } = useSSOContext() + const [integrationLinkingWidgets] = useState( + () => + getMeta('integrationLinkingWidgets') || + importOverleafModules('integrationLinkingWidgets') + ) + const [referenceLinkingWidgets] = useState( + () => + getMeta('referenceLinkingWidgets') || + importOverleafModules('referenceLinkingWidgets') + ) + const hasIntegrationLinkingSection = integrationLinkingWidgets.length const hasReferencesLinkingSection = referenceLinkingWidgets.length const hasSSOLinkingSection = Object.keys(subscriptions).length > 0 diff --git a/services/web/frontend/js/features/settings/components/root.tsx b/services/web/frontend/js/features/settings/components/root.tsx index 5481033963..4731ae2dfa 100644 --- a/services/web/frontend/js/features/settings/components/root.tsx +++ b/services/web/frontend/js/features/settings/components/root.tsx @@ -64,12 +64,16 @@ function SettingsPageContent() { - {isOverleaf ? : null} -
- -
{isOverleaf ? ( <> + +
+ + ) : null} + + {isOverleaf ? ( + <> +

diff --git a/services/web/frontend/js/features/settings/context/sso-context.tsx b/services/web/frontend/js/features/settings/context/sso-context.tsx index e9f3c1712a..0cc0583c30 100644 --- a/services/web/frontend/js/features/settings/context/sso-context.tsx +++ b/services/web/frontend/js/features/settings/context/sso-context.tsx @@ -35,7 +35,7 @@ type SSOProviderProps = { export function SSOProvider({ children }: SSOProviderProps) { const isMountedRef = useIsMounted() - const oauthProviders = getMeta('ol-oauthProviders') as OAuthProviders + const oauthProviders = getMeta('ol-oauthProviders', {}) as OAuthProviders const thirdPartyIds = getMeta('ol-thirdPartyIds') as ThirdPartyIds const [subscriptions, setSubscriptions] = useState< diff --git a/services/web/frontend/stories/settings/helpers/account-info.js b/services/web/frontend/stories/settings/helpers/account-info.js index cebd81e7bc..e3a893f948 100644 --- a/services/web/frontend/stories/settings/helpers/account-info.js +++ b/services/web/frontend/stories/settings/helpers/account-info.js @@ -9,11 +9,13 @@ export function defaultSetupMocks(fetchMock) { export function setDefaultMeta() { window.metaAttributesCache = window.metaAttributesCache || new Map() window.metaAttributesCache.set('ol-user', { + ...window.metaAttributesCache.get('ol-user'), email: 'sherlock@holmes.co.uk', first_name: 'Sherlock', last_name: 'Holmes', }) window.metaAttributesCache.set('ol-ExposedSettings', { + ...window.metaAttributesCache.get('ol-ExposedSettings'), hasAffiliationsFeature: false, }) window.metaAttributesCache.set('ol-isExternalAuthenticationSystemUsed', false) diff --git a/services/web/frontend/stories/settings/helpers/emails.js b/services/web/frontend/stories/settings/helpers/emails.js index 1a74d17f55..02871c504e 100644 --- a/services/web/frontend/stories/settings/helpers/emails.js +++ b/services/web/frontend/stories/settings/helpers/emails.js @@ -72,6 +72,7 @@ export function errorsMocks(fetchMock) { export function setDefaultMeta() { window.metaAttributesCache = window.metaAttributesCache || new Map() window.metaAttributesCache.set('ol-ExposedSettings', { + ...window.metaAttributesCache.get('ol-ExposedSettings'), hasAffiliationsFeature: true, hasSamlFeature: true, samlInitPath: 'saml/init', diff --git a/services/web/frontend/stories/settings/helpers/leave.js b/services/web/frontend/stories/settings/helpers/leave.js index 7e5003afc1..8b74f03c1b 100644 --- a/services/web/frontend/stories/settings/helpers/leave.js +++ b/services/web/frontend/stories/settings/helpers/leave.js @@ -9,6 +9,9 @@ export function defaultSetupMocks(fetchMock) { export function setDefaultMeta() { window.metaAttributesCache = window.metaAttributesCache || new Map() window.metaAttributesCache.set('ol-usersEmail', 'user@primary.com') - window.metaAttributesCache.set('ol-ExposedSettings', { isOverleaf: true }) + window.metaAttributesCache.set('ol-ExposedSettings', { + ...window.metaAttributesCache.get('ol-ExposedSettings'), + isOverleaf: true, + }) window.metaAttributesCache.set('ol-hasPassword', true) } diff --git a/services/web/frontend/stories/settings/helpers/linking.js b/services/web/frontend/stories/settings/helpers/linking.js index 647fcb0427..410f43a956 100644 --- a/services/web/frontend/stories/settings/helpers/linking.js +++ b/services/web/frontend/stories/settings/helpers/linking.js @@ -12,6 +12,7 @@ export function defaultSetupMocks(fetchMock) { export function setDefaultMeta() { window.metaAttributesCache.set('ol-user', { + ...window.metaAttributesCache.get('ol-user'), features: { github: true, dropbox: true, mendeley: false, zotero: false }, refProviders: { mendeley: true, @@ -55,4 +56,6 @@ export function setDefaultMeta() { linkPath: '/auth/twitter', }, }) + window.metaAttributesCache.delete('integrationLinkingWidgets') + window.metaAttributesCache.delete('referenceLinkingWidgets') } diff --git a/services/web/frontend/stories/settings/helpers/password.js b/services/web/frontend/stories/settings/helpers/password.js index d7ede1d1bb..ebc58cc38e 100644 --- a/services/web/frontend/stories/settings/helpers/password.js +++ b/services/web/frontend/stories/settings/helpers/password.js @@ -22,6 +22,7 @@ export function defaultSetupMocks(fetchMock) { export function setDefaultMeta() { window.metaAttributesCache = window.metaAttributesCache || new Map() window.metaAttributesCache.set('ol-ExposedSettings', { + ...window.metaAttributesCache.get('ol-ExposedSettings'), isOverleaf: true, }) window.metaAttributesCache.set('ol-isExternalAuthenticationSystemUsed', false) diff --git a/services/web/frontend/stories/settings/page.stories.js b/services/web/frontend/stories/settings/page.stories.js index a37073405e..26e456d4d7 100644 --- a/services/web/frontend/stories/settings/page.stories.js +++ b/services/web/frontend/stories/settings/page.stories.js @@ -22,7 +22,7 @@ import { } from './helpers/linking' import { UserProvider } from '../../js/shared/context/user-context' -export const Root = args => { +export const Overleaf = args => { setDefaultLeaveMeta() setDefaultAccountInfoMeta() setDefaultPasswordMeta() @@ -43,6 +43,30 @@ export const Root = args => { ) } +export const ServerPro = args => { + setDefaultAccountInfoMeta() + setDefaultPasswordMeta() + useFetchMock(fetchMock => { + defaultSetupAccountInfoMocks(fetchMock) + defaultSetupPasswordMocks(fetchMock) + }) + + window.metaAttributesCache.set('ol-ExposedSettings', { + ...window.metaAttributesCache.get('ol-ExposedSettings'), + hasAffiliationsFeature: false, + isOverleaf: false, + }) + window.metaAttributesCache.set('integrationLinkingWidgets', []) + window.metaAttributesCache.set('referenceLinkingWidgets', []) + window.metaAttributesCache.delete('ol-oauthProviders') + + return ( + + + + ) +} + export default { title: 'Account Settings / Full Page', component: SettingsPageRoot, diff --git a/services/web/test/frontend/features/settings/components/linking-section.test.tsx b/services/web/test/frontend/features/settings/components/linking-section.test.tsx new file mode 100644 index 0000000000..1492a2d75e --- /dev/null +++ b/services/web/test/frontend/features/settings/components/linking-section.test.tsx @@ -0,0 +1,98 @@ +import { expect } from 'chai' +import { screen, render } from '@testing-library/react' +import fetchMock from 'fetch-mock' +import LinkingSection from '../../../../../frontend/js/features/settings/components/linking-section' +import { UserProvider } from '../../../../../frontend/js/shared/context/user-context' +import { SSOProvider } from '../../../../../frontend/js/features/settings/context/sso-context' + +function renderSectionWithProviders() { + render(, { + wrapper: ({ children }) => ( + + {children} + + ), + }) +} + +const mockOauthProviders = { + google: { + descriptionKey: 'login_with_service', + descriptionOptions: { service: 'Google' }, + name: 'Google', + linkPath: '/auth/google', + }, + orcid: { + descriptionKey: 'oauth_orcid_description', + descriptionOptions: { + link: '/blog/434', + appName: 'Overleaf', + }, + name: 'Orcid', + linkPath: '/auth/orcid', + }, + twitter: { + hideWhenNotLinked: true, + name: 'Twitter', + linkPath: '/auth/twitter', + }, +} + +describe('', function () { + beforeEach(function () { + window.metaAttributesCache = window.metaAttributesCache || new Map() + window.metaAttributesCache.set('ol-user', {}) + + // suppress integrations and references widgets as they cannot be tested in + // all environments + window.metaAttributesCache.set('integrationLinkingWidgets', []) + window.metaAttributesCache.set('referenceLinkingWidgets', []) + + window.metaAttributesCache.set('ol-thirdPartyIds', { + google: 'google-id', + }) + + window.metaAttributesCache.set('ol-oauthProviders', mockOauthProviders) + }) + + afterEach(function () { + window.metaAttributesCache = new Map() + fetchMock.reset() + }) + + it('shows header', async function () { + renderSectionWithProviders() + + screen.getByText('Integrations') + screen.getByText( + 'You can link your Overleaf account with other services to enable the features described below' + ) + }) + + it('lists SSO providers', async function () { + renderSectionWithProviders() + screen.getByText('linked accounts') + + screen.getByText('Google') + screen.getByText('Log in with Google') + screen.getByRole('button', { name: 'Unlink' }) + + screen.getByText('Orcid') + screen.getByText( + /Securely establish your identity by linking your ORCID iD/ + ) + const helpLink = screen.getByRole('link', { name: 'Learn more' }) + expect(helpLink.getAttribute('href')).to.equal('/blog/434') + const linkButton = screen.getByRole('link', { name: 'Link' }) + expect(linkButton.getAttribute('href')).to.equal('/auth/orcid?intent=link') + + expect(screen.queryByText('Twitter')).to.not.exist + }) + + it('does not show providers section when empty', async function () { + window.metaAttributesCache.delete('ol-oauthProviders') + renderSectionWithProviders() + + expect(screen.queryByText('linked accounts')).to.not.exist + }) +})