Merge pull request #29916 from overleaf/dp-cleanup-editor-redesign-opt-out

Cleanup editor-redesign-opt-out feature flag

GitOrigin-RevId: 6a1dc6908fa6276181f9e3996e0b14cfb7537786
This commit is contained in:
David
2025-12-17 09:06:46 +00:00
committed by Copybot
parent efcf776a2d
commit 1d831b5776
13 changed files with 38 additions and 396 deletions
@@ -459,7 +459,6 @@ const _ProjectController = {
'wf-citations-checker-on-selection', 'wf-citations-checker-on-selection',
'writefull-asymetric-queue-size-per-model', 'writefull-asymetric-queue-size-per-model',
'pdf-dark-mode', 'pdf-dark-mode',
'editor-redesign-opt-out',
'email-notifications', 'email-notifications',
].filter(Boolean) ].filter(Boolean)
@@ -41,13 +41,6 @@ async function buildUserSettings(req, res, user) {
const enableNewEditorLegacy = const enableNewEditorLegacy =
user.ace.enableNewEditor ?? defaultLegacyEnableNewEditor user.ace.enableNewEditor ?? defaultLegacyEnableNewEditor
const assignment = await SplitTestHandler.promises.getAssignment(
req,
res,
'editor-redesign-opt-out'
)
const isOptOutEnabled = assignment.variant === 'enabled'
return { return {
mode: user.ace.mode, mode: user.ace.mode,
editorTheme: user.ace.theme, editorTheme: user.ace.theme,
@@ -64,9 +57,7 @@ async function buildUserSettings(req, res, user) {
mathPreview: user.ace.mathPreview, mathPreview: user.ace.mathPreview,
breadcrumbs: user.ace.breadcrumbs, breadcrumbs: user.ace.breadcrumbs,
referencesSearchMode: user.ace.referencesSearchMode, referencesSearchMode: user.ace.referencesSearchMode,
enableNewEditor: isOptOutEnabled enableNewEditor: enableNewEditorStageFour,
? enableNewEditorStageFour
: enableNewEditorLegacy,
enableNewEditorLegacy, enableNewEditorLegacy,
darkModePdf: user.ace.darkModePdf ?? false, darkModePdf: user.ace.darkModePdf ?? false,
} }
@@ -22,7 +22,6 @@ import { expressify } from '@overleaf/promise-utils'
import { acceptsJson } from '../../infrastructure/RequestContentTypeDetection.mjs' import { acceptsJson } from '../../infrastructure/RequestContentTypeDetection.mjs'
import Modules from '../../infrastructure/Modules.mjs' import Modules from '../../infrastructure/Modules.mjs'
import OneTimeTokenHandler from '../Security/OneTimeTokenHandler.mjs' import OneTimeTokenHandler from '../Security/OneTimeTokenHandler.mjs'
import SplitTestHandler from '../SplitTests/SplitTestHandler.mjs'
async function _sendSecurityAlertClearedSessions(user) { async function _sendSecurityAlertClearedSessions(user) {
const emailOptions = { const emailOptions = {
@@ -412,18 +411,7 @@ async function updateUserSettings(req, res, next) {
user.ace.referencesSearchMode = mode user.ace.referencesSearchMode = mode
} }
if (body.enableNewEditor != null) { if (body.enableNewEditor != null) {
const assignment = await SplitTestHandler.promises.getAssignment( user.ace.enableNewEditorStageFour = Boolean(body.enableNewEditor)
req,
res,
'editor-redesign-opt-out'
)
const isOptOutStageEnabled = assignment.variant === 'enabled'
if (isOptOutStageEnabled) {
user.ace.enableNewEditorStageFour = Boolean(body.enableNewEditor)
} else {
user.ace.enableNewEditor = Boolean(body.enableNewEditor)
}
} }
if (body.darkModePdf != null) { if (body.darkModePdf != null) {
user.ace.darkModePdf = Boolean(body.darkModePdf) user.ace.darkModePdf = Boolean(body.darkModePdf)
@@ -184,7 +184,6 @@
"back_to_subscription": "", "back_to_subscription": "",
"back_to_your_projects": "", "back_to_your_projects": "",
"basic_compile_time": "", "basic_compile_time": "",
"be_one_of_the_first_to_try_out_the_new_and_improved_overleaf_editor": "",
"before_you_use_error_assistant": "", "before_you_use_error_assistant": "",
"beta_program_already_participating": "", "beta_program_already_participating": "",
"beta_program_benefits": "", "beta_program_benefits": "",
@@ -1235,7 +1234,6 @@
"overleaf_labs": "", "overleaf_labs": "",
"overleaf_logo": "", "overleaf_logo": "",
"overleafs_functionality_meets_my_needs": "", "overleafs_functionality_meets_my_needs": "",
"overleafs_new_look_is_here": "",
"overview": "", "overview": "",
"overwrite": "", "overwrite": "",
"overwriting_the_original_folder": "", "overwriting_the_original_folder": "",
@@ -2044,12 +2042,9 @@
"try_for_free": "", "try_for_free": "",
"try_it_for_free": "", "try_it_for_free": "",
"try_now": "", "try_now": "",
"try_out_the_new_editor_now": "",
"try_premium_for_free": "", "try_premium_for_free": "",
"try_recompile_project_or_troubleshoot": "", "try_recompile_project_or_troubleshoot": "",
"try_relinking_provider": "", "try_relinking_provider": "",
"try_the_new_editor_design": "",
"try_the_new_look": "",
"try_to_compile_despite_errors": "", "try_to_compile_despite_errors": "",
"turn_off": "", "turn_off": "",
"turn_off_link_sharing": "", "turn_off_link_sharing": "",
@@ -4,14 +4,12 @@ import { useTranslation } from 'react-i18next'
import { useSwitchEnableNewEditorState } from '../ide-redesign/hooks/use-switch-enable-new-editor-state' import { useSwitchEnableNewEditorState } from '../ide-redesign/hooks/use-switch-enable-new-editor-state'
import MaterialIcon from '@/shared/components/material-icon' import MaterialIcon from '@/shared/components/material-icon'
import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics' import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics'
import { useFeatureFlag } from '@/shared/context/split-test-context'
import OldEditorWarningTooltip from '../ide-redesign/components/old-editor-warning-tooltip' import OldEditorWarningTooltip from '../ide-redesign/components/old-editor-warning-tooltip'
const TryNewEditorButton = () => { const TryNewEditorButton = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { loading, setEditorRedesignStatus } = useSwitchEnableNewEditorState() const { loading, setEditorRedesignStatus } = useSwitchEnableNewEditorState()
const { sendEvent } = useEditorAnalytics() const { sendEvent } = useEditorAnalytics()
const isNewEditorOptOutStage = useFeatureFlag('editor-redesign-opt-out')
const [buttonElt, setButtonElt] = useState<HTMLButtonElement | null>(null) const [buttonElt, setButtonElt] = useState<HTMLButtonElement | null>(null)
const buttonRef = useCallback((node: HTMLButtonElement) => { const buttonRef = useCallback((node: HTMLButtonElement) => {
if (node !== null) { if (node !== null) {
@@ -37,11 +35,9 @@ const TryNewEditorButton = () => {
ref={buttonRef} ref={buttonRef}
> >
<MaterialIcon type="fiber_new" /> <MaterialIcon type="fiber_new" />
{isNewEditorOptOutStage {t('switch_to_new_look')}
? t('switch_to_new_look')
: t('try_the_new_editor_design')}
</OLButton> </OLButton>
{isNewEditorOptOutStage && <OldEditorWarningTooltip target={buttonElt} />} <OldEditorWarningTooltip target={buttonElt} />
</div> </div>
) )
} }
@@ -2,27 +2,15 @@ import { memo } from 'react'
import ForceDisconnected from '@/features/ide-react/components/modals/force-disconnected' import ForceDisconnected from '@/features/ide-react/components/modals/force-disconnected'
import { UnsavedDocs } from '@/features/ide-react/components/unsaved-docs/unsaved-docs' import { UnsavedDocs } from '@/features/ide-react/components/unsaved-docs/unsaved-docs'
import SystemMessages from '@/shared/components/system-messages' import SystemMessages from '@/shared/components/system-messages'
import NewEditorPromoModal from '@/features/ide-redesign/components/new-editor-promo-modal'
import NewEditorIntroModal from '@/features/ide-redesign/components/new-editor-intro-modal'
import NewEditorOptOutIntroModal from '@/features/ide-redesign/components/new-editor-opt-out-intro-modal' import NewEditorOptOutIntroModal from '@/features/ide-redesign/components/new-editor-opt-out-intro-modal'
import { useFeatureFlag } from '@/shared/context/split-test-context'
export const Modals = memo(() => { export const Modals = memo(() => {
const isNewEditorOptOutStage = useFeatureFlag('editor-redesign-opt-out')
return ( return (
<> <>
<ForceDisconnected /> <ForceDisconnected />
<UnsavedDocs /> <UnsavedDocs />
<SystemMessages /> <SystemMessages />
{isNewEditorOptOutStage ? ( <NewEditorOptOutIntroModal />
<NewEditorOptOutIntroModal />
) : (
<>
<NewEditorPromoModal />
<NewEditorIntroModal />
</>
)}
</> </>
) )
}) })
@@ -1,80 +0,0 @@
import {
OLModal,
OLModalBody,
OLModalFooter,
OLModalHeader,
OLModalTitle,
} from '@/shared/components/ol/ol-modal'
import { useCallback, useEffect, useState } from 'react'
import useTutorial from '@/shared/hooks/promotions/use-tutorial'
import OLButton from '@/shared/components/ol/ol-button'
import { useTranslation } from 'react-i18next'
import { useEditorContext } from '@/shared/context/editor-context'
import { useIsNewEditorEnabledAsExistingUser } from '../utils/new-editor-utils'
import { useNewEditorTourContext } from '../contexts/new-editor-tour-context'
import promoVideo from './new-editor-promo-video.mp4'
const TUTORIAL_KEY = 'new-editor-intro'
export default function NewEditorIntroModal() {
const { inactiveTutorials } = useEditorContext()
const {
tryShowingPopup,
showPopup: showModal,
dismissTutorial,
completeTutorial,
clearPopup,
} = useTutorial(TUTORIAL_KEY, {
name: TUTORIAL_KEY,
})
const { startTour } = useNewEditorTourContext()
const { t } = useTranslation()
const canShow = useIsNewEditorEnabledAsExistingUser()
const [hasShown, setHasShown] = useState(false)
useEffect(() => {
if (canShow && !hasShown && !inactiveTutorials.includes(TUTORIAL_KEY)) {
tryShowingPopup('notification-prompt')
setHasShown(true)
}
}, [tryShowingPopup, inactiveTutorials, canShow, hasShown])
const startProductTour = useCallback(() => {
completeTutorial({ event: 'notification-click', action: 'complete' })
startTour()
clearPopup()
}, [completeTutorial, startTour, clearPopup])
const closeModal = useCallback(() => {
dismissTutorial('notification-dismiss')
clearPopup()
}, [dismissTutorial, clearPopup])
if (!canShow) {
return null
}
return (
<OLModal show={showModal} onHide={closeModal}>
<OLModalHeader>
<OLModalTitle>{t('introducing_overleafs_new_look')}</OLModalTitle>
</OLModalHeader>
<OLModalBody className="new-editor-intro-modal-body">
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
<video autoPlay loop muted>
<source src={promoVideo} type="video/mp4" />
</video>
<div>
{t('weve_made_it_easier_to_find_and_use_the_tools_you_need_today')}
</div>
</OLModalBody>
<OLModalFooter>
<OLButton onClick={startProductTour} variant="primary">
{t('explore_what_s_new')}
</OLButton>
</OLModalFooter>
</OLModal>
)
}
@@ -1,102 +0,0 @@
import {
OLModal,
OLModalBody,
OLModalFooter,
OLModalHeader,
OLModalTitle,
} from '@/shared/components/ol/ol-modal'
import { useSwitchEnableNewEditorState } from '../hooks/use-switch-enable-new-editor-state'
import { useCallback, useEffect, useState } from 'react'
import useTutorial from '@/shared/hooks/promotions/use-tutorial'
import OLButton from '@/shared/components/ol/ol-button'
import { Trans, useTranslation } from 'react-i18next'
import { useEditorContext } from '@/shared/context/editor-context'
import {
canUseNewEditorAsExistingUser,
useIsNewEditorEnabled,
} from '../utils/new-editor-utils'
import promoVideo from './new-editor-promo-video.mp4'
const TUTORIAL_KEY = 'new-editor-opt-in'
export default function NewEditorPromoModal() {
const { inactiveTutorials } = useEditorContext()
const {
tryShowingPopup,
showPopup: showModal,
dismissTutorial,
completeTutorial,
clearPopup,
} = useTutorial(TUTORIAL_KEY, {
name: TUTORIAL_KEY,
})
const { setEditorRedesignStatus } = useSwitchEnableNewEditorState()
const { t } = useTranslation()
const newEditor = useIsNewEditorEnabled()
const canShow = canUseNewEditorAsExistingUser() && !newEditor
const [hasShown, setHasShown] = useState(false)
useEffect(() => {
if (canShow && !hasShown && !inactiveTutorials.includes(TUTORIAL_KEY)) {
tryShowingPopup('notification-prompt')
setHasShown(true)
}
}, [tryShowingPopup, inactiveTutorials, canShow, hasShown])
const switchToNewEditor = useCallback(() => {
setEditorRedesignStatus(true)
completeTutorial({ event: 'notification-click', action: 'complete' })
clearPopup()
}, [setEditorRedesignStatus, completeTutorial, clearPopup])
const closeModal = useCallback(() => {
dismissTutorial('notification-dismiss')
clearPopup()
}, [dismissTutorial, clearPopup])
if (!canShow) {
return null
}
return (
<OLModal show={showModal} onHide={closeModal}>
<OLModalHeader>
<OLModalTitle>{t('overleafs_new_look_is_here')}</OLModalTitle>
</OLModalHeader>
<OLModalBody className="new-editor-promo-modal-body">
<div>
{t(
'be_one_of_the_first_to_try_out_the_new_and_improved_overleaf_editor'
)}
</div>
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
<video autoPlay loop muted>
<source src={promoVideo} type="video/mp4" />
</video>
<div>
<Trans
i18nKey="try_out_the_new_editor_now"
components={[
/* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
<a
href="https://www.overleaf.com/blog/introducing-overleafs-new-look"
target="_blank"
rel="noopener noreferrer"
key="link"
/>,
]}
/>
</div>
</OLModalBody>
<OLModalFooter>
<OLButton onClick={closeModal} variant="secondary">
{t('not_now')}
</OLButton>
<OLButton onClick={switchToNewEditor} variant="primary">
{t('try_the_new_look')}
</OLButton>
</OLModalFooter>
</OLModal>
)
}
@@ -1,5 +1,4 @@
import { postJSON } from '@/infrastructure/fetch-json' import { postJSON } from '@/infrastructure/fetch-json'
import { useFeatureFlag } from '@/shared/context/split-test-context'
import { useUserSettingsContext } from '@/shared/context/user-settings-context' import { useUserSettingsContext } from '@/shared/context/user-settings-context'
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
@@ -7,20 +6,15 @@ export const useSwitchEnableNewEditorState = () => {
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [error, setError] = useState('') const [error, setError] = useState('')
const { setUserSettings } = useUserSettingsContext() const { setUserSettings } = useUserSettingsContext()
const isNewEditorOptOutStage = useFeatureFlag('editor-redesign-opt-out')
const setEditorRedesignStatus = useCallback( const setEditorRedesignStatus = useCallback(
(status: boolean): Promise<void> => { (status: boolean): Promise<void> => {
setLoading(true) setLoading(true)
setError('') setError('')
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
postJSON( postJSON('/user/settings', {
// Ensure that feature flag overrides are preserved in the request body: { enableNewEditor: status },
`/user/settings?editor-redesign-opt-out=${isNewEditorOptOutStage ? 'enabled' : 'default'}`, })
{
body: { enableNewEditor: status },
}
)
.then(() => { .then(() => {
setUserSettings(current => ({ setUserSettings(current => ({
...current, ...current,
@@ -37,7 +31,7 @@ export const useSwitchEnableNewEditorState = () => {
}) })
}) })
}, },
[setUserSettings, isNewEditorOptOutStage] [setUserSettings]
) )
return { loading, error, setEditorRedesignStatus } return { loading, error, setEditorRedesignStatus }
} }
@@ -1,67 +1,16 @@
import { useUserSettingsContext } from '@/shared/context/user-settings-context' import { useUserSettingsContext } from '@/shared/context/user-settings-context'
import getMeta from '@/utils/meta' import getMeta from '@/utils/meta'
import { isSplitTestEnabled, getSplitTestVariant } from '@/utils/splitTestUtils'
const ignoringUserCutoffDate = // For e2e tests purposes, allow overriding to old editor
new URLSearchParams(window.location.search).get('skip-new-user-check') === export const oldEditorOverride =
'true' new URLSearchParams(window.location.search).get('old-editor-override') ===
// For E2E tests, allow forcing a user to be treated as an existing user
const existingUserOverride =
new URLSearchParams(window.location.search).get('existing-user-override') ===
'true' 'true'
// We don't want to enable the new editor on server-pro/CE until we have fully rolled it out on SaaS // We don't want to enable the new editor on server-pro/CE until we have fully rolled it out on SaaS
const { isOverleaf } = getMeta('ol-ExposedSettings') const { isOverleaf } = getMeta('ol-ExposedSettings')
const SPLIT_TEST_USER_CUTOFF_DATE = new Date(Date.UTC(2025, 8, 23, 13, 0, 0)) // 2pm British Summer Time on September 23, 2025
const NEW_USER_CUTOFF_DATE = new Date(Date.UTC(2025, 10, 12, 12, 0, 0)) // 12pm GMT on November 12, 2025
export const isNewUser = () => {
if (existingUserOverride) return false
if (ignoringUserCutoffDate) return true
const user = getMeta('ol-user')
if (!user.signUpDate) return false
const createdAt = new Date(user.signUpDate)
return createdAt > NEW_USER_CUTOFF_DATE
}
export const isSplitTestUser = () => {
if (existingUserOverride) return false
const user = getMeta('ol-user')
if (!user.signUpDate) return false
const createdAt = new Date(user.signUpDate)
return (
createdAt > SPLIT_TEST_USER_CUTOFF_DATE && createdAt <= NEW_USER_CUTOFF_DATE
)
}
export const canUseNewEditorAsExistingUser = () => {
return !canUseNewEditorAsNewUser() && isSplitTestEnabled('editor-redesign')
}
export const canUseNewEditorAsNewUser = () => {
const newUserTestVariant = getSplitTestVariant('editor-redesign-new-users')
return (
isOverleaf &&
(isNewUser() || (isSplitTestUser() && newUserTestVariant !== 'default'))
)
}
export const canUseNewEditor = () => { export const canUseNewEditor = () => {
return canUseNewEditorAsExistingUser() || canUseNewEditorAsNewUser() return isOverleaf && !oldEditorOverride
}
export const useIsNewEditorEnabledAsExistingUser = () => {
const { userSettings } = useUserSettingsContext()
const hasAccess = canUseNewEditorAsExistingUser()
const enabled = userSettings.enableNewEditor
return hasAccess && enabled
} }
export const useIsNewEditorEnabled = () => { export const useIsNewEditorEnabled = () => {
@@ -1,4 +1,3 @@
.new-editor-promo-modal-body,
.new-editor-intro-modal-body { .new-editor-intro-modal-body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
-5
View File
@@ -233,7 +233,6 @@
"basic": "Basic", "basic": "Basic",
"basic_compile_time": "Basic compile time", "basic_compile_time": "Basic compile time",
"basic_compile_timeout_on_fast_servers": "Basic compile timeout on fast servers", "basic_compile_timeout_on_fast_servers": "Basic compile timeout on fast servers",
"be_one_of_the_first_to_try_out_the_new_and_improved_overleaf_editor": "Be one of the first to try out the improved __appName__ editor design, bringing you a cleaner, less cluttered interface to help you focus on what matters—your work.",
"before_you_use_error_assistant": "Before you use Error Assist", "before_you_use_error_assistant": "Before you use Error Assist",
"beta": "Beta", "beta": "Beta",
"beta_feature_badge": "Beta feature badge", "beta_feature_badge": "Beta feature badge",
@@ -1615,7 +1614,6 @@
"overleaf_plans_and_pricing": "overleaf plans and pricing", "overleaf_plans_and_pricing": "overleaf plans and pricing",
"overleaf_template_gallery": "overleaf template gallery", "overleaf_template_gallery": "overleaf template gallery",
"overleafs_functionality_meets_my_needs": "Overleaf’s functionality meets my needs.", "overleafs_functionality_meets_my_needs": "Overleaf’s functionality meets my needs.",
"overleafs_new_look_is_here": "__appName__’s new look is here",
"overview": "Overview", "overview": "Overview",
"overwrite": "Overwrite", "overwrite": "Overwrite",
"overwriting_the_original_folder": "Overwriting the original folder will delete it and all the files it contains.", "overwriting_the_original_folder": "Overwriting the original folder will delete it and all the files it contains.",
@@ -2576,12 +2574,9 @@
"try_for_free": "Try for free", "try_for_free": "Try for free",
"try_it_for_free": "Try it for free", "try_it_for_free": "Try it for free",
"try_now": "Try Now", "try_now": "Try Now",
"try_out_the_new_editor_now": "Try out the new design now (you can switch back at any time), or <0>read more about the changes we’re making</0>.",
"try_premium_for_free": "Try Premium for free", "try_premium_for_free": "Try Premium for free",
"try_recompile_project_or_troubleshoot": "Please try recompiling the project from scratch, and if that doesn’t help, follow our <0>troubleshooting guide</0>.", "try_recompile_project_or_troubleshoot": "Please try recompiling the project from scratch, and if that doesn’t help, follow our <0>troubleshooting guide</0>.",
"try_relinking_provider": "It looks like you need to re-link your __provider__ account.", "try_relinking_provider": "It looks like you need to re-link your __provider__ account.",
"try_the_new_editor_design": "Try the new editor design",
"try_the_new_look": "Try the new look",
"try_to_compile_despite_errors": "Try to compile despite errors", "try_to_compile_despite_errors": "Try to compile despite errors",
"turn_off": "Turn off", "turn_off": "Turn off",
"turn_off_link_sharing": "Turn off link sharing", "turn_off_link_sharing": "Turn off link sharing",
@@ -8,14 +8,6 @@ vi.mock('../../../../app/src/Features/Errors/Errors.js', () => {
return vi.importActual('../../../../app/src/Features/Errors/Errors.js') return vi.importActual('../../../../app/src/Features/Errors/Errors.js')
}) })
vi.mock('../../../../app/src/infrastructure/Metrics.js', () => ({
default: {
analyticsQueue: {
inc: vi.fn(),
},
},
}))
describe('UserController', function () { describe('UserController', function () {
beforeEach(async function (ctx) { beforeEach(async function (ctx) {
ctx.user_id = '323123' ctx.user_id = '323123'
@@ -151,12 +143,6 @@ describe('UserController', function () {
}, },
} }
ctx.SplitTestHandler = {
promises: {
getAssignment: sinon.stub().resolves({ variant: 'default' }),
},
}
vi.doMock('../../../../app/src/Features/Helpers/UrlHelper', () => ({ vi.doMock('../../../../app/src/Features/Helpers/UrlHelper', () => ({
default: ctx.UrlHelper, default: ctx.UrlHelper,
})) }))
@@ -253,13 +239,6 @@ describe('UserController', function () {
default: ctx.Modules, default: ctx.Modules,
})) }))
vi.doMock(
'../../../../app/src/Features/SplitTests/SplitTestHandler.mjs',
() => ({
default: ctx.SplitTestHandler,
})
)
ctx.UserController = (await import(modulePath)).default ctx.UserController = (await import(modulePath)).default
ctx.res = { ctx.res = {
@@ -586,85 +565,36 @@ describe('UserController', function () {
}) })
}) })
describe('when editor-redesign-opt-out is set to default', function () { it('should set enableNewEditorStageFour to true', function (ctx) {
beforeEach(function (ctx) { return new Promise(resolve => {
ctx.SplitTestHandler.promises.getAssignment.resolves({ ctx.req.body = { enableNewEditor: true }
variant: 'default', ctx.res.sendStatus = code => {
}) ctx.user.ace.enableNewEditorStageFour.should.equal(true)
}) resolve()
}
it('should set enableNewEditor to true', function (ctx) { ctx.UserController.updateUserSettings(ctx.req, ctx.res)
return new Promise(resolve => {
ctx.req.body = { enableNewEditor: true }
ctx.res.sendStatus = code => {
ctx.user.ace.enableNewEditor.should.equal(true)
resolve()
}
ctx.UserController.updateUserSettings(ctx.req, ctx.res)
})
})
it('should set enableNewEditor to false', function (ctx) {
return new Promise(resolve => {
ctx.req.body = { enableNewEditor: false }
ctx.res.sendStatus = code => {
ctx.user.ace.enableNewEditor.should.equal(false)
resolve()
}
ctx.UserController.updateUserSettings(ctx.req, ctx.res)
})
})
it('should keep enableNewEditor a boolean', function (ctx) {
return new Promise(resolve => {
ctx.req.body = { enableNewEditor: 'foobar' }
ctx.res.sendStatus = code => {
ctx.user.ace.enableNewEditor.should.equal(true)
resolve()
}
ctx.UserController.updateUserSettings(ctx.req, ctx.res)
})
}) })
}) })
describe('when editor-redesign-opt-out is set to enabled', function () { it('should set enableNewEditorStageFour to false', function (ctx) {
beforeEach(function (ctx) { return new Promise(resolve => {
ctx.SplitTestHandler.promises.getAssignment.resolves({ ctx.req.body = { enableNewEditor: false }
variant: 'enabled', ctx.res.sendStatus = code => {
}) ctx.user.ace.enableNewEditorStageFour.should.equal(false)
resolve()
}
ctx.UserController.updateUserSettings(ctx.req, ctx.res)
}) })
})
it('should set enableNewEditorStageFour to true', function (ctx) { it('should keep enableNewEditorStageFour a boolean', function (ctx) {
return new Promise(resolve => { return new Promise(resolve => {
ctx.req.body = { enableNewEditor: true } ctx.req.body = { enableNewEditor: 'foobar' }
ctx.res.sendStatus = code => { ctx.res.sendStatus = code => {
ctx.user.ace.enableNewEditorStageFour.should.equal(true) ctx.user.ace.enableNewEditorStageFour.should.equal(true)
resolve() resolve()
} }
ctx.UserController.updateUserSettings(ctx.req, ctx.res) ctx.UserController.updateUserSettings(ctx.req, ctx.res)
})
})
it('should set enableNewEditorStageFour to false', function (ctx) {
return new Promise(resolve => {
ctx.req.body = { enableNewEditor: false }
ctx.res.sendStatus = code => {
ctx.user.ace.enableNewEditorStageFour.should.equal(false)
resolve()
}
ctx.UserController.updateUserSettings(ctx.req, ctx.res)
})
})
it('should keep enableNewEditorStageFour a boolean', function (ctx) {
return new Promise(resolve => {
ctx.req.body = { enableNewEditor: 'foobar' }
ctx.res.sendStatus = code => {
ctx.user.ace.enableNewEditorStageFour.should.equal(true)
resolve()
}
ctx.UserController.updateUserSettings(ctx.req, ctx.res)
})
}) })
}) })