diff --git a/services/web/app/src/Features/Editor/EditorController.mjs b/services/web/app/src/Features/Editor/EditorController.mjs index b9987a3f15..773fe38c6c 100644 --- a/services/web/app/src/Features/Editor/EditorController.mjs +++ b/services/web/app/src/Features/Editor/EditorController.mjs @@ -669,6 +669,24 @@ const EditorController = { ) }, + setReferenceFormat(projectId, newReferenceFormat, callback) { + ProjectOptionsHandler.setReferenceFormat( + projectId, + newReferenceFormat, + function (err) { + if (err) { + return callback(err) + } + EditorRealTimeController.emitToRoom( + projectId, + 'referenceFormatUpdated', + newReferenceFormat + ) + callback() + } + ) + }, + _notifyProjectUsersOfNewFolders(projectId, folders, callback) { async.eachSeries( folders, @@ -732,5 +750,6 @@ EditorController.promises = { setPublicAccessLevel: promisify(EditorController.setPublicAccessLevel), setRootDoc: promisify(EditorController.setRootDoc), setMainBibliographyDoc: promisify(EditorController.setMainBibliographyDoc), + setReferenceFormat: promisify(EditorController.setReferenceFormat), } export default EditorController diff --git a/services/web/app/src/Features/Project/ProjectController.mjs b/services/web/app/src/Features/Project/ProjectController.mjs index f2ded0314d..856d7ca0ba 100644 --- a/services/web/app/src/Features/Project/ProjectController.mjs +++ b/services/web/app/src/Features/Project/ProjectController.mjs @@ -92,6 +92,7 @@ const updateProjectSettingsSchema = z.object({ name: z.string().optional(), rootDocId: zz.objectId().optional(), spellCheckLanguage: z.string().optional(), + referenceFormat: z.enum(['bibtex', 'biblatex']).optional(), }), }) @@ -144,6 +145,13 @@ const _ProjectController = { ) } + if (body.referenceFormat != null) { + await EditorController.promises.setReferenceFormat( + projectId, + body.referenceFormat + ) + } + res.sendStatus(204) }, diff --git a/services/web/app/src/Features/Project/ProjectEditorHandler.mjs b/services/web/app/src/Features/Project/ProjectEditorHandler.mjs index dcf3cd80f5..15884736c6 100644 --- a/services/web/app/src/Features/Project/ProjectEditorHandler.mjs +++ b/services/web/app/src/Features/Project/ProjectEditorHandler.mjs @@ -24,6 +24,7 @@ export default ProjectEditorHandler = { compiler: project.compiler, description: project.description, spellCheckLanguage: project.spellCheckLanguage, + referenceFormat: project.referenceFormat, png2pdf: project.png2pdf, deletedByExternalDataSource: project.deletedByExternalDataSource || false, imageName: diff --git a/services/web/app/src/Features/Project/ProjectOptionsHandler.mjs b/services/web/app/src/Features/Project/ProjectOptionsHandler.mjs index accb3144df..5ed9a54e4b 100644 --- a/services/web/app/src/Features/Project/ProjectOptionsHandler.mjs +++ b/services/web/app/src/Features/Project/ProjectOptionsHandler.mjs @@ -119,6 +119,15 @@ const ProjectOptionsHandler = { const { otMigrationStage } = project.overleaf.history return { otMigrationStage } }, + + async setReferenceFormat(projectId, newReferenceFormat) { + if (!newReferenceFormat) { + return + } + const conditions = { _id: projectId } + const update = { referenceFormat: newReferenceFormat } + return Project.updateOne(conditions, update, {}) + }, } export default { @@ -135,5 +144,6 @@ export default { setHistoryRangesSupport: callbackify( ProjectOptionsHandler.setHistoryRangesSupport ), + setReferenceFormat: callbackify(ProjectOptionsHandler.setReferenceFormat), promises: ProjectOptionsHandler, } diff --git a/services/web/app/src/models/Project.mjs b/services/web/app/src/models/Project.mjs index 9ca3cd2431..ebc1f9a2a5 100644 --- a/services/web/app/src/models/Project.mjs +++ b/services/web/app/src/models/Project.mjs @@ -115,6 +115,7 @@ export const ProjectSchema = new Schema( }, ], deferredTpdsFlushCounter: { type: Number }, + referenceFormat: { type: String }, }, { minimize: false } ) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 081ad605cd..c8e179436e 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -214,6 +214,8 @@ "beta_program_already_participating": "", "beta_program_benefits": "", "beta_program_not_participating": "", + "biblatex": "", + "bibtex": "", "bibtex_duplicates_keys": "", "billed_annually_at": "", "billed_monthly_at": "", @@ -1622,6 +1624,7 @@ "reference_count": "", "reference_count_plural": "", "reference_error_relink_hint": "", + "reference_format": "", "reference_manager_linked": "", "reference_manager_lowercase": "", "reference_manager_searched_groups": "", @@ -1632,6 +1635,7 @@ "reference_search_settings": "", "reference_search_style": "", "reference_sync": "", + "references": "", "references_from_these_libraries_will_be_included_in_your_reference_search_results": "", "refresh": "", "refresh_page_after_linking_dropbox": "", @@ -1806,7 +1810,6 @@ "select_all_projects": "", "select_an_output_file": "", "select_an_output_file_figure_modal": "", - "select_bib_file": "", "select_cells_in_a_single_row_to_merge": "", "select_color": "", "select_entry": "", diff --git a/services/web/frontend/js/features/ide-react/connection/join-project-payload.ts b/services/web/frontend/js/features/ide-react/connection/join-project-payload.ts index 5fa4aab408..f48cce8cca 100644 --- a/services/web/frontend/js/features/ide-react/connection/join-project-payload.ts +++ b/services/web/frontend/js/features/ide-react/connection/join-project-payload.ts @@ -4,7 +4,11 @@ import { PermissionsLevel } from '@/features/ide-react/types/permissions' export type JoinProjectPayloadProject = Pick< Project, Exclude -> & { rootDoc_id?: string; publicAccesLevel?: string } +> & { + rootDoc_id?: string + mainBibliographyDoc_id?: string + publicAccesLevel?: string +} export type JoinProjectPayload = { permissionsLevel: PermissionsLevel diff --git a/services/web/frontend/js/features/ide-react/context/ide-react-context.tsx b/services/web/frontend/js/features/ide-react/context/ide-react-context.tsx index 32ea07d88a..3ec4b80598 100644 --- a/services/web/frontend/js/features/ide-react/context/ide-react-context.tsx +++ b/services/web/frontend/js/features/ide-react/context/ide-react-context.tsx @@ -103,12 +103,18 @@ export const IdeReactProvider: FC = ({ children }) => { function handleJoinProjectResponse({ project: { rootDoc_id: rootDocId, + mainBibliographyDoc_id: mainBibliographyDocId, publicAccesLevel: publicAccessLevel, ..._project }, permissionsLevel, }: JoinProjectPayload) { - const project = { ..._project, rootDocId, publicAccessLevel } + const project = { + ..._project, + rootDocId, + mainBibliographyDocId, + publicAccessLevel, + } // Cast the project from the payload as ProjectMetadata to ensure it has // the correct type for the context. It must be close enough because the diff --git a/services/web/frontend/js/features/ide-settings/components/reference-settings/reference-format-setting.tsx b/services/web/frontend/js/features/ide-settings/components/reference-settings/reference-format-setting.tsx new file mode 100644 index 0000000000..857ebf6720 --- /dev/null +++ b/services/web/frontend/js/features/ide-settings/components/reference-settings/reference-format-setting.tsx @@ -0,0 +1,31 @@ +import { useTranslation } from 'react-i18next' +import DropdownSetting from '../dropdown-setting' +import { useProjectSettingsContext } from '../../context/project-settings-context' +import { usePermissionsContext } from '@/features/ide-react/context/permissions-context' + +export default function ReferenceFormatSetting() { + const { t } = useTranslation() + const { write } = usePermissionsContext() + const { referenceFormat, setReferenceFormat } = useProjectSettingsContext() + + return ( + + ) +} diff --git a/services/web/frontend/js/features/ide-settings/components/reference-settings/root-bib-document-setting.tsx b/services/web/frontend/js/features/ide-settings/components/reference-settings/root-bib-document-setting.tsx new file mode 100644 index 0000000000..1d50440dfc --- /dev/null +++ b/services/web/frontend/js/features/ide-settings/components/reference-settings/root-bib-document-setting.tsx @@ -0,0 +1,35 @@ +import { useFileTreeData } from '@/shared/context/file-tree-data-context' +import { useMemo } from 'react' +import { usePermissionsContext } from '@/features/ide-react/context/permissions-context' +import DropdownSetting from '../dropdown-setting' +import { useTranslation } from 'react-i18next' +import { useBibliographyDocId } from '../../hooks/use-bibliography-doc-id' + +export default function RootBibDocumentSetting() { + const { docs } = useFileTreeData() + const { write } = usePermissionsContext() + const { t } = useTranslation() + const { setMainBibliographyDocId, mainBibliographyDocId } = + useBibliographyDocId() + + const validDocsOptions = useMemo(() => { + return docs + ?.filter(doc => doc.doc.name.toLowerCase().endsWith('.bib')) + .map(doc => ({ value: doc.doc.id, label: doc.path })) + }, [docs]) + + return ( + + ) +} diff --git a/services/web/frontend/js/features/ide-settings/context/project-settings-context.tsx b/services/web/frontend/js/features/ide-settings/context/project-settings-context.tsx index 4b88b8f5fe..046ceca170 100644 --- a/services/web/frontend/js/features/ide-settings/context/project-settings-context.tsx +++ b/services/web/frontend/js/features/ide-settings/context/project-settings-context.tsx @@ -13,6 +13,9 @@ type ProjectSettingsSetterContextValue = { setSpellCheckLanguage: ( spellCheckLanguage: ProjectSettings['spellCheckLanguage'] ) => void + setReferenceFormat: ( + referenceFormat: ProjectSettings['referenceFormat'] + ) => Promise setAutoComplete: (autoComplete: UserSettings['autoComplete']) => void setAutoPairDelimiters: ( autoPairDelimiters: UserSettings['autoPairDelimiters'] @@ -67,6 +70,8 @@ export const ProjectSettingsProvider: FC = ({ setRootDocId, spellCheckLanguage, setSpellCheckLanguage, + referenceFormat, + setReferenceFormat, } = useProjectWideSettings() const { @@ -174,6 +179,8 @@ export const ProjectSettingsProvider: FC = ({ setMendeley, papers, setPapers, + referenceFormat, + setReferenceFormat, }), [ compiler, @@ -230,6 +237,8 @@ export const ProjectSettingsProvider: FC = ({ setMendeley, papers, setPapers, + referenceFormat, + setReferenceFormat, ] ) diff --git a/services/web/frontend/js/features/ide-settings/context/settings-modal-context.tsx b/services/web/frontend/js/features/ide-settings/context/settings-modal-context.tsx index f6aee4a303..a26b34fe3b 100644 --- a/services/web/frontend/js/features/ide-settings/context/settings-modal-context.tsx +++ b/services/web/frontend/js/features/ide-settings/context/settings-modal-context.tsx @@ -32,6 +32,8 @@ import FontSizeSetting from '@/features/ide-settings/components/appearance-setti import LineHeightSetting from '@/features/ide-settings/components/appearance-settings/line-height-setting' import FontFamilySetting from '@/features/ide-settings/components/appearance-settings/font-family-setting' import DarkModePdfSetting from '@/features/ide-settings/components/appearance-settings/dark-mode-pdf-setting' +import RootBibDocumentSetting from '../components/reference-settings/root-bib-document-setting' +import ReferenceFormatSetting from '../components/reference-settings/reference-format-setting' import { useProjectSettingsContext } from '@/features/ide-settings/context/project-settings-context' import { useFeatureFlag } from '@/shared/context/split-test-context' @@ -149,11 +151,6 @@ export const SettingsModalProvider: FC = ({ key: 'pdfViewer', component: , }, - { - key: 'write-and-cite-settings', - component: , - hidden: !ReferenceSearchSetting, - }, { key: 'floating-menu', component: , @@ -236,6 +233,31 @@ export const SettingsModalProvider: FC = ({ }, ], }, + { + key: 'references', + title: t('references'), + icon: 'book_5', + sections: [ + { + key: 'general', + settings: [ + { + key: 'mainBibliographyDocId', + component: , + }, + { + key: 'referenceFormat', + component: , + }, + { + key: 'write-and-cite-settings', + component: , + hidden: !ReferenceSearchSetting, + }, + ], + }, + ], + }, { key: 'appearance', title: t('appearance'), diff --git a/services/web/frontend/js/features/ide-settings/hooks/use-bibliography-doc-id.tsx b/services/web/frontend/js/features/ide-settings/hooks/use-bibliography-doc-id.tsx new file mode 100644 index 0000000000..b42255cec8 --- /dev/null +++ b/services/web/frontend/js/features/ide-settings/hooks/use-bibliography-doc-id.tsx @@ -0,0 +1,35 @@ +import { useCallback } from 'react' +import { useProjectContext } from '@/shared/context/project-context' +import type { ProjectSettings } from '../utils/api' +import useSaveProjectSettings from './use-save-project-settings' +import { usePermissionsContext } from '@/features/ide-react/context/permissions-context' + +export const useBibliographyDocId = () => { + const { project } = useProjectContext() + const mainBibliographyDocId = project?.mainBibliographyDocId + const saveProjectSettings = useSaveProjectSettings() + const { write } = usePermissionsContext() + + const setMainBibliographyDocIdFunc = useCallback( + async ( + newMainBibliographyDocId: ProjectSettings['mainBibliographyDocId'] + ) => { + if (write) { + try { + await saveProjectSettings( + 'mainBibliographyDocId', + newMainBibliographyDocId + ) + } catch (err) { + // TODO: retry mechanism (max 10x before failed completely and rollback the old value) + } + } + }, + [write, saveProjectSettings] + ) + + return { + mainBibliographyDocId, + setMainBibliographyDocId: setMainBibliographyDocIdFunc, + } +} diff --git a/services/web/frontend/js/features/ide-settings/hooks/use-project-wide-settings-socket-listener.tsx b/services/web/frontend/js/features/ide-settings/hooks/use-project-wide-settings-socket-listener.tsx index 88393a15b5..338bdb580b 100644 --- a/services/web/frontend/js/features/ide-settings/hooks/use-project-wide-settings-socket-listener.tsx +++ b/services/web/frontend/js/features/ide-settings/hooks/use-project-wide-settings-socket-listener.tsx @@ -44,6 +44,15 @@ export default function useProjectWideSettingsSocketListener() { [project, updateProject] ) + const setReferenceFormat = useCallback( + (referenceFormat: ProjectSettings['referenceFormat']) => { + if (project) { + updateProject({ referenceFormat }) + } + }, + [project, updateProject] + ) + useEffect(() => { // data is not available on initial mounting const dataAvailable = !!project @@ -53,6 +62,7 @@ export default function useProjectWideSettingsSocketListener() { socket.on('imageNameUpdated', setImageName) socket.on('png2pdfUpdated', setPng2pdf) socket.on('spellCheckLanguageUpdated', setSpellCheckLanguage) + socket.on('referenceFormatUpdated', setReferenceFormat) return () => { socket.removeListener('compilerUpdated', setCompiler) socket.removeListener('imageNameUpdated', setImageName) @@ -61,6 +71,7 @@ export default function useProjectWideSettingsSocketListener() { 'spellCheckLanguageUpdated', setSpellCheckLanguage ) + socket.removeListener('referenceFormatUpdated', setReferenceFormat) } } }, [ @@ -68,6 +79,7 @@ export default function useProjectWideSettingsSocketListener() { project, setCompiler, setImageName, + setReferenceFormat, setPng2pdf, setSpellCheckLanguage, ]) diff --git a/services/web/frontend/js/features/ide-settings/hooks/use-project-wide-settings.tsx b/services/web/frontend/js/features/ide-settings/hooks/use-project-wide-settings.tsx index faa2c614b2..a61a75328f 100644 --- a/services/web/frontend/js/features/ide-settings/hooks/use-project-wide-settings.tsx +++ b/services/web/frontend/js/features/ide-settings/hooks/use-project-wide-settings.tsx @@ -31,6 +31,15 @@ export default function useProjectWideSettings() { [saveProjectSettings] ) + const setReferenceFormat = useCallback( + async (newReferenceFormat: ProjectSettings['referenceFormat']) => { + await saveProjectSettings('referenceFormat', newReferenceFormat).catch( + debugConsole.error + ) + }, + [saveProjectSettings] + ) + const setPng2pdf = useCallback( async (newPng2pdf: ProjectSettings['png2pdf']) => { await saveProjectSettings('png2pdf', newPng2pdf).catch(debugConsole.error) @@ -46,6 +55,8 @@ export default function useProjectWideSettings() { setCompiler, imageName: project?.imageName, setImageName, + referenceFormat: project?.referenceFormat, + setReferenceFormat, png2pdf: project?.png2pdf ?? png2pdfEnabled, setPng2pdf, rootDocId, diff --git a/services/web/frontend/js/features/ide-settings/utils/api.ts b/services/web/frontend/js/features/ide-settings/utils/api.ts index 21c2db2665..967edbfae7 100644 --- a/services/web/frontend/js/features/ide-settings/utils/api.ts +++ b/services/web/frontend/js/features/ide-settings/utils/api.ts @@ -13,6 +13,8 @@ export interface ProjectSettings { rootDocId: string spellCheckLanguage: string name: string + mainBibliographyDocId?: string + referenceFormat?: 'bibtex' | 'biblatex' } type SaveUserSettings = Partial< diff --git a/services/web/frontend/js/shared/context/types/project-metadata.tsx b/services/web/frontend/js/shared/context/types/project-metadata.tsx index f559b12948..2601201616 100644 --- a/services/web/frontend/js/shared/context/types/project-metadata.tsx +++ b/services/web/frontend/js/shared/context/types/project-metadata.tsx @@ -50,7 +50,6 @@ export type MyAccessRequest = Pick< export interface ProjectMetadata extends ProjectSettings { _id: string - mainBibliographyDocId?: string members: ProjectMember[] invites: ProjectMember[] editAccessRequests?: EditAccessRequest[] diff --git a/services/web/locales/da.json b/services/web/locales/da.json index d0cfff2ad4..240d49f1b4 100644 --- a/services/web/locales/da.json +++ b/services/web/locales/da.json @@ -1566,7 +1566,6 @@ "select_all_projects": "Vælg alle projekter", "select_an_output_file": "Vælg en outputfil", "select_an_output_file_figure_modal": "Vælg en outputfil", - "select_bib_file": "Vælg .bib fil", "select_cells_in_a_single_row_to_merge": "Vælg celler i en enkelt række at flette", "select_color": "Vælg farven __name__", "select_folder_from_project": "Vælg mappe fra projekt", diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 8bc3256610..7867d27ac3 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -283,7 +283,9 @@ "beta_program_not_participating": "You are not enrolled in the beta program", "beta_program_opt_in_action": "Opt-in to beta program", "beta_program_opt_out_action": "Opt-out of beta program", + "biblatex": "BibLaTeX", "bibliographies": "Bibliographies", + "bibtex": "BibTeX", "bibtex_duplicates_keys": "The file __fileName__ contains more than one entry with the citation key __key__. Only the first occurrence is shown in this list.", "billed_annually_at": "Billed annually at <0>__price__ <1>(includes plan and any add-ons)", "billed_monthly_at": "Billed monthly at <0>__price__ <1>(includes plan and any add-ons)", @@ -2116,6 +2118,7 @@ "reference_count": "__count__ reference", "reference_count_plural": "__count__ references", "reference_error_relink_hint": "If this error persists, try re-linking your account here:", + "reference_format": "Reference format", "reference_manager_linked": "Reference manager linked", "reference_manager_lowercase": "reference manager", "reference_manager_searched_groups": "__provider__ search groups", @@ -2127,6 +2130,7 @@ "reference_search_settings": "Reference search settings", "reference_search_style": "Reference search style", "reference_sync": "Reference manager sync", + "references": "References", "references_from_these_libraries_will_be_included_in_your_reference_search_results": "References from these libraries will be included in your reference search results.", "refresh": "Refresh", "refresh_page_after_linking_dropbox": "Please refresh this page after linking your account to Dropbox.", @@ -2340,7 +2344,6 @@ "select_all_projects": "Select all projects", "select_an_output_file": "Select an Output File", "select_an_output_file_figure_modal": "Select an output file", - "select_bib_file": "Select .bib file", "select_cells_in_a_single_row_to_merge": "Select cells in a single row to merge", "select_color": "Select color __name__", "select_entry": "Select entry", @@ -2729,7 +2732,7 @@ "this_could_be_because_we_cant_support_some_elements_of_the_table": "This could be because we can’t yet support some elements of the table in the table preview. Or there may be an error in the table’s LaTeX code.", "this_field_is_required": "This field is required", "this_grants_access_to_features_2": "This grants you access to <0>__appName__ <0>__featureType__ features.", - "this_is_the_file_that_references_pulled_from_your_reference_manager_will_be_added_to": "This is the file that references pulled from your reference manager will be added to.", + "this_is_the_file_that_references_pulled_from_your_reference_manager_will_be_added_to": "New references are saved to the .bib file you select here. If your project doesn’t have one yet, add a .bib file to choose it.", "this_is_your_template": "This is your template from your project", "this_organization_is_tax_exempt": "This organization is tax exempt", "this_policy_applies_to_all_projects_owned_by_your_group_members": "This policy applies to all projects owned by your group members. Project owners can apply stricter sharing rules for individual projects.", diff --git a/services/web/locales/zh-CN.json b/services/web/locales/zh-CN.json index ef70497d2f..5060a73b17 100644 --- a/services/web/locales/zh-CN.json +++ b/services/web/locales/zh-CN.json @@ -1782,7 +1782,6 @@ "select_all_projects": "全选", "select_an_output_file": "选择输出文件", "select_an_output_file_figure_modal": "选择一个输出文件", - "select_bib_file": "选择 .bib 文件", "select_cells_in_a_single_row_to_merge": "在一行中选择单元格合并", "select_color": "选择颜色 __name__", "select_folder_from_project": "从项目中选择文件夹", diff --git a/services/web/test/frontend/features/ide-settings/settings/reference-format-setting.test.tsx b/services/web/test/frontend/features/ide-settings/settings/reference-format-setting.test.tsx new file mode 100644 index 0000000000..a258a85778 --- /dev/null +++ b/services/web/test/frontend/features/ide-settings/settings/reference-format-setting.test.tsx @@ -0,0 +1,79 @@ +import { screen, within, render } from '@testing-library/react' +import { expect } from 'chai' +import fetchMock from 'fetch-mock' +import { SettingsModalProvider } from '@/features/ide-settings/context/settings-modal-context' +import { + EditorProviders, + projectDefaults, +} from '../../../helpers/editor-providers' +import ReferenceFormatSetting from '@/features/ide-settings/components/reference-settings/reference-format-setting' +import userEvent from '@testing-library/user-event' + +// bibtex is listed first as it differs from the component's default +// ('biblatex'), so selecting it is guaranteed to trigger a change. +const OPTIONS = [ + { + label: 'BibTeX', + value: 'bibtex', + }, + { + label: 'BibLaTeX', + value: 'biblatex', + }, +] + +describe('', function () { + afterEach(function () { + fetchMock.removeRoutes().clearHistory() + }) + + it('defaults to BibLaTeX', function () { + render( + + + + + + ) + + const select = screen.getByLabelText( + 'Reference format' + ) as HTMLSelectElement + expect(select.value).to.equal('biblatex') + }) + + it('each option is shown and can be selected', async function () { + render( + + + + + + ) + + const saveSettingsMock = fetchMock.post( + `express:/project/:projectId/settings`, + { + status: 200, + }, + { delay: 0 } + ) + + const select = screen.getByLabelText('Reference format') + + 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( + `/project/${projectDefaults._id}/settings`, + { + body: { referenceFormat: option.value }, + } + ) + ).to.be.true + } + }) +}) diff --git a/services/web/test/frontend/features/ide-settings/settings/root-bib-document-setting.test.tsx b/services/web/test/frontend/features/ide-settings/settings/root-bib-document-setting.test.tsx new file mode 100644 index 0000000000..716a8123ad --- /dev/null +++ b/services/web/test/frontend/features/ide-settings/settings/root-bib-document-setting.test.tsx @@ -0,0 +1,105 @@ +import { screen, within, render } from '@testing-library/react' +import { expect } from 'chai' +import fetchMock from 'fetch-mock' +import { Folder } from '../../../../../types/folder' +import { SettingsModalProvider } from '@/features/ide-settings/context/settings-modal-context' +import { + EditorProviders, + projectDefaults, +} from '../../../helpers/editor-providers' +import RootBibDocumentSetting from '@/features/ide-settings/components/reference-settings/root-bib-document-setting' +import userEvent from '@testing-library/user-event' + +const OPTIONS = [ + { + label: 'main.bib', + value: '123abc', + }, + { + label: 'another.bib', + value: '123abcd', + }, +] + +describe('', function () { + const rootFolder: Folder = { + _id: 'root-folder-id', + name: 'rootFolder', + docs: [ + { + _id: '123abc', + name: 'main.bib', + }, + { + _id: '123abcd', + name: 'another.bib', + }, + { + _id: '123abce', + name: 'main.tex', + }, + ], + fileRefs: [], + folders: [], + } + + afterEach(function () { + fetchMock.removeRoutes().clearHistory() + }) + + it('only lists .bib files as options', function () { + render( + + + + + + ) + + const select = screen.getByLabelText( + 'Main bibliography file for this project' + ) + + expect(within(select).getByText('main.bib')).to.exist + expect(within(select).getByText('another.bib')).to.exist + expect(within(select).queryByText('main.tex')).to.be.null + }) + + it('each option is shown and can be selected', async function () { + render( + + + + + + ) + + const saveSettingsMock = fetchMock.post( + `express:/project/:projectId/settings`, + { + status: 200, + }, + { delay: 0 } + ) + + const select = screen.getByLabelText( + 'Main bibliography file for this project' + ) + + // 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: { mainBibliographyDocId: option.value }, + } + ) + ).to.be.true + } + }) +})