Ver código fonte

Merge pull request #31609 from overleaf/mj-split-test-system-overall

[web] Add split test for defaulting to system theme

GitOrigin-RevId: 3f5301349074725c56f423ed51662064e52d6aeb
Mathias Jakobsen 5 meses atrás
pai
commit
6c1003a852

+ 28 - 1
services/web/app/src/Features/Project/UserSettingsHelper.mjs

@@ -30,6 +30,33 @@ async function getEnableNewEditorLegacyDefault(req, res, user) {
   return false
 }
 
+const SYSTEM_THEME_USER_CUTOFF_DATE = new Date(Date.UTC(2026, 1, 15, 12, 0, 0)) // 12pm GMT on February 15, 2026
+
+async function getOverallTheme(req, res, user) {
+  if (user.ace.overallTheme != null) {
+    return user.ace.overallTheme
+  }
+
+  if (user.signUpDate < SYSTEM_THEME_USER_CUTOFF_DATE) {
+    // default / dark
+    return ''
+  }
+
+  const systemOverallSplitTestAssignment =
+    await SplitTestHandler.promises.getAssignment(
+      req,
+      res,
+      'new-user-system-overall-theme'
+    )
+
+  if (systemOverallSplitTestAssignment.variant === 'system') {
+    return 'system'
+  }
+
+  // default / dark
+  return ''
+}
+
 async function buildUserSettings(req, res, user) {
   const defaultLegacyEnableNewEditor = await getEnableNewEditorLegacyDefault(
     req,
@@ -53,7 +80,7 @@ async function buildUserSettings(req, res, user) {
     syntaxValidation: user.ace.syntaxValidation,
     fontFamily: user.ace.fontFamily || 'lucida',
     lineHeight: user.ace.lineHeight || 'normal',
-    overallTheme: user.ace.overallTheme,
+    overallTheme: await getOverallTheme(req, res, user),
     mathPreview: user.ace.mathPreview,
     breadcrumbs: user.ace.breadcrumbs,
     referencesSearchMode: user.ace.referencesSearchMode,

+ 1 - 1
services/web/app/src/models/User.mjs

@@ -77,7 +77,7 @@ export const UserSchema = new Schema(
     ace: {
       mode: { type: String, default: 'none' },
       theme: { type: String, default: 'textmate' },
-      overallTheme: { type: String, default: '' },
+      overallTheme: { type: String },
       // When overallTheme is `system`, we switch between `lightTheme` and `darkTheme` based on system settings
       // When overallTheme is `light-` or empty, we use the `theme` option.
       lightTheme: { type: String, default: 'textmate' },

+ 165 - 0
services/web/test/unit/src/Project/UserSettingsHelper.test.mjs

@@ -0,0 +1,165 @@
+import { vi, expect } from 'vitest'
+import sinon from 'sinon'
+const modulePath = '../../../../app/src/Features/Project/UserSettingsHelper.mjs'
+
+describe('UserSettingsHelper', function () {
+  beforeEach(async function (ctx) {
+    ctx.SplitTestHandler = {
+      promises: { getAssignment: sinon.stub() },
+    }
+
+    vi.doMock(
+      '../../../../app/src/Features/SplitTests/SplitTestHandler.mjs',
+      () => ({
+        default: ctx.SplitTestHandler,
+      })
+    )
+
+    ctx.req = { query: {} }
+    ctx.res = {}
+    ctx.UserSettingsHelper = (await import(modulePath)).default
+  })
+
+  describe('for user with overall theme set to value', function () {
+    beforeEach(async function (ctx) {
+      const user = {
+        ace: {
+          overallTheme: 'light',
+        },
+        signUpDate: new Date('2022-01-01'),
+      }
+
+      ctx.settings = await ctx.UserSettingsHelper.buildUserSettings(
+        ctx.req,
+        ctx.res,
+        user
+      )
+    })
+
+    it('should return the user settings with the correct overall theme', function (ctx) {
+      expect(ctx.settings.overallTheme).toBe('light')
+    })
+
+    it('should not check split test', function (ctx) {
+      expect(ctx.SplitTestHandler.promises.getAssignment).not.toHaveBeenCalled
+    })
+  })
+
+  describe('for user with no overall theme set', function () {
+    describe('for new users in treatment group', function () {
+      beforeEach(async function (ctx) {
+        const user = {
+          ace: {},
+          signUpDate: new Date('2026-02-16T00:00:00Z'),
+        }
+
+        ctx.SplitTestHandler.promises.getAssignment
+          .withArgs(ctx.req, ctx.res, 'new-user-system-overall-theme')
+          .resolves({
+            variant: 'system',
+          })
+
+        ctx.settings = await ctx.UserSettingsHelper.buildUserSettings(
+          ctx.req,
+          ctx.res,
+          user
+        )
+      })
+
+      it('should default to system theme', function (ctx) {
+        expect(ctx.settings.overallTheme).toBe('system')
+      })
+
+      it('should check split test', function (ctx) {
+        expect(ctx.SplitTestHandler.promises.getAssignment).toHaveBeenCalled
+      })
+    })
+
+    describe('for new users in control group', function () {
+      beforeEach(async function (ctx) {
+        const user = {
+          ace: {},
+          signUpDate: new Date('2026-02-16T00:00:00Z'),
+        }
+
+        ctx.SplitTestHandler.promises.getAssignment
+          .withArgs(ctx.req, ctx.res, 'new-user-system-overall-theme')
+          .resolves({
+            variant: 'default',
+          })
+
+        ctx.settings = await ctx.UserSettingsHelper.buildUserSettings(
+          ctx.req,
+          ctx.res,
+          user
+        )
+      })
+
+      it('should default to dark theme', function (ctx) {
+        expect(ctx.settings.overallTheme).toBe('')
+      })
+
+      it('should check split test', function (ctx) {
+        expect(ctx.SplitTestHandler.promises.getAssignment).toHaveBeenCalled
+      })
+    })
+
+    describe('for old users in control group', function () {
+      beforeEach(async function (ctx) {
+        const user = {
+          ace: {},
+          signUpDate: new Date('2025-02-15T00:00:00Z'),
+        }
+
+        ctx.SplitTestHandler.promises.getAssignment
+          .withArgs(ctx.req, ctx.res, 'new-user-system-overall-theme')
+          .resolves({
+            variant: 'default',
+          })
+
+        ctx.settings = await ctx.UserSettingsHelper.buildUserSettings(
+          ctx.req,
+          ctx.res,
+          user
+        )
+      })
+
+      it('should default to dark theme', function (ctx) {
+        expect(ctx.settings.overallTheme).toBe('')
+      })
+
+      it('should not check split test', function (ctx) {
+        expect(ctx.SplitTestHandler.promises.getAssignment).not.toHaveBeenCalled
+      })
+    })
+
+    describe('for old users in treatment group', function () {
+      beforeEach(async function (ctx) {
+        const user = {
+          ace: {},
+          signUpDate: new Date('2025-02-15T00:00:00Z'),
+        }
+
+        ctx.SplitTestHandler.promises.getAssignment
+          .withArgs(ctx.req, ctx.res, 'new-user-system-overall-theme')
+          .resolves({
+            variant: 'system',
+          })
+
+        ctx.settings = await ctx.UserSettingsHelper.buildUserSettings(
+          ctx.req,
+          ctx.res,
+          user
+        )
+      })
+
+      it('should default to dark theme', function (ctx) {
+        expect(ctx.settings.overallTheme).toBe('')
+      })
+
+      it('should not check split test', function (ctx) {
+        expect(ctx.SplitTestHandler.promises.getAssignment).not.toHaveBeenCalled
+      })
+    })
+  })
+})