Răsfoiți Sursa

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

(Re)Cleanup editor-redesign-opt-out feature flag

GitOrigin-RevId: afefced1f77de2b8b27e1333ffa647ac41561e31
Mathias Jakobsen 7 luni în urmă
părinte
comite
5e49f421c2

+ 0 - 1
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)
 

+ 1 - 10
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,
   }

+ 1 - 13
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)

+ 0 - 5
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": "",

+ 2 - 6
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<HTMLButtonElement | null>(null)
   const buttonRef = useCallback((node: HTMLButtonElement) => {
     if (node !== null) {
@@ -37,11 +35,9 @@ const TryNewEditorButton = () => {
         ref={buttonRef}
       >
         <MaterialIcon type="fiber_new" />
-        {isNewEditorOptOutStage
-          ? t('switch_to_new_look')
-          : t('try_the_new_editor_design')}
+        {t('switch_to_new_look')}
       </OLButton>
-      {isNewEditorOptOutStage && <OldEditorWarningTooltip target={buttonElt} />}
+      <OldEditorWarningTooltip target={buttonElt} />
     </div>
   )
 }

+ 1 - 13
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 (
     <>
       <ForceDisconnected />
       <UnsavedDocs />
       <SystemMessages />
-      {isNewEditorOptOutStage ? (
-        <NewEditorOptOutIntroModal />
-      ) : (
-        <>
-          <NewEditorPromoModal />
-          <NewEditorIntroModal />
-        </>
-      )}
+      <NewEditorOptOutIntroModal />
     </>
   )
 })

+ 0 - 80
services/web/frontend/js/features/ide-redesign/components/new-editor-intro-modal.tsx

@@ -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>
-  )
-}

+ 0 - 102
services/web/frontend/js/features/ide-redesign/components/new-editor-promo-modal.tsx

@@ -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>
-  )
-}

+ 4 - 10
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<void> => {
       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 }
 }

+ 4 - 55
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 = () => {

+ 0 - 1
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;

+ 0 - 5
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</0>.",
   "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_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",

+ 25 - 95
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 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 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)
       })
     })