From bf384683f0cc75fa795c98ea3bb3c3caac4dbe22 Mon Sep 17 00:00:00 2001 From: David <33458145+davidmcpowell@users.noreply.github.com> Date: Tue, 16 Dec 2025 16:06:34 +0000 Subject: [PATCH] Merge pull request #30393 from overleaf/dp-test-revert-2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert "Merge pull request #29916 from overleaf/dp-cleanup-editor-red… GitOrigin-RevId: c2f14fb55e74a1fcb026e37822774724c36bc0dc --- .../Features/Project/ProjectController.mjs | 1 + .../Features/Project/UserSettingsHelper.mjs | 11 +- .../app/src/Features/User/UserController.mjs | 14 +- .../web/frontend/extracted-translations.json | 5 + .../try-new-editor-button.tsx | 8 +- .../ide-react/components/modals/modals.tsx | 14 +- .../components/new-editor-intro-modal.tsx | 80 ++++++++++++ .../components/new-editor-promo-modal.tsx | 102 +++++++++++++++ .../use-switch-enable-new-editor-state.ts | 14 +- .../ide-redesign/utils/new-editor-utils.ts | 59 ++++++++- .../pages/editor/new-editor-promo-modal.scss | 1 + services/web/locales/en.json | 5 + .../unit/src/User/UserController.test.mjs | 120 ++++++++++++++---- 13 files changed, 396 insertions(+), 38 deletions(-) create mode 100644 services/web/frontend/js/features/ide-redesign/components/new-editor-intro-modal.tsx create mode 100644 services/web/frontend/js/features/ide-redesign/components/new-editor-promo-modal.tsx diff --git a/services/web/app/src/Features/Project/ProjectController.mjs b/services/web/app/src/Features/Project/ProjectController.mjs index 099b4adbdb..ca4109ad80 100644 --- a/services/web/app/src/Features/Project/ProjectController.mjs +++ b/services/web/app/src/Features/Project/ProjectController.mjs @@ -459,6 +459,7 @@ const _ProjectController = { 'wf-citations-checker-on-selection', 'writefull-asymetric-queue-size-per-model', 'pdf-dark-mode', + 'editor-redesign-opt-out', 'email-notifications', ].filter(Boolean) diff --git a/services/web/app/src/Features/Project/UserSettingsHelper.mjs b/services/web/app/src/Features/Project/UserSettingsHelper.mjs index 03c946d7f7..90c7b1e931 100644 --- a/services/web/app/src/Features/Project/UserSettingsHelper.mjs +++ b/services/web/app/src/Features/Project/UserSettingsHelper.mjs @@ -41,6 +41,13 @@ async function buildUserSettings(req, res, user) { const enableNewEditorLegacy = user.ace.enableNewEditor ?? defaultLegacyEnableNewEditor + const assignment = await SplitTestHandler.promises.getAssignment( + req, + res, + 'editor-redesign-opt-out' + ) + const isOptOutEnabled = assignment.variant === 'enabled' + return { mode: user.ace.mode, editorTheme: user.ace.theme, @@ -57,7 +64,9 @@ async function buildUserSettings(req, res, user) { mathPreview: user.ace.mathPreview, breadcrumbs: user.ace.breadcrumbs, referencesSearchMode: user.ace.referencesSearchMode, - enableNewEditor: enableNewEditorStageFour, + enableNewEditor: isOptOutEnabled + ? enableNewEditorStageFour + : enableNewEditorLegacy, enableNewEditorLegacy, darkModePdf: user.ace.darkModePdf ?? false, } diff --git a/services/web/app/src/Features/User/UserController.mjs b/services/web/app/src/Features/User/UserController.mjs index 8967fe075e..1f3e43d113 100644 --- a/services/web/app/src/Features/User/UserController.mjs +++ b/services/web/app/src/Features/User/UserController.mjs @@ -22,6 +22,7 @@ import { expressify } from '@overleaf/promise-utils' import { acceptsJson } from '../../infrastructure/RequestContentTypeDetection.mjs' import Modules from '../../infrastructure/Modules.mjs' import OneTimeTokenHandler from '../Security/OneTimeTokenHandler.mjs' +import SplitTestHandler from '../SplitTests/SplitTestHandler.mjs' async function _sendSecurityAlertClearedSessions(user) { const emailOptions = { @@ -411,7 +412,18 @@ async function updateUserSettings(req, res, next) { user.ace.referencesSearchMode = mode } if (body.enableNewEditor != null) { - user.ace.enableNewEditorStageFour = Boolean(body.enableNewEditor) + const assignment = await SplitTestHandler.promises.getAssignment( + 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) { user.ace.darkModePdf = Boolean(body.darkModePdf) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 8cae69d211..65f8c170e2 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -184,6 +184,7 @@ "back_to_subscription": "", "back_to_your_projects": "", "basic_compile_time": "", + "be_one_of_the_first_to_try_out_the_new_and_improved_overleaf_editor": "", "before_you_use_error_assistant": "", "beta_program_already_participating": "", "beta_program_benefits": "", @@ -1234,6 +1235,7 @@ "overleaf_labs": "", "overleaf_logo": "", "overleafs_functionality_meets_my_needs": "", + "overleafs_new_look_is_here": "", "overview": "", "overwrite": "", "overwriting_the_original_folder": "", @@ -2042,9 +2044,12 @@ "try_for_free": "", "try_it_for_free": "", "try_now": "", + "try_out_the_new_editor_now": "", "try_premium_for_free": "", "try_recompile_project_or_troubleshoot": "", "try_relinking_provider": "", + "try_the_new_editor_design": "", + "try_the_new_look": "", "try_to_compile_despite_errors": "", "turn_off": "", "turn_off_link_sharing": "", diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/try-new-editor-button.tsx b/services/web/frontend/js/features/editor-navigation-toolbar/try-new-editor-button.tsx index d573d8813c..215d089d17 100644 --- a/services/web/frontend/js/features/editor-navigation-toolbar/try-new-editor-button.tsx +++ b/services/web/frontend/js/features/editor-navigation-toolbar/try-new-editor-button.tsx @@ -4,12 +4,14 @@ import { useTranslation } from 'react-i18next' import { useSwitchEnableNewEditorState } from '../ide-redesign/hooks/use-switch-enable-new-editor-state' import MaterialIcon from '@/shared/components/material-icon' 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' const TryNewEditorButton = () => { const { t } = useTranslation() const { loading, setEditorRedesignStatus } = useSwitchEnableNewEditorState() const { sendEvent } = useEditorAnalytics() + const isNewEditorOptOutStage = useFeatureFlag('editor-redesign-opt-out') const [buttonElt, setButtonElt] = useState(null) const buttonRef = useCallback((node: HTMLButtonElement) => { if (node !== null) { @@ -35,9 +37,11 @@ const TryNewEditorButton = () => { ref={buttonRef} > - {t('switch_to_new_look')} + {isNewEditorOptOutStage + ? t('switch_to_new_look') + : t('try_the_new_editor_design')} - + {isNewEditorOptOutStage && } ) } diff --git a/services/web/frontend/js/features/ide-react/components/modals/modals.tsx b/services/web/frontend/js/features/ide-react/components/modals/modals.tsx index ba9b428c63..d8404f58ec 100644 --- a/services/web/frontend/js/features/ide-react/components/modals/modals.tsx +++ b/services/web/frontend/js/features/ide-react/components/modals/modals.tsx @@ -2,15 +2,27 @@ import { memo } from 'react' import ForceDisconnected from '@/features/ide-react/components/modals/force-disconnected' import { UnsavedDocs } from '@/features/ide-react/components/unsaved-docs/unsaved-docs' 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 { useFeatureFlag } from '@/shared/context/split-test-context' export const Modals = memo(() => { + const isNewEditorOptOutStage = useFeatureFlag('editor-redesign-opt-out') + return ( <> - + {isNewEditorOptOutStage ? ( + + ) : ( + <> + + + + )} ) }) diff --git a/services/web/frontend/js/features/ide-redesign/components/new-editor-intro-modal.tsx b/services/web/frontend/js/features/ide-redesign/components/new-editor-intro-modal.tsx new file mode 100644 index 0000000000..bf722d369f --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/new-editor-intro-modal.tsx @@ -0,0 +1,80 @@ +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 ( + + + {t('introducing_overleafs_new_look')} + + + {/* eslint-disable-next-line jsx-a11y/media-has-caption */} + +
+ {t('weve_made_it_easier_to_find_and_use_the_tools_you_need_today')} +
+
+ + + {t('explore_what_s_new')} + + +
+ ) +} diff --git a/services/web/frontend/js/features/ide-redesign/components/new-editor-promo-modal.tsx b/services/web/frontend/js/features/ide-redesign/components/new-editor-promo-modal.tsx new file mode 100644 index 0000000000..573b899908 --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/new-editor-promo-modal.tsx @@ -0,0 +1,102 @@ +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 ( + + + {t('overleafs_new_look_is_here')} + + +
+ {t( + 'be_one_of_the_first_to_try_out_the_new_and_improved_overleaf_editor' + )} +
+ {/* eslint-disable-next-line jsx-a11y/media-has-caption */} + +
+ , + ]} + /> +
+
+ + + {t('not_now')} + + + {t('try_the_new_look')} + + +
+ ) +} diff --git a/services/web/frontend/js/features/ide-redesign/hooks/use-switch-enable-new-editor-state.ts b/services/web/frontend/js/features/ide-redesign/hooks/use-switch-enable-new-editor-state.ts index 3d0b2484c8..b8a909cbf8 100644 --- a/services/web/frontend/js/features/ide-redesign/hooks/use-switch-enable-new-editor-state.ts +++ b/services/web/frontend/js/features/ide-redesign/hooks/use-switch-enable-new-editor-state.ts @@ -1,4 +1,5 @@ import { postJSON } from '@/infrastructure/fetch-json' +import { useFeatureFlag } from '@/shared/context/split-test-context' import { useUserSettingsContext } from '@/shared/context/user-settings-context' import { useCallback, useState } from 'react' @@ -6,15 +7,20 @@ export const useSwitchEnableNewEditorState = () => { const [loading, setLoading] = useState(false) const [error, setError] = useState('') const { setUserSettings } = useUserSettingsContext() + const isNewEditorOptOutStage = useFeatureFlag('editor-redesign-opt-out') const setEditorRedesignStatus = useCallback( (status: boolean): Promise => { setLoading(true) setError('') return new Promise((resolve, reject) => { - postJSON('/user/settings', { - body: { enableNewEditor: status }, - }) + postJSON( + // Ensure that feature flag overrides are preserved in the request + `/user/settings?editor-redesign-opt-out=${isNewEditorOptOutStage ? 'enabled' : 'default'}`, + { + body: { enableNewEditor: status }, + } + ) .then(() => { setUserSettings(current => ({ ...current, @@ -31,7 +37,7 @@ export const useSwitchEnableNewEditorState = () => { }) }) }, - [setUserSettings] + [setUserSettings, isNewEditorOptOutStage] ) return { loading, error, setEditorRedesignStatus } } diff --git a/services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts b/services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts index 02c3c83dfd..87c5dcca70 100644 --- a/services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts +++ b/services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts @@ -1,16 +1,67 @@ import { useUserSettingsContext } from '@/shared/context/user-settings-context' import getMeta from '@/utils/meta' +import { isSplitTestEnabled, getSplitTestVariant } from '@/utils/splitTestUtils' -// For e2e tests purposes, allow overriding to old editor -export const oldEditorOverride = - new URLSearchParams(window.location.search).get('old-editor-override') === +const ignoringUserCutoffDate = + new URLSearchParams(window.location.search).get('skip-new-user-check') === + 'true' + +// 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' // 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 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 = () => { - return isOverleaf && !oldEditorOverride + return canUseNewEditorAsExistingUser() || canUseNewEditorAsNewUser() +} + +export const useIsNewEditorEnabledAsExistingUser = () => { + const { userSettings } = useUserSettingsContext() + const hasAccess = canUseNewEditorAsExistingUser() + const enabled = userSettings.enableNewEditor + return hasAccess && enabled } export const useIsNewEditorEnabled = () => { diff --git a/services/web/frontend/stylesheets/pages/editor/new-editor-promo-modal.scss b/services/web/frontend/stylesheets/pages/editor/new-editor-promo-modal.scss index d215c3e74b..093735a150 100644 --- a/services/web/frontend/stylesheets/pages/editor/new-editor-promo-modal.scss +++ b/services/web/frontend/stylesheets/pages/editor/new-editor-promo-modal.scss @@ -1,3 +1,4 @@ +.new-editor-promo-modal-body, .new-editor-intro-modal-body { display: flex; flex-direction: column; diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 6fb5525765..d35cc921ae 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -233,6 +233,7 @@ "basic": "Basic", "basic_compile_time": "Basic compile time", "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", "beta": "Beta", "beta_feature_badge": "Beta feature badge", @@ -1614,6 +1615,7 @@ "overleaf_plans_and_pricing": "overleaf plans and pricing", "overleaf_template_gallery": "overleaf template gallery", "overleafs_functionality_meets_my_needs": "Overleaf’s functionality meets my needs.", + "overleafs_new_look_is_here": "__appName__’s new look is here", "overview": "Overview", "overwrite": "Overwrite", "overwriting_the_original_folder": "Overwriting the original folder will delete it and all the files it contains.", @@ -2574,9 +2576,12 @@ "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 out the new design now (you can switch back at any time), or <0>read more about the changes we’re making.", "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.", "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", "turn_off": "Turn off", "turn_off_link_sharing": "Turn off link sharing", diff --git a/services/web/test/unit/src/User/UserController.test.mjs b/services/web/test/unit/src/User/UserController.test.mjs index 23b17b645a..48ab2c1b29 100644 --- a/services/web/test/unit/src/User/UserController.test.mjs +++ b/services/web/test/unit/src/User/UserController.test.mjs @@ -8,6 +8,14 @@ vi.mock('../../../../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 () { beforeEach(async function (ctx) { ctx.user_id = '323123' @@ -143,6 +151,12 @@ describe('UserController', function () { }, } + ctx.SplitTestHandler = { + promises: { + getAssignment: sinon.stub().resolves({ variant: 'default' }), + }, + } + vi.doMock('../../../../app/src/Features/Helpers/UrlHelper', () => ({ default: ctx.UrlHelper, })) @@ -239,6 +253,13 @@ describe('UserController', function () { default: ctx.Modules, })) + vi.doMock( + '../../../../app/src/Features/SplitTests/SplitTestHandler.mjs', + () => ({ + default: ctx.SplitTestHandler, + }) + ) + ctx.UserController = (await import(modulePath)).default ctx.res = { @@ -565,36 +586,85 @@ describe('UserController', function () { }) }) - it('should set enableNewEditorStageFour to true', function (ctx) { - return new Promise(resolve => { - ctx.req.body = { enableNewEditor: true } - ctx.res.sendStatus = code => { - ctx.user.ace.enableNewEditorStageFour.should.equal(true) - resolve() - } - ctx.UserController.updateUserSettings(ctx.req, ctx.res) + describe('when editor-redesign-opt-out is set to default', function () { + beforeEach(function (ctx) { + ctx.SplitTestHandler.promises.getAssignment.resolves({ + variant: 'default', + }) + }) + + it('should set enableNewEditor to true', function (ctx) { + 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) + }) }) }) - 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) + describe('when editor-redesign-opt-out is set to enabled', function () { + beforeEach(function (ctx) { + ctx.SplitTestHandler.promises.getAssignment.resolves({ + variant: 'enabled', + }) }) - }) - 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) + it('should set enableNewEditorStageFour to true', function (ctx) { + return new Promise(resolve => { + ctx.req.body = { enableNewEditor: true } + ctx.res.sendStatus = code => { + ctx.user.ace.enableNewEditorStageFour.should.equal(true) + resolve() + } + 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) + }) }) })