Data handling for settings in editor left menu react migration (#10470)

- Importing SettingsController from the angular code, which enables post requests for every select menu and socket handler for compiler, texlive version, and main document select option
 - New context for the data handling infrastructure between react and angular. The data is still located in the angular version, and I use the context only as a proxy to fetch/post new data.

GitOrigin-RevId: 59009bceb128d82969a2318e90036aacf79f9887
This commit is contained in:
M Fahru 2022-11-29 11:00:10 -07:00 committed by Copybot
parent 079a0dcae4
commit f27562eb12
34 changed files with 753 additions and 83 deletions

View file

@ -1 +1,2 @@
editor-left-menu()
div(ng-controller="SettingsController")
editor-left-menu()

View file

@ -1,18 +1,23 @@
import { useTranslation } from 'react-i18next'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
export default function SettingsAutoCloseBrackets() {
const { t } = useTranslation()
const { autoPairDelimiters, setAutoPairDelimiters } =
useProjectSettingsContext()
return (
<SettingsMenuSelect
onChange={setAutoPairDelimiters}
value={autoPairDelimiters}
options={[
{
value: 'true',
value: true,
label: t('on'),
},
{
value: 'false',
value: false,
label: t('off'),
},
]}

View file

@ -1,18 +1,22 @@
import { useTranslation } from 'react-i18next'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
export default function SettingsAutoComplete() {
const { t } = useTranslation()
const { autoComplete, setAutoComplete } = useProjectSettingsContext()
return (
<SettingsMenuSelect
onChange={setAutoComplete}
value={autoComplete}
options={[
{
value: 'true',
value: true,
label: t('on'),
},
{
value: 'false',
value: false,
label: t('off'),
},
]}

View file

@ -1,17 +1,22 @@
import { useTranslation } from 'react-i18next'
import type { ProjectCompiler } from '../../../../../../types/project-settings'
import { useEditorContext } from '../../../../shared/context/editor-context'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
export default function SettingsCompiler() {
const { t } = useTranslation()
const { permissionsLevel } = useEditorContext()
const { compiler, setCompiler } = useProjectSettingsContext()
if (permissionsLevel === 'readOnly') {
return null
}
return (
<SettingsMenuSelect
<SettingsMenuSelect<ProjectCompiler>
onChange={setCompiler}
value={compiler}
options={[
{
value: 'pdflatex',

View file

@ -2,28 +2,17 @@ import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import isValidTeXFile from '../../../../main/is-valid-tex-file'
import { useEditorContext } from '../../../../shared/context/editor-context'
import { useProjectContext } from '../../../../shared/context/project-context'
import useScopeValue from '../../../../shared/hooks/use-scope-value'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
import type { Option } from './settings-menu-select'
type Doc = {
doc: {
name: string
id: string
type: string
selected: boolean
}
path: string
}
import type { MainDocument } from '../../../../../../types/project-settings'
export default function SettingsDocument() {
const { t } = useTranslation()
const { permissionsLevel } = useEditorContext()
const { rootDocId } = useProjectContext()
const [docs] = useScopeValue<Doc[] | undefined>('docs')
const [docs] = useScopeValue<MainDocument[] | undefined>('docs')
const { rootDocId, setRootDocId } = useProjectSettingsContext()
const validDocsOptions = useMemo(() => {
const filteredDocs =
@ -45,6 +34,8 @@ export default function SettingsDocument() {
return (
<SettingsMenuSelect
onChange={setRootDocId}
value={rootDocId}
options={validDocsOptions}
label={t('main_document')}
name="rootDoc_id"

View file

@ -1,6 +1,7 @@
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import getMeta from '../../../../utils/meta'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
import type { Option } from './settings-menu-select'
@ -10,6 +11,7 @@ export default function SettingsEditorTheme() {
const legacyEditorThemes = getMeta('ol-legacyEditorThemes') as
| string[]
| undefined
const { editorTheme, setEditorTheme } = useProjectSettingsContext()
const options = useMemo(() => {
const editorThemeOptions: Array<Option> =
@ -36,9 +38,11 @@ export default function SettingsEditorTheme() {
return (
<SettingsMenuSelect
onChange={setEditorTheme}
value={editorTheme}
options={options}
label={t('editor_theme')}
name="editorTheme"
options={options}
/>
)
}

View file

@ -1,11 +1,16 @@
import { useTranslation } from 'react-i18next'
import { FontFamily } from '../../../../../../modules/source-editor/frontend/js/extensions/theme'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
export default function SettingsFontFamily() {
const { t } = useTranslation()
const { fontFamily, setFontFamily } = useProjectSettingsContext()
return (
<SettingsMenuSelect
<SettingsMenuSelect<FontFamily>
onChange={setFontFamily}
value={fontFamily}
options={[
{
value: 'monaco',

View file

@ -1,4 +1,5 @@
import { useTranslation } from 'react-i18next'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
import type { Option } from './settings-menu-select'
@ -10,9 +11,12 @@ const options: Array<Option> = sizes.map(size => ({
export default function SettingsFontSize() {
const { t } = useTranslation()
const { fontSize, setFontSize } = useProjectSettingsContext()
return (
<SettingsMenuSelect
onChange={setFontSize}
value={fontSize}
options={options}
label={t('font_size')}
name="fontSize"

View file

@ -3,16 +3,13 @@ import { useTranslation } from 'react-i18next'
import getMeta from '../../../../utils/meta'
import SettingsMenuSelect from './settings-menu-select'
import type { Option } from './settings-menu-select'
/* eslint-disable react/no-unused-prop-types */
type AllowedImageName = {
imageDesc: string
imageName: string
}
/* eslint-enable */
import { useProjectSettingsContext } from '../../context/project-settings-context'
import type { AllowedImageName } from '../../../../../../types/project-settings'
export default function SettingsImageName() {
const { t } = useTranslation()
const { imageName, setImageName } = useProjectSettingsContext()
const allowedImageNames = getMeta('ol-allowedImageNames') as
| AllowedImageName[]
| undefined
@ -32,6 +29,8 @@ export default function SettingsImageName() {
return (
<SettingsMenuSelect
onChange={setImageName}
value={imageName}
options={options}
label={t('tex_live_version')}
name="imageName"

View file

@ -1,11 +1,16 @@
import { useTranslation } from 'react-i18next'
import type { Keybindings } from '../../../../../../types/project-settings'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
export default function SettingsKeybindings() {
const { t } = useTranslation()
const { mode, setMode } = useProjectSettingsContext()
return (
<SettingsMenuSelect
<SettingsMenuSelect<Keybindings>
onChange={setMode}
value={mode}
options={[
{
value: 'default',

View file

@ -1,11 +1,16 @@
import { useTranslation } from 'react-i18next'
import type { LineHeight } from '../../../../../../modules/source-editor/frontend/js/extensions/theme'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
export default function SettingsLineHeight() {
const { t } = useTranslation()
const { lineHeight, setLineHeight } = useProjectSettingsContext()
return (
<SettingsMenuSelect
<SettingsMenuSelect<LineHeight>
onChange={setLineHeight}
value={lineHeight}
options={[
{
value: 'compact',

View file

@ -1,43 +1,68 @@
export type Option = {
value: string
import { ChangeEventHandler, useCallback } from 'react'
type PossibleValue = string | boolean
export type Option<T extends PossibleValue = string> = {
value: T
label: string
ariaHidden?: 'true' | 'false'
disabled?: boolean
}
export type Optgroup = {
export type Optgroup<T extends PossibleValue = string> = {
label: string
options: Array<Option>
options: Array<Option<T>>
}
type SettingsMenuSelectProps = {
type SettingsMenuSelectProps<T extends PossibleValue = string> = {
label: string
name: string
options: Array<Option>
optgroup?: Optgroup
options: Array<Option<T>>
optgroup?: Optgroup<T>
loading?: boolean
onChange: (val: T) => void
value?: T
}
export default function SettingsMenuSelect({
export default function SettingsMenuSelect<T extends PossibleValue = string>({
label,
name,
options,
optgroup,
loading,
}: SettingsMenuSelectProps) {
onChange,
value,
}: SettingsMenuSelectProps<T>) {
const handleChange: ChangeEventHandler<HTMLSelectElement> = useCallback(
event => {
let value: PossibleValue = event.target.value
if (value === 'true' || value === 'false') {
value = value === 'true'
}
onChange(value as T)
},
[onChange]
)
return (
<div className="form-group left-menu-setting">
<label htmlFor={name}>{label}</label>
<label htmlFor={`settings-menu-${name}`}>{label}</label>
{loading ? (
<p className="loading pull-right">
<i className="fa fa-fw fa-spin fa-refresh" />
</p>
) : (
<select name={name} className="form-control">
<select
id={`settings-menu-${name}`}
className="form-control"
onChange={handleChange}
value={value?.toString()}
>
{options.map(option => (
<option
key={`${name}-${option.value}`}
value={option.value}
value={option.value.toString()}
aria-hidden={option.ariaHidden}
disabled={option.disabled}
>
@ -47,7 +72,10 @@ export default function SettingsMenuSelect({
{optgroup ? (
<optgroup label={optgroup.label}>
{optgroup.options.map(option => (
<option value={option.value} key={option.value}>
<option
value={option.value.toString()}
key={option.value.toString()}
>
{option.label}
</option>
))}

View file

@ -2,25 +2,22 @@ import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useLayoutContext } from '../../../../shared/context/layout-context'
import getMeta from '../../../../utils/meta'
import SettingsMenuSelect from './settings-menu-select'
import type { Option } from './settings-menu-select'
type OverallTheme = {
name: string
path: string
val: string
}
import SettingsMenuSelect, { Option } from './settings-menu-select'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import type { OverallThemeMeta } from '../../../../../../types/project-settings'
import type { OverallTheme } from '../../../../../../modules/source-editor/frontend/js/extensions/theme'
export default function SettingsOverallTheme() {
const { t } = useTranslation()
const overallThemes = getMeta('ol-overallThemes') as
| OverallTheme[]
| OverallThemeMeta[]
| undefined
const { loadingStyleSheet } = useLayoutContext() as {
loadingStyleSheet: boolean
}
const { overallTheme, setOverallTheme } = useProjectSettingsContext()
const options: Array<Option> = useMemo(
const options: Array<Option<OverallTheme>> = useMemo(
() =>
overallThemes?.map(({ name, val }) => ({
value: val,
@ -37,7 +34,9 @@ export default function SettingsOverallTheme() {
}
return (
<SettingsMenuSelect
<SettingsMenuSelect<OverallTheme>
onChange={setOverallTheme}
value={overallTheme}
options={options}
loading={loadingStyleSheet}
label={t('overall_theme')}

View file

@ -1,11 +1,16 @@
import { useTranslation } from 'react-i18next'
import type { PdfViewer } from '../../../../../../types/project-settings'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
export default function SettingsPdfViewer() {
const { t } = useTranslation()
const { pdfViewer, setPdfViewer } = useProjectSettingsContext()
return (
<SettingsMenuSelect
<SettingsMenuSelect<PdfViewer>
onChange={setPdfViewer}
value={pdfViewer}
options={[
{
value: 'pdfjs',

View file

@ -1,17 +1,17 @@
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import getMeta from '../../../../utils/meta'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
import type { Optgroup } from './settings-menu-select'
type Language = {
name: string
code: string
}
import type { SpellCheckLanguage } from '../../../../../../types/project-settings'
export default function SettingsSpellCheckLanguage() {
const { t } = useTranslation()
const languages = getMeta('ol-languages') as Language[] | undefined
const languages = getMeta('ol-languages') as SpellCheckLanguage[] | undefined
const { spellCheckLanguage, setSpellCheckLanguage } =
useProjectSettingsContext()
const optgroup: Optgroup = useMemo(
() => ({
@ -27,6 +27,8 @@ export default function SettingsSpellCheckLanguage() {
return (
<SettingsMenuSelect
onChange={setSpellCheckLanguage}
value={spellCheckLanguage}
options={[{ value: '', label: t('off') }]}
optgroup={optgroup}
label={t('spell_check')}

View file

@ -1,18 +1,22 @@
import { useTranslation } from 'react-i18next'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
export default function SettingsSyntaxValidation() {
const { t } = useTranslation()
const { syntaxValidation, setSyntaxValidation } = useProjectSettingsContext()
return (
<SettingsMenuSelect
<SettingsMenuSelect<boolean>
onChange={setSyntaxValidation}
value={syntaxValidation}
options={[
{
value: 'true',
value: true,
label: t('on'),
},
{
value: 'false',
value: false,
label: t('off'),
},
]}

View file

@ -0,0 +1,166 @@
import { createContext, useContext, useMemo } from 'react'
import type { PropsWithChildren } from 'react'
import type {
FontFamily,
LineHeight,
OverallTheme,
} from '../../../../../modules/source-editor/frontend/js/extensions/theme'
import type {
Keybindings,
PdfViewer,
ProjectCompiler,
} from '../../../../../types/project-settings'
import useScopeValue from '../../../shared/hooks/use-scope-value'
type ProjectSettingsContextValue = {
compiler: ProjectCompiler
setCompiler: (compiler: ProjectCompiler) => void
imageName: string
setImageName: (imageName: string) => void
rootDocId: string
setRootDocId: (rootDocId: string) => void
spellCheckLanguage: string
setSpellCheckLanguage: (spellCheckLanguage: string) => void
autoComplete: boolean
setAutoComplete: (autoComplete: boolean) => void
autoPairDelimiters: boolean
setAutoPairDelimiters: (autoPairDelimiters: boolean) => void
syntaxValidation: boolean
setSyntaxValidation: (syntaxValidation: boolean) => void
mode: Keybindings
setMode: (mode: Keybindings) => void
editorTheme: string
setEditorTheme: (editorTheme: string) => void
overallTheme: OverallTheme
setOverallTheme: (overallTheme: OverallTheme) => void
fontSize: string
setFontSize: (fontSize: string) => void
fontFamily: FontFamily
setFontFamily: (fontFamily: FontFamily) => void
lineHeight: LineHeight
setLineHeight: (lineHeight: LineHeight) => void
pdfViewer: PdfViewer
setPdfViewer: (pdfViewer: PdfViewer) => void
}
export const ProjectSettingsContext = createContext<
ProjectSettingsContextValue | undefined
>(undefined)
export function ProjectSettingsProvider({
children,
}: PropsWithChildren<Record<string, never>>) {
const [compiler, setCompiler] =
useScopeValue<ProjectCompiler>('project.compiler')
const [imageName, setImageName] = useScopeValue<string>('project.imageName')
const [rootDocId, setRootDocId] = useScopeValue<string>('project.rootDoc_id')
const [spellCheckLanguage, setSpellCheckLanguage] = useScopeValue<string>(
'project.spellCheckLanguage'
)
const [autoComplete, setAutoComplete] = useScopeValue<boolean>(
'settings.autoComplete'
)
const [autoPairDelimiters, setAutoPairDelimiters] = useScopeValue<boolean>(
'settings.autoPairDelimiters'
)
const [syntaxValidation, setSyntaxValidation] = useScopeValue<boolean>(
'settings.syntaxValidation'
)
const [editorTheme, setEditorTheme] = useScopeValue<string>(
'settings.editorTheme'
)
const [overallTheme, setOverallTheme] = useScopeValue<OverallTheme>(
'settings.overallTheme'
)
const [mode, setMode] = useScopeValue<Keybindings>('settings.mode')
const [fontSize, setFontSize] = useScopeValue<string>('settings.fontSize')
const [fontFamily, setFontFamily] = useScopeValue<FontFamily>(
'settings.fontFamily'
)
const [lineHeight, setLineHeight] = useScopeValue<LineHeight>(
'settings.lineHeight'
)
const [pdfViewer, setPdfViewer] =
useScopeValue<PdfViewer>('settings.pdfViewer')
const value: ProjectSettingsContextValue = useMemo(
() => ({
compiler,
setCompiler,
imageName,
setImageName,
rootDocId,
setRootDocId,
spellCheckLanguage,
setSpellCheckLanguage,
autoComplete,
setAutoComplete,
autoPairDelimiters,
setAutoPairDelimiters,
syntaxValidation,
setSyntaxValidation,
editorTheme,
setEditorTheme,
overallTheme,
setOverallTheme,
mode,
setMode,
fontSize,
setFontSize,
fontFamily,
setFontFamily,
lineHeight,
setLineHeight,
pdfViewer,
setPdfViewer,
}),
[
compiler,
setCompiler,
imageName,
setImageName,
rootDocId,
setRootDocId,
spellCheckLanguage,
setSpellCheckLanguage,
autoComplete,
setAutoComplete,
autoPairDelimiters,
setAutoPairDelimiters,
syntaxValidation,
setSyntaxValidation,
editorTheme,
setEditorTheme,
overallTheme,
setOverallTheme,
mode,
setMode,
fontSize,
setFontSize,
fontFamily,
setFontFamily,
lineHeight,
setLineHeight,
pdfViewer,
setPdfViewer,
]
)
return (
<ProjectSettingsContext.Provider value={value}>
{children}
</ProjectSettingsContext.Provider>
)
}
export function useProjectSettingsContext() {
const context = useContext(ProjectSettingsContext)
if (!context) {
throw new Error(
'useProjectSettingsContext is only available inside ProjectSettingsProvider'
)
}
return context
}

View file

@ -1,9 +1,9 @@
const validTeXFileRegExp = new RegExp(
`\\.(${window.ExposedSettings.validRootDocExtensions.join('|')})$`,
'i'
)
function isValidTeXFile(filename) {
const validTeXFileRegExp = new RegExp(
`\\.(${window.ExposedSettings.validRootDocExtensions.join('|')})$`,
'i'
)
return validTeXFileRegExp.test(filename)
}

View file

@ -12,6 +12,7 @@ import { ChatProvider } from '../../features/chat/context/chat-context'
import { ProjectProvider } from './project-context'
import { SplitTestProvider } from './split-test-context'
import { FileTreeDataProvider } from './file-tree-data-context'
import { ProjectSettingsProvider } from '../../features/editor-left-menu/context/project-settings-context'
export function ContextRoot({ children, ide, settings }) {
return (
@ -22,13 +23,15 @@ export function ContextRoot({ children, ide, settings }) {
<FileTreeDataProvider>
<DetachProvider>
<EditorProvider settings={settings}>
<LayoutProvider>
<LocalCompileProvider>
<DetachCompileProvider>
<ChatProvider>{children}</ChatProvider>
</DetachCompileProvider>
</LocalCompileProvider>
</LayoutProvider>
<ProjectSettingsProvider>
<LayoutProvider>
<LocalCompileProvider>
<DetachCompileProvider>
<ChatProvider>{children}</ChatProvider>
</DetachCompileProvider>
</LocalCompileProvider>
</LayoutProvider>
</ProjectSettingsProvider>
</EditorProvider>
</DetachProvider>
</FileTreeDataProvider>

View file

@ -0,0 +1,23 @@
import { screen, within } from '@testing-library/dom'
import { expect } from 'chai'
import fetchMock from 'fetch-mock'
import SettingsAutoCloseBrackets from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-auto-close-brackets'
import { renderWithEditorContext } from '../../../../helpers/render-with-context'
describe('<SettingsAutoCloseBrackets />', function () {
afterEach(function () {
fetchMock.reset()
})
it('shows correct menu', async function () {
renderWithEditorContext(<SettingsAutoCloseBrackets />)
const select = screen.getByLabelText('Auto-close Brackets')
const optionOn = within(select).getByText('On')
expect(optionOn.getAttribute('value')).to.equal('true')
const optionOff = within(select).getByText('Off')
expect(optionOff.getAttribute('value')).to.equal('false')
})
})

View file

@ -0,0 +1,23 @@
import { screen, within } from '@testing-library/dom'
import { expect } from 'chai'
import fetchMock from 'fetch-mock'
import SettingsAutoComplete from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-auto-complete'
import { renderWithEditorContext } from '../../../../helpers/render-with-context'
describe('<SettingsAutoComplete />', function () {
afterEach(function () {
fetchMock.reset()
})
it('shows correct menu', async function () {
renderWithEditorContext(<SettingsAutoComplete />)
const select = screen.getByLabelText('Auto-complete')
const optionOn = within(select).getByText('On')
expect(optionOn.getAttribute('value')).to.equal('true')
const optionOff = within(select).getByText('Off')
expect(optionOff.getAttribute('value')).to.equal('false')
})
})

View file

@ -0,0 +1,47 @@
import { screen, within } from '@testing-library/dom'
import { expect } from 'chai'
import sinon from 'sinon'
import fetchMock from 'fetch-mock'
import SettingsDocument from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-document'
import * as isValidTeXFileModule from '../../../../../../frontend/js/main/is-valid-tex-file'
import { renderWithEditorContext } from '../../../../helpers/render-with-context'
import type { MainDocument } from '../../../../../../types/project-settings'
describe('<SettingsDocument />', function () {
let isValidTeXFileStub: sinon.SinonStub
const docs: MainDocument[] = [
{
path: 'main.tex',
doc: {
name: 'main.tex',
id: '123abc',
type: 'doc',
selected: false,
},
},
]
beforeEach(function () {
isValidTeXFileStub = sinon
.stub(isValidTeXFileModule, 'default')
.returns(true)
})
afterEach(function () {
fetchMock.reset()
isValidTeXFileStub.restore()
})
it('shows correct menu', async function () {
renderWithEditorContext(<SettingsDocument />, {
scope: {
docs,
},
})
const select = screen.getByLabelText('Main document')
const optionOn = within(select).getByText('main.tex')
expect(optionOn.getAttribute('value')).to.equal('123abc')
})
})

View file

@ -0,0 +1,37 @@
import { screen, within } from '@testing-library/dom'
import { expect } from 'chai'
import fetchMock from 'fetch-mock'
import SettingsEditorTheme from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-editor-theme'
import { renderWithEditorContext } from '../../../../helpers/render-with-context'
describe('<SettingsEditorTheme />', function () {
const editorThemes = ['editortheme-1', 'editortheme-2', 'editortheme-3']
const legacyEditorThemes = ['legacytheme-1', 'legacytheme-2', 'legacytheme-3']
beforeEach(function () {
window.metaAttributesCache.set('ol-editorThemes', editorThemes)
window.metaAttributesCache.set('ol-legacyEditorThemes', legacyEditorThemes)
})
afterEach(function () {
fetchMock.reset()
window.metaAttributesCache = new Map()
})
it('shows correct menu', async function () {
renderWithEditorContext(<SettingsEditorTheme />)
const select = screen.getByLabelText('Editor theme')
for (const theme of editorThemes) {
const option = within(select).getByText(theme.replace(/_/g, ' '))
expect(option.getAttribute('value')).to.equal(theme)
}
for (const theme of legacyEditorThemes) {
const option = within(select).getByText(theme.replace(/_/g, ' '))
expect(option.getAttribute('value')).to.equal(theme)
}
})
})

View file

@ -0,0 +1,23 @@
import { screen, within } from '@testing-library/dom'
import { expect } from 'chai'
import fetchMock from 'fetch-mock'
import SettingsFontFamily from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-font-family'
import { renderWithEditorContext } from '../../../../helpers/render-with-context'
describe('<SettingsFontFamily />', function () {
afterEach(function () {
fetchMock.reset()
})
it('shows correct menu', async function () {
renderWithEditorContext(<SettingsFontFamily />)
const select = screen.getByLabelText('Font Family')
const optionMonaco = within(select).getByText('Monaco / Menlo / Consolas')
expect(optionMonaco.getAttribute('value')).to.equal('monaco')
const optionLucida = within(select).getByText('Lucida / Source Code Pro')
expect(optionLucida.getAttribute('value')).to.equal('lucida')
})
})

View file

@ -0,0 +1,24 @@
import { screen, within } from '@testing-library/dom'
import { expect } from 'chai'
import fetchMock from 'fetch-mock'
import SettingsFontSize from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-font-size'
import { renderWithEditorContext } from '../../../../helpers/render-with-context'
describe('<SettingsFontSize />', function () {
const sizes = ['10', '11', '12', '13', '14', '16', '18', '20', '22', '24']
afterEach(function () {
fetchMock.reset()
})
it('shows correct menu', async function () {
renderWithEditorContext(<SettingsFontSize />)
const select = screen.getByLabelText('Font Size')
for (const size of sizes) {
const option = within(select).getByText(`${size}px`)
expect(option.getAttribute('value')).to.equal(size)
}
})
})

View file

@ -0,0 +1,39 @@
import { screen, within } from '@testing-library/dom'
import { expect } from 'chai'
import fetchMock from 'fetch-mock'
import SettingsImageName from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-image-name'
import type { AllowedImageName } from '../../../../../../types/project-settings'
import { renderWithEditorContext } from '../../../../helpers/render-with-context'
describe('<SettingsImageName />', function () {
const allowedImageNames: AllowedImageName[] = [
{
imageDesc: 'Image 1',
imageName: 'img-1',
},
{
imageDesc: 'Image 2',
imageName: 'img-2',
},
]
beforeEach(function () {
window.metaAttributesCache.set('ol-allowedImageNames', allowedImageNames)
})
afterEach(function () {
fetchMock.reset()
window.metaAttributesCache = new Map()
})
it('shows correct menu', async function () {
renderWithEditorContext(<SettingsImageName />)
const select = screen.getByLabelText('TeX Live version')
for (const { imageName, imageDesc } of allowedImageNames) {
const option = within(select).getByText(imageDesc)
expect(option.getAttribute('value')).to.equal(imageName)
}
})
})

View file

@ -0,0 +1,26 @@
import { screen, within } from '@testing-library/dom'
import { expect } from 'chai'
import fetchMock from 'fetch-mock'
import SettingsKeybindings from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-keybindings'
import { renderWithEditorContext } from '../../../../helpers/render-with-context'
describe('<SettingsKeybindings />', function () {
afterEach(function () {
fetchMock.reset()
})
it('shows correct menu', async function () {
renderWithEditorContext(<SettingsKeybindings />)
const select = screen.getByLabelText('Keybindings')
const optionNone = within(select).getByText('None')
expect(optionNone.getAttribute('value')).to.equal('default')
const optionVim = within(select).getByText('Vim')
expect(optionVim.getAttribute('value')).to.equal('vim')
const optionEmacs = within(select).getByText('Emacs')
expect(optionEmacs.getAttribute('value')).to.equal('emacs')
})
})

View file

@ -0,0 +1,26 @@
import { screen, within } from '@testing-library/dom'
import { expect } from 'chai'
import fetchMock from 'fetch-mock'
import SettingsLineHeight from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-line-height'
import { renderWithEditorContext } from '../../../../helpers/render-with-context'
describe('<SettingsLineHeight />', function () {
afterEach(function () {
fetchMock.reset()
})
it('shows correct menu', async function () {
renderWithEditorContext(<SettingsLineHeight />)
const select = screen.getByLabelText('Line Height')
const optionCompact = within(select).getByText('Compact')
expect(optionCompact.getAttribute('value')).to.equal('compact')
const optionNormal = within(select).getByText('Normal')
expect(optionNormal.getAttribute('value')).to.equal('normal')
const optionWide = within(select).getByText('Wide')
expect(optionWide.getAttribute('value')).to.equal('wide')
})
})

View file

@ -0,0 +1,41 @@
import { screen, within } from '@testing-library/dom'
import { expect } from 'chai'
import fetchMock from 'fetch-mock'
import SettingsOverallTheme from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-overall-theme'
import type { OverallThemeMeta } from '../../../../../../types/project-settings'
import { renderWithEditorContext } from '../../../../helpers/render-with-context'
describe('<SettingsOverallTheme />', function () {
const overallThemes: OverallThemeMeta[] = [
{
name: 'Overall Theme 1',
val: '',
path: 'https://overleaf.com/overalltheme-1.css',
},
{
name: 'Overall Theme 2',
val: 'light-',
path: 'https://overleaf.com/overalltheme-2.css',
},
]
beforeEach(function () {
window.metaAttributesCache.set('ol-overallThemes', overallThemes)
})
afterEach(function () {
fetchMock.reset()
window.metaAttributesCache = new Map()
})
it('shows correct menu', async function () {
renderWithEditorContext(<SettingsOverallTheme />)
const select = screen.getByLabelText('Overall theme')
for (const theme of overallThemes) {
const option = within(select).getByText(theme.name)
expect(option.getAttribute('value')).to.equal(theme.val)
}
})
})

View file

@ -0,0 +1,23 @@
import { screen, within } from '@testing-library/dom'
import { expect } from 'chai'
import fetchMock from 'fetch-mock'
import SettingsPdfViewer from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-pdf-viewer'
import { renderWithEditorContext } from '../../../../helpers/render-with-context'
describe('<SettingsPdfViewer />', function () {
afterEach(function () {
fetchMock.reset()
})
it('shows correct menu', async function () {
renderWithEditorContext(<SettingsPdfViewer />)
const select = screen.getByLabelText('PDF Viewer')
const optionOverleaf = within(select).getByText('Overleaf')
expect(optionOverleaf.getAttribute('value')).to.equal('pdfjs')
const optionBrowser = within(select).getByText('Browser')
expect(optionBrowser.getAttribute('value')).to.equal('native')
})
})

View file

@ -0,0 +1,42 @@
import { screen, within } from '@testing-library/dom'
import { expect } from 'chai'
import fetchMock from 'fetch-mock'
import SettingsSpellCheckLanguage from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-spell-check-language'
import type { SpellCheckLanguage } from '../../../../../../types/project-settings'
import { renderWithEditorContext } from '../../../../helpers/render-with-context'
describe('<SettingsSpellCheckLanguage />', function () {
const languages: SpellCheckLanguage[] = [
{
name: 'Lang 1',
code: 'lang-1',
},
{
name: 'Lang 2',
code: 'lang-2',
},
]
beforeEach(function () {
window.metaAttributesCache.set('ol-languages', languages)
})
afterEach(function () {
fetchMock.reset()
window.metaAttributesCache = new Map()
})
it('shows correct menu', async function () {
renderWithEditorContext(<SettingsSpellCheckLanguage />)
const select = screen.getByLabelText('Spell check')
const optionEmpty = within(select).getByText('Off')
expect(optionEmpty.getAttribute('value')).to.equal('')
for (const language of languages) {
const option = within(select).getByText(language.name)
expect(option.getAttribute('value')).to.equal(language.code)
}
})
})

View file

@ -0,0 +1,23 @@
import { screen, within } from '@testing-library/dom'
import { expect } from 'chai'
import fetchMock from 'fetch-mock'
import SettingsSyntaxValidation from '../../../../../../frontend/js/features/editor-left-menu/components/settings/settings-syntax-validation'
import { renderWithEditorContext } from '../../../../helpers/render-with-context'
describe('<SettingsSyntaxValidation />', function () {
afterEach(function () {
fetchMock.reset()
})
it('shows correct menu', async function () {
renderWithEditorContext(<SettingsSyntaxValidation />)
const select = screen.getByLabelText('Code check')
const optionOn = within(select).getByText('On')
expect(optionOn.getAttribute('value')).to.equal('true')
const optionOff = within(select).getByText('Off')
expect(optionOff.getAttribute('value')).to.equal('false')
})
})

View file

@ -12,6 +12,7 @@ import { DetachProvider } from '../../../frontend/js/shared/context/detach-conte
import { LayoutProvider } from '../../../frontend/js/shared/context/layout-context'
import { LocalCompileProvider } from '../../../frontend/js/shared/context/local-compile-context'
import { DetachCompileProvider } from '../../../frontend/js/shared/context/detach-compile-context'
import { ProjectSettingsProvider } from '../../../frontend/js/features/editor-left-menu/context/project-settings-context'
// these constants can be imported in tests instead of
// using magic strings
@ -112,11 +113,15 @@ export function EditorProviders({
<FileTreeDataProvider>
<DetachProvider>
<EditorProvider settings={{}}>
<LayoutProvider>
<LocalCompileProvider>
<DetachCompileProvider>{children}</DetachCompileProvider>
</LocalCompileProvider>
</LayoutProvider>
<ProjectSettingsProvider>
<LayoutProvider>
<LocalCompileProvider>
<DetachCompileProvider>
{children}
</DetachCompileProvider>
</LocalCompileProvider>
</LayoutProvider>
</ProjectSettingsProvider>
</EditorProvider>
</DetachProvider>
</FileTreeDataProvider>

View file

@ -0,0 +1,33 @@
import { OverallTheme } from '../modules/source-editor/frontend/js/extensions/theme'
export type AllowedImageName = {
imageDesc: string
imageName: string
}
export type MainDocument = {
doc: {
name: string
id: string
type: string
selected: boolean
}
path: string
}
export type ProjectCompiler = 'pdflatex' | 'latex' | 'xelatex' | 'lualatex'
export type Keybindings = 'default' | 'vim' | 'emacs'
export type OverallThemeMeta = {
name: string
path: string
val: OverallTheme
}
export type PdfViewer = 'pdfjs' | 'native'
export type SpellCheckLanguage = {
name: string
code: string
}