Merge pull request #13947 from overleaf/mf-tw-tpr-not-original-importer

Improve user behaviour on Mendeley/Zotero refresh screen UI

GitOrigin-RevId: 50f83e88f14e1708d46dcfbd53c4e7d62684b4dc
This commit is contained in:
M Fahru 2023-10-04 12:36:34 -07:00 committed by Copybot
parent e3a99a82db
commit 8b9f69012c
13 changed files with 527 additions and 117 deletions

View file

@ -499,7 +499,6 @@
"hotkeys": "",
"how_it_works": "",
"i_want_to_stay": "",
"if_error_persists_try_relinking_provider": "",
"if_you_need_to_customize_your_table_further_you_can": "",
"ignore_validation_errors": "",
"ill_take_it": "",
@ -752,6 +751,7 @@
"only_group_admin_or_managers_can_delete_your_account_3": "",
"only_group_admin_or_managers_can_delete_your_account_4": "",
"only_group_admin_or_managers_can_delete_your_account_5": "",
"only_importer_can_refresh": "",
"open_file": "",
"open_link": "",
"open_project": "",
@ -1035,6 +1035,7 @@
"showing_x_results_of_total": "",
"signature_algorithm": "",
"single_sign_on_sso": "",
"something_not_right": "",
"something_went_wrong_loading_pdf_viewer": "",
"something_went_wrong_processing_the_request": "",
"something_went_wrong_rendering_pdf": "",
@ -1215,6 +1216,7 @@
"try_it_for_free": "",
"try_premium_for_free": "",
"try_recompile_project_or_troubleshoot": "",
"try_relinking_provider": "",
"try_to_compile_despite_errors": "",
"turn_off_link_sharing": "",
"turn_on_link_sharing": "",

View file

@ -1,30 +1,25 @@
import { useState, useCallback, type ElementType } from 'react'
import { useState, type ElementType } from 'react'
import PropTypes from 'prop-types'
import { Trans, useTranslation } from 'react-i18next'
import Icon from '../../../shared/components/icon'
import { formatTime, relativeDate } from '../../utils/format-date'
import { postJSON } from '../../../infrastructure/fetch-json'
import { useEditorContext } from '../../../shared/context/editor-context'
import { useProjectContext } from '../../../shared/context/project-context'
import { Nullable } from '../../../../../types/utils'
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
import useAbortController from '../../../shared/hooks/use-abort-controller'
import { LinkedFileIcon } from './file-view-icons'
import { BinaryFile, hasProvider, LinkedFile } from '../types/binary-file'
import FileViewRefreshButton from './file-view-refresh-button'
import FileViewNotOriginalImporter from './file-view-not-original-importer'
import FileViewRefreshError from './file-view-refresh-error'
const tprLinkedFileInfo = importOverleafModules('tprLinkedFileInfo') as {
import: { LinkedFileInfo: ElementType }
path: string
}[]
const tprLinkedFileRefreshError = importOverleafModules(
'tprLinkedFileRefreshError'
) as {
import: { LinkedFileRefreshError: ElementType }
path: string
}[]
const MAX_URL_LENGTH = 60
const FRONT_OF_URL_LENGTH = 35
const FILLER = '...'
@ -55,10 +50,7 @@ export default function FileViewHeader({ file }: FileViewHeaderProps) {
})
const { t } = useTranslation()
const [refreshing, setRefreshing] = useState(false)
const [refreshError, setRefreshError] = useState(null)
const { signal } = useAbortController()
const [refreshError, setRefreshError] = useState<Nullable<string>>(null)
let fileInfo
if (file.linkedFileData) {
@ -83,29 +75,6 @@ export default function FileViewHeader({ file }: FileViewHeaderProps) {
}
}
const refreshFile = useCallback(() => {
setRefreshing(true)
// Replacement of the file handled by the file tree
window.expectingLinkedFileRefreshedSocketFor = file.name
const body = {
shouldReindexReferences:
file.linkedFileData?.provider === 'mendeley' ||
file.linkedFileData?.provider === 'zotero' ||
/\.bib$/.test(file.name),
}
postJSON(`/project/${projectId}/linked_file/${file.id}/refresh`, {
signal,
body,
})
.then(() => {
setRefreshing(false)
})
.catch(err => {
setRefreshing(false)
setRefreshError(err.data?.message || err.message)
})
}, [file, projectId, signal])
return (
<div>
{file.linkedFileData && fileInfo}
@ -114,14 +83,7 @@ export default function FileViewHeader({ file }: FileViewHeaderProps) {
<LinkedFileInfo key={path} file={file} />
))}
{file.linkedFileData && permissionsLevel !== 'readOnly' && (
<button
className="btn btn-primary"
onClick={refreshFile}
disabled={refreshing}
>
<Icon type="refresh" spin={refreshing} fw />
<span>{refreshing ? t('refreshing') + '...' : t('refresh')}</span>
</button>
<FileViewRefreshButton file={file} setRefreshError={setRefreshError} />
)}
&nbsp;
<a
@ -133,18 +95,9 @@ export default function FileViewHeader({ file }: FileViewHeaderProps) {
&nbsp;
<span>{t('download')}</span>
</a>
{file.linkedFileData && <FileViewNotOriginalImporter file={file} />}
{refreshError && (
<div className="row">
<br />
<div className="alert alert-danger col-md-6 col-md-offset-3">
{t('access_denied')}: {refreshError}
{tprLinkedFileRefreshError.map(
({ import: { LinkedFileRefreshError }, path }) => (
<LinkedFileRefreshError key={path} file={file} />
)
)}
</div>
</div>
<FileViewRefreshError file={file} refreshError={refreshError} />
)}
</div>
)

View file

@ -0,0 +1,38 @@
import { useTranslation } from 'react-i18next'
import { capitalize } from 'lodash'
import { useUserContext } from '@/shared/context/user-context'
import { BinaryFile, hasProvider } from '../types/binary-file'
type FileViewNotOriginalImporterProps = {
file: BinaryFile
}
export default function FileViewNotOriginalImporter({
file,
}: FileViewNotOriginalImporterProps) {
const { t } = useTranslation()
const { id: userId } = useUserContext()
const isMendeleyOrZotero =
hasProvider(file, 'mendeley') || hasProvider(file, 'zotero')
if (!isMendeleyOrZotero) {
return null
}
const isImporter = file.linkedFileData.importer_id === userId
if (isImporter) {
return null
}
return (
<div className="row">
<div className="alert">
{t('only_importer_can_refresh', {
provider: capitalize(file.linkedFileData.provider),
})}
</div>
</div>
)
}

View file

@ -0,0 +1,76 @@
import {
type Dispatch,
type SetStateAction,
useCallback,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import classNames from 'classnames'
import Icon from '@/shared/components/icon'
import { postJSON } from '@/infrastructure/fetch-json'
import { useProjectContext } from '@/shared/context/project-context'
import useAbortController from '@/shared/hooks/use-abort-controller'
import { useUserContext } from '@/shared/context/user-context'
import { hasProvider, type BinaryFile } from '../types/binary-file'
import { Nullable } from '../../../../../types/utils'
type FileViewRefreshButtonProps = {
setRefreshError: Dispatch<SetStateAction<Nullable<string>>>
file: BinaryFile
}
export default function FileViewRefreshButton({
setRefreshError,
file,
}: FileViewRefreshButtonProps) {
const { signal } = useAbortController()
const { t } = useTranslation()
const [refreshing, setRefreshing] = useState(false)
const { _id: projectId } = useProjectContext()
const { id: userId } = useUserContext()
const isMendeleyOrZotero =
hasProvider(file, 'mendeley') || hasProvider(file, 'zotero')
let isImporter
if (isMendeleyOrZotero) {
isImporter = file.linkedFileData.importer_id === userId
}
const buttonClickable = isMendeleyOrZotero ? isImporter : true
const refreshFile = useCallback(() => {
setRefreshing(true)
// Replacement of the file handled by the file tree
window.expectingLinkedFileRefreshedSocketFor = file.name
const body = {
shouldReindexReferences: isMendeleyOrZotero || /\.bib$/.test(file.name),
}
postJSON(`/project/${projectId}/linked_file/${file.id}/refresh`, {
signal,
body,
})
.then(() => {
setRefreshing(false)
})
.catch(err => {
setRefreshing(false)
setRefreshError(err.data?.message || err.message)
})
}, [file, projectId, signal, setRefreshError, isMendeleyOrZotero])
return (
<button
className={classNames('btn', {
'btn-primary': buttonClickable,
'btn-secondary': !buttonClickable,
})}
onClick={refreshFile}
disabled={refreshing || !buttonClickable}
>
<Icon type="refresh" spin={refreshing} fw />
<span>{refreshing ? `${t('refreshing')}` : t('refresh')}</span>
</button>
)
}

View file

@ -0,0 +1,87 @@
import type { ElementType } from 'react'
import { useTranslation } from 'react-i18next'
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
import { BinaryFile, hasProvider } from '../types/binary-file'
const tprLinkedFileRefreshError = importOverleafModules(
'tprLinkedFileRefreshError'
) as {
import: { LinkedFileRefreshError: ElementType }
path: string
}[]
type FileViewRefreshErrorProps = {
file: BinaryFile
refreshError: string
}
export default function FileViewRefreshError({
file,
refreshError,
}: FileViewRefreshErrorProps) {
const isMendeleyOrZotero =
hasProvider(file, 'mendeley') || hasProvider(file, 'zotero')
return (
<div className="row">
<br />
{isMendeleyOrZotero ? (
<FileViewMendeleyOrZoteroRefreshError file={file} />
) : (
<FileViewDefaultRefreshError refreshError={refreshError} />
)}
</div>
)
}
type FileViewMendeleyOrZoteroRefreshErrorProps = {
file: BinaryFile
}
function FileViewMendeleyOrZoteroRefreshError({
file,
}: FileViewMendeleyOrZoteroRefreshErrorProps) {
const { t } = useTranslation()
return (
<div
className="alert alert-danger col-md-10 col-md-offset-1"
style={{ display: 'flex', alignItems: 'center', gap: '10px' }}
>
<div>
{t('something_not_right')}!&nbsp;
{tprLinkedFileRefreshError.map(
({ import: { LinkedFileRefreshError }, path }) => (
<LinkedFileRefreshError key={path} file={file} />
)
)}
</div>
<div className="text-center">
<button className="btn btn-danger">
<a
href="/user/settings"
target="_blank"
style={{ fontWeight: 'bold', textDecoration: 'none' }}
>
{t('go_to_settings')}
</a>
</button>
</div>
</div>
)
}
type FileViewDefaultRefreshErrorProps = {
refreshError: string
}
function FileViewDefaultRefreshError({
refreshError,
}: FileViewDefaultRefreshErrorProps) {
const { t } = useTranslation()
return (
<div className="alert alert-danger col-md-6 col-md-offset-3">
{t('access_denied')}: {refreshError}
</div>
)
}

View file

@ -705,7 +705,6 @@
"how_to_insert_images": "Hvordan indsætter jeg figurer",
"hundreds_templates_info": "Skab smukke dokumenter ved at starte fra vores galleri af LaTeX skabeloner for journaler, konferencer, afhandlinger, rapporter, CVer og meget mere.",
"i_want_to_stay": "Jeg ønsker at blive",
"if_error_persists_try_relinking_provider": "Hvis denne fejl fortsætter kan du prøve at re-etablere fobindelsen til din __provider__ konto her",
"if_have_existing_can_link": "Hvis du har en eksisterende <b>__appName__</b>-konto under en anden e-mailaddresse, kan du forbinde den til din <b>__institutionName__</b>-konto ved at klikke <b>__clickText__</b>",
"if_owner_can_link": "Hvis du ejer <b>__appName__</b>-kontoen under <b>__email__</b>, vil du få mulighed for at forbinde den til din institutionelle konto hos <b>__institutionName__</b>.",
"ignore_and_continue_institution_linking": "Du kan også springe det over, og <a href=\"__link__\">fortsætte til __appName__ med kontoen for <b>__email__</b></a>.",

View file

@ -711,7 +711,6 @@
"how_to_insert_images": "So fügst du Bilder ein",
"hundreds_templates_info": "Erstelle schöne Dokumente ausgehend von unserer Galerie mit LaTeX-Vorlagen für Zeitschriften, Konferenzen, Abschlussarbeiten, Berichte, Lebensläufe und vieles mehr.",
"i_want_to_stay": "Ich möchte bleiben",
"if_error_persists_try_relinking_provider": "Wenn dieser Fehler weiterhin besteht, versuche, dein __provider__-Konto hier erneut zu verknüpfen",
"if_have_existing_can_link": "Wenn du ein vorhandenes <b>__appName__</b>-Konto mit einer anderen E-Mail-Adresse hast, kannst du es mit deinem <b>__institutionName__</b>-Konto verknüpfen, indem du auf <b>„__clickText__“</b> klickst.",
"if_owner_can_link": "Wenn du das <b>__appName__</b> Konto mit <b>__email__</b> besitzt, kannst du es mit deinem institutionellen Konto <b>__institutionName__</b> verknüpfen.",
"ignore_and_continue_institution_linking": "Du kannst dies auch ignorieren und <a href=\"__link__\">weiter zu __appName__ mit deinem <b>__email__</b>-Konto</a> gehen.",

View file

@ -786,7 +786,6 @@
"how_to_insert_images": "How to insert images",
"hundreds_templates_info": "Produce beautiful documents starting from our gallery of LaTeX templates for journals, conferences, theses, reports, CVs and much more.",
"i_want_to_stay": "I want to stay",
"if_error_persists_try_relinking_provider": "If this error persists, try re-linking your __provider__ account here",
"if_have_existing_can_link": "If you have an existing <b>__appName__</b> account on another email, you can link it to your <b>__institutionName__</b> account by clicking <b>__clickText__</b>.",
"if_owner_can_link": "If you own the <b>__appName__</b> account with <b>__email__</b>, you will be allowed to link it to your <b>__institutionName__</b> institutional account.",
"if_you_need_to_customize_your_table_further_you_can": "If you need to customize your table further, you can. Using LaTeX code, you can change anything from table styles and border styles to colors and column widths. <0>Read our guide</0> to using tables in LaTeX to help you get started.",
@ -1194,6 +1193,7 @@
"only_group_admin_or_managers_can_delete_your_account_3": "Your group admin and group managers will be able to reassign ownership of your projects to another group member.",
"only_group_admin_or_managers_can_delete_your_account_4": "Once you have become a managed user, you cannot change back. <0>Learn more about managed Overleaf accounts.</0>",
"only_group_admin_or_managers_can_delete_your_account_5": "For more information, see the \"Managed Accounts\" section in our terms of use, which you agree to by clicking Accept invitation",
"only_importer_can_refresh": "Only the person who originally imported this __provider__ file can refresh it.",
"open_a_file_on_the_left": "Open a file on the left",
"open_as_template": "Open as Template",
"open_file": "Edit file",
@ -1611,6 +1611,7 @@
"skip_to_content": "Skip to content",
"sl_gives_you_free_stuff_see_progress_below": "When someone starts using __appName__ after your recommendation well give you some <strong>free stuff</strong> to say thanks! Check your progress below.",
"sl_included_history_of_changes_blurb": "__appName__ includes a history of all of your changes so you can see exactly who changed what, and when. This makes it extremely easy to keep up to date with any progress made by your collaborators and allows you to review recent work.",
"something_not_right": "Somethings not right",
"something_went_wrong_canceling_your_subscription": "Something went wrong canceling your subscription. Please contact support.",
"something_went_wrong_loading_pdf_viewer": "Something went wrong loading the PDF viewer. This might be caused by issues like <0>temporary network problems</0> or an <0>outdated web browser</0>. Please follow the <1>troubleshooting steps for access, loading and display problems</1>. If the issue persists, please <2>let us know</2>.",
"something_went_wrong_processing_the_request": "Something went wrong processing the request",
@ -1858,6 +1859,7 @@
"try_now": "Try Now",
"try_premium_for_free": "Try Premium for free",
"try_recompile_project_or_troubleshoot": "Please try recompiling the project from scratch, and if that doesnt help, follow our <0>troubleshooting guide</0>.",
"try_relinking_provider": "It looks like you need to re-link your __provider__ account.",
"try_to_compile_despite_errors": "Try to compile despite errors",
"turn_off_link_sharing": "Turn off link sharing",
"turn_on_link_sharing": "Turn on link sharing",

View file

@ -447,7 +447,6 @@
"hotkeys": "快捷键",
"hundreds_templates_info": "从我们的 LaTeX 模板库开始,为期刊、会议、论文、报告、简历等制作漂亮的文档。",
"i_want_to_stay": "我要留下",
"if_error_persists_try_relinking_provider": "如果此错误仍然存在请尝试在此处重新链接您的__provider__帐户",
"if_have_existing_can_link": "如果您在另一封电子邮件中有一个现有的 <b>__appName__</b> 帐户,您可以通过单击 <b>__clickText__</b> 将其链接到您的 <b>__institutionName__</b> 账户。",
"if_owner_can_link": "如果您在<b>__appName__</b>拥有账户<b>__email__</b>,您可以将其链接到您的 <b>__institutionName__</b> 机构帐户。",
"ignore_and_continue_institution_linking": "您也可以忽略此项,然后<a href=\"__link__\">继续在 __appName__ 上使用您的 <b>__email__</b> 帐户</a>。",

View file

@ -1,13 +1,9 @@
import {
screen,
fireEvent,
waitForElementToBeRemoved,
} from '@testing-library/react'
import { expect } from 'chai'
import { screen } from '@testing-library/react'
import fetchMock from 'fetch-mock'
import { renderWithEditorContext } from '../../../helpers/render-with-context'
import FileViewHeader from '../../../../../frontend/js/features/file-view/components/file-view-header'
import { USER_ID } from '../../../helpers/editor-providers'
describe('<FileViewHeader/>', function () {
const urlFile = {
@ -26,6 +22,7 @@ describe('<FileViewHeader/>', function () {
source_project_id: 'source-project-id',
source_entity_path: '/source-entity-path.ext',
provider: 'project_file',
importer_id: USER_ID,
},
created: new Date(2021, 1, 17, 3, 24).toISOString(),
}
@ -40,14 +37,6 @@ describe('<FileViewHeader/>', function () {
created: new Date(2021, 1, 17, 3, 24).toISOString(),
}
const thirdPartyReferenceFile = {
name: 'example.tex',
linkedFileData: {
provider: 'zotero',
},
created: new Date(2021, 1, 17, 3, 24).toISOString(),
}
beforeEach(function () {
fetchMock.reset()
})
@ -85,48 +74,6 @@ describe('<FileViewHeader/>', function () {
})
})
describe('The refresh button', async function () {
it('Changes text when the file is refreshing', async function () {
fetchMock.post(
'express:/project/:project_id/linked_file/:file_id/refresh',
{
new_file_id: '5ff7418157b4e144321df5c4',
}
)
renderWithEditorContext(<FileViewHeader file={projectFile} />)
fireEvent.click(screen.getByRole('button', { name: 'Refresh' }))
await waitForElementToBeRemoved(() =>
screen.getByText('Refreshing', { exact: false })
)
await screen.findByText('Refresh')
})
it('Reindexes references after refreshing a file from a third-party provider', async function () {
fetchMock.post(
'express:/project/:project_id/linked_file/:file_id/refresh',
{
new_file_id: '5ff7418157b4e144321df5c4',
}
)
renderWithEditorContext(<FileViewHeader file={thirdPartyReferenceFile} />)
fireEvent.click(screen.getByRole('button', { name: 'Refresh' }))
await waitForElementToBeRemoved(() =>
screen.getByText('Refreshing', { exact: false })
)
expect(fetchMock.done()).to.be.true
const lastCallBody = JSON.parse(fetchMock.lastCall()[1].body)
expect(lastCallBody.shouldReindexReferences).to.be.true
})
})
describe('The download button', function () {
it('exists', function () {
renderWithEditorContext(<FileViewHeader file={urlFile} />)

View file

@ -0,0 +1,108 @@
import { screen } from '@testing-library/react'
import { expect } from 'chai'
import FileViewNotOriginalImporter from '@/features/file-view/components/file-view-not-original-importer'
import { BinaryFile } from '@/features/file-view/types/binary-file'
import { renderWithEditorContext } from '../../../helpers/render-with-context'
import { USER_ID } from '../../../helpers/editor-providers'
describe('<FileViewNotOriginalImporter />', function () {
describe('provider is not mendeley and zotero', function () {
it('does not show error if provider is not mendeley and zotero', function () {
const urlFile: BinaryFile<'url'> = {
id: '123abc',
_id: '123abc',
linkedFileData: {
provider: 'url',
url: '/url/file.png',
},
created: new Date(2023, 1, 17, 3, 24),
name: 'file.png',
type: 'file',
selected: true,
}
renderWithEditorContext(<FileViewNotOriginalImporter file={urlFile} />)
const text = screen.queryByText(
/Only the person who originally imported this/
)
expect(text).to.not.exist
})
})
describe('provider is mendeley or zotero', function () {
it('does not show error if current user is the original importer of the file', function () {
const mendeleyFile: BinaryFile<'mendeley'> = {
id: '123abc',
_id: '123abc',
linkedFileData: {
provider: 'mendeley',
importer_id: USER_ID,
},
created: new Date(2023, 1, 17, 3, 24),
name: 'references.bib',
type: 'file',
selected: true,
}
renderWithEditorContext(
<FileViewNotOriginalImporter file={mendeleyFile} />
)
const text = screen.queryByText(
'Only the person who originally imported this Mendeley file can refresh it.'
)
expect(text).to.not.exist
})
it('shows error if provider is mendeley and current user is not the original importer of the file', function () {
const mendeleyFile: BinaryFile<'mendeley'> = {
id: '123abc',
_id: '123abc',
linkedFileData: {
provider: 'mendeley',
importer_id: 'user123',
},
created: new Date(2023, 1, 17, 3, 24),
name: 'references.bib',
type: 'file',
selected: true,
}
renderWithEditorContext(
<FileViewNotOriginalImporter file={mendeleyFile} />
)
const text = screen.getByText(
'Only the person who originally imported this Mendeley file can refresh it.'
)
expect(text).to.exist
})
it('shows error if provider is zotero and current user is not the original importer of the file', function () {
const zoteroFile: BinaryFile<'zotero'> = {
id: '123abc',
_id: '123abc',
linkedFileData: {
provider: 'zotero',
importer_id: 'user123',
},
created: new Date(2023, 1, 17, 3, 24),
name: 'references.bib',
type: 'file',
selected: true,
}
renderWithEditorContext(<FileViewNotOriginalImporter file={zoteroFile} />)
const text = screen.getByText(
'Only the person who originally imported this Zotero file can refresh it.'
)
expect(text).to.exist
})
})
})

View file

@ -0,0 +1,104 @@
import {
screen,
fireEvent,
waitForElementToBeRemoved,
} from '@testing-library/react'
import { expect } from 'chai'
import fetchMock from 'fetch-mock'
import FileViewRefreshButton from '@/features/file-view/components/file-view-refresh-button'
import { renderWithEditorContext } from '../../../helpers/render-with-context'
import { USER_ID } from '../../../helpers/editor-providers'
describe('<FileViewRefreshButton />', function () {
const projectFile = {
name: 'example.tex',
linkedFileData: {
v1_source_doc_id: 'v1-source-id',
source_project_id: 'source-project-id',
source_entity_path: '/source-entity-path.ext',
provider: 'project_file',
importer_id: USER_ID,
},
created: new Date(2021, 1, 17, 3, 24).toISOString(),
}
const thirdPartyReferenceFile = {
name: 'example.tex',
linkedFileData: {
provider: 'zotero',
importer_id: USER_ID,
},
created: new Date(2021, 1, 17, 3, 24).toISOString(),
}
const thirdPartyNotOriginalImporterReferenceFile = {
name: 'references.bib',
linkedFileData: {
v1_source_doc_id: 'v1-source-id',
source_project_id: 'source-project-id',
source_entity_path: '/source-entity-path.ext',
provider: 'mendeley',
importer_id: '123abc',
},
created: new Date(2021, 1, 17, 3, 24).toISOString(),
}
beforeEach(function () {
fetchMock.reset()
})
it('Changes text when the file is refreshing', async function () {
fetchMock.post(
'express:/project/:project_id/linked_file/:file_id/refresh',
{
new_file_id: '5ff7418157b4e144321df5c4',
}
)
renderWithEditorContext(<FileViewRefreshButton file={projectFile} />)
fireEvent.click(screen.getByRole('button', { name: 'Refresh' }))
await waitForElementToBeRemoved(() =>
screen.getByText('Refreshing', { exact: false })
)
await screen.findByText('Refresh')
})
it('Reindexes references after refreshing a file from a third-party provider', async function () {
fetchMock.post(
'express:/project/:project_id/linked_file/:file_id/refresh',
{
new_file_id: '5ff7418157b4e144321df5c4',
}
)
renderWithEditorContext(
<FileViewRefreshButton file={thirdPartyReferenceFile} />
)
fireEvent.click(screen.getByRole('button', { name: 'Refresh' }))
await waitForElementToBeRemoved(() =>
screen.getByText('Refreshing', { exact: false })
)
expect(fetchMock.done()).to.be.true
const lastCallBody = JSON.parse(fetchMock.lastCall()[1].body)
expect(lastCallBody.shouldReindexReferences).to.be.true
})
it('is disabled when user is not original importer', function () {
renderWithEditorContext(
<FileViewRefreshButton
file={thirdPartyNotOriginalImporterReferenceFile}
/>
)
const button = screen.getByRole('button', { name: 'Refresh' })
expect(button.disabled).to.equal(true)
})
})

View file

@ -0,0 +1,96 @@
import { render, screen } from '@testing-library/react'
import FileViewRefreshError from '@/features/file-view/components/file-view-refresh-error'
import type { BinaryFile } from '@/features/file-view/types/binary-file'
import { expect } from 'chai'
describe('<FileViewRefreshError />', function () {
describe('<FileViewMendeleyOrZoteroRefreshError />', function () {
it('shows correct error message for mendeley', async function () {
const mendeleyFile: BinaryFile<'mendeley'> = {
id: '123abc',
_id: '123abc',
linkedFileData: {
provider: 'mendeley',
importer_id: 'user123456',
},
created: new Date(2023, 1, 17, 3, 24),
name: 'references.bib',
type: 'file',
selected: true,
}
render(
<FileViewRefreshError
file={mendeleyFile}
refreshError="error_message"
/>
)
screen.getByText(/Somethings not right!/)
screen.getByText(
/It looks like you need to re-link your Mendeley account./
)
const goToSettingsLink = screen.getByRole('link', {
name: 'Go to settings',
})
expect(goToSettingsLink.getAttribute('href')).to.equal('/user/settings')
})
it('shows correct error message for zotero', async function () {
const zoteroFile: BinaryFile<'zotero'> = {
id: '123abc',
_id: '123abc',
linkedFileData: {
provider: 'zotero',
importer_id: 'user123456',
},
created: new Date(2023, 1, 17, 3, 24),
name: 'references.bib',
type: 'file',
selected: true,
}
render(
<FileViewRefreshError file={zoteroFile} refreshError="error_message" />
)
screen.getByText(/Somethings not right!/)
screen.getByText(/It looks like you need to re-link your Zotero account./)
const goToSettingsLink = screen.getByRole('link', {
name: 'Go to settings',
})
expect(goToSettingsLink.getAttribute('href')).to.equal('/user/settings')
})
})
describe('<FileViewDefaultRefreshError />', function () {
it('shows correct error message', function () {
const anotherProjectFile: BinaryFile<'project_file'> = {
id: '123abc',
_id: '123abc',
linkedFileData: {
provider: 'project_file',
source_project_id: 'some-id',
source_entity_path: '/path/',
},
created: new Date(2023, 1, 17, 3, 24),
name: 'frog.jpg',
type: 'file',
selected: true,
}
render(
<FileViewRefreshError
file={anotherProjectFile}
refreshError="An error message"
/>
)
screen.getByText('Access Denied: An error message')
})
})
})