Merge pull request #28497 from overleaf/mj-system-editor-theme-dark-light-split
[web] Split editor theme into two when using system overall theme GitOrigin-RevId: 1efa5553fdff8a17de634017882feb2ede614cd6
This commit is contained in:
committed by
Copybot
parent
96a071dd04
commit
b554b0cfcc
@@ -51,6 +51,8 @@ async function buildUserSettings(req, res, user) {
|
||||
return {
|
||||
mode: user.ace.mode,
|
||||
editorTheme: user.ace.theme,
|
||||
editorLightTheme: user.ace.lightTheme,
|
||||
editorDarkTheme: user.ace.darkTheme,
|
||||
fontSize: user.ace.fontSize,
|
||||
autoComplete: user.ace.autoComplete,
|
||||
autoPairDelimiters: user.ace.autoPairDelimiters,
|
||||
|
||||
@@ -368,6 +368,12 @@ async function updateUserSettings(req, res, next) {
|
||||
if (body.editorTheme != null) {
|
||||
user.ace.theme = body.editorTheme
|
||||
}
|
||||
if (body.editorLightTheme != null) {
|
||||
user.ace.lightTheme = body.editorLightTheme
|
||||
}
|
||||
if (body.editorDarkTheme != null) {
|
||||
user.ace.darkTheme = body.editorDarkTheme
|
||||
}
|
||||
if (body.overallTheme != null) {
|
||||
user.ace.overallTheme = body.overallTheme
|
||||
}
|
||||
|
||||
@@ -89,6 +89,10 @@ export const UserSchema = new Schema(
|
||||
mode: { type: String, default: 'none' },
|
||||
theme: { type: String, default: 'textmate' },
|
||||
overallTheme: { type: String, default: '' },
|
||||
// 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' },
|
||||
darkTheme: { type: String, default: 'overleaf_dark' },
|
||||
fontSize: { type: Number, default: '12' },
|
||||
autoComplete: { type: Boolean, default: true },
|
||||
autoPairDelimiters: { type: Boolean, default: true },
|
||||
|
||||
@@ -528,6 +528,8 @@
|
||||
"editor_only": "",
|
||||
"editor_only_hide_pdf": "",
|
||||
"editor_theme": "",
|
||||
"editor_theme_dark": "",
|
||||
"editor_theme_light": "",
|
||||
"edits_become_suggestions": "",
|
||||
"educational_disclaimer": "",
|
||||
"educational_disclaimer_heading": "",
|
||||
@@ -1861,6 +1863,8 @@
|
||||
"thanks_settings_updated": "",
|
||||
"the_add_on_will_remain_active_until": "",
|
||||
"the_code_editor_color_scheme": "",
|
||||
"the_code_editor_color_scheme_dark_mode": "",
|
||||
"the_code_editor_color_scheme_light_mode": "",
|
||||
"the_following_files_already_exist_in_this_project": "",
|
||||
"the_following_files_and_folders_already_exist_in_this_project": "",
|
||||
"the_following_folder_already_exists_in_this_project": "",
|
||||
|
||||
@@ -5,20 +5,28 @@ import { useUserSettingsContext } from '@/shared/context/user-settings-context'
|
||||
export default memo(function LeftMenuMask() {
|
||||
const { setLeftMenuShown } = useLayoutContext()
|
||||
const { userSettings } = useUserSettingsContext()
|
||||
const { editorTheme, overallTheme } = userSettings
|
||||
const [original] = useState({ editorTheme, overallTheme })
|
||||
const { editorTheme, editorLightTheme, editorDarkTheme, overallTheme } =
|
||||
userSettings
|
||||
const [original] = useState({
|
||||
editorTheme,
|
||||
overallTheme,
|
||||
editorLightTheme,
|
||||
editorDarkTheme,
|
||||
})
|
||||
const maskRef = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (maskRef.current) {
|
||||
if (
|
||||
editorTheme !== original.editorTheme ||
|
||||
editorLightTheme !== original.editorLightTheme ||
|
||||
editorDarkTheme !== original.editorDarkTheme ||
|
||||
overallTheme !== original.overallTheme
|
||||
) {
|
||||
maskRef.current.style.opacity = '0'
|
||||
}
|
||||
}
|
||||
}, [editorTheme, overallTheme, original])
|
||||
}, [editorTheme, editorLightTheme, editorDarkTheme, overallTheme, original])
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
||||
|
||||
@@ -50,8 +50,8 @@ export default function SettingsMenu() {
|
||||
<SettingsAutoCloseBrackets />
|
||||
<SettingsSyntaxValidation />
|
||||
<SettingsMathPreview />
|
||||
<SettingsEditorTheme />
|
||||
<SettingsOverallTheme />
|
||||
<SettingsEditorTheme />
|
||||
<SettingsKeybindings />
|
||||
<SettingsFontSize />
|
||||
<SettingsFontFamily />
|
||||
|
||||
+33
-2
@@ -2,12 +2,43 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useProjectSettingsContext } from '../../context/project-settings-context'
|
||||
import SettingsMenuSelect from './settings-menu-select'
|
||||
import { useEditorThemesOptionGroups } from '../../hooks/use-editor-theme-option-groups'
|
||||
import { isIEEEBranded } from '@/utils/is-ieee-branded'
|
||||
|
||||
export default function SettingsEditorTheme() {
|
||||
const { t } = useTranslation()
|
||||
const { editorTheme, setEditorTheme } = useProjectSettingsContext()
|
||||
|
||||
const optGroups = useEditorThemesOptionGroups()
|
||||
const {
|
||||
editorTheme,
|
||||
setEditorTheme,
|
||||
editorLightTheme,
|
||||
editorDarkTheme,
|
||||
setEditorLightTheme,
|
||||
setEditorDarkTheme,
|
||||
overallTheme,
|
||||
} = useProjectSettingsContext()
|
||||
|
||||
if (overallTheme === 'system' && !isIEEEBranded()) {
|
||||
return (
|
||||
<>
|
||||
<SettingsMenuSelect
|
||||
onChange={setEditorLightTheme}
|
||||
value={editorLightTheme}
|
||||
optgroups={optGroups}
|
||||
label={t('editor_theme_light')}
|
||||
name="editorLightTheme"
|
||||
translateOptions="no"
|
||||
/>
|
||||
<SettingsMenuSelect
|
||||
onChange={setEditorDarkTheme}
|
||||
value={editorDarkTheme}
|
||||
optgroups={optGroups}
|
||||
label={t('editor_theme_dark')}
|
||||
name="editorDarkTheme"
|
||||
translateOptions="no"
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsMenuSelect
|
||||
|
||||
+16
@@ -21,6 +21,10 @@ type ProjectSettingsSetterContextValue = {
|
||||
) => void
|
||||
setMode: (mode: UserSettings['mode']) => void
|
||||
setEditorTheme: (editorTheme: UserSettings['editorTheme']) => void
|
||||
setEditorLightTheme: (
|
||||
editorLightTheme: UserSettings['editorLightTheme']
|
||||
) => void
|
||||
setEditorDarkTheme: (editorDarkTheme: UserSettings['editorDarkTheme']) => void
|
||||
setOverallTheme: (overallTheme: UserSettings['overallTheme']) => void
|
||||
setFontSize: (fontSize: UserSettings['fontSize']) => void
|
||||
setFontFamily: (fontFamily: UserSettings['fontFamily']) => void
|
||||
@@ -62,6 +66,10 @@ export const ProjectSettingsProvider: FC<React.PropsWithChildren> = ({
|
||||
setSyntaxValidation,
|
||||
editorTheme,
|
||||
setEditorTheme,
|
||||
editorLightTheme,
|
||||
setEditorLightTheme,
|
||||
editorDarkTheme,
|
||||
setEditorDarkTheme,
|
||||
overallTheme,
|
||||
setOverallTheme,
|
||||
mode,
|
||||
@@ -102,6 +110,10 @@ export const ProjectSettingsProvider: FC<React.PropsWithChildren> = ({
|
||||
setSyntaxValidation,
|
||||
editorTheme,
|
||||
setEditorTheme,
|
||||
editorLightTheme,
|
||||
setEditorLightTheme,
|
||||
editorDarkTheme,
|
||||
setEditorDarkTheme,
|
||||
overallTheme,
|
||||
setOverallTheme,
|
||||
mode,
|
||||
@@ -138,6 +150,10 @@ export const ProjectSettingsProvider: FC<React.PropsWithChildren> = ({
|
||||
setSyntaxValidation,
|
||||
editorTheme,
|
||||
setEditorTheme,
|
||||
editorLightTheme,
|
||||
setEditorLightTheme,
|
||||
editorDarkTheme,
|
||||
setEditorDarkTheme,
|
||||
overallTheme,
|
||||
setOverallTheme,
|
||||
mode,
|
||||
|
||||
@@ -14,6 +14,8 @@ export default function useUserWideSettings() {
|
||||
autoPairDelimiters,
|
||||
syntaxValidation,
|
||||
editorTheme,
|
||||
editorLightTheme,
|
||||
editorDarkTheme,
|
||||
mode,
|
||||
fontSize,
|
||||
fontFamily,
|
||||
@@ -53,6 +55,20 @@ export default function useUserWideSettings() {
|
||||
[saveUserSettings]
|
||||
)
|
||||
|
||||
const setEditorLightTheme = useCallback(
|
||||
(editorLightTheme: UserSettings['editorLightTheme']) => {
|
||||
saveUserSettings('editorLightTheme', editorLightTheme)
|
||||
},
|
||||
[saveUserSettings]
|
||||
)
|
||||
|
||||
const setEditorDarkTheme = useCallback(
|
||||
(editorDarkTheme: UserSettings['editorDarkTheme']) => {
|
||||
saveUserSettings('editorDarkTheme', editorDarkTheme)
|
||||
},
|
||||
[saveUserSettings]
|
||||
)
|
||||
|
||||
const setMode = useCallback(
|
||||
(mode: UserSettings['mode']) => {
|
||||
saveUserSettings('mode', mode)
|
||||
@@ -118,6 +134,10 @@ export default function useUserWideSettings() {
|
||||
setSyntaxValidation,
|
||||
editorTheme,
|
||||
setEditorTheme,
|
||||
editorLightTheme,
|
||||
setEditorLightTheme,
|
||||
editorDarkTheme,
|
||||
setEditorDarkTheme,
|
||||
overallTheme,
|
||||
setOverallTheme,
|
||||
mode,
|
||||
|
||||
+38
-1
@@ -2,13 +2,49 @@ import { useProjectSettingsContext } from '@/features/editor-left-menu/context/p
|
||||
import DropdownSetting from '../dropdown-setting'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useEditorThemesOptionGroups } from '@/features/editor-left-menu/hooks/use-editor-theme-option-groups'
|
||||
import { isIEEEBranded } from '@/utils/is-ieee-branded'
|
||||
|
||||
export default function EditorThemeSetting() {
|
||||
const { editorTheme, setEditorTheme } = useProjectSettingsContext()
|
||||
const {
|
||||
editorTheme,
|
||||
setEditorTheme,
|
||||
editorLightTheme,
|
||||
setEditorLightTheme,
|
||||
editorDarkTheme,
|
||||
setEditorDarkTheme,
|
||||
overallTheme,
|
||||
} = useProjectSettingsContext()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const optGroups = useEditorThemesOptionGroups()
|
||||
|
||||
if (overallTheme === 'system' && !isIEEEBranded()) {
|
||||
return (
|
||||
<>
|
||||
<DropdownSetting
|
||||
id="editorLightTheme"
|
||||
label={t('editor_theme_light')}
|
||||
description={t('the_code_editor_color_scheme_light_mode')}
|
||||
optgroups={optGroups}
|
||||
onChange={setEditorLightTheme}
|
||||
value={editorLightTheme}
|
||||
translateOptions="no"
|
||||
width="wide"
|
||||
/>
|
||||
<DropdownSetting
|
||||
id="editorDarkTheme"
|
||||
label={t('editor_theme_dark')}
|
||||
description={t('the_code_editor_color_scheme_dark_mode')}
|
||||
optgroups={optGroups}
|
||||
onChange={setEditorDarkTheme}
|
||||
value={editorDarkTheme}
|
||||
translateOptions="no"
|
||||
width="wide"
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownSetting
|
||||
id="editorTheme"
|
||||
@@ -18,6 +54,7 @@ export default function EditorThemeSetting() {
|
||||
onChange={setEditorTheme}
|
||||
value={editorTheme}
|
||||
translateOptions="no"
|
||||
width="wide"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ import { SearchQuery } from '@codemirror/search'
|
||||
import { beforeChangeDocEffect } from '@/features/source-editor/extensions/before-change-doc'
|
||||
import { useActiveOverallTheme } from '@/shared/hooks/use-active-overall-theme'
|
||||
import { useEditorSelectionContext } from '@/shared/context/editor-selection-context'
|
||||
import { useActiveEditorTheme } from '@/shared/hooks/use-active-editor-theme'
|
||||
|
||||
function useCodeMirrorScope(view: EditorView) {
|
||||
const { fileTreeData } = useFileTreeData()
|
||||
@@ -82,7 +83,6 @@ function useCodeMirrorScope(view: EditorView) {
|
||||
fontSize,
|
||||
lineHeight,
|
||||
autoComplete,
|
||||
editorTheme,
|
||||
autoPairDelimiters,
|
||||
mode,
|
||||
syntaxValidation,
|
||||
@@ -91,6 +91,7 @@ function useCodeMirrorScope(view: EditorView) {
|
||||
enableNewEditor,
|
||||
} = userSettings
|
||||
const activeOverallTheme = useActiveOverallTheme()
|
||||
const editorTheme = useActiveEditorTheme()
|
||||
|
||||
const { onlineUserCursorHighlights } = useOnlineUsersContext()
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ const defaultSettings: UserSettings = {
|
||||
autoPairDelimiters: true,
|
||||
syntaxValidation: false,
|
||||
editorTheme: 'textmate',
|
||||
editorDarkTheme: 'overleaf_dark',
|
||||
editorLightTheme: 'textmate',
|
||||
overallTheme: '',
|
||||
mode: 'default',
|
||||
fontSize: 12,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { useActiveOverallTheme } from './use-active-overall-theme'
|
||||
import { useUserSettingsContext } from '../context/user-settings-context'
|
||||
import { isIEEEBranded } from '@/utils/is-ieee-branded'
|
||||
|
||||
export const useActiveEditorTheme = () => {
|
||||
const activeOverallTheme = useActiveOverallTheme()
|
||||
const {
|
||||
userSettings: {
|
||||
overallTheme,
|
||||
editorTheme,
|
||||
editorLightTheme,
|
||||
editorDarkTheme,
|
||||
},
|
||||
} = useUserSettingsContext()
|
||||
if (overallTheme !== 'system' || isIEEEBranded()) {
|
||||
return editorTheme
|
||||
} else {
|
||||
return activeOverallTheme === 'dark' ? editorDarkTheme : editorLightTheme
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,6 @@ import { useSplitTestContext } from '../context/split-test-context'
|
||||
|
||||
export type ActiveOverallTheme = 'dark' | 'light'
|
||||
|
||||
const mediaWatcher = window.matchMedia?.('(prefers-color-scheme: dark)') ?? {
|
||||
// If matchMedia is not supported, use the default (dark) theme
|
||||
matches: true,
|
||||
addEventListener: () => {},
|
||||
removeEventListener: () => {},
|
||||
}
|
||||
|
||||
function getTheme(
|
||||
overallTheme: OverallTheme,
|
||||
prefersDark: boolean
|
||||
@@ -33,8 +26,13 @@ export const useActiveOverallTheme = (
|
||||
featureFlag?: string
|
||||
): ActiveOverallTheme => {
|
||||
const { splitTestVariants } = useSplitTestContext()
|
||||
const [browserPrefersDarkMode, setBrowserPrefersDarkMode] = useState(
|
||||
mediaWatcher.matches
|
||||
const [browserPrefersDarkMode, setBrowserPrefersDarkMode] = useState(() =>
|
||||
// If matchMedia is not supported, use the default (dark) theme
|
||||
{
|
||||
return (
|
||||
window.matchMedia?.('(prefers-color-scheme: dark)')?.matches ?? true
|
||||
)
|
||||
}
|
||||
)
|
||||
const {
|
||||
userSettings: { overallTheme },
|
||||
@@ -50,6 +48,9 @@ export const useActiveOverallTheme = (
|
||||
}, [overallTheme, browserPrefersDarkMode, featureFlag, splitTestVariants])
|
||||
|
||||
useEffect(() => {
|
||||
const mediaWatcher = window.matchMedia?.('(prefers-color-scheme: dark)')
|
||||
if (!mediaWatcher) return
|
||||
|
||||
const listener = (e: MediaQueryListEvent) => {
|
||||
setBrowserPrefersDarkMode(e.matches)
|
||||
}
|
||||
|
||||
@@ -673,6 +673,8 @@
|
||||
"editor_only": "Editor only",
|
||||
"editor_only_hide_pdf": "Editor only <0>(hide PDF)</0>",
|
||||
"editor_theme": "Editor theme",
|
||||
"editor_theme_dark": "Dark editor theme",
|
||||
"editor_theme_light": "Light editor theme",
|
||||
"edits_become_suggestions": "Edits become suggestions",
|
||||
"educational_disclaimer": "I confirm that users will be students or faculty using Overleaf primarily for study and teaching, and can provide evidence of this if requested.",
|
||||
"educational_disclaimer_heading": "Educational discount confirmation",
|
||||
@@ -2381,6 +2383,8 @@
|
||||
"thanks_settings_updated": "Thanks, your settings have been updated.",
|
||||
"the_add_on_will_remain_active_until": "The add-on will remain active until the end of the current billing period.",
|
||||
"the_code_editor_color_scheme": "The code editor color scheme",
|
||||
"the_code_editor_color_scheme_dark_mode": "The code editor color scheme for dark mode",
|
||||
"the_code_editor_color_scheme_light_mode": "The code editor color scheme for light mode",
|
||||
"the_file_supplied_is_of_an_unsupported_type ": "The link to open this content on Overleaf pointed to the wrong kind of file. Valid file types are .tex documents and .zip files. If this keeps happening for links on a particular site, please report this to them.",
|
||||
"the_following_files_already_exist_in_this_project": "The following files already exist in this project:",
|
||||
"the_following_files_and_folders_already_exist_in_this_project": "The following files and folders already exist in this project:",
|
||||
|
||||
+70
-11
@@ -5,6 +5,8 @@ import SettingsEditorTheme from '../../../../../../frontend/js/features/editor-l
|
||||
import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/editor-left-menu-context'
|
||||
import { EditorProviders } from '../../../../helpers/editor-providers'
|
||||
|
||||
const MOCK_IEEE_BRAND_ID = 123
|
||||
|
||||
describe('<SettingsEditorTheme />', function () {
|
||||
const editorThemes = [
|
||||
{ name: 'editortheme-1', dark: false },
|
||||
@@ -21,23 +23,18 @@ describe('<SettingsEditorTheme />', function () {
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-editorThemes', editorThemes)
|
||||
window.metaAttributesCache.set('ol-legacyEditorThemes', legacyEditorThemes)
|
||||
window.metaAttributesCache.set('ol-brandVariation', {
|
||||
brand_id: undefined,
|
||||
})
|
||||
window.metaAttributesCache.get('ol-ExposedSettings').ieeeBrandId =
|
||||
MOCK_IEEE_BRAND_ID
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('shows correct menu', async function () {
|
||||
render(
|
||||
<EditorProviders>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsEditorTheme />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
const select = screen.getByLabelText('Editor theme')
|
||||
|
||||
function checkSelect(select: HTMLElement) {
|
||||
for (const theme of editorThemes) {
|
||||
const option = within(select).getByText(theme.name.replace(/_/g, ' '))
|
||||
expect(option.getAttribute('value')).to.equal(theme.name)
|
||||
@@ -49,5 +46,67 @@ describe('<SettingsEditorTheme />', function () {
|
||||
)
|
||||
expect(option.getAttribute('value')).to.equal(theme.name)
|
||||
}
|
||||
}
|
||||
|
||||
describe('with default theme', function () {
|
||||
beforeEach(function () {
|
||||
render(
|
||||
<EditorProviders userSettings={{ overallTheme: '' }}>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsEditorTheme />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
})
|
||||
|
||||
it('shows correct menu', async function () {
|
||||
const select = screen.getByLabelText('Editor theme')
|
||||
expect(select).to.exist
|
||||
checkSelect(select)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with system theme', function () {
|
||||
beforeEach(function () {
|
||||
render(
|
||||
<EditorProviders userSettings={{ overallTheme: 'system' }}>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsEditorTheme />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
})
|
||||
|
||||
it('shows correct menu', async function () {
|
||||
const select = screen.queryByLabelText('Editor theme')
|
||||
expect(select).to.not.exist
|
||||
const lightSelect = screen.getByLabelText('Light editor theme')
|
||||
expect(lightSelect).to.exist
|
||||
checkSelect(lightSelect)
|
||||
const darkSelect = screen.getByLabelText('Dark editor theme')
|
||||
expect(darkSelect).to.exist
|
||||
checkSelect(darkSelect)
|
||||
})
|
||||
})
|
||||
|
||||
describe('with IEEE branding', function () {
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-brandVariation', {
|
||||
brand_id: MOCK_IEEE_BRAND_ID,
|
||||
})
|
||||
render(
|
||||
<EditorProviders userSettings={{ overallTheme: 'system' }}>
|
||||
<EditorLeftMenuProvider>
|
||||
<SettingsEditorTheme />
|
||||
</EditorLeftMenuProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
})
|
||||
|
||||
it('ignores the system theme and shows single selection', async function () {
|
||||
const select = screen.getByLabelText('Editor theme')
|
||||
expect(select).to.exist
|
||||
checkSelect(select)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+72
-14
@@ -6,6 +6,8 @@ import { EditorProviders } from '../../../helpers/editor-providers'
|
||||
import EditorThemeSetting from '@/features/ide-redesign/components/settings/appearance-settings/editor-theme-setting'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
|
||||
const MOCK_IEEE_BRAND_ID = 123
|
||||
|
||||
describe('<EditorThemeSetting />', function () {
|
||||
const editorThemes = [
|
||||
{ name: 'editortheme-1', dark: false },
|
||||
@@ -21,21 +23,18 @@ describe('<EditorThemeSetting />', function () {
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-editorThemes', editorThemes)
|
||||
window.metaAttributesCache.set('ol-legacyEditorThemes', legacyEditorThemes)
|
||||
window.metaAttributesCache.set('ol-brandVariation', {
|
||||
brand_id: undefined,
|
||||
})
|
||||
window.metaAttributesCache.get('ol-ExposedSettings').ieeeBrandId =
|
||||
MOCK_IEEE_BRAND_ID
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('each option is shown and can be selected', async function () {
|
||||
render(
|
||||
<EditorProviders>
|
||||
<SettingsModalProvider>
|
||||
<EditorThemeSetting />
|
||||
</SettingsModalProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
async function checkSelect(select: HTMLElement, settingName: string) {
|
||||
const saveSettingsMock = fetchMock.post(
|
||||
`express:/user/settings`,
|
||||
{
|
||||
@@ -43,16 +42,13 @@ describe('<EditorThemeSetting />', function () {
|
||||
},
|
||||
{ delay: 0 }
|
||||
)
|
||||
|
||||
const select = screen.getByLabelText('Editor theme')
|
||||
|
||||
for (const theme of editorThemes) {
|
||||
const option = within(select).getByText(theme.name.replace(/_/g, ' '))
|
||||
expect(option.getAttribute('value')).to.equal(theme.name)
|
||||
await userEvent.selectOptions(select, [option])
|
||||
expect(
|
||||
saveSettingsMock.callHistory.called(`/user/settings`, {
|
||||
body: { editorTheme: theme.name },
|
||||
body: { [settingName]: theme.name },
|
||||
})
|
||||
).to.be.true
|
||||
}
|
||||
@@ -65,9 +61,71 @@ describe('<EditorThemeSetting />', function () {
|
||||
await userEvent.selectOptions(select, [option])
|
||||
expect(
|
||||
saveSettingsMock.callHistory.called(`/user/settings`, {
|
||||
body: { editorTheme: theme.name },
|
||||
body: { [settingName]: theme.name },
|
||||
})
|
||||
).to.be.true
|
||||
}
|
||||
}
|
||||
|
||||
describe('with default theme', function () {
|
||||
beforeEach(function () {
|
||||
render(
|
||||
<EditorProviders userSettings={{ overallTheme: '' }}>
|
||||
<SettingsModalProvider>
|
||||
<EditorThemeSetting />
|
||||
</SettingsModalProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
})
|
||||
|
||||
it('each option is shown and can be selected', async function () {
|
||||
const select = screen.getByLabelText('Editor theme')
|
||||
expect(select).to.exist
|
||||
await checkSelect(select, 'editorTheme')
|
||||
})
|
||||
})
|
||||
|
||||
describe('with system theme', function () {
|
||||
beforeEach(function () {
|
||||
render(
|
||||
<EditorProviders userSettings={{ overallTheme: 'system' }}>
|
||||
<SettingsModalProvider>
|
||||
<EditorThemeSetting />
|
||||
</SettingsModalProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
})
|
||||
|
||||
it('splits the setting into two', async function () {
|
||||
const select = screen.queryByLabelText('Editor theme')
|
||||
expect(select).to.not.exist
|
||||
const lightModeSelect = screen.getByLabelText('Light editor theme')
|
||||
expect(lightModeSelect).to.exist
|
||||
await checkSelect(lightModeSelect, 'editorLightTheme')
|
||||
const darkModeSelect = screen.getByLabelText('Dark editor theme')
|
||||
expect(darkModeSelect).to.exist
|
||||
await checkSelect(darkModeSelect, 'editorDarkTheme')
|
||||
})
|
||||
})
|
||||
|
||||
describe('with IEEE branding', function () {
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-brandVariation', {
|
||||
brand_id: MOCK_IEEE_BRAND_ID,
|
||||
})
|
||||
render(
|
||||
<EditorProviders userSettings={{ overallTheme: 'system' }}>
|
||||
<SettingsModalProvider>
|
||||
<EditorThemeSetting />
|
||||
</SettingsModalProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
})
|
||||
|
||||
it('ignores the system theme and shows single selection', async function () {
|
||||
const select = screen.getByLabelText('Editor theme')
|
||||
expect(select).to.exist
|
||||
await checkSelect(select, 'editorTheme')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { useActiveEditorTheme } from '@/shared/hooks/use-active-editor-theme'
|
||||
import { EditorProviders } from '../../helpers/editor-providers'
|
||||
import { SplitTestProvider } from '@/shared/context/split-test-context'
|
||||
|
||||
const MOCK_IEEE_BRAND_ID = 123
|
||||
|
||||
const TestComponent = ({ overallTheme }: { overallTheme: string }) => {
|
||||
return (
|
||||
<SplitTestProvider>
|
||||
<EditorProviders
|
||||
userSettings={{
|
||||
overallTheme,
|
||||
editorTheme: 'default-theme',
|
||||
editorLightTheme: 'light-theme',
|
||||
editorDarkTheme: 'dark-theme',
|
||||
}}
|
||||
>
|
||||
<TestComponentInner />
|
||||
</EditorProviders>
|
||||
</SplitTestProvider>
|
||||
)
|
||||
}
|
||||
|
||||
const TestComponentInner = () => {
|
||||
const editorTheme = useActiveEditorTheme()
|
||||
return <div data-testid="editor-theme">{editorTheme}</div>
|
||||
}
|
||||
|
||||
describe('useActiveEditorTheme', function () {
|
||||
describe('when overall theme is specific mode', function () {
|
||||
it('Uses editorTheme when in dark mode', function () {
|
||||
cy.mount(<TestComponent overallTheme="" />)
|
||||
cy.findByTestId('editor-theme').should('have.text', 'default-theme')
|
||||
})
|
||||
|
||||
it('Uses editorTheme when in light mode', function () {
|
||||
cy.mount(<TestComponent overallTheme="light-" />)
|
||||
cy.findByTestId('editor-theme').should('have.text', 'default-theme')
|
||||
})
|
||||
})
|
||||
|
||||
describe('when overall theme is system', function () {
|
||||
function stubMediaQuery(prefersDark: boolean, isIEEE = false) {
|
||||
cy.window().then(win => {
|
||||
win.metaAttributesCache.set('ol-brandVariation', {
|
||||
brand_id: isIEEE ? MOCK_IEEE_BRAND_ID : undefined,
|
||||
})
|
||||
win.metaAttributesCache.get('ol-ExposedSettings').ieeeBrandId =
|
||||
MOCK_IEEE_BRAND_ID
|
||||
cy.stub(win, 'matchMedia')
|
||||
.withArgs('(prefers-color-scheme: dark)')
|
||||
.returns({
|
||||
matches: prefersDark,
|
||||
addEventListener: () => {},
|
||||
removeEventListener: () => {},
|
||||
} as any)
|
||||
})
|
||||
}
|
||||
|
||||
it('uses editorDarkTheme when in dark mode', function () {
|
||||
stubMediaQuery(true)
|
||||
cy.mount(<TestComponent overallTheme="system" />)
|
||||
cy.findByTestId('editor-theme').should('have.text', 'dark-theme')
|
||||
})
|
||||
|
||||
it('uses editorLightTheme when in light mode', function () {
|
||||
stubMediaQuery(false)
|
||||
cy.mount(<TestComponent overallTheme="system" />)
|
||||
cy.findByTestId('editor-theme').should('have.text', 'light-theme')
|
||||
})
|
||||
|
||||
it('uses editorTheme when in IEEE document', function () {
|
||||
stubMediaQuery(false, true)
|
||||
cy.mount(<TestComponent overallTheme="system" />)
|
||||
cy.findByTestId('editor-theme').should('have.text', 'default-theme')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,81 @@
|
||||
import { EditorProviders } from '../../helpers/editor-providers'
|
||||
import { SplitTestProvider } from '@/shared/context/split-test-context'
|
||||
import { useActiveOverallTheme } from '@/shared/hooks/use-active-overall-theme'
|
||||
|
||||
const MOCK_IEEE_BRAND_ID = 123
|
||||
|
||||
const TestComponent = ({ overallTheme }: { overallTheme: string }) => {
|
||||
return (
|
||||
<SplitTestProvider>
|
||||
<EditorProviders
|
||||
userSettings={{
|
||||
overallTheme,
|
||||
}}
|
||||
>
|
||||
<TestComponentInner />
|
||||
</EditorProviders>
|
||||
</SplitTestProvider>
|
||||
)
|
||||
}
|
||||
|
||||
const TestComponentInner = () => {
|
||||
const overallTheme = useActiveOverallTheme()
|
||||
return <div data-testid="overall-theme">{overallTheme}</div>
|
||||
}
|
||||
|
||||
describe('useActiveOverallTheme', function () {
|
||||
beforeEach(function () {
|
||||
cy.window().then(win => {
|
||||
win.metaAttributesCache.set('ol-brandVariation', { brand_id: undefined })
|
||||
win.metaAttributesCache.get('ol-ExposedSettings').ieeeBrandId =
|
||||
MOCK_IEEE_BRAND_ID
|
||||
})
|
||||
})
|
||||
|
||||
it('Is dark in default mode', function () {
|
||||
cy.mount(<TestComponent overallTheme="" />)
|
||||
cy.findByTestId('overall-theme').should('have.text', 'dark')
|
||||
})
|
||||
|
||||
it('Is light when in light mode', function () {
|
||||
cy.mount(<TestComponent overallTheme="light-" />)
|
||||
cy.findByTestId('overall-theme').should('have.text', 'light')
|
||||
})
|
||||
|
||||
describe('when overall theme is system', function () {
|
||||
function stubMediaQuery(prefersDark: boolean) {
|
||||
cy.window().then(win => {
|
||||
cy.stub(win, 'matchMedia')
|
||||
.withArgs('(prefers-color-scheme: dark)')
|
||||
.returns({
|
||||
matches: prefersDark,
|
||||
addEventListener: () => {},
|
||||
removeEventListener: () => {},
|
||||
} as any)
|
||||
})
|
||||
}
|
||||
|
||||
it('is dark when browser prefers dark', function () {
|
||||
stubMediaQuery(true)
|
||||
cy.mount(<TestComponent overallTheme="system" />)
|
||||
cy.findByTestId('overall-theme').should('have.text', 'dark')
|
||||
})
|
||||
|
||||
it('is light when browser prefers light', function () {
|
||||
stubMediaQuery(false)
|
||||
cy.mount(<TestComponent overallTheme="system" />)
|
||||
cy.findByTestId('overall-theme').should('have.text', 'light')
|
||||
})
|
||||
|
||||
it('uses dark when in IEEE document', function () {
|
||||
stubMediaQuery(false)
|
||||
cy.window().then(win => {
|
||||
win.metaAttributesCache.set('ol-brandVariation', {
|
||||
brand_id: MOCK_IEEE_BRAND_ID,
|
||||
})
|
||||
})
|
||||
cy.mount(<TestComponent overallTheme="system" />)
|
||||
cy.findByTestId('overall-theme').should('have.text', 'dark')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -9,6 +9,8 @@ export type UserSettings = {
|
||||
autoPairDelimiters: boolean
|
||||
syntaxValidation: boolean
|
||||
editorTheme: string
|
||||
editorLightTheme: string
|
||||
editorDarkTheme: string
|
||||
overallTheme: OverallTheme
|
||||
mode: Keybindings
|
||||
fontSize: number
|
||||
|
||||
Reference in New Issue
Block a user