diff --git a/services/web/app/src/Features/Project/ProjectController.mjs b/services/web/app/src/Features/Project/ProjectController.mjs index ca4109ad80..099b4adbdb 100644 --- a/services/web/app/src/Features/Project/ProjectController.mjs +++ b/services/web/app/src/Features/Project/ProjectController.mjs @@ -459,7 +459,6 @@ 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 90c7b1e931..03c946d7f7 100644 --- a/services/web/app/src/Features/Project/UserSettingsHelper.mjs +++ b/services/web/app/src/Features/Project/UserSettingsHelper.mjs @@ -41,13 +41,6 @@ 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, @@ -64,9 +57,7 @@ async function buildUserSettings(req, res, user) { mathPreview: user.ace.mathPreview, breadcrumbs: user.ace.breadcrumbs, referencesSearchMode: user.ace.referencesSearchMode, - enableNewEditor: isOptOutEnabled - ? enableNewEditorStageFour - : enableNewEditorLegacy, + enableNewEditor: enableNewEditorStageFour, 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 1f3e43d113..8967fe075e 100644 --- a/services/web/app/src/Features/User/UserController.mjs +++ b/services/web/app/src/Features/User/UserController.mjs @@ -22,7 +22,6 @@ 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 = { @@ -412,18 +411,7 @@ async function updateUserSettings(req, res, next) { user.ace.referencesSearchMode = mode } if (body.enableNewEditor != null) { - 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) - } + user.ace.enableNewEditorStageFour = 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 cbd1becc43..4279d8a8c3 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -184,7 +184,6 @@ "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": "", @@ -1237,7 +1236,6 @@ "overleaf_labs": "", "overleaf_logo": "", "overleafs_functionality_meets_my_needs": "", - "overleafs_new_look_is_here": "", "overview": "", "overwrite": "", "overwriting_the_original_folder": "", @@ -2048,12 +2046,9 @@ "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 215d089d17..d573d8813c 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,14 +4,12 @@ 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) { @@ -37,11 +35,9 @@ const TryNewEditorButton = () => { ref={buttonRef} > - {isNewEditorOptOutStage - ? t('switch_to_new_look') - : t('try_the_new_editor_design')} + {t('switch_to_new_look')} - {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 d8404f58ec..ba9b428c63 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,27 +2,15 @@ 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 deleted file mode 100644 index bf722d369f..0000000000 --- a/services/web/frontend/js/features/ide-redesign/components/new-editor-intro-modal.tsx +++ /dev/null @@ -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 ( - - - {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 deleted file mode 100644 index 573b899908..0000000000 --- a/services/web/frontend/js/features/ide-redesign/components/new-editor-promo-modal.tsx +++ /dev/null @@ -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 ( - - - {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 b8a909cbf8..3d0b2484c8 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,5 +1,4 @@ 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' @@ -7,20 +6,15 @@ 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( - // Ensure that feature flag overrides are preserved in the request - `/user/settings?editor-redesign-opt-out=${isNewEditorOptOutStage ? 'enabled' : 'default'}`, - { - body: { enableNewEditor: status }, - } - ) + postJSON('/user/settings', { + body: { enableNewEditor: status }, + }) .then(() => { setUserSettings(current => ({ ...current, @@ -37,7 +31,7 @@ export const useSwitchEnableNewEditorState = () => { }) }) }, - [setUserSettings, isNewEditorOptOutStage] + [setUserSettings] ) 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 87c5dcca70..02c3c83dfd 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,67 +1,16 @@ import { useUserSettingsContext } from '@/shared/context/user-settings-context' import getMeta from '@/utils/meta' -import { isSplitTestEnabled, getSplitTestVariant } from '@/utils/splitTestUtils' -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') === +// For e2e tests purposes, allow overriding to old editor +export const oldEditorOverride = + new URLSearchParams(window.location.search).get('old-editor-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 canUseNewEditorAsExistingUser() || canUseNewEditorAsNewUser() -} - -export const useIsNewEditorEnabledAsExistingUser = () => { - const { userSettings } = useUserSettingsContext() - const hasAccess = canUseNewEditorAsExistingUser() - const enabled = userSettings.enableNewEditor - return hasAccess && enabled + return isOverleaf && !oldEditorOverride } 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 093735a150..d215c3e74b 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,4 +1,3 @@ -.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 fb4a0bbc22..651c066307 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -233,7 +233,6 @@ "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", @@ -1617,7 +1616,6 @@ "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.", @@ -2580,12 +2578,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 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 48ab2c1b29..23b17b645a 100644 --- a/services/web/test/unit/src/User/UserController.test.mjs +++ b/services/web/test/unit/src/User/UserController.test.mjs @@ -8,14 +8,6 @@ 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' @@ -151,12 +143,6 @@ describe('UserController', function () { }, } - ctx.SplitTestHandler = { - promises: { - getAssignment: sinon.stub().resolves({ variant: 'default' }), - }, - } - vi.doMock('../../../../app/src/Features/Helpers/UrlHelper', () => ({ default: ctx.UrlHelper, })) @@ -253,13 +239,6 @@ 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 = { @@ -586,85 +565,36 @@ describe('UserController', function () { }) }) - 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 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 enabled', function () { - beforeEach(function (ctx) { - ctx.SplitTestHandler.promises.getAssignment.resolves({ - variant: 'enabled', - }) + 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 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) - }) + 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) }) })