Przeglądaj źródła

Merge pull request #29253 from overleaf/dp-settings-modal-test-improvements

Improvements to settings modal frontend tests

GitOrigin-RevId: cbf30df51aa4b290580340507ba591e863a4f83b
David 9 miesięcy temu
rodzic
commit
43e96ad863
15 zmienionych plików z 526 dodań i 58 usunięć
  1. 44 0
      services/web/test/frontend/features/settings-modal/settings/auto-compile-setting.test.tsx
  2. 51 0
      services/web/test/frontend/features/settings-modal/settings/breadcrumbs-setting.test.tsx
  3. 47 12
      services/web/test/frontend/features/settings-modal/settings/compiler-setting.test.tsx
  4. 45 0
      services/web/test/frontend/features/settings-modal/settings/draft-setting.test.tsx
  5. 22 2
      services/web/test/frontend/features/settings-modal/settings/editor-theme-setting.test.tsx
  6. 36 10
      services/web/test/frontend/features/settings-modal/settings/font-family-setting.test.tsx
  7. 16 1
      services/web/test/frontend/features/settings-modal/settings/font-size-setting.test.tsx
  8. 24 2
      services/web/test/frontend/features/settings-modal/settings/image-name-setting.test.tsx
  9. 36 9
      services/web/test/frontend/features/settings-modal/settings/keybinding-setting.test.tsx
  10. 34 8
      services/web/test/frontend/features/settings-modal/settings/line-height-setting.test.tsx
  11. 18 2
      services/web/test/frontend/features/settings-modal/settings/overall-theme-setting.test.tsx
  12. 32 6
      services/web/test/frontend/features/settings-modal/settings/pdf-viewer-setting.test.tsx
  13. 44 4
      services/web/test/frontend/features/settings-modal/settings/root-document-setting.test.tsx
  14. 33 2
      services/web/test/frontend/features/settings-modal/settings/spell-check-setting.test.tsx
  15. 44 0
      services/web/test/frontend/features/settings-modal/settings/stop-on-first-error-setting.test.tsx

+ 44 - 0
services/web/test/frontend/features/settings-modal/settings/auto-compile-setting.test.tsx

@@ -0,0 +1,44 @@
+import { screen, render } from '@testing-library/react'
+import { expect } from 'chai'
+import fetchMock from 'fetch-mock'
+import { SettingsModalProvider } from '@/features/ide-redesign/contexts/settings-modal-context'
+import {
+  EditorProviders,
+  projectDefaults,
+} from '../../../helpers/editor-providers'
+import AutoCompileSetting from '@/features/ide-redesign/components/settings/compiler-settings/auto-compile-setting'
+import localStorage from '@/infrastructure/local-storage'
+import userEvent from '@testing-library/user-event'
+
+describe('<AutoCompileSetting />', function () {
+  afterEach(function () {
+    fetchMock.removeRoutes().clearHistory()
+  })
+
+  it('can toggle', async function () {
+    render(
+      <EditorProviders>
+        <SettingsModalProvider>
+          <AutoCompileSetting />
+        </SettingsModalProvider>
+      </EditorProviders>
+    )
+
+    const toggle = screen.getByLabelText('Autocompile')
+    const startingCheckedValue = (toggle as HTMLInputElement).checked
+
+    // Toggle the checkbox
+    await userEvent.click(toggle)
+    expect((toggle as HTMLInputElement).checked).to.equal(!startingCheckedValue)
+    expect(
+      localStorage.getItem(`autocompile_enabled:${projectDefaults._id}`)
+    ).to.equal(!startingCheckedValue)
+
+    // Toggle back to original value
+    await userEvent.click(toggle)
+    expect((toggle as HTMLInputElement).checked).to.equal(startingCheckedValue)
+    expect(
+      !!localStorage.getItem(`autocompile_enabled:${projectDefaults._id}`)
+    ).to.equal(startingCheckedValue)
+  })
+})

+ 51 - 0
services/web/test/frontend/features/settings-modal/settings/breadcrumbs-setting.test.tsx

@@ -0,0 +1,51 @@
+import { screen, render } from '@testing-library/react'
+import { expect } from 'chai'
+import fetchMock from 'fetch-mock'
+import { SettingsModalProvider } from '@/features/ide-redesign/contexts/settings-modal-context'
+import { EditorProviders } from '../../../helpers/editor-providers'
+import BreadcrumbsSetting from '@/features/ide-redesign/components/settings/editor-settings/breadcrumbs-setting'
+
+describe('<BreadcrumbsSetting />', function () {
+  afterEach(function () {
+    fetchMock.removeRoutes().clearHistory()
+  })
+
+  it('can toggle', async function () {
+    render(
+      <EditorProviders>
+        <SettingsModalProvider>
+          <BreadcrumbsSetting />
+        </SettingsModalProvider>
+      </EditorProviders>
+    )
+
+    const saveSettingsMock = fetchMock.post(
+      `express:/user/settings`,
+      {
+        status: 200,
+      },
+      { delay: 0 }
+    )
+
+    const toggle = screen.getByLabelText('Breadcrumbs')
+    const startingCheckedValue = (toggle as HTMLInputElement).checked
+
+    // Toggle the checkbox
+    toggle.click()
+    expect((toggle as HTMLInputElement).checked).to.equal(!startingCheckedValue)
+    expect(
+      saveSettingsMock.callHistory.called(`/user/settings`, {
+        body: { breadcrumbs: !startingCheckedValue },
+      })
+    ).to.be.true
+
+    // Toggle back to original value
+    toggle.click()
+    expect((toggle as HTMLInputElement).checked).to.equal(startingCheckedValue)
+    expect(
+      saveSettingsMock.callHistory.called(`/user/settings`, {
+        body: { breadcrumbs: startingCheckedValue },
+      })
+    ).to.be.true
+  })
+})

+ 47 - 12
services/web/test/frontend/features/settings-modal/settings/compiler-setting.test.tsx

@@ -2,15 +2,38 @@ import { screen, within, render } from '@testing-library/react'
 import { expect } from 'chai'
 import fetchMock from 'fetch-mock'
 import { SettingsModalProvider } from '@/features/ide-redesign/contexts/settings-modal-context'
-import { EditorProviders } from '../../../helpers/editor-providers'
+import {
+  EditorProviders,
+  projectDefaults,
+} from '../../../helpers/editor-providers'
 import CompilerSetting from '@/features/ide-redesign/components/settings/compiler-settings/compiler-setting'
+import userEvent from '@testing-library/user-event'
+
+const OPTIONS = [
+  {
+    label: 'pdfLaTeX',
+    value: 'pdflatex',
+  },
+  {
+    label: 'LaTeX',
+    value: 'latex',
+  },
+  {
+    label: 'XeLaTeX',
+    value: 'xelatex',
+  },
+  {
+    label: 'LuaLaTeX',
+    value: 'lualatex',
+  },
+]
 
 describe('<CompilerSetting />', function () {
   afterEach(function () {
     fetchMock.removeRoutes().clearHistory()
   })
 
-  it('shows correct menu', async function () {
+  it('each option is shown and can be selected', async function () {
     render(
       <EditorProviders>
         <SettingsModalProvider>
@@ -19,18 +42,30 @@ describe('<CompilerSetting />', function () {
       </EditorProviders>
     )
 
-    const select = screen.getByLabelText('Compiler')
-
-    const optionPdfLaTeX = within(select).getByText('pdfLaTeX')
-    expect(optionPdfLaTeX.getAttribute('value')).to.equal('pdflatex')
+    const saveSettingsMock = fetchMock.post(
+      `express:/project/:projectId/settings`,
+      {
+        status: 200,
+      },
+      { delay: 0 }
+    )
 
-    const optionLaTeX = within(select).getByText('LaTeX')
-    expect(optionLaTeX.getAttribute('value')).to.equal('latex')
+    const select = screen.getByLabelText('Compiler')
 
-    const optionXeLaTeX = within(select).getByText('XeLaTeX')
-    expect(optionXeLaTeX.getAttribute('value')).to.equal('xelatex')
+    // Reverse order so we test changing to each option
+    for (const option of OPTIONS.reverse()) {
+      const optionElement = within(select).getByText(option.label)
+      expect(optionElement.getAttribute('value')).to.equal(option.value)
+      await userEvent.selectOptions(select, [optionElement])
 
-    const optionLuaLaTeX = within(select).getByText('LuaLaTeX')
-    expect(optionLuaLaTeX.getAttribute('value')).to.equal('lualatex')
+      expect(
+        saveSettingsMock.callHistory.called(
+          `/project/${projectDefaults._id}/settings`,
+          {
+            body: { compiler: option.value },
+          }
+        )
+      ).to.be.true
+    }
   })
 })

+ 45 - 0
services/web/test/frontend/features/settings-modal/settings/draft-setting.test.tsx

@@ -0,0 +1,45 @@
+import { screen, within, render } from '@testing-library/react'
+import { expect } from 'chai'
+import { SettingsModalProvider } from '@/features/ide-redesign/contexts/settings-modal-context'
+import {
+  EditorProviders,
+  projectDefaults,
+} from '../../../helpers/editor-providers'
+import userEvent from '@testing-library/user-event'
+import DraftSetting from '@/features/ide-redesign/components/settings/compiler-settings/draft-setting'
+
+const OPTIONS = [
+  {
+    label: 'Normal',
+    value: false,
+  },
+  {
+    label: 'Fast [draft]',
+    value: true,
+  },
+]
+
+describe('<DraftSetting />', function () {
+  it('each option is shown and can be selected', async function () {
+    render(
+      <EditorProviders>
+        <SettingsModalProvider>
+          <DraftSetting />
+        </SettingsModalProvider>
+      </EditorProviders>
+    )
+
+    const select = screen.getByLabelText('Compile mode')
+
+    for (const option of OPTIONS) {
+      const optionElement = within(select).getByText(option.label)
+      expect(optionElement.getAttribute('value')).to.equal(
+        option.value.toString()
+      )
+      await userEvent.selectOptions(select, [optionElement])
+      expect(!!localStorage.getItem(`draft:${projectDefaults._id}`)).to.equal(
+        option.value
+      )
+    }
+  })
+})

+ 22 - 2
services/web/test/frontend/features/settings-modal/settings/editor-theme-setting.test.tsx

@@ -4,10 +4,10 @@ import fetchMock from 'fetch-mock'
 import { SettingsModalProvider } from '@/features/ide-redesign/contexts/settings-modal-context'
 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'
 
 describe('<EditorThemeSetting />', function () {
   const editorThemes = ['editortheme-1', 'editortheme-2', 'editortheme-3']
-
   const legacyEditorThemes = ['legacytheme-1', 'legacytheme-2', 'legacytheme-3']
 
   beforeEach(function () {
@@ -19,7 +19,7 @@ describe('<EditorThemeSetting />', function () {
     fetchMock.removeRoutes().clearHistory()
   })
 
-  it('shows correct menu', async function () {
+  it('each option is shown and can be selected', async function () {
     render(
       <EditorProviders>
         <SettingsModalProvider>
@@ -28,11 +28,25 @@ describe('<EditorThemeSetting />', function () {
       </EditorProviders>
     )
 
+    const saveSettingsMock = fetchMock.post(
+      `express:/user/settings`,
+      {
+        status: 200,
+      },
+      { delay: 0 }
+    )
+
     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)
+      await userEvent.selectOptions(select, [option])
+      expect(
+        saveSettingsMock.callHistory.called(`/user/settings`, {
+          body: { editorTheme: theme },
+        })
+      ).to.be.true
     }
 
     for (const theme of legacyEditorThemes) {
@@ -40,6 +54,12 @@ describe('<EditorThemeSetting />', function () {
         theme.replace(/_/g, ' ') + ' (Legacy)'
       )
       expect(option.getAttribute('value')).to.equal(theme)
+      await userEvent.selectOptions(select, [option])
+      expect(
+        saveSettingsMock.callHistory.called(`/user/settings`, {
+          body: { editorTheme: theme },
+        })
+      ).to.be.true
     }
   })
 })

+ 36 - 10
services/web/test/frontend/features/settings-modal/settings/font-family-setting.test.tsx

@@ -4,13 +4,29 @@ import fetchMock from 'fetch-mock'
 import { SettingsModalProvider } from '@/features/ide-redesign/contexts/settings-modal-context'
 import { EditorProviders } from '../../../helpers/editor-providers'
 import FontFamilySetting from '@/features/ide-redesign/components/settings/appearance-settings/font-family-setting'
+import userEvent from '@testing-library/user-event'
+
+const options = [
+  {
+    label: 'Monaco / Menlo / Consolas',
+    value: 'monaco',
+  },
+  {
+    label: 'Lucida / Source Code Pro',
+    value: 'lucida',
+  },
+  {
+    label: 'OpenDyslexic Mono',
+    value: 'opendyslexicmono',
+  },
+]
 
 describe('<FontFamilySetting />', function () {
   afterEach(function () {
     fetchMock.removeRoutes().clearHistory()
   })
 
-  it('shows correct menu', async function () {
+  it('each option is shown and can be selected', async function () {
     render(
       <EditorProviders>
         <SettingsModalProvider>
@@ -19,17 +35,27 @@ describe('<FontFamilySetting />', function () {
       </EditorProviders>
     )
 
-    const select = screen.getByLabelText('Editor font family')
+    const saveSettingsMock = fetchMock.post(
+      'express:/user/settings',
+      {
+        status: 200,
+      },
+      { delay: 0 }
+    )
 
-    const optionMonaco = within(select).getByText('Monaco / Menlo / Consolas')
-    expect(optionMonaco.getAttribute('value')).to.equal('monaco')
+    const select = screen.getByLabelText('Editor font family')
 
-    const optionLucida = within(select).getByText('Lucida / Source Code Pro')
-    expect(optionLucida.getAttribute('value')).to.equal('lucida')
+    // Reverse order so we test changing to each option
+    for (const option of options.reverse()) {
+      const optionElement = within(select).getByText(option.label)
+      expect(optionElement.getAttribute('value')).to.equal(option.value)
+      await userEvent.selectOptions(select, [optionElement])
 
-    const optionOpenDyslexicMono = within(select).getByText('OpenDyslexic Mono')
-    expect(optionOpenDyslexicMono.getAttribute('value')).to.equal(
-      'opendyslexicmono'
-    )
+      expect(
+        saveSettingsMock.callHistory.called('/user/settings', {
+          body: { fontFamily: option.value },
+        })
+      ).to.be.true
+    }
   })
 })

+ 16 - 1
services/web/test/frontend/features/settings-modal/settings/font-size-setting.test.tsx

@@ -4,6 +4,7 @@ import fetchMock from 'fetch-mock'
 import { SettingsModalProvider } from '@/features/ide-redesign/contexts/settings-modal-context'
 import { EditorProviders } from '../../../helpers/editor-providers'
 import FontSizeSetting from '@/features/ide-redesign/components/settings/appearance-settings/font-size-setting'
+import userEvent from '@testing-library/user-event'
 
 describe('<FontSizeSetting />', function () {
   const sizes = ['10', '11', '12', '13', '14', '16', '18', '20', '22', '24']
@@ -12,7 +13,7 @@ describe('<FontSizeSetting />', function () {
     fetchMock.removeRoutes().clearHistory()
   })
 
-  it('shows correct menu', async function () {
+  it('each option is shown and can be selected', async function () {
     render(
       <EditorProviders>
         <SettingsModalProvider>
@@ -21,11 +22,25 @@ describe('<FontSizeSetting />', function () {
       </EditorProviders>
     )
 
+    const saveSettingsMock = fetchMock.post(
+      'express:/user/settings',
+      {
+        status: 200,
+      },
+      { delay: 0 }
+    )
+
     const select = screen.getByLabelText('Editor font size')
 
     for (const size of sizes) {
       const option = within(select).getByText(`${size}px`)
       expect(option.getAttribute('value')).to.equal(size)
+      await userEvent.selectOptions(select, [option])
+      expect(
+        saveSettingsMock.callHistory.called('/user/settings', {
+          body: { fontSize: Number(size) },
+        })
+      ).to.be.true
     }
   })
 })

+ 24 - 2
services/web/test/frontend/features/settings-modal/settings/image-name-setting.test.tsx

@@ -3,8 +3,12 @@ import { expect } from 'chai'
 import fetchMock from 'fetch-mock'
 import type { ImageName } from '../../../../../types/project-settings'
 import { SettingsModalProvider } from '@/features/ide-redesign/contexts/settings-modal-context'
-import { EditorProviders } from '../../../helpers/editor-providers'
+import {
+  EditorProviders,
+  projectDefaults,
+} from '../../../helpers/editor-providers'
 import ImageNameSetting from '@/features/ide-redesign/components/settings/compiler-settings/image-name-setting'
+import userEvent from '@testing-library/user-event'
 
 describe('<ImageNameSetting />', function () {
   const imageNames: ImageName[] = [
@@ -28,7 +32,7 @@ describe('<ImageNameSetting />', function () {
     fetchMock.removeRoutes().clearHistory()
   })
 
-  it('shows correct menu', async function () {
+  it('each option is shown and can be selected', async function () {
     render(
       <EditorProviders>
         <SettingsModalProvider>
@@ -37,11 +41,29 @@ describe('<ImageNameSetting />', function () {
       </EditorProviders>
     )
 
+    const saveSettingsMock = fetchMock.post(
+      `express:/project/:projectId/settings`,
+      {
+        status: 200,
+      },
+      { delay: 0 }
+    )
+
     const select = screen.getByLabelText('TeX Live version')
 
     for (const { imageName, imageDesc } of imageNames) {
       const option = within(select).getByText(imageDesc)
       expect(option.getAttribute('value')).to.equal(imageName)
+      await userEvent.selectOptions(select, [option])
+
+      expect(
+        saveSettingsMock.callHistory.called(
+          `/project/${projectDefaults._id}/settings`,
+          {
+            body: { imageName },
+          }
+        )
+      ).to.be.true
     }
   })
 })

+ 36 - 9
services/web/test/frontend/features/settings-modal/settings/keybinding-setting.test.tsx

@@ -4,13 +4,29 @@ import fetchMock from 'fetch-mock'
 import { EditorProviders } from '../../../helpers/editor-providers'
 import { SettingsModalProvider } from '@/features/ide-redesign/contexts/settings-modal-context'
 import KeybindingSetting from '@/features/ide-redesign/components/settings/editor-settings/keybinding-setting'
+import userEvent from '@testing-library/user-event'
+
+const OPTIONS = [
+  {
+    label: 'None',
+    value: 'default',
+  },
+  {
+    label: 'Vim',
+    value: 'vim',
+  },
+  {
+    label: 'Emacs',
+    value: 'emacs',
+  },
+]
 
 describe('<KeybindingSetting />', function () {
   afterEach(function () {
     fetchMock.removeRoutes().clearHistory()
   })
 
-  it('shows correct menu', async function () {
+  it('each option is shown and can be selected', async function () {
     render(
       <EditorProviders>
         <SettingsModalProvider>
@@ -19,15 +35,26 @@ describe('<KeybindingSetting />', function () {
       </EditorProviders>
     )
 
-    const select = screen.getByLabelText('Keybindings')
-
-    const optionNone = within(select).getByText('None')
-    expect(optionNone.getAttribute('value')).to.equal('default')
+    const saveSettingsMock = fetchMock.post(
+      'express:/user/settings',
+      {
+        status: 200,
+      },
+      { delay: 0 }
+    )
 
-    const optionVim = within(select).getByText('Vim')
-    expect(optionVim.getAttribute('value')).to.equal('vim')
+    const select = screen.getByLabelText('Keybindings')
 
-    const optionEmacs = within(select).getByText('Emacs')
-    expect(optionEmacs.getAttribute('value')).to.equal('emacs')
+    // Reverse order so we test changing to each option
+    for (const option of OPTIONS.reverse()) {
+      const optionElement = within(select).getByText(option.label)
+      expect(optionElement.getAttribute('value')).to.equal(option.value)
+      await userEvent.selectOptions(select, [optionElement])
+      expect(
+        saveSettingsMock.callHistory.called('/user/settings', {
+          body: { mode: option.value },
+        })
+      ).to.be.true
+    }
   })
 })

+ 34 - 8
services/web/test/frontend/features/settings-modal/settings/line-height-setting.test.tsx

@@ -4,13 +4,29 @@ import fetchMock from 'fetch-mock'
 import { EditorProviders } from '../../../helpers/editor-providers'
 import { SettingsModalProvider } from '@/features/ide-redesign/contexts/settings-modal-context'
 import LineHeightSetting from '@/features/ide-redesign/components/settings/appearance-settings/line-height-setting'
+import userEvent from '@testing-library/user-event'
+
+const OPTIONS = [
+  {
+    label: 'Compact',
+    value: 'compact',
+  },
+  {
+    label: 'Normal',
+    value: 'normal',
+  },
+  {
+    label: 'Wide',
+    value: 'wide',
+  },
+]
 
 describe('<LineHeightSetting />', function () {
   afterEach(function () {
     fetchMock.removeRoutes().clearHistory()
   })
 
-  it('shows correct menu', async function () {
+  it('each option is shown and can be selected', async function () {
     render(
       <EditorProviders>
         <SettingsModalProvider>
@@ -21,13 +37,23 @@ describe('<LineHeightSetting />', function () {
 
     const select = screen.getByLabelText('Editor 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 saveSettingsMock = fetchMock.post(
+      'express:/user/settings',
+      {
+        status: 200,
+      },
+      { delay: 0 }
+    )
 
-    const optionWide = within(select).getByText('Wide')
-    expect(optionWide.getAttribute('value')).to.equal('wide')
+    for (const option of OPTIONS) {
+      const optionElement = within(select).getByText(option.label)
+      expect(optionElement.getAttribute('value')).to.equal(option.value)
+      await userEvent.selectOptions(select, [optionElement])
+      expect(
+        saveSettingsMock.callHistory.called('/user/settings', {
+          body: { lineHeight: option.value },
+        })
+      ).to.be.true
+    }
   })
 })

+ 18 - 2
services/web/test/frontend/features/settings-modal/settings/overall-theme-setting.test.tsx

@@ -7,6 +7,7 @@ import getMeta from '@/utils/meta'
 import { EditorProviders } from '../../../helpers/editor-providers'
 import { SettingsModalProvider } from '@/features/ide-redesign/contexts/settings-modal-context'
 import OverallThemeSetting from '@/features/ide-redesign/components/settings/appearance-settings/overall-theme-setting'
+import userEvent from '@testing-library/user-event'
 
 const IEEE_BRAND_ID = 1234
 const OTHER_BRAND_ID = 2234
@@ -36,7 +37,7 @@ describe('<OverallThemeSetting />', function () {
     fetchMock.removeRoutes().clearHistory()
   })
 
-  it('shows correct menu', async function () {
+  it('each option is shown and can be selected', async function () {
     render(
       <EditorProviders>
         <SettingsModalProvider>
@@ -45,11 +46,26 @@ describe('<OverallThemeSetting />', function () {
       </EditorProviders>
     )
 
+    const saveSettingsMock = fetchMock.post(
+      'express:/user/settings',
+      {
+        status: 200,
+      },
+      { delay: 0 }
+    )
+
     const select = screen.getByLabelText('Overall theme')
 
-    for (const theme of overallThemes) {
+    // Reverse order so we test changing to each option
+    for (const theme of overallThemes.reverse()) {
       const option = within(select).getByText(theme.name)
       expect(option.getAttribute('value')).to.equal(theme.val)
+      await userEvent.selectOptions(select, [option])
+      expect(
+        saveSettingsMock.callHistory.called('/user/settings', {
+          body: { overallTheme: theme.val },
+        })
+      ).to.be.true
     }
   })
   describe('Branded Project', function () {

+ 32 - 6
services/web/test/frontend/features/settings-modal/settings/pdf-viewer-setting.test.tsx

@@ -4,13 +4,25 @@ import fetchMock from 'fetch-mock'
 import { EditorProviders } from '../../../helpers/editor-providers'
 import { SettingsModalProvider } from '@/features/ide-redesign/contexts/settings-modal-context'
 import PDFViewerSetting from '@/features/ide-redesign/components/settings/editor-settings/pdf-viewer-setting'
+import userEvent from '@testing-library/user-event'
+
+const OPTIONS = [
+  {
+    label: 'Overleaf',
+    value: 'pdfjs',
+  },
+  {
+    label: 'Browser',
+    value: 'native',
+  },
+]
 
 describe('<PDFViewerSetting />', function () {
   afterEach(function () {
     fetchMock.removeRoutes().clearHistory()
   })
 
-  it('shows correct menu', async function () {
+  it('each option is shown and can be selected', async function () {
     render(
       <EditorProviders>
         <SettingsModalProvider>
@@ -19,12 +31,26 @@ describe('<PDFViewerSetting />', function () {
       </EditorProviders>
     )
 
-    const select = screen.getByLabelText('PDF Viewer')
+    const saveSettingsMock = fetchMock.post(
+      'express:/user/settings',
+      {
+        status: 200,
+      },
+      { delay: 0 }
+    )
 
-    const optionOverleaf = within(select).getByText('Overleaf')
-    expect(optionOverleaf.getAttribute('value')).to.equal('pdfjs')
+    const select = screen.getByLabelText('PDF Viewer')
 
-    const optionBrowser = within(select).getByText('Browser')
-    expect(optionBrowser.getAttribute('value')).to.equal('native')
+    // Reverse order so we test changing to each option
+    for (const option of OPTIONS.reverse()) {
+      const optionElement = within(select).getByText(option.label)
+      expect(optionElement.getAttribute('value')).to.equal(option.value)
+      await userEvent.selectOptions(select, [optionElement])
+      expect(
+        saveSettingsMock.callHistory.called('/user/settings', {
+          body: { pdfViewer: option.value },
+        })
+      ).to.be.true
+    }
   })
 })

+ 44 - 4
services/web/test/frontend/features/settings-modal/settings/root-document-setting.test.tsx

@@ -3,8 +3,23 @@ import { expect } from 'chai'
 import fetchMock from 'fetch-mock'
 import { Folder } from '../../../../../types/folder'
 import { SettingsModalProvider } from '@/features/ide-redesign/contexts/settings-modal-context'
-import { EditorProviders } from '../../../helpers/editor-providers'
+import {
+  EditorProviders,
+  projectDefaults,
+} from '../../../helpers/editor-providers'
 import RootDocumentSetting from '@/features/ide-redesign/components/settings/compiler-settings/root-document-setting'
+import userEvent from '@testing-library/user-event'
+
+const OPTIONS = [
+  {
+    label: 'main.tex',
+    value: '123abc',
+  },
+  {
+    label: 'another.tex',
+    value: '123abcd',
+  },
+]
 
 describe('<RootDocumentSetting />', function () {
   const rootFolder: Folder = {
@@ -15,6 +30,10 @@ describe('<RootDocumentSetting />', function () {
         _id: '123abc',
         name: 'main.tex',
       },
+      {
+        _id: '123abcd',
+        name: 'another.tex',
+      },
     ],
     fileRefs: [],
     folders: [],
@@ -34,7 +53,7 @@ describe('<RootDocumentSetting />', function () {
     window.metaAttributesCache.set('ol-ExposedSettings', originalSettings)
   })
 
-  it('shows correct menu', async function () {
+  it('each option is shown and can be selected', async function () {
     render(
       <EditorProviders rootFolder={[rootFolder as any]}>
         <SettingsModalProvider>
@@ -43,9 +62,30 @@ describe('<RootDocumentSetting />', function () {
       </EditorProviders>
     )
 
+    const saveSettingsMock = fetchMock.post(
+      `express:/project/:projectId/settings`,
+      {
+        status: 200,
+      },
+      { delay: 0 }
+    )
+
     const select = screen.getByLabelText('Main document')
 
-    const optionOn = within(select).getByText('main.tex')
-    expect(optionOn.getAttribute('value')).to.equal('123abc')
+    // Reverse order so we test changing to each option
+    for (const option of OPTIONS.reverse()) {
+      const optionElement = within(select).getByText(option.label)
+      expect(optionElement.getAttribute('value')).to.equal(option.value)
+      await userEvent.selectOptions(select, [optionElement])
+
+      expect(
+        saveSettingsMock.callHistory.called(
+          `/project/${projectDefaults._id}/settings`,
+          {
+            body: { rootDocId: option.value },
+          }
+        )
+      ).to.be.true
+    }
   })
 })

+ 33 - 2
services/web/test/frontend/features/settings-modal/settings/spell-check-setting.test.tsx

@@ -2,9 +2,13 @@ import { screen, within, render } from '@testing-library/react'
 import { expect } from 'chai'
 import fetchMock from 'fetch-mock'
 import type { SpellCheckLanguage } from '../../../../../types/project-settings'
-import { EditorProviders } from '../../../helpers/editor-providers'
+import {
+  EditorProviders,
+  projectDefaults,
+} from '../../../helpers/editor-providers'
 import { SettingsModalProvider } from '@/features/ide-redesign/contexts/settings-modal-context'
 import SpellCheckSetting from '@/features/ide-redesign/components/settings/editor-settings/spell-check-setting'
+import userEvent from '@testing-library/user-event'
 
 describe('<SpellCheckSetting />', function () {
   const languages: SpellCheckLanguage[] = [
@@ -28,7 +32,7 @@ describe('<SpellCheckSetting />', function () {
     fetchMock.removeRoutes().clearHistory()
   })
 
-  it('shows correct menu', async function () {
+  it('each option is shown and can be selected', async function () {
     render(
       <EditorProviders>
         <SettingsModalProvider>
@@ -37,14 +41,41 @@ describe('<SpellCheckSetting />', function () {
       </EditorProviders>
     )
 
+    const saveSettingsMock = fetchMock.post(
+      `express:/project/:projectId/settings`,
+      {
+        status: 200,
+      },
+      { delay: 0 }
+    )
+
     const select = screen.getByLabelText('Spellcheck language')
 
     const optionEmpty = within(select).getByText('Off')
     expect(optionEmpty.getAttribute('value')).to.equal('')
+    await userEvent.selectOptions(select, [optionEmpty])
+    expect(
+      saveSettingsMock.callHistory.called(
+        `/project/${projectDefaults._id}/settings`,
+        {
+          body: { spellCheckLanguage: '' },
+        }
+      )
+    ).to.be.true
 
     for (const language of languages) {
       const option = within(select).getByText(language.name)
       expect(option.getAttribute('value')).to.equal(language.code)
+      await userEvent.selectOptions(select, [option])
+
+      expect(
+        saveSettingsMock.callHistory.called(
+          `/project/${projectDefaults._id}/settings`,
+          {
+            body: { spellCheckLanguage: language.code },
+          }
+        )
+      ).to.be.true
     }
   })
 })

+ 44 - 0
services/web/test/frontend/features/settings-modal/settings/stop-on-first-error-setting.test.tsx

@@ -0,0 +1,44 @@
+import { screen, render } from '@testing-library/react'
+import { expect } from 'chai'
+import fetchMock from 'fetch-mock'
+import { SettingsModalProvider } from '@/features/ide-redesign/contexts/settings-modal-context'
+import {
+  EditorProviders,
+  projectDefaults,
+} from '../../../helpers/editor-providers'
+import localStorage from '@/infrastructure/local-storage'
+import userEvent from '@testing-library/user-event'
+import StopOnFirstErrorSetting from '@/features/ide-redesign/components/settings/compiler-settings/stop-on-first-error-setting'
+
+describe('<StopOnFirstErrorSetting />', function () {
+  afterEach(function () {
+    fetchMock.removeRoutes().clearHistory()
+  })
+
+  it('can toggle', async function () {
+    render(
+      <EditorProviders>
+        <SettingsModalProvider>
+          <StopOnFirstErrorSetting />
+        </SettingsModalProvider>
+      </EditorProviders>
+    )
+
+    const toggle = screen.getByLabelText('Stop on first error')
+    const startingCheckedValue = (toggle as HTMLInputElement).checked
+
+    // Toggle the checkbox
+    await userEvent.click(toggle)
+    expect((toggle as HTMLInputElement).checked).to.equal(!startingCheckedValue)
+    expect(
+      localStorage.getItem(`stop_on_first_error:${projectDefaults._id}`)
+    ).to.equal(!startingCheckedValue)
+
+    // Toggle back to original value
+    await userEvent.click(toggle)
+    expect((toggle as HTMLInputElement).checked).to.equal(startingCheckedValue)
+    expect(
+      !!localStorage.getItem(`stop_on_first_error:${projectDefaults._id}`)
+    ).to.equal(startingCheckedValue)
+  })
+})