From 78b2efd122fe743e1286418fc2ee8102145c088c Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Fri, 8 May 2026 12:13:43 -0400 Subject: [PATCH 01/15] fix: adding more tests --- public/locales/en/file.json | 3 +- public/locales/es/file.json | 3 +- .../DownloadWithTermsAndGuestbookModal.tsx | 7 +- .../useGuestbookCollectSubmission.ts | 11 ++- .../access-file-menu/FileToolOptions.tsx | 2 +- .../FileEmbeddedExternalTool.tsx | 53 ++++++++++- .../DatasetToolOptions.spec.tsx | 91 +++++++++++++++++++ .../file-action-buttons/FileTools.spec.tsx | 44 +++++++++ .../access-file-menu/FileToolOptions.spec.tsx | 79 ++++++++++++++++ .../FileEmbeddedExternalTool.spec.tsx | 55 +++++++++++ 10 files changed, 338 insertions(+), 10 deletions(-) diff --git a/public/locales/en/file.json b/public/locales/en/file.json index 2b166abee..0c25faf4a 100644 --- a/public/locales/en/file.json +++ b/public/locales/en/file.json @@ -160,6 +160,7 @@ "getCategoriesError": "Something went wrong fetching available categories. Try again later.", "previewTab": { "openInNewWindow": "Open in New Window", - "defaultLoadingToolError": "Something went wrong loading the external tool. Try again later." + "defaultLoadingToolError": "Something went wrong loading the external tool. Try again later.", + "termsRequired": "Accept the dataset terms before previewing this file." } } diff --git a/public/locales/es/file.json b/public/locales/es/file.json index 6fb27e80d..1b4e28533 100644 --- a/public/locales/es/file.json +++ b/public/locales/es/file.json @@ -160,6 +160,7 @@ "getCategoriesError": "Algo salió mal al obtener las categorías disponibles. Intenta nuevamente más tarde.", "previewTab": { "openInNewWindow": "Abrir en una nueva ventana", - "defaultLoadingToolError": "Algo salió mal al cargar la herramienta externa. Intenta nuevamente más tarde." + "defaultLoadingToolError": "Algo salió mal al cargar la herramienta externa. Intenta nuevamente más tarde.", + "termsRequired": "Acepta los términos del dataset antes de previsualizar este fichero." } } diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithTermsAndGuestbookModal.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithTermsAndGuestbookModal.tsx index 1e873c074..6512cd243 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithTermsAndGuestbookModal.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithTermsAndGuestbookModal.tsx @@ -31,6 +31,7 @@ interface DownloadWithTermsAndGuestbookModalProps { datasetCustomTerms?: CustomTermsModel show: boolean handleClose: () => void + onAccept?: () => void } type GuestbookFormValues = Record @@ -44,7 +45,8 @@ export function DownloadWithTermsAndGuestbookModal({ datasetLicense, datasetCustomTerms, show, - handleClose + handleClose, + onAccept }: DownloadWithTermsAndGuestbookModalProps) { const { t: tFiles } = useTranslation('files') const { t: tDataset } = useTranslation('dataset') @@ -226,7 +228,8 @@ export function DownloadWithTermsAndGuestbookModal({ format, handleClose, accessRepository, - downloadFromSignedUrl + downloadFromSignedUrl, + onSubmitSuccess: onAccept }) useEffect(() => { diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/useGuestbookCollectSubmission.ts b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/useGuestbookCollectSubmission.ts index 0dcecb195..ede74d90d 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/useGuestbookCollectSubmission.ts +++ b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/useGuestbookCollectSubmission.ts @@ -20,6 +20,7 @@ interface UseGuestbookCollectSubmissionProps { handleClose: () => void accessRepository: AccessRepository downloadFromSignedUrl: (signedUrl: string) => Promise + onSubmitSuccess?: () => void } interface HandleSubmitProps { @@ -34,7 +35,8 @@ export const useGuestbookCollectSubmission = ({ format, handleClose, accessRepository, - downloadFromSignedUrl + downloadFromSignedUrl, + onSubmitSuccess }: UseGuestbookCollectSubmissionProps) => { const { t: tFiles } = useTranslation('files') const [hasAttemptedAccept, setHasAttemptedAccept] = useState(false) @@ -108,6 +110,12 @@ export const useGuestbookCollectSubmission = ({ setIsSubmittingGuestbook(false) } + if (signedUrl && onSubmitSuccess) { + handleModalClose() + onSubmitSuccess() + return + } + if (signedUrl) { void downloadFromSignedUrl(signedUrl) .then(() => { @@ -132,6 +140,7 @@ export const useGuestbookCollectSubmission = ({ fileIds, format, handleModalClose, + onSubmitSuccess, tFiles ] ) diff --git a/src/sections/file/file-action-buttons/access-file-menu/FileToolOptions.tsx b/src/sections/file/file-action-buttons/access-file-menu/FileToolOptions.tsx index 1b3adf353..b162f80f3 100644 --- a/src/sections/file/file-action-buttons/access-file-menu/FileToolOptions.tsx +++ b/src/sections/file/file-action-buttons/access-file-menu/FileToolOptions.tsx @@ -55,7 +55,7 @@ const FileToolOptions = ({ fileId, fileType, kind }: FileToolOptionsProps) => { - {tools.map((tool) => ( + {applicableTools.map((tool) => ( (null) const [fileExternalToolResolved, setFileExternalToolResolved] = useState(null) + const [showTermsAndGuestbookModal, setShowTermsAndGuestbookModal] = useState(false) + const [termsAndGuestbookAccepted, setTermsAndGuestbookAccepted] = useState(false) const moreThanOneTool = applicableTools.length > 1 + const bypassTermsGuard = + file.datasetVersion.publishingStatus === DatasetPublishingStatus.DRAFT || + file.permissions.canEditOwnerDataset + const hasGuestbook = file.guestbookId !== undefined + const hasCustomTerms = file.datasetCustomTerms !== undefined + const hasNonDefaultLicense = + file.datasetLicense !== undefined && file.datasetLicense.name !== defaultLicense.name + const requiresTermsAndGuestbook = + !bypassTermsGuard && (hasGuestbook || hasCustomTerms || hasNonDefaultLicense) + const isWaitingForTermsAndGuestbook = requiresTermsAndGuestbook && !termsAndGuestbookAccepted const handleToolSelect = (eventKey: string | null) => setToolIdSelected(Number(eventKey)) @@ -50,6 +64,10 @@ export const FileEmbeddedExternalTool = ({ // Loads the tool every time the tab is in view or the tool selection changes. useEffect(() => { if (!isInView) return + if (isWaitingForTermsAndGuestbook) { + setShowTermsAndGuestbookModal(true) + return + } const fetchFileExternalToolResolved = async () => { setIframeLoaded(false) @@ -78,10 +96,30 @@ export const FileEmbeddedExternalTool = ({ } void fetchFileExternalToolResolved() - }, [isInView, toolIdSelected, externalToolsRepository, file.id, t, i18n.languages]) + }, [ + isInView, + isWaitingForTermsAndGuestbook, + toolIdSelected, + externalToolsRepository, + file.id, + t, + i18n.languages + ]) return (
+ {requiresTermsAndGuestbook && showTermsAndGuestbookModal && ( + setShowTermsAndGuestbookModal(false)} + onAccept={() => setTermsAndGuestbookAccepted(true)} + /> + )}
{moreThanOneTool && ( )} {/* Keep overlay on top of the iframe while it loads to mask flickering */} -
- -
+ {!isWaitingForTermsAndGuestbook && ( +
+ +
+ )} + {isWaitingForTermsAndGuestbook && !showTermsAndGuestbookModal && ( + + {t('termsRequired')} + + )} {/* Show error message if fetching the tool URL fails or the iframe somehow fails. */} {errorLoadingTool && ( diff --git a/tests/component/sections/dataset/dataset-action-buttons/DatasetToolOptions.spec.tsx b/tests/component/sections/dataset/dataset-action-buttons/DatasetToolOptions.spec.tsx index c83b395a0..3e5a1b116 100644 --- a/tests/component/sections/dataset/dataset-action-buttons/DatasetToolOptions.spec.tsx +++ b/tests/component/sections/dataset/dataset-action-buttons/DatasetToolOptions.spec.tsx @@ -6,6 +6,7 @@ import { import { ExternalToolsProvider } from '@/shared/contexts/external-tools/ExternalToolsProvider' import { DatasetExternalToolResolvedMother } from '@tests/component/externalTools/domain/models/DatasetExternalToolResolvedMother' import { ExternalToolsMother } from '@tests/component/externalTools/domain/models/ExternalToolsMother' +import { ToolScope, ToolType } from '@/externalTools/domain/models/ExternalTool' const testExternalToolsRepository: ExternalToolsRepository = {} as ExternalToolsRepository @@ -53,6 +54,25 @@ describe('DatasetToolOptions', () => { cy.findByText('Dataset Explore Tool').should('exist') }) + it('renders only dataset explore tools for explore options', () => { + testExternalToolsRepository.getExternalTools = cy.stub().resolves([ + testDatasetExploreTool, + testDatasetConfigureTool, + ExternalToolsMother.createFileExploreTool() + ]) + + cy.customMount( + + + + ) + + cy.findByText('Explore Options').should('exist') + cy.findByText('Dataset Explore Tool').should('exist') + cy.findByText('Dataset Configure Tool').should('not.exist') + cy.findByText('File Explore Tool').should('not.exist') + }) + it('renders nothing if there are no dataset explore tools', () => { testExternalToolsRepository.getExternalTools = cy.stub().resolves([]) cy.customMount( @@ -140,6 +160,77 @@ describe('DatasetToolOptions', () => { }) }) + it('calls getDatasetExternalToolResolved with the selected configure tool id', () => { + testExternalToolsRepository.getExternalTools = cy.stub().resolves([ + testDatasetConfigureTool, + { + ...testDatasetConfigureTool, + id: 55, + displayName: 'Second Dataset Configure Tool' + } + ]) + testExternalToolsRepository.getDatasetExternalToolResolved = cy + .stub() + .as('getDatasetExternalToolResolved') + .resolves( + DatasetExternalToolResolvedMother.create({ + toolUrlResolved: 'https://example.com/configure-tool' + }) + ) + + const fakeWindow = { + closed: false, + document: { title: '' }, + location: { href: '' }, + close: cy.stub().as('windowCloseStub') + } + + cy.window().then((win) => { + cy.stub(win, 'open').as('windowOpen').returns(fakeWindow) + }) + + cy.customMount( + + + + ) + + cy.findByText('Second Dataset Configure Tool').click() + + cy.get('@getDatasetExternalToolResolved') + .should('have.been.calledOnce') + .its('firstCall.args') + .then((args) => { + expect(args[0]).to.equal('doi:10.5072/FK2/CONFIGURE') + expect(args[1]).to.equal(55) + expect(args[2]).to.have.property('preview', false) + }) + }) + + it('does not render dataset tools that require unsupported requirements metadata', () => { + testExternalToolsRepository.getExternalTools = cy.stub().resolves([ + { + id: 77, + displayName: 'Dataset Explore Tool With Requirements', + description: 'Description for Dataset Explore Tool With Requirements', + scope: ToolScope.Dataset, + types: [ToolType.Explore], + requirements: { + auxFilesExist: [{ formatTag: 'DP', formatVersion: '1.0' }] + } + } + ]) + + cy.customMount( + + + + ) + + cy.findByText('Explore Options').should('not.exist') + cy.findByText('Dataset Explore Tool With Requirements').should('not.exist') + }) + it('shows an error toast if fetching the tool URL fails', () => { testExternalToolsRepository.getDatasetExternalToolResolved = cy .stub() diff --git a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/FileTools.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/FileTools.spec.tsx index 9ff86f86a..274d6bd7f 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/FileTools.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/FileTools.spec.tsx @@ -5,6 +5,7 @@ import { ExternalToolsProvider } from '@/shared/contexts/external-tools/External import { ExternalToolsMother } from '@tests/component/externalTools/domain/models/ExternalToolsMother' import { FileMetadataMother } from '@tests/component/files/domain/models/FileMetadataMother' import { FilePreviewMother } from '@tests/component/files/domain/models/FilePreviewMother' +import { ToolScope, ToolType } from '@/externalTools/domain/models/ExternalTool' const testFilePreview = FilePreviewMother.createDefault() // text/plain file const testExternalToolsRepository: ExternalToolsRepository = {} as ExternalToolsRepository @@ -20,6 +21,15 @@ describe('FileTools', () => { }) it('renders external tool buttons when user can download the file and there are applicable tools', () => { + testExternalToolsRepository.getExternalTools = cy + .stub() + .resolves([ + ExternalToolsMother.createFilePreviewTool(), + ExternalToolsMother.createFileQueryTool(), + ExternalToolsMother.createFileExploreTool(), + ExternalToolsMother.createFileConfigureTool() + ]) + cy.customMount( @@ -45,6 +55,8 @@ describe('FileTools', () => { .and('include', `id=${testFilePreview.id}`) .and('include', `datasetVersion=${testFilePreview.datasetVersionNumber.toString()}`) .and('include', `${QueryParamKey.TOOL_TYPE}=query`) + + cy.findAllByRole('link').should('have.length', 2) }) it('does not render external tool buttons when user cannot download the file', () => { @@ -53,6 +65,9 @@ describe('FileTools', () => { ) + + cy.findByRole('link', { name: `Preview ${testFilePreview.name}` }).should('not.exist') + cy.findByRole('link', { name: `Query ${testFilePreview.name}` }).should('not.exist') }) it('does not render external tool buttons when there are no applicable tools for the file type', () => { @@ -67,5 +82,34 @@ describe('FileTools', () => { ) + + cy.findByRole('link', { name: `Preview ${fileWithoutApplicableTools.name}` }).should( + 'not.exist' + ) + cy.findByRole('link', { name: `Query ${fileWithoutApplicableTools.name}` }).should('not.exist') + }) + + it('does not render preview or query buttons for tools with unsupported requirements metadata', () => { + testExternalToolsRepository.getExternalTools = cy.stub().resolves([ + { + id: 77, + displayName: 'Preview Tool With Requirements', + description: 'Description for Preview Tool With Requirements', + scope: ToolScope.File, + types: [ToolType.Preview], + contentType: 'text/plain', + requirements: { + auxFilesExist: [{ formatTag: 'DP', formatVersion: '1.0' }] + } + } + ]) + + cy.customMount( + + + + ) + + cy.findByRole('link', { name: `Preview ${testFilePreview.name}` }).should('not.exist') }) }) diff --git a/tests/component/sections/file/file-action-buttons/access-file-menu/FileToolOptions.spec.tsx b/tests/component/sections/file/file-action-buttons/access-file-menu/FileToolOptions.spec.tsx index 3efcb91de..d51121454 100644 --- a/tests/component/sections/file/file-action-buttons/access-file-menu/FileToolOptions.spec.tsx +++ b/tests/component/sections/file/file-action-buttons/access-file-menu/FileToolOptions.spec.tsx @@ -7,6 +7,7 @@ import { import { ExternalToolsProvider } from '@/shared/contexts/external-tools/ExternalToolsProvider' import { ExternalToolsMother } from '@tests/component/externalTools/domain/models/ExternalToolsMother' import { FileExternalToolResolvedMother } from '@tests/component/externalTools/domain/models/FileExternalToolResolvedMother' +import { ToolScope, ToolType } from '@/externalTools/domain/models/ExternalTool' const testExternalToolsRepository: ExternalToolsRepository = {} as ExternalToolsRepository const testFileExploreTool = ExternalToolsMother.createFileExploreTool() @@ -22,6 +23,17 @@ describe('FileToolOptions', () => { describe('FileExploreToolsOptions', () => { it('renders the tool options if file explore tools are available and compatible with the type', () => { + testExternalToolsRepository.getExternalTools = cy.stub().resolves([ + testFileExploreTool, + { + ...testFileExploreTool, + id: 33, + displayName: 'PDF Explore Tool', + contentType: 'application/pdf' + }, + testFileQueryTool + ]) + cy.customMount( @@ -32,6 +44,8 @@ describe('FileToolOptions', () => { cy.findByText('Configure Options').should('not.exist') cy.findByText('Explore Options').should('exist') cy.findByText('File Explore Tool').should('exist') + cy.findByText('PDF Explore Tool').should('not.exist') + cy.findByText('File Query Tool').should('not.exist') }) it('does not render the tool options if there are not applicable tools for the file type', () => { @@ -135,6 +149,71 @@ describe('FileToolOptions', () => { }) }) + it('calls getFileExternalToolResolved with preview=false and locale', () => { + testExternalToolsRepository.getFileExternalToolResolved = cy + .stub() + .as('getFileExternalToolResolved') + .resolves( + FileExternalToolResolvedMother.create({ + toolUrlResolved: 'https://example.com/query-tool' + }) + ) + + const fakeWindow = { + closed: false, + document: { title: '' }, + location: { href: '' }, + close: cy.stub().as('windowCloseStub') + } + + cy.window().then((win) => { + cy.stub(win, 'open').as('windowOpen').returns(fakeWindow) + }) + + cy.customMount( + + + + ) + + cy.findByText('File Query Tool').click() + + cy.get('@getFileExternalToolResolved') + .should('have.been.calledOnce') + .its('firstCall.args') + .then((args) => { + expect(args[0]).to.equal(123) + expect(args[1]).to.equal(testFileQueryTool.id) + expect(args[2]).to.have.property('preview', false) + expect(args[2]).to.have.property('locale').that.is.a('string').and.is.not.empty + }) + }) + + it('does not render file tools that require unsupported requirements metadata', () => { + testExternalToolsRepository.getExternalTools = cy.stub().resolves([ + { + id: 77, + displayName: 'File Explore Tool With Requirements', + description: 'Description for File Explore Tool With Requirements', + scope: ToolScope.File, + types: [ToolType.Explore], + contentType: 'text/plain', + requirements: { + auxFilesExist: [{ formatTag: 'DP', formatVersion: '1.0' }] + } + } + ]) + + cy.customMount( + + + + ) + + cy.findByText('Explore Options').should('not.exist') + cy.findByText('File Explore Tool With Requirements').should('not.exist') + }) + it('shows an error toast if fetching the tool URL fails', () => { testExternalToolsRepository.getFileExternalToolResolved = cy .stub() diff --git a/tests/component/sections/file/file-embedded-external-tool/FileEmbeddedExternalTool.spec.tsx b/tests/component/sections/file/file-embedded-external-tool/FileEmbeddedExternalTool.spec.tsx index 469c57039..689a59feb 100644 --- a/tests/component/sections/file/file-embedded-external-tool/FileEmbeddedExternalTool.spec.tsx +++ b/tests/component/sections/file/file-embedded-external-tool/FileEmbeddedExternalTool.spec.tsx @@ -5,8 +5,13 @@ import { WriteError } from '@iqss/dataverse-client-javascript' import { ExternalToolsMother } from '@tests/component/externalTools/domain/models/ExternalToolsMother' import { FileExternalToolResolvedMother } from '@tests/component/externalTools/domain/models/FileExternalToolResolvedMother' import { FileMother } from '@tests/component/files/domain/models/FileMother' +import { AccessRepository } from '@/access/domain/repositories/AccessRepository' +import { AccessRepositoryProvider } from '@/sections/access/AccessRepositoryProvider' +import { CustomTermsMother } from '@tests/component/dataset/domain/models/TermsOfUseMother' +import { FilePermissionsMother } from '@tests/component/files/domain/models/FilePermissionsMother' const externalToolsRepository: ExternalToolsRepository = {} as ExternalToolsRepository // Used for fetching the tool resolved URL +const accessRepository: AccessRepository = {} as AccessRepository const testFile = FileMother.createRealistic() // text/plain file const filePreviewTool = ExternalToolsMother.createFilePreviewTool() // id: 2 @@ -130,6 +135,56 @@ describe('FileEmbeddedExternalTool', () => { cy.findByTestId('external-tool-iframe').should('not.exist') }) + it('requires dataset terms acceptance before loading the external tool', () => { + const fileWithCustomTerms = FileMother.createRealistic({ + permissions: FilePermissionsMother.create({ + canDownloadFile: true, + canEditOwnerDataset: false, + canManageFilePermissions: false + }), + datasetCustomTerms: CustomTermsMother.create({ + termsOfUse: 'Preview requires accepting these custom terms.' + }) + }) + externalToolsRepository.getFileExternalToolResolved = cy + .stub() + .as('getFileExternalToolResolved') + .resolves(filePreviewToolResolved) + accessRepository.submitGuestbookForDatasetDownload = cy.stub().resolves('signed-url-dataset') + accessRepository.submitGuestbookForDatafileDownload = cy + .stub() + .as('submitGuestbookForDatafileDownload') + .resolves('signed-url-datafile') + accessRepository.submitGuestbookForDatafilesDownload = cy + .stub() + .resolves('signed-url-datafiles') + + cy.customMount( + + + + ) + + cy.findByRole('dialog').should('exist') + cy.findByText('Preview requires accepting these custom terms.').should('exist') + cy.findByTestId('external-tool-iframe').should('not.exist') + cy.get('@getFileExternalToolResolved').should('not.have.been.called') + + cy.findByRole('button', { name: 'Accept' }).click() + + cy.get('@submitGuestbookForDatafileDownload').should('have.been.calledOnce') + cy.findByRole('dialog').should('not.exist') + cy.findByTestId('external-tool-iframe') + .should('exist') + .should('have.attr', 'src', filePreviewToolResolved.toolUrlResolved) + }) + describe('error handling', () => { it('shows js dataverse error message if fetching the tool URL fails with a JSDataverseError', () => { externalToolsRepository.getFileExternalToolResolved = cy From dcc23e19a79506c33254109f26428b9443228a93 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Fri, 8 May 2026 15:54:39 -0400 Subject: [PATCH 02/15] fix: lint --- .../DatasetToolOptions.spec.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/component/sections/dataset/dataset-action-buttons/DatasetToolOptions.spec.tsx b/tests/component/sections/dataset/dataset-action-buttons/DatasetToolOptions.spec.tsx index 3e5a1b116..dc4d9cbbe 100644 --- a/tests/component/sections/dataset/dataset-action-buttons/DatasetToolOptions.spec.tsx +++ b/tests/component/sections/dataset/dataset-action-buttons/DatasetToolOptions.spec.tsx @@ -55,11 +55,13 @@ describe('DatasetToolOptions', () => { }) it('renders only dataset explore tools for explore options', () => { - testExternalToolsRepository.getExternalTools = cy.stub().resolves([ - testDatasetExploreTool, - testDatasetConfigureTool, - ExternalToolsMother.createFileExploreTool() - ]) + testExternalToolsRepository.getExternalTools = cy + .stub() + .resolves([ + testDatasetExploreTool, + testDatasetConfigureTool, + ExternalToolsMother.createFileExploreTool() + ]) cy.customMount( From e3ac75fb08b97527489382866b183d242e699bf6 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Mon, 11 May 2026 15:46:51 -0400 Subject: [PATCH 03/15] fix: external tool guestbook integration --- public/locales/en/file.json | 2 +- .../datasetTermsNavigation.ts | 27 +++++++++++++++++++ .../edit-guestbook/EditGuestbook.tsx | 23 ++++++---------- .../EditLicenseAndTerms.tsx | 21 +++++---------- .../EditTermsOfAccess.tsx | 27 +++++++------------ .../edit-dataset-terms/EditGuestbook.spec.tsx | 4 +-- .../EditLicenseAndTerms.spec.tsx | 21 +++++++++++---- .../EditTermsOfAccess.spec.tsx | 11 +++++++- 8 files changed, 81 insertions(+), 55 deletions(-) create mode 100644 src/sections/edit-dataset-terms/datasetTermsNavigation.ts diff --git a/public/locales/en/file.json b/public/locales/en/file.json index 0c25faf4a..f20bf0fa8 100644 --- a/public/locales/en/file.json +++ b/public/locales/en/file.json @@ -161,6 +161,6 @@ "previewTab": { "openInNewWindow": "Open in New Window", "defaultLoadingToolError": "Something went wrong loading the external tool. Try again later.", - "termsRequired": "Accept the dataset terms before previewing this file." + "termsRequired": "Accept the dataset terms or guestbooks before previewing this file." } } diff --git a/src/sections/edit-dataset-terms/datasetTermsNavigation.ts b/src/sections/edit-dataset-terms/datasetTermsNavigation.ts new file mode 100644 index 000000000..f518b213e --- /dev/null +++ b/src/sections/edit-dataset-terms/datasetTermsNavigation.ts @@ -0,0 +1,27 @@ +import { + Dataset, + DatasetNonNumericVersionSearchParam, + DatasetPublishingStatus +} from '@/dataset/domain/models/Dataset' +import { QueryParamKey, Route } from '@/sections/Route.enum' + +export const buildDatasetTermsReturnUrl = (dataset: Dataset): string => { + const searchParams = new URLSearchParams() + searchParams.set(QueryParamKey.PERSISTENT_ID, dataset.persistentId) + + if (dataset.version.publishingStatus === DatasetPublishingStatus.DRAFT) { + searchParams.set(QueryParamKey.VERSION, DatasetNonNumericVersionSearchParam.DRAFT) + } else { + searchParams.set(QueryParamKey.VERSION, dataset.version.number.toString()) + } + + return `${Route.DATASETS}?${searchParams.toString()}` +} + +export const buildDatasetDraftReturnUrl = (dataset: Dataset): string => { + const searchParams = new URLSearchParams() + searchParams.set(QueryParamKey.PERSISTENT_ID, dataset.persistentId) + searchParams.set(QueryParamKey.VERSION, DatasetNonNumericVersionSearchParam.DRAFT) + + return `${Route.DATASETS}?${searchParams.toString()}` +} diff --git a/src/sections/edit-dataset-terms/edit-guestbook/EditGuestbook.tsx b/src/sections/edit-dataset-terms/edit-guestbook/EditGuestbook.tsx index d91e7185b..ef3126ded 100644 --- a/src/sections/edit-dataset-terms/edit-guestbook/EditGuestbook.tsx +++ b/src/sections/edit-dataset-terms/edit-guestbook/EditGuestbook.tsx @@ -5,16 +5,12 @@ import { useNavigate } from 'react-router-dom' import { toast } from 'react-toastify' import { Guestbook } from '@/guestbooks/domain/models/Guestbook' import { GuestbookRepository } from '@/guestbooks/domain/repositories/GuestbookRepository' -import { - DatasetNonNumericVersionSearchParam, - DatasetPublishingStatus -} from '@/dataset/domain/models/Dataset' import { useGetGuestbooksByCollectionId } from '@/sections/guestbooks/useGetGuestbooksByCollectionId' -import { QueryParamKey, Route } from '@/sections/Route.enum' import { useAssignDatasetGuestbook } from './useAssignDatasetGuestbook' import { useRemoveDatasetGuestbook } from './useRemoveDatasetGuestbook' import { useDataset } from '../../dataset/DatasetContext' import { PreviewGuestbookModal } from '@/sections/guestbooks/preview-modal/PreviewGuestbookModal' +import { buildDatasetDraftReturnUrl, buildDatasetTermsReturnUrl } from '../datasetTermsNavigation' import styles from './EditGuestbook.module.scss' interface EditGuestbookProps { @@ -40,16 +36,13 @@ export function EditGuestbook({ const navigateToDatasetView = useCallback(() => { if (!dataset) return - const searchParams = new URLSearchParams() - searchParams.set(QueryParamKey.PERSISTENT_ID, dataset.persistentId) + navigate(buildDatasetTermsReturnUrl(dataset)) + }, [dataset, navigate]) - if (dataset.version.publishingStatus === DatasetPublishingStatus.DRAFT) { - searchParams.set(QueryParamKey.VERSION, DatasetNonNumericVersionSearchParam.DRAFT) - } else { - searchParams.set(QueryParamKey.VERSION, dataset.version.number.toString()) - } + const navigateToDatasetDraftView = useCallback(() => { + if (!dataset) return - navigate(`${Route.DATASETS}?${searchParams.toString()}`) + navigate(buildDatasetDraftReturnUrl(dataset)) }, [dataset, navigate]) const handleCancel = () => { @@ -70,7 +63,7 @@ export function EditGuestbook({ onSuccessfulAssignDatasetGuestbook: () => { toast.success(t('alerts.termsUpdated.alertText')) refreshDataset() - navigateToDatasetView() + navigateToDatasetDraftView() } }) const { @@ -82,7 +75,7 @@ export function EditGuestbook({ onSuccessfulRemoveDatasetGuestbook: () => { toast.success(t('alerts.termsUpdated.alertText')) refreshDataset() - navigateToDatasetView() + navigateToDatasetDraftView() } }) diff --git a/src/sections/edit-dataset-terms/edit-license-and-terms/EditLicenseAndTerms.tsx b/src/sections/edit-dataset-terms/edit-license-and-terms/EditLicenseAndTerms.tsx index 02abc4d56..17af24cbd 100644 --- a/src/sections/edit-dataset-terms/edit-license-and-terms/EditLicenseAndTerms.tsx +++ b/src/sections/edit-dataset-terms/edit-license-and-terms/EditLicenseAndTerms.tsx @@ -10,11 +10,7 @@ import { useGetLicenses } from './useGetLicenses' import { DatasetRepository } from '@/dataset/domain/repositories/DatasetRepository' import { useDataset } from '../../dataset/DatasetContext' import { useUpdateDatasetLicense } from './useUpdateDatasetLicense' -import { Route, QueryParamKey } from '../../Route.enum' -import { - DatasetNonNumericVersionSearchParam, - DatasetPublishingStatus -} from '../../../dataset/domain/models/Dataset' +import { buildDatasetDraftReturnUrl, buildDatasetTermsReturnUrl } from '../datasetTermsNavigation' import styles from './EditLicenseAndTerms.module.scss' const CUSTOM_LICENSE_VALUE = 'CUSTOM' as const @@ -50,16 +46,13 @@ export function EditLicenseAndTerms({ const navigateToDatasetView = useCallback(() => { if (!dataset) return - const searchParams = new URLSearchParams() - searchParams.set(QueryParamKey.PERSISTENT_ID, dataset.persistentId) + navigate(buildDatasetTermsReturnUrl(dataset)) + }, [dataset, navigate]) - if (dataset.version.publishingStatus === DatasetPublishingStatus.DRAFT) { - searchParams.set(QueryParamKey.VERSION, DatasetNonNumericVersionSearchParam.DRAFT) - } else { - searchParams.set(QueryParamKey.VERSION, dataset.version.number.toString()) - } + const navigateToDatasetDraftView = useCallback(() => { + if (!dataset) return - navigate(`${Route.DATASETS}?${searchParams.toString()}`) + navigate(buildDatasetDraftReturnUrl(dataset)) }, [dataset, navigate]) const { handleUpdateLicense, isLoading, error } = useUpdateDatasetLicense({ @@ -67,7 +60,7 @@ export function EditLicenseAndTerms({ onSuccessfulUpdateLicense: () => { toast.success(t('alerts.licenseUpdated.alertText')) refreshDataset() - navigateToDatasetView() + navigateToDatasetDraftView() } }) diff --git a/src/sections/edit-dataset-terms/edit-terms-of-access/EditTermsOfAccess.tsx b/src/sections/edit-dataset-terms/edit-terms-of-access/EditTermsOfAccess.tsx index fb39ee1df..a6de26ae3 100644 --- a/src/sections/edit-dataset-terms/edit-terms-of-access/EditTermsOfAccess.tsx +++ b/src/sections/edit-dataset-terms/edit-terms-of-access/EditTermsOfAccess.tsx @@ -1,19 +1,15 @@ -import { useEffect, useMemo, useRef } from 'react' +import { useCallback, useEffect, useMemo, useRef } from 'react' import { useTranslation } from 'react-i18next' import { useForm, Controller, FormProvider, useWatch } from 'react-hook-form' import { toast } from 'react-toastify' import { Form, Row, Col, Button, Alert } from '@iqss/dataverse-design-system' import styles from '../edit-license-and-terms/EditLicenseAndTerms.module.scss' -import { - DatasetNonNumericVersionSearchParam, - DatasetPublishingStatus, - TermsOfAccess -} from '@/dataset/domain/models/Dataset' +import { TermsOfAccess } from '@/dataset/domain/models/Dataset' import { DatasetRepository } from '@/dataset/domain/repositories/DatasetRepository' import { useDataset } from '../../dataset/DatasetContext' import { useUpdateTermsOfAccess } from './useUpdateTermsOfAccess' -import { QueryParamKey, Route } from '@/sections/Route.enum' import { useNavigate } from 'react-router-dom' +import { buildDatasetDraftReturnUrl, buildDatasetTermsReturnUrl } from '../datasetTermsNavigation' interface EditTermsOfAccessProps { datasetRepository: DatasetRepository @@ -43,12 +39,18 @@ export function EditTermsOfAccess({ const initialTermsOfAccess = (dataset?.termsOfUse.termsOfAccess as TermsOfAccess) ?? defaultTermsOfAccess const formContainerRef = useRef(null) + const navigateToDatasetDraftView = useCallback(() => { + if (!dataset) return + + navigate(buildDatasetDraftReturnUrl(dataset)) + }, [dataset, navigate]) const { handleUpdateTermsOfAccess, isLoading, error } = useUpdateTermsOfAccess({ datasetRepository, onSuccessfulUpdateTermsOfAccess: () => { toast.success(t('alerts.termsUpdated.alertText')) refreshDataset() + navigateToDatasetDraftView() } }) @@ -122,16 +124,7 @@ export function EditTermsOfAccess({ const handleCancel = () => { if (!dataset) return - const searchParams = new URLSearchParams() - searchParams.set(QueryParamKey.PERSISTENT_ID, dataset.persistentId) - - if (dataset.version.publishingStatus === DatasetPublishingStatus.DRAFT) { - searchParams.set(QueryParamKey.VERSION, DatasetNonNumericVersionSearchParam.DRAFT) - } else { - searchParams.set(QueryParamKey.VERSION, dataset.version.number.toString()) - } - - navigate(`${Route.DATASETS}?${searchParams.toString()}`) + navigate(buildDatasetTermsReturnUrl(dataset)) } return ( diff --git a/tests/component/sections/edit-dataset-terms/EditGuestbook.spec.tsx b/tests/component/sections/edit-dataset-terms/EditGuestbook.spec.tsx index 3dff96744..b907f4fbc 100644 --- a/tests/component/sections/edit-dataset-terms/EditGuestbook.spec.tsx +++ b/tests/component/sections/edit-dataset-terms/EditGuestbook.spec.tsx @@ -331,7 +331,7 @@ describe('EditGuestbook', () => { ) }) - it('navigates with numeric version query param after successful submit for non-draft datasets', () => { + it('navigates with DRAFT version query param after successful submit for non-draft datasets', () => { ;(guestbookRepository.assignDatasetGuestbook as Cypress.Agent).resolves( undefined ) @@ -356,7 +356,7 @@ describe('EditGuestbook', () => { cy.findByRole('button', { name: 'Save Changes' }).click() cy.findByTestId('location-display').should( 'have.text', - '/datasets?persistentId=doi%3A10.5072%2FFK2%2FRELEASEDPID&version=1.0' + '/datasets?persistentId=doi%3A10.5072%2FFK2%2FRELEASEDPID&version=DRAFT' ) }) diff --git a/tests/component/sections/edit-dataset-terms/EditLicenseAndTerms.spec.tsx b/tests/component/sections/edit-dataset-terms/EditLicenseAndTerms.spec.tsx index 3c776f751..86ec0748e 100644 --- a/tests/component/sections/edit-dataset-terms/EditLicenseAndTerms.spec.tsx +++ b/tests/component/sections/edit-dataset-terms/EditLicenseAndTerms.spec.tsx @@ -357,11 +357,18 @@ describe('EditLicenseAndTerms', () => { cy.customMount( withProviders( - , - mockDatasetWithLicense + <> + + + , + DatasetMother.create({ + ...mockDatasetWithLicense, + persistentId: 'doi:10.5072/FK2/LICENSEPID', + version: DatasetVersionMother.createReleased() + }) ) ) @@ -371,6 +378,10 @@ describe('EditLicenseAndTerms', () => { cy.findByRole('button', { name: 'Save Changes' }).click() cy.findByText('The license for this dataset has been updated.').should('exist') + cy.findByTestId('location-display').should( + 'have.text', + '/datasets?persistentId=doi%3A10.5072%2FFK2%2FLICENSEPID&version=DRAFT' + ) }) it('displays success toast when custom terms are updated successfully', () => { diff --git a/tests/component/sections/edit-dataset-terms/EditTermsOfAccess.spec.tsx b/tests/component/sections/edit-dataset-terms/EditTermsOfAccess.spec.tsx index eb1a023e8..7e42b5f94 100644 --- a/tests/component/sections/edit-dataset-terms/EditTermsOfAccess.spec.tsx +++ b/tests/component/sections/edit-dataset-terms/EditTermsOfAccess.spec.tsx @@ -274,9 +274,14 @@ describe('EditTermsOfAccess', () => { cy.customMount( withProviders( - , + <> + + + , DatasetMother.create({ id: 123, + persistentId: 'doi:10.5072/FK2/TERMSPID', + version: DatasetVersionMother.createReleased(), termsOfUse: { termsOfAccess } }) ) @@ -288,6 +293,10 @@ describe('EditTermsOfAccess', () => { cy.findByRole('button', { name: 'Save Changes' }).click() cy.findByText('The terms for this dataset have been updated.').should('exist') + cy.findByTestId('location-display').should( + 'have.text', + '/datasets?persistentId=doi%3A10.5072%2FFK2%2FTERMSPID&version=DRAFT' + ) }) it('displays success toast when request access checkbox is toggled', () => { From 999d6a665b71d3436301598f8fa1921c13f51a45 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Tue, 12 May 2026 03:28:24 -0400 Subject: [PATCH 04/15] fix: e2e test --- .../e2e-integration/e2e/sections/dataset/Dataset.spec.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index 1bba11036..8d767c06e 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -817,7 +817,8 @@ describe('Dataset', () => { it('downloads the dataset directly for dataset editors even when a guestbook is assigned', () => { const guestbookName = `Guestbook ${faker.datatype.uuid()}` - cy.wrap(DatasetHelper.createWithFiles(FileHelper.createMany(2))).then((dataset) => { + cy.wrap(DatasetHelper.createWithFiles(FileHelper.createMany(2))).then(async (dataset) => { + await TestsUtils.waitForNoLocks(dataset.persistentId) cy.wrap( GuestbookHelper.createAndGetByName(guestbookName).then(async (guestbook) => { await GuestbookHelper.assignToDataset(Number(dataset.id), guestbook.id) @@ -848,7 +849,8 @@ describe('Dataset', () => { it('opens the guestbook modal for guests when downloading a dataset with an assigned guestbook', () => { const guestbookName = `Guestbook ${faker.datatype.uuid()}` - cy.wrap(DatasetHelper.createWithFiles(FileHelper.createMany(2))).then((dataset) => { + cy.wrap(DatasetHelper.createWithFiles(FileHelper.createMany(2))).then(async (dataset) => { + await TestsUtils.waitForNoLocks(dataset.persistentId) cy.wrap( GuestbookHelper.createAndGetByName(guestbookName).then(async (guestbook) => { await GuestbookHelper.assignToDataset(Number(dataset.id), guestbook.id) @@ -878,6 +880,7 @@ describe('Dataset', () => { it('opens the custom terms modal for guests when downloading a dataset with custom terms and no guestbook', () => { cy.wrap( DatasetHelper.createWithFiles(FileHelper.createMany(2)).then(async (dataset) => { + await TestsUtils.waitForNoLocks(dataset.persistentId) await DatasetHelper.setCustomTermsOfUse(dataset.id, { termsOfUse: 'These are custom terms of use for testing' }) @@ -906,6 +909,7 @@ describe('Dataset', () => { it('downloads the dataset directly for editors even when custom terms exist without a guestbook', () => { cy.wrap( DatasetHelper.createWithFiles(FileHelper.createMany(2)).then(async (dataset) => { + await TestsUtils.waitForNoLocks(dataset.persistentId) await DatasetHelper.setCustomTermsOfUse(dataset.id, { termsOfUse: 'These are custom terms of use for testing' }) From 1496b02f27307de57a6953a56b2110645c8884e9 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Wed, 10 Jun 2026 12:55:31 -0400 Subject: [PATCH 05/15] fix: make the test timeout longer --- tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx index 8d767c06e..67877855f 100644 --- a/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx +++ b/tests/e2e-integration/e2e/sections/dataset/Dataset.spec.tsx @@ -965,7 +965,8 @@ describe('Dataset', () => { cy.wrap( DatasetHelper.createWithFiles(FileHelper.createMany(3)).then((dataset) => DatasetHelper.publish(dataset.persistentId) - ) + ), + { timeout: 30_000 } ) .its('persistentId') .then((persistentId: string) => { From ffeed64d0566df7731b791fc65eb2ebc18dbeb36 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Mon, 29 Jun 2026 12:43:45 -0400 Subject: [PATCH 06/15] fix: add a link to the information to open modal of terms --- public/locales/en/file.json | 3 +- public/locales/es/file.json | 3 +- .../FileEmbeddedExternalTool.tsx | 17 ++++++- .../FileEmbeddedExternalTool.spec.tsx | 50 +++++++++++++++++++ 4 files changed, 69 insertions(+), 4 deletions(-) diff --git a/public/locales/en/file.json b/public/locales/en/file.json index f20bf0fa8..78c146450 100644 --- a/public/locales/en/file.json +++ b/public/locales/en/file.json @@ -161,6 +161,7 @@ "previewTab": { "openInNewWindow": "Open in New Window", "defaultLoadingToolError": "Something went wrong loading the external tool. Try again later.", - "termsRequired": "Accept the dataset terms or guestbooks before previewing this file." + "acceptTermsAndGuestbooks": "Accept the dataset terms or guestbooks", + "termsRequired": "before previewing this file." } } diff --git a/public/locales/es/file.json b/public/locales/es/file.json index 1b4e28533..ebd98356b 100644 --- a/public/locales/es/file.json +++ b/public/locales/es/file.json @@ -161,6 +161,7 @@ "previewTab": { "openInNewWindow": "Abrir en una nueva ventana", "defaultLoadingToolError": "Algo salió mal al cargar la herramienta externa. Intenta nuevamente más tarde.", - "termsRequired": "Acepta los términos del dataset antes de previsualizar este fichero." + "acceptTermsAndGuestbooks": "Acepta los términos del dataset o guestbooks", + "termsRequired": "antes de previsualizar este fichero." } } diff --git a/src/sections/file/file-embedded-external-tool/FileEmbeddedExternalTool.tsx b/src/sections/file/file-embedded-external-tool/FileEmbeddedExternalTool.tsx index 50d20b253..2f9ee3e48 100644 --- a/src/sections/file/file-embedded-external-tool/FileEmbeddedExternalTool.tsx +++ b/src/sections/file/file-embedded-external-tool/FileEmbeddedExternalTool.tsx @@ -2,7 +2,13 @@ import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import cn from 'classnames' import { WriteError } from '@iqss/dataverse-client-javascript' -import { Alert, DropdownButton, DropdownButtonItem, Spinner } from '@iqss/dataverse-design-system' +import { + Alert, + Button, + DropdownButton, + DropdownButtonItem, + Spinner +} from '@iqss/dataverse-design-system' import { BoxArrowUpRight } from 'react-bootstrap-icons' import { ExternalTool } from '@/externalTools/domain/models/ExternalTool' import { ExternalToolsRepository } from '@/externalTools/domain/repositories/ExternalToolsRepository' @@ -178,7 +184,14 @@ export const FileEmbeddedExternalTool = ({ )} {isWaitingForTermsAndGuestbook && !showTermsAndGuestbookModal && ( - {t('termsRequired')} + + {t('termsRequired')} )} {/* Show error message if fetching the tool URL fails or the iframe somehow fails. */} diff --git a/tests/component/sections/file/file-embedded-external-tool/FileEmbeddedExternalTool.spec.tsx b/tests/component/sections/file/file-embedded-external-tool/FileEmbeddedExternalTool.spec.tsx index 689a59feb..77131360d 100644 --- a/tests/component/sections/file/file-embedded-external-tool/FileEmbeddedExternalTool.spec.tsx +++ b/tests/component/sections/file/file-embedded-external-tool/FileEmbeddedExternalTool.spec.tsx @@ -185,6 +185,56 @@ describe('FileEmbeddedExternalTool', () => { .should('have.attr', 'src', filePreviewToolResolved.toolUrlResolved) }) + it('allows reopening the terms dialog after canceling it', () => { + const fileWithCustomTerms = FileMother.createRealistic({ + permissions: FilePermissionsMother.create({ + canDownloadFile: true, + canEditOwnerDataset: false, + canManageFilePermissions: false + }), + datasetCustomTerms: CustomTermsMother.create({ + termsOfUse: 'Preview requires accepting these custom terms.' + }) + }) + externalToolsRepository.getFileExternalToolResolved = cy + .stub() + .as('getFileExternalToolResolved') + .resolves(filePreviewToolResolved) + accessRepository.submitGuestbookForDatasetDownload = cy.stub().resolves('signed-url-dataset') + accessRepository.submitGuestbookForDatafileDownload = cy + .stub() + .as('submitGuestbookForDatafileDownload') + .resolves('signed-url-datafile') + accessRepository.submitGuestbookForDatafilesDownload = cy + .stub() + .resolves('signed-url-datafiles') + + cy.customMount( + + + + ) + + cy.findByRole('dialog').should('exist') + cy.findByRole('button', { name: 'Cancel' }).click() + + cy.findByRole('dialog').should('not.exist') + cy.findByRole('button', { name: 'Accept the dataset terms or guestbooks' }).should('exist') + cy.findByText('before previewing this file.').should('exist') + cy.findByRole('button', { name: 'Accept the dataset terms or guestbooks' }).click() + + cy.findByRole('dialog').should('exist') + cy.findByText('Preview requires accepting these custom terms.').should('exist') + cy.get('@getFileExternalToolResolved').should('not.have.been.called') + cy.findByTestId('external-tool-iframe').should('not.exist') + }) + describe('error handling', () => { it('shows js dataverse error message if fetching the tool URL fails with a JSDataverseError', () => { externalToolsRepository.getFileExternalToolResolved = cy From 6265a0c3f4d2697d43057b7948b6574ab798b42f Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Mon, 29 Jun 2026 14:17:09 -0400 Subject: [PATCH 07/15] fix: add tests to improve test coverage --- ...ownloadWithTermsAndGuestbookModal.spec.tsx | 221 +++++++++++++++++- .../useGetDatasetReviews.spec.tsx | 107 +++++++++ 2 files changed, 327 insertions(+), 1 deletion(-) create mode 100644 tests/component/sections/dataset/dataset-reviews/useGetDatasetReviews.spec.tsx diff --git a/tests/component/sections/dataset/dataset-files/guestbook/DownloadWithTermsAndGuestbookModal.spec.tsx b/tests/component/sections/dataset/dataset-files/guestbook/DownloadWithTermsAndGuestbookModal.spec.tsx index 66095d974..bc17f2e15 100644 --- a/tests/component/sections/dataset/dataset-files/guestbook/DownloadWithTermsAndGuestbookModal.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/guestbook/DownloadWithTermsAndGuestbookModal.spec.tsx @@ -1,6 +1,11 @@ import { DownloadWithTermsAndGuestbookModal } from '@/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/DownloadWithTermsAndGuestbookModal' +import { + getGuestbookCustomQuestionFieldName, + GuestbookCollectForm, + isGuestbookCollectFormEmailValid +} from '@/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/GuestbookCollectForm' import { DatasetContext } from '@/sections/dataset/DatasetContext' -import { Guestbook } from '@/guestbooks/domain/models/Guestbook' +import { Guestbook, GuestbookCustomQuestion } from '@/guestbooks/domain/models/Guestbook' import { DatasetLicense } from '@/dataset/domain/models/Dataset' import { FileDownloadMode } from '@/files/domain/models/FileMetadata' import { GuestbookRepository } from '@/guestbooks/domain/repositories/GuestbookRepository' @@ -90,6 +95,83 @@ const datasetLicense: DatasetLicense = { uri: 'https://creativecommons.org/publicdomain/zero/1.0/' } +const guestbookCollectFormLicense: DatasetLicense = { + ...datasetLicense, + iconUri: 'https://example.com/cc0.png' +} + +const guestbookCollectFormCustomTerms = { + termsOfUse: 'Custom terms for this dataset', + confidentialityDeclaration: 'Confidentiality declaration text' +} + +const guestbookCollectFormCustomQuestions: GuestbookCustomQuestion[] = [ + { + question: 'Hidden question', + required: false, + displayOrder: 0, + type: 'text', + hidden: true + }, + { + question: 'Research purpose', + required: true, + displayOrder: 1, + type: 'text', + hidden: false + }, + { + question: 'Preferred format', + required: false, + displayOrder: 2, + type: 'options', + hidden: false, + optionValues: [ + { value: 'CSV', displayOrder: 1 }, + { value: 'JSON', displayOrder: 2 } + ] + }, + { + question: 'Additional context', + required: false, + displayOrder: 3, + type: 'textarea', + hidden: false + } +] + +const guestbookCollectFormGuestbook: Guestbook = { + ...guestbook, + customQuestions: guestbookCollectFormCustomQuestions +} + +const guestbookCollectFormBaseProps = { + formValues: { + name: 'Test User', + email: 'test.user@example.com', + institution: 'Example University', + 'custom-question-1-0': 'Existing research purpose', + 'custom-question-2-1': 'CSV', + 'custom-question-3-2': 'Existing context' + }, + hasAttemptedAccept: false, + accountFieldErrors: { + name: null, + email: null, + institution: null, + position: null + }, + customQuestionErrors: { + 'custom-question-1-0': null, + 'custom-question-2-1': null, + 'custom-question-3-2': null + }, + accountFieldKeys: ['name', 'email', 'institution'], + shouldLockIdentityFields: false, + isAccountFieldRequired: (fieldName: string) => fieldName === 'name' || fieldName === 'email', + onFieldChange: () => {} +} + describe('DownloadWithTermsAndGuestbookModal', () => { let getGuestbookImpl: (guestbookId: number) => Promise let submitGuestbookForDatafileDownloadImpl: ( @@ -865,4 +947,141 @@ describe('DownloadWithTermsAndGuestbookModal', () => { cy.findByText(/Error/).should('exist') cy.findByText(/Something went wrong downloading the file. Try again later./i).should('exist') }) + + describe.only('GuestbookCollectForm', () => { + it('builds custom question field names and validates email addresses', () => { + expect( + getGuestbookCustomQuestionFieldName(guestbookCollectFormCustomQuestions[1], 0) + ).to.equal('custom-question-1-0') + expect(isGuestbookCollectFormEmailValid('test.user@example.com')).to.equal(true) + expect(isGuestbookCollectFormEmailValid('not-an-email')).to.equal(false) + }) + + it('renders license details including the license icon', () => { + cy.customMount( + + ) + + cy.findByText('License/Data Use Agreement').should('exist') + cy.findByRole('img', { name: `License image for ${guestbookCollectFormLicense.name}` }) + .should('have.attr', 'src', guestbookCollectFormLicense.iconUri) + .and('have.attr', 'title', guestbookCollectFormLicense.name) + cy.findByRole('link', { name: guestbookCollectFormLicense.name }).should( + 'have.attr', + 'href', + guestbookCollectFormLicense.uri + ) + cy.findByText('Custom terms for this dataset').should('exist') + }) + + it('renders account fields, locks identity fields, and reports account validation errors', () => { + const onFieldChange = cy.stub().as('onFieldChange') + + cy.customMount( + + ) + + cy.findByLabelText(/^Name/).should('be.disabled') + cy.findByLabelText(/^Email/).should('be.disabled') + cy.findByLabelText(/^Institution/) + .should('not.be.disabled') + .type('N') + + cy.findByText('Name is required.').should('exist') + cy.findByText('Institution is invalid.').should('exist') + cy.get('@onFieldChange').should('have.been.calledWith', 'institution', 'N') + }) + + it('renders visible custom question types and reports custom question validation errors', () => { + const onFieldChange = cy.stub().as('onFieldChange') + + cy.customMount( + + ) + + cy.findByText('Additional Questions').should('exist') + cy.findByText('Hidden question').should('not.exist') + + cy.findByText('Research purpose') + .parents('div') + .first() + .within(() => { + cy.findByRole('textbox').type('R') + cy.findByText('Research purpose is required.').should('exist') + }) + + cy.findByText('Preferred format') + .parents('div') + .first() + .within(() => { + cy.findByRole('button', { name: 'CSV' }).click() + }) + cy.findByRole('button', { name: 'JSON' }).click() + cy.findByText('Preferred format is required.').should('exist') + + cy.findByText('Additional context') + .parents('div') + .first() + .within(() => { + cy.get('textarea').type('M') + }) + + cy.get('@onFieldChange').should('have.been.calledWith', 'custom-question-1-0', 'R') + cy.get('@onFieldChange').should('have.been.calledWith', 'custom-question-2-1', 'JSON') + cy.get('@onFieldChange').should('have.been.calledWith', 'custom-question-3-2', 'M') + }) + + it('does not render the additional questions section when all custom questions are hidden', () => { + cy.customMount( + ({ + ...question, + hidden: true + })) + }} + /> + ) + + cy.findByText('Additional Questions').should('not.exist') + cy.findByText('Hidden question').should('not.exist') + }) + }) }) diff --git a/tests/component/sections/dataset/dataset-reviews/useGetDatasetReviews.spec.tsx b/tests/component/sections/dataset/dataset-reviews/useGetDatasetReviews.spec.tsx new file mode 100644 index 000000000..c6a82278a --- /dev/null +++ b/tests/component/sections/dataset/dataset-reviews/useGetDatasetReviews.spec.tsx @@ -0,0 +1,107 @@ +import { renderHook, waitFor } from '@testing-library/react' +import { DatasetReview } from '@/dataset/domain/models/DatasetReview' +import { DatasetRepository } from '@/dataset/domain/repositories/DatasetRepository' +import { useGetDatasetReviews } from '@/sections/dataset/dataset-reviews/useGetDatasetReviews' + +const datasetRepository: DatasetRepository = {} as DatasetRepository + +const datasetId = 'doi:10.5072/FK2/ABC123' + +const datasetReviews: DatasetReview[] = [ + { + id: 23, + title: 'Review of Some Title', + authors: ['Reviewer One'], + persistentId: 'doi:10.5072/FK2/REVIEW1', + persistentIdUrl: 'https://doi.org/10.5072/FK2/REVIEW1', + citation: 'Review citation', + citationHtml: 'Review citation', + datePublished: '2026-02-03', + description: 'A review dataset', + rubricMetadataBlocks: [] + } +] + +describe('useGetDatasetReviews', () => { + it('should return dataset reviews correctly', async () => { + datasetRepository.getDatasetReviews = cy.stub().resolves(datasetReviews) + + const { result } = renderHook(() => + useGetDatasetReviews({ + datasetRepository, + datasetId + }) + ) + + expect(result.current.isLoading).to.deep.equal(true) + expect(result.current.error).to.deep.equal(null) + expect(result.current.datasetReviews).to.deep.equal([]) + + await waitFor(() => { + expect(result.current.isLoading).to.deep.equal(false) + expect(result.current.error).to.deep.equal(null) + expect(result.current.datasetReviews).to.deep.equal(datasetReviews) + }) + + cy.wrap(datasetRepository.getDatasetReviews).should('have.been.calledWith', datasetId) + }) + + it('should return the error message when the request fails with an Error', async () => { + datasetRepository.getDatasetReviews = cy.stub().rejects(new Error('Error message')) + + const { result } = renderHook(() => + useGetDatasetReviews({ + datasetRepository, + datasetId + }) + ) + + expect(result.current.isLoading).to.deep.equal(true) + expect(result.current.error).to.deep.equal(null) + expect(result.current.datasetReviews).to.deep.equal([]) + + await waitFor(() => { + expect(result.current.isLoading).to.deep.equal(false) + expect(result.current.error).to.deep.equal('Error message') + expect(result.current.datasetReviews).to.deep.equal([]) + }) + }) + + it('should return the default error message when the request fails with an Error without a message', async () => { + datasetRepository.getDatasetReviews = cy.stub().rejects(new Error('')) + + const { result } = renderHook(() => + useGetDatasetReviews({ + datasetRepository, + datasetId + }) + ) + + await waitFor(() => { + expect(result.current.isLoading).to.deep.equal(false) + expect(result.current.error).to.deep.equal( + 'Something went wrong getting the dataset reviews. Try again later.' + ) + expect(result.current.datasetReviews).to.deep.equal([]) + }) + }) + + it('should return the default error message when the request fails with a non-Error exception', async () => { + datasetRepository.getDatasetReviews = cy.stub().rejects('Unexpected error') + + const { result } = renderHook(() => + useGetDatasetReviews({ + datasetRepository, + datasetId + }) + ) + + await waitFor(() => { + expect(result.current.isLoading).to.deep.equal(false) + expect(result.current.error).to.deep.equal( + 'Something went wrong getting the dataset reviews. Try again later.' + ) + expect(result.current.datasetReviews).to.deep.equal([]) + }) + }) +}) From adf53bd9f2fa45731e070b153807276c3dcde6d0 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Mon, 29 Jun 2026 16:28:51 -0400 Subject: [PATCH 08/15] fix: add tests to improve test coverage --- .../CollectionItemsPanel.spec.tsx | 98 +++++++++++++++++++ .../sections/dataset/Dataset.spec.tsx | 81 +++++++++++++++ ...ownloadWithTermsAndGuestbookModal.spec.tsx | 40 +++++++- .../DatasetGuestbook.spec.tsx | 9 ++ .../EditDatasetTerms.spec.tsx | 7 +- .../sections/session/SessionProvider.spec.tsx | 21 +++- .../templates/DatasetTemplates.spec.tsx | 95 ++++++++++++++++++ .../EditDatasetTerms.spec.tsx | 2 - 8 files changed, 344 insertions(+), 9 deletions(-) diff --git a/tests/component/sections/collection/collection-items-panel/CollectionItemsPanel.spec.tsx b/tests/component/sections/collection/collection-items-panel/CollectionItemsPanel.spec.tsx index a5f57b9f1..a2ed043e0 100644 --- a/tests/component/sections/collection/collection-items-panel/CollectionItemsPanel.spec.tsx +++ b/tests/component/sections/collection/collection-items-panel/CollectionItemsPanel.spec.tsx @@ -5,6 +5,8 @@ import { CollectionRepository } from '@/collection/domain/repositories/Collectio import { CollectionItemsMother } from '@tests/component/collection/domain/models/CollectionItemsMother' import { CollectionItemType } from '@/collection/domain/models/CollectionItemType' import { WithRepositories } from '@tests/component/WithRepositories' +import { useLocation } from 'react-router-dom' +import { FilterQuery } from '@/collection/domain/models/CollectionSearchCriteria' const ROOT_COLLECTION_ALIAS = 'root' const collectionRepository: CollectionRepository = {} as CollectionRepository @@ -39,6 +41,12 @@ function CollectionItemsPanel({ ) } +function LocationDisplay() { + const location = useLocation() + + return
{`${location.pathname}${location.search}`}
+} + describe('CollectionItemsPanel', () => { beforeEach(() => { cy.viewport(1280, 720) @@ -494,6 +502,96 @@ describe('CollectionItemsPanel', () => { .click() }) + it('adds a facet filter to the URL', () => { + cy.customMount( + <> + + + + ) + + cy.findByRole('button', { name: /Add Department facet filter/ }).click() + cy.findByTestId('location-display').should('contain', 'fqs=dvCategory%3ADepartment') + }) + + it('removes the last selected facet from the URL', () => { + cy.customMount( + <> + + + , + ['/collections/root?fqs=dvCategory%3ADepartment'] + ) + + cy.findAllByRole('button', { name: /Remove Department facet filter/ }) + .first() + .click() + cy.findByTestId('location-display').should('not.contain', 'fqs=') + }) + + it('ignores invalid filter queries when building advanced search links', () => { + cy.customMount( + + ) + + cy.findByRole('link', { name: 'Advanced Search' }) + .should('have.attr', 'href') + .and('include', '/collections/root/search?fqs=dvCategory%3ADepartment') + .and('not.include', 'invalid-filter-query') + }) + + it('removes invalid selected facets without keeping empty filter query params', () => { + cy.customMount( + <> + + + + ) + + cy.findByRole('button', { name: /Remove Unknown facet filter/ }).click() + cy.findByTestId('location-display').should('not.contain', 'fqs=') + }) + it('it calls the loadItemsOnBackAndForwardNavigation on pop state event when navigating back and forward', () => { cy.customMount( {} const fileRepository: FileRepository = {} as FileRepository @@ -153,6 +156,12 @@ const versionSummaryInfo: DatasetVersionSummaryInfo[] = [ const testDatasetMetadataExportFormats = DatasetMetadataExportFormatsMother.create() const termsTabLabelRegex = /^Terms(?: and Guestbook)?$/ +function LocationDisplay() { + const location = useLocation() + + return
{`${location.pathname}${location.search}`}
+} + describe('Dataset', () => { const mountWithDataset = ( component: ReactNode, @@ -378,6 +387,34 @@ describe('Dataset', () => { cy.findAllByRole('tab', { name: 'Versions' }).should('have.length', 1) }) + it('redirects to the dataset page when publish completes', () => { + const publishedDataset = DatasetMother.create({ + persistentId: 'doi:10.5072/FK2/PUBLISHDONE' + }) + + cy.clock() + mountWithDataset( + <> + + + , + publishedDataset + ) + + cy.findByText('Publish in Progress').should('exist') + cy.tick(2_000) + cy.findByTestId('location-display').should( + 'have.text', + '/datasets?persistentId=doi:10.5072/FK2/PUBLISHDONE' + ) + }) + it('renders the breadcrumbs', () => { mountWithDataset( { cy.findByTestId('dataset-guestbook-section').should('exist') }) + it('opens the terms tab from the custom terms summary link', () => { + const customTermsDataset = DatasetMother.create({ + license: undefined, + termsOfUse: TermsOfUseMother.create({ + customTerms: { + termsOfUse: 'Custom terms summary text', + confidentialityDeclaration: '', + specialPermissions: '', + restrictions: '', + citationRequirements: '', + depositorRequirements: '', + conditions: '', + disclaimer: '' + } + }) + }) + + mountWithDataset( + <> + + + , + customTermsDataset + ) + + cy.findByRole('button', { name: 'Custom Dataset Terms' }).click() + + cy.findByTestId('location-display').should('contain', '?tab=terms') + }) + it('renders the read-only terms tab title when user cannot edit dataset', () => { const readOnlyDataset = DatasetMother.create({ permissions: DatasetPermissionsMother.createWithUpdateDatasetNotAllowed() @@ -708,4 +780,13 @@ describe('Dataset', () => { cy.findByText('Contributors').should('exist') cy.findByText('Published On').should('exist') }) + + it('renders the dataset tabs skeleton', () => { + cy.customMount() + + cy.findByRole('tab', { name: 'Files' }).should('exist') + cy.findByRole('tab', { name: 'Metadata' }).should('exist') + cy.findByRole('tab', { name: 'Terms' }).should('exist') + cy.findByRole('tab', { name: 'Versions' }).should('exist') + }) }) diff --git a/tests/component/sections/dataset/dataset-files/guestbook/DownloadWithTermsAndGuestbookModal.spec.tsx b/tests/component/sections/dataset/dataset-files/guestbook/DownloadWithTermsAndGuestbookModal.spec.tsx index bc17f2e15..e0fb93920 100644 --- a/tests/component/sections/dataset/dataset-files/guestbook/DownloadWithTermsAndGuestbookModal.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/guestbook/DownloadWithTermsAndGuestbookModal.spec.tsx @@ -561,6 +561,44 @@ describe('DownloadWithTermsAndGuestbookModal', () => { cy.findByLabelText(/^Email/).should('be.disabled') }) + it('prefills and locks all account fields for authenticated users', () => { + getGuestbookImpl = () => + Promise.resolve({ + ...guestbook, + institutionRequired: true, + positionRequired: true + }) + + cy.mountAuthenticated( + withRepositories( + + ), + undefined, + { + displayName: 'Authenticated User', + email: 'authenticated.user@example.com', + affiliation: 'Example Institute', + position: 'Data Curator' + } + ) + + cy.findByLabelText(/^Name/).should('be.disabled').and('have.value', 'Authenticated User') + cy.findByLabelText(/^Email/) + .should('be.disabled') + .and('have.value', 'authenticated.user@example.com') + cy.findByLabelText(/^Institution/) + .should('not.be.disabled') + .and('have.value', 'Example Institute') + cy.findByLabelText(/^Position/) + .should('not.be.disabled') + .and('have.value', 'Data Curator') + }) + it('submits filled form and accepts for multiple files', () => { const handleClose = cy.stub().as('handleClose') @@ -948,7 +986,7 @@ describe('DownloadWithTermsAndGuestbookModal', () => { cy.findByText(/Something went wrong downloading the file. Try again later./i).should('exist') }) - describe.only('GuestbookCollectForm', () => { + describe('GuestbookCollectForm', () => { it('builds custom question field names and validates email addresses', () => { expect( getGuestbookCustomQuestionFieldName(guestbookCollectFormCustomQuestions[1], 0) diff --git a/tests/component/sections/dataset/dataset-guestbook/DatasetGuestbook.spec.tsx b/tests/component/sections/dataset/dataset-guestbook/DatasetGuestbook.spec.tsx index 42df75645..a0f618542 100644 --- a/tests/component/sections/dataset/dataset-guestbook/DatasetGuestbook.spec.tsx +++ b/tests/component/sections/dataset/dataset-guestbook/DatasetGuestbook.spec.tsx @@ -79,6 +79,13 @@ describe('DatasetGuestbook', () => { displayOrder: 1, type: 'text', hidden: false + }, + { + question: 'Would you like follow-up contact?', + required: false, + displayOrder: 2, + type: 'text', + hidden: false } ] }) @@ -89,6 +96,8 @@ describe('DatasetGuestbook', () => { cy.findByRole('button', { name: 'Preview Guestbook' }).click() cy.findByRole('dialog').should('be.visible') cy.findByRole('dialog').within(() => { + cy.findByText('How will you use this data? (Required)').should('exist') + cy.findByText('Would you like follow-up contact? (Optional)').should('exist') cy.findAllByRole('button', { name: 'Close' }).last().click() }) cy.findByRole('dialog').should('not.exist') diff --git a/tests/component/sections/edit-dataset-terms/EditDatasetTerms.spec.tsx b/tests/component/sections/edit-dataset-terms/EditDatasetTerms.spec.tsx index 0caf998fd..4bcab793d 100644 --- a/tests/component/sections/edit-dataset-terms/EditDatasetTerms.spec.tsx +++ b/tests/component/sections/edit-dataset-terms/EditDatasetTerms.spec.tsx @@ -151,7 +151,7 @@ describe('EditDatasetTerms', () => { ) @@ -343,7 +343,6 @@ describe('EditDatasetTerms', () => { , dataset @@ -367,7 +366,7 @@ describe('EditDatasetTerms', () => { // Force an invalid key to hit the default branch in getCurrentFormDirtyState defaultActiveTabKey={'unknown-tab' as unknown as EditDatasetTermsTabKey} licenseRepository={licenseRepository} - datasetRepository={datasetRepository} + guestbookRepository={guestbookRepository} />, dataset ) @@ -389,7 +388,7 @@ describe('EditDatasetTerms', () => { , dataset ) diff --git a/tests/component/sections/session/SessionProvider.spec.tsx b/tests/component/sections/session/SessionProvider.spec.tsx index cce06c8e7..029d14923 100644 --- a/tests/component/sections/session/SessionProvider.spec.tsx +++ b/tests/component/sections/session/SessionProvider.spec.tsx @@ -1,4 +1,4 @@ -import { Route, Routes } from 'react-router-dom' +import { Route, Routes, useLocation } from 'react-router-dom' import { AuthContext } from 'react-oauth2-code-pkce' import { ReadError } from '@iqss/dataverse-client-javascript' import { UserRepository } from '@/users/domain/repositories/UserRepository' @@ -34,6 +34,12 @@ describe('SessionProvider', () => { ) } + const LocationDisplay = () => { + const location = useLocation() + + return

{`${location.pathname}${location.search}`}

+ } + const renderComponent = ({ loginInProgress, withTokenPresent @@ -57,7 +63,16 @@ describe('SessionProvider', () => { }> } /> - Sign up} /> + + + +
Sign up
+ + } + />
@@ -166,6 +181,8 @@ describe('SessionProvider', () => { }) cy.findByText('Sign up').should('exist') + cy.findByText(BEARER_TOKEN_IS_VALID_BUT_NOT_LINKED_MESSAGE).should('exist') + cy.findByTestId('location').should('have.text', '/sign-up?validTokenButNotLinkedAccount=true') }) it('should detect any other ReadError instances', () => { diff --git a/tests/component/sections/templates/DatasetTemplates.spec.tsx b/tests/component/sections/templates/DatasetTemplates.spec.tsx index 47b9befbd..5d6e85096 100644 --- a/tests/component/sections/templates/DatasetTemplates.spec.tsx +++ b/tests/component/sections/templates/DatasetTemplates.spec.tsx @@ -9,6 +9,8 @@ import { NotImplementedModalProvider } from '../../../../src/sections/not-implem import { MetadataBlockInfoMother } from '../../metadata-block-info/domain/models/MetadataBlockInfoMother' import { CitationMetadataBlockInfoMother } from '../../metadata-block-info/domain/models/CitationMetadataBlockInfoMother' import { UpwardHierarchyNodeMother } from '../../shared/hierarchy/domain/models/UpwardHierarchyNodeMother' +import { useLocation } from 'react-router-dom' +import { Template } from '@/templates/domain/models/Template' const collectionRepository: CollectionRepository = {} as CollectionRepository const templateRepository: TemplateRepository = {} as TemplateRepository @@ -34,6 +36,13 @@ const template = TemplateMother.create({ name: 'Template', isDefault: false }) + +function LocationDisplay() { + const location = useLocation() + + return
{`${location.pathname}${location.search}`}
+} + describe('Dataset Templates', () => { beforeEach(() => { collectionRepository.getById = cy.stub().resolves(collection) @@ -61,6 +70,19 @@ describe('Dataset Templates', () => { ) + const mountDatasetTemplatesWithLocation = () => + cy.customMount( + + + + + ) + it('shows not found when the collection does not exist', () => { collectionRepository.getById = cy.stub().resolves(null) @@ -117,6 +139,14 @@ describe('Dataset Templates', () => { }) }) + it('navigates to the create template page', () => { + mountDatasetTemplatesWithLocation() + + cy.findByRole('button', { name: 'Create Dataset Template' }).click() + + cy.findByTestId('location-display').should('have.text', '/create') + }) + it('shows Default as disabled and hides Make Default for the default template', () => { const [templateDefault, templateOther] = TemplateMother.createTemplates([ { id: 1, name: 'Template Default', isDefault: true }, @@ -240,6 +270,9 @@ describe('Dataset Templates', () => { cy.findByRole('button', { name: 'Template Name' }).click() getTableNames().should('deep.equal', ['Gamma', 'Beta', 'Alpha']) + + cy.findByRole('button', { name: 'Template Name' }).click() + getTableNames().should('deep.equal', ['Alpha', 'Beta', 'Gamma']) }) it('resets sorting direction when selecting a different column', () => { @@ -569,6 +602,68 @@ describe('Dataset Templates', () => { cy.wrap(templateRepository.createTemplate).should('have.been.calledOnce') cy.wrap(templateRepository.getTemplatesByCollectionId).should('have.been.calledOnce') }) + + it('copies a template without metadata blocks', () => { + const templateWithoutMetadataBlocks = { + ...TemplateMother.create({ + id: 12, + name: 'Template Without Metadata Blocks', + collectionAlias: 'root', + isDefault: false + }), + datasetMetadataBlocks: undefined + } as unknown as Template + + templateRepository.getTemplatesByCollectionId = cy + .stub() + .resolves([templateWithoutMetadataBlocks]) + templateRepository.getTemplate = cy.stub().resolves(templateWithoutMetadataBlocks) + templateRepository.createTemplate = cy.stub().resolves() + metadataBlockInfoRepository.getByCollectionId = cy.stub().resolves([ + { + ...CitationMetadataBlockInfoMother.get(), + metadataFields: undefined + } + ]) + + mountDatasetTemplates() + + cy.findByRole('button', { name: 'Copy' }).click({ force: true }) + + cy.findByText('Template copied.').should('exist') + cy.wrap(templateRepository.createTemplate).should( + 'have.been.calledWith', + { + name: 'copy Template Without Metadata Blocks', + isDefault: false, + fields: [], + instructions: templateWithoutMetadataBlocks.instructions + }, + 'root' + ) + }) + + it('shows the generic copy error when copying fails with an unknown error', () => { + const templateWithMetadata = TemplateMother.create({ + id: 13, + name: 'Template Copy Unknown Error', + collectionAlias: 'root' + }) + + templateRepository.getTemplatesByCollectionId = cy.stub().resolves([templateWithMetadata]) + templateRepository.getTemplate = cy.stub().resolves(templateWithMetadata) + templateRepository.createTemplate = cy.stub().rejects('unknown failure') + metadataBlockInfoRepository.getByCollectionId = cy + .stub() + .resolves([CitationMetadataBlockInfoMother.get()]) + + mountDatasetTemplates() + + cy.findByRole('button', { name: 'Copy' }).click({ force: true }) + + cy.findByText(/Something went wrong copying the template. Try again later./i).should('exist') + cy.wrap(templateRepository.getTemplatesByCollectionId).should('have.been.calledOnce') + }) }) describe('Preview Template', () => { diff --git a/tests/e2e-integration/e2e/sections/edit-dataset-terms/EditDatasetTerms.spec.tsx b/tests/e2e-integration/e2e/sections/edit-dataset-terms/EditDatasetTerms.spec.tsx index f30c0f89a..ab1db77b0 100644 --- a/tests/e2e-integration/e2e/sections/edit-dataset-terms/EditDatasetTerms.spec.tsx +++ b/tests/e2e-integration/e2e/sections/edit-dataset-terms/EditDatasetTerms.spec.tsx @@ -117,8 +117,6 @@ describe('Edit Dataset Terms', () => { cy.findByLabelText('Size of Collection').type('500 MB') cy.findByRole('button', { name: 'Save Changes' }).click() - cy.findByRole('button', { name: 'Cancel' }).click() - cy.findByRole('tab', { name: /^Terms(?: and Guestbook)?$/ }).click() cy.findByText(/Data can be accessed through the university data center/i).should('exist') cy.findByText(/dataowner@university.edu/i).should('exist') From 776f24ddb7455afed9a737fc307681554fcdc9fe Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Wed, 8 Jul 2026 13:22:23 -0400 Subject: [PATCH 09/15] redirect to draft version of dataset after editing terms --- .../EditDatasetTermsHelper.ts | 25 +++ .../edit-guestbook/EditGuestbook.tsx | 33 ++-- .../EditLicenseAndTerms.tsx | 31 ++-- .../EditTermsOfAccess.tsx | 36 ++-- .../EditDatasetTerms.spec.tsx | 172 ++++++++++++++++-- 5 files changed, 227 insertions(+), 70 deletions(-) diff --git a/src/sections/edit-dataset-terms/EditDatasetTermsHelper.ts b/src/sections/edit-dataset-terms/EditDatasetTermsHelper.ts index 8b80e2be2..9a20e41b0 100644 --- a/src/sections/edit-dataset-terms/EditDatasetTermsHelper.ts +++ b/src/sections/edit-dataset-terms/EditDatasetTermsHelper.ts @@ -1,3 +1,10 @@ +import { + DatasetNonNumericVersionSearchParam, + DatasetPublishingStatus +} from '@/dataset/domain/models/Dataset' +import type { Dataset } from '@/dataset/domain/models/Dataset' +import { QueryParamKey, Route } from '@/sections/Route.enum' + export class EditDatasetTermsHelper { static EDIT_DATASET_TERMS_TABS_KEYS = { datasetTerms: 'datasetTerms', @@ -16,7 +23,25 @@ export class EditDatasetTermsHelper { ] ?? this.EDIT_DATASET_TERMS_TABS_KEYS.datasetTerms ) } + + public static buildDatasetPageUrl( + dataset: Dataset, + version: EditDatasetTermsDatasetPageVersion = 'current' + ): string { + const searchParams = new URLSearchParams() + searchParams.set(QueryParamKey.PERSISTENT_ID, dataset.persistentId) + + if (version === 'draft' || dataset.version.publishingStatus === DatasetPublishingStatus.DRAFT) { + searchParams.set(QueryParamKey.VERSION, DatasetNonNumericVersionSearchParam.DRAFT) + } else { + searchParams.set(QueryParamKey.VERSION, dataset.version.number.toString()) + } + + return `${Route.DATASETS}?${searchParams.toString()}` + } } export type EditDatasetTermsTabKey = (typeof EditDatasetTermsHelper.EDIT_DATASET_TERMS_TABS_KEYS)[keyof typeof EditDatasetTermsHelper.EDIT_DATASET_TERMS_TABS_KEYS] + +export type EditDatasetTermsDatasetPageVersion = 'current' | 'draft' diff --git a/src/sections/edit-dataset-terms/edit-guestbook/EditGuestbook.tsx b/src/sections/edit-dataset-terms/edit-guestbook/EditGuestbook.tsx index d91e7185b..ec47ec515 100644 --- a/src/sections/edit-dataset-terms/edit-guestbook/EditGuestbook.tsx +++ b/src/sections/edit-dataset-terms/edit-guestbook/EditGuestbook.tsx @@ -5,16 +5,15 @@ import { useNavigate } from 'react-router-dom' import { toast } from 'react-toastify' import { Guestbook } from '@/guestbooks/domain/models/Guestbook' import { GuestbookRepository } from '@/guestbooks/domain/repositories/GuestbookRepository' -import { - DatasetNonNumericVersionSearchParam, - DatasetPublishingStatus -} from '@/dataset/domain/models/Dataset' import { useGetGuestbooksByCollectionId } from '@/sections/guestbooks/useGetGuestbooksByCollectionId' -import { QueryParamKey, Route } from '@/sections/Route.enum' import { useAssignDatasetGuestbook } from './useAssignDatasetGuestbook' import { useRemoveDatasetGuestbook } from './useRemoveDatasetGuestbook' import { useDataset } from '../../dataset/DatasetContext' import { PreviewGuestbookModal } from '@/sections/guestbooks/preview-modal/PreviewGuestbookModal' +import { + EditDatasetTermsDatasetPageVersion, + EditDatasetTermsHelper +} from '../EditDatasetTermsHelper' import styles from './EditGuestbook.module.scss' interface EditGuestbookProps { @@ -37,20 +36,14 @@ export function EditGuestbook({ const collectionIdOrAlias = dataset?.parentCollectionNode?.id const collectionName = dataset?.parentCollectionNode?.name ?? '' - const navigateToDatasetView = useCallback(() => { - if (!dataset) return - - const searchParams = new URLSearchParams() - searchParams.set(QueryParamKey.PERSISTENT_ID, dataset.persistentId) + const navigateToDatasetView = useCallback( + (version?: EditDatasetTermsDatasetPageVersion) => { + if (!dataset) return - if (dataset.version.publishingStatus === DatasetPublishingStatus.DRAFT) { - searchParams.set(QueryParamKey.VERSION, DatasetNonNumericVersionSearchParam.DRAFT) - } else { - searchParams.set(QueryParamKey.VERSION, dataset.version.number.toString()) - } - - navigate(`${Route.DATASETS}?${searchParams.toString()}`) - }, [dataset, navigate]) + navigate(EditDatasetTermsHelper.buildDatasetPageUrl(dataset, version)) + }, + [dataset, navigate] + ) const handleCancel = () => { navigateToDatasetView() @@ -70,7 +63,7 @@ export function EditGuestbook({ onSuccessfulAssignDatasetGuestbook: () => { toast.success(t('alerts.termsUpdated.alertText')) refreshDataset() - navigateToDatasetView() + navigateToDatasetView('draft') } }) const { @@ -82,7 +75,7 @@ export function EditGuestbook({ onSuccessfulRemoveDatasetGuestbook: () => { toast.success(t('alerts.termsUpdated.alertText')) refreshDataset() - navigateToDatasetView() + navigateToDatasetView('draft') } }) diff --git a/src/sections/edit-dataset-terms/edit-license-and-terms/EditLicenseAndTerms.tsx b/src/sections/edit-dataset-terms/edit-license-and-terms/EditLicenseAndTerms.tsx index ef5b96f84..026211269 100644 --- a/src/sections/edit-dataset-terms/edit-license-and-terms/EditLicenseAndTerms.tsx +++ b/src/sections/edit-dataset-terms/edit-license-and-terms/EditLicenseAndTerms.tsx @@ -9,12 +9,11 @@ import { LicenseRepository } from '../../../licenses/domain/repositories/License import { useGetLicenses } from './useGetLicenses' import { useDataset } from '../../dataset/DatasetContext' import { useUpdateDatasetLicense } from './useUpdateDatasetLicense' -import { Route, QueryParamKey } from '../../Route.enum' -import { - DatasetNonNumericVersionSearchParam, - DatasetPublishingStatus -} from '../../../dataset/domain/models/Dataset' import { useDatasetRepositories } from '@/shared/contexts/repositories/RepositoriesProvider' +import { + EditDatasetTermsDatasetPageVersion, + EditDatasetTermsHelper +} from '../EditDatasetTermsHelper' import styles from './EditLicenseAndTerms.module.scss' const CUSTOM_LICENSE_VALUE = 'CUSTOM' as const @@ -46,27 +45,21 @@ export function EditLicenseAndTerms({ autoFetch: true }) - const navigateToDatasetView = useCallback(() => { - if (!dataset) return + const navigateToDatasetView = useCallback( + (version?: EditDatasetTermsDatasetPageVersion) => { + if (!dataset) return - const searchParams = new URLSearchParams() - searchParams.set(QueryParamKey.PERSISTENT_ID, dataset.persistentId) - - if (dataset.version.publishingStatus === DatasetPublishingStatus.DRAFT) { - searchParams.set(QueryParamKey.VERSION, DatasetNonNumericVersionSearchParam.DRAFT) - } else { - searchParams.set(QueryParamKey.VERSION, dataset.version.number.toString()) - } - - navigate(`${Route.DATASETS}?${searchParams.toString()}`) - }, [dataset, navigate]) + navigate(EditDatasetTermsHelper.buildDatasetPageUrl(dataset, version)) + }, + [dataset, navigate] + ) const { handleUpdateLicense, isLoading, error } = useUpdateDatasetLicense({ datasetRepository, onSuccessfulUpdateLicense: () => { toast.success(t('alerts.licenseUpdated.alertText')) refreshDataset() - navigateToDatasetView() + navigateToDatasetView('draft') } }) diff --git a/src/sections/edit-dataset-terms/edit-terms-of-access/EditTermsOfAccess.tsx b/src/sections/edit-dataset-terms/edit-terms-of-access/EditTermsOfAccess.tsx index bd96bdf24..e03bce82d 100644 --- a/src/sections/edit-dataset-terms/edit-terms-of-access/EditTermsOfAccess.tsx +++ b/src/sections/edit-dataset-terms/edit-terms-of-access/EditTermsOfAccess.tsx @@ -1,19 +1,18 @@ -import { useEffect, useMemo, useRef } from 'react' +import { useCallback, useEffect, useMemo, useRef } from 'react' import { useTranslation } from 'react-i18next' import { useForm, Controller, FormProvider, useWatch } from 'react-hook-form' import { toast } from 'react-toastify' import { Form, Row, Col, Button, Alert } from '@iqss/dataverse-design-system' import styles from '../edit-license-and-terms/EditLicenseAndTerms.module.scss' -import { - DatasetNonNumericVersionSearchParam, - DatasetPublishingStatus, - TermsOfAccess -} from '@/dataset/domain/models/Dataset' +import { TermsOfAccess } from '@/dataset/domain/models/Dataset' import { useDataset } from '../../dataset/DatasetContext' import { useUpdateTermsOfAccess } from './useUpdateTermsOfAccess' -import { QueryParamKey, Route } from '@/sections/Route.enum' import { useNavigate } from 'react-router-dom' import { useDatasetRepositories } from '@/shared/contexts/repositories/RepositoriesProvider' +import { + EditDatasetTermsDatasetPageVersion, + EditDatasetTermsHelper +} from '../EditDatasetTermsHelper' interface EditTermsOfAccessProps { onFormStateChange?: (isDirty: boolean) => void @@ -41,11 +40,21 @@ export function EditTermsOfAccess({ onFormStateChange }: EditTermsOfAccessProps) (dataset?.termsOfUse.termsOfAccess as TermsOfAccess) ?? defaultTermsOfAccess const formContainerRef = useRef(null) + const navigateToDatasetView = useCallback( + (version?: EditDatasetTermsDatasetPageVersion) => { + if (!dataset) return + + navigate(EditDatasetTermsHelper.buildDatasetPageUrl(dataset, version)) + }, + [dataset, navigate] + ) + const { handleUpdateTermsOfAccess, isLoading, error } = useUpdateTermsOfAccess({ datasetRepository, onSuccessfulUpdateTermsOfAccess: () => { toast.success(t('alerts.termsUpdated.alertText')) refreshDataset() + navigateToDatasetView('draft') } }) @@ -117,18 +126,7 @@ export function EditTermsOfAccess({ onFormStateChange }: EditTermsOfAccessProps) }, [initialTermsOfAccess, isRequestAccessEnabled, t]) const handleCancel = () => { - if (!dataset) return - - const searchParams = new URLSearchParams() - searchParams.set(QueryParamKey.PERSISTENT_ID, dataset.persistentId) - - if (dataset.version.publishingStatus === DatasetPublishingStatus.DRAFT) { - searchParams.set(QueryParamKey.VERSION, DatasetNonNumericVersionSearchParam.DRAFT) - } else { - searchParams.set(QueryParamKey.VERSION, dataset.version.number.toString()) - } - - navigate(`${Route.DATASETS}?${searchParams.toString()}`) + navigateToDatasetView() } return ( diff --git a/tests/component/sections/edit-dataset-terms/EditDatasetTerms.spec.tsx b/tests/component/sections/edit-dataset-terms/EditDatasetTerms.spec.tsx index 0caf998fd..41ab21e09 100644 --- a/tests/component/sections/edit-dataset-terms/EditDatasetTerms.spec.tsx +++ b/tests/component/sections/edit-dataset-terms/EditDatasetTerms.spec.tsx @@ -1,4 +1,5 @@ import { ReactNode } from 'react' +import { useLocation } from 'react-router-dom' import { EditDatasetTerms } from '@/sections/edit-dataset-terms/EditDatasetTerms' import { DatasetProvider } from '@/sections/dataset/DatasetProvider' import { LicenseRepository } from '@/licenses/domain/repositories/LicenseRepository' @@ -7,7 +8,10 @@ import { EditDatasetTermsHelper, EditDatasetTermsTabKey } from '@/sections/edit-dataset-terms/EditDatasetTermsHelper' -import { DatasetMother } from '@tests/component/dataset/domain/models/DatasetMother' +import { + DatasetMother, + DatasetVersionMother +} from '@tests/component/dataset/domain/models/DatasetMother' import { TermsOfUseMother, TermsOfAccessMother @@ -88,8 +92,14 @@ const mockGuestbooks: Guestbook[] = [ } ] +const LocationDisplay = () => { + const location = useLocation() + + return
{`${location.pathname}${location.search}`}
+} + describe('EditDatasetTerms', () => { - const withProviders = (component: ReactNode, dataset: Dataset) => { + const withProviders = (component: ReactNode, dataset: Dataset | undefined) => { datasetRepository.getByPersistentId = cy.stub().resolves(dataset) datasetRepository.getByPrivateUrlToken = cy.stub().resolves(dataset) return ( @@ -138,22 +148,54 @@ describe('EditDatasetTerms', () => { EditDatasetTermsHelper.EDIT_DATASET_TERMS_TABS_KEYS.datasetTerms ) }) + + it('builds a dataset page URL for the current dataset version', () => { + const dataset = DatasetMother.create({ + persistentId: 'some-persistent-id', + version: DatasetVersionMother.createReleased() + }) + + expect(EditDatasetTermsHelper.buildDatasetPageUrl(dataset)).to.equal( + '/datasets?persistentId=some-persistent-id&version=1.0' + ) + }) + + it('builds a dataset page URL for the draft dataset version', () => { + const dataset = DatasetMother.create({ + persistentId: 'some-persistent-id', + version: DatasetVersionMother.createReleased() + }) + + expect(EditDatasetTermsHelper.buildDatasetPageUrl(dataset, 'draft')).to.equal( + '/datasets?persistentId=some-persistent-id&version=DRAFT' + ) + }) + + it('keeps draft datasets on the draft version when building current dataset page URLs', () => { + const dataset = DatasetMother.create({ + persistentId: 'some-persistent-id', + version: DatasetVersionMother.createDraftAsLatestVersion() + }) + + expect(EditDatasetTermsHelper.buildDatasetPageUrl(dataset)).to.equal( + '/datasets?persistentId=some-persistent-id&version=DRAFT' + ) + }) }) it('renders NotFoundPage when dataset is missing', () => { datasetRepository.getByPersistentId = cy.stub().resolves(undefined) datasetRepository.getByPrivateUrlToken = cy.stub().resolves(undefined) - + const dataset = DatasetMother.createEmpty() cy.customMount( - + withProviders( - + guestbookRepository={guestbookRepository} + />, + dataset + ) ) cy.findByTestId('not-found-page').should('exist') @@ -343,7 +385,6 @@ describe('EditDatasetTerms', () => { , dataset @@ -367,7 +408,7 @@ describe('EditDatasetTerms', () => { // Force an invalid key to hit the default branch in getCurrentFormDirtyState defaultActiveTabKey={'unknown-tab' as unknown as EditDatasetTermsTabKey} licenseRepository={licenseRepository} - datasetRepository={datasetRepository} + guestbookRepository={guestbookRepository} />, dataset ) @@ -389,7 +430,7 @@ describe('EditDatasetTerms', () => { , dataset ) @@ -444,6 +485,39 @@ describe('EditDatasetTerms', () => { cy.findByTestId('customTerms.termsOfUse').should('exist') cy.findByRole('button', { name: 'Save Changes' }).should('exist') }) + + it('navigates to the draft dataset version after saving license edits', () => { + const dataset = DatasetMother.create({ + persistentId: 'some-persistent-id', + version: DatasetVersionMother.createReleased(), + license: mockLicenses[0], + termsOfUse: TermsOfUseMother.withoutCustomTerms() + }) + datasetRepository.updateDatasetLicense = cy.stub().resolves() + + cy.customMount( + withProviders( + <> + + + , + dataset + ) + ) + + cy.get('select').select('CC BY 4.0') + cy.findByRole('button', { name: 'Save Changes' }).click() + + cy.wrap(datasetRepository.updateDatasetLicense).should('have.been.called') + cy.findByTestId('current-location').should( + 'have.text', + '/datasets?persistentId=some-persistent-id&version=DRAFT' + ) + }) }) describe('Restricted Files Tab Integration', () => { @@ -523,6 +597,48 @@ describe('EditDatasetTerms', () => { cy.findByRole('button', { name: 'Save Changes' }).should('be.enabled') }) + + it('navigates to the draft dataset version after saving restricted file terms edits', () => { + const dataset = DatasetMother.create({ + persistentId: 'some-persistent-id', + version: DatasetVersionMother.createReleased(), + license: mockLicenses[0], + termsOfUse: TermsOfUseMother.withoutCustomTerms({ + termsOfAccess: TermsOfAccessMother.create({ + fileAccessRequest: true, + termsOfAccessForRestrictedFiles: 'Existing access terms' + }) + }) + }) + datasetRepository.updateTermsOfAccess = cy.stub().resolves() + + cy.customMount( + withProviders( + <> + + + , + dataset + ) + ) + + cy.findByLabelText(/Terms of Access for Restricted Files/i) + .clear() + .type('Updated access terms') + cy.findByRole('button', { name: 'Save Changes' }).click() + + cy.wrap(datasetRepository.updateTermsOfAccess).should('have.been.called') + cy.findByTestId('current-location').should( + 'have.text', + '/datasets?persistentId=some-persistent-id&version=DRAFT' + ) + }) }) describe('Guestbook Tab Integration', () => { @@ -600,6 +716,38 @@ describe('EditDatasetTerms', () => { cy.findByText(/Email/).should('exist') }) }) + + it('navigates to the draft dataset version after saving guestbook edits', () => { + const dataset = DatasetMother.create({ + persistentId: 'some-persistent-id', + version: DatasetVersionMother.createReleased(), + license: mockLicenses[0], + guestbookId: mockGuestbooks[0].id + }) + + cy.customMount( + withProviders( + <> + + + , + dataset + ) + ) + + cy.findByLabelText('Secondary Guestbook').click() + cy.findByRole('button', { name: 'Save Changes' }).click() + + cy.wrap(guestbookRepository.assignDatasetGuestbook).should('have.been.called') + cy.findByTestId('current-location').should( + 'have.text', + '/datasets?persistentId=some-persistent-id&version=DRAFT' + ) + }) }) describe('Tab State Guard Branches', () => { From 7af09ec40a7df1c90fb2c16f83e3e37970393c7e Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Wed, 8 Jul 2026 16:30:40 -0400 Subject: [PATCH 10/15] fix component test --- .../sections/edit-dataset-terms/EditGuestbook.spec.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/component/sections/edit-dataset-terms/EditGuestbook.spec.tsx b/tests/component/sections/edit-dataset-terms/EditGuestbook.spec.tsx index 3dff96744..49fcbd936 100644 --- a/tests/component/sections/edit-dataset-terms/EditGuestbook.spec.tsx +++ b/tests/component/sections/edit-dataset-terms/EditGuestbook.spec.tsx @@ -331,7 +331,7 @@ describe('EditGuestbook', () => { ) }) - it('navigates with numeric version query param after successful submit for non-draft datasets', () => { + it('navigates with Draft param after successful submit for non-draft datasets', () => { ;(guestbookRepository.assignDatasetGuestbook as Cypress.Agent).resolves( undefined ) @@ -356,7 +356,7 @@ describe('EditGuestbook', () => { cy.findByRole('button', { name: 'Save Changes' }).click() cy.findByTestId('location-display').should( 'have.text', - '/datasets?persistentId=doi%3A10.5072%2FFK2%2FRELEASEDPID&version=1.0' + '/datasets?persistentId=doi%3A10.5072%2FFK2%2FRELEASEDPID&version=DRAFT' ) }) From b6edd2c2c4ff7cbc080e8de9787749810f0ca7ec Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Wed, 8 Jul 2026 16:45:39 -0400 Subject: [PATCH 11/15] update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46e13f875..279bc9970 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ This changelog follows the principles of [Keep a Changelog](https://keepachangel ### Fixed +- Edit Dataset Terms: navigate to the draft version of the dataset after saving changes to the terms, instead of the latest published version. + ### Removed --- From 7dec3329c5295bed84848ca7329c715ba75f049d Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Wed, 8 Jul 2026 17:24:54 -0400 Subject: [PATCH 12/15] increase test coverage --- .../EditDatasetTerms.spec.tsx | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/tests/component/sections/edit-dataset-terms/EditDatasetTerms.spec.tsx b/tests/component/sections/edit-dataset-terms/EditDatasetTerms.spec.tsx index 41ab21e09..da8db641c 100644 --- a/tests/component/sections/edit-dataset-terms/EditDatasetTerms.spec.tsx +++ b/tests/component/sections/edit-dataset-terms/EditDatasetTerms.spec.tsx @@ -518,6 +518,70 @@ describe('EditDatasetTerms', () => { '/datasets?persistentId=some-persistent-id&version=DRAFT' ) }) + + it('navigates to the draft dataset version after saving custom terms edits', () => { + const dataset = DatasetMother.create({ + persistentId: 'some-persistent-id', + version: DatasetVersionMother.createReleased(), + license: mockLicenses[0], + termsOfUse: TermsOfUseMother.withoutCustomTerms() + }) + datasetRepository.updateDatasetLicense = cy.stub().resolves() + + cy.customMount( + withProviders( + <> + + + , + dataset + ) + ) + + cy.get('select').select('Custom Dataset Terms') + cy.findByTestId('customTerms.termsOfUse').type('Updated custom terms') + cy.findByRole('button', { name: 'Save Changes' }).click() + + cy.wrap(datasetRepository.updateDatasetLicense).should('have.been.called') + cy.findByTestId('current-location').should( + 'have.text', + '/datasets?persistentId=some-persistent-id&version=DRAFT' + ) + }) + + it('navigates to the current dataset version after canceling license edits', () => { + const dataset = DatasetMother.create({ + persistentId: 'some-persistent-id', + version: DatasetVersionMother.createReleased(), + license: mockLicenses[0], + termsOfUse: TermsOfUseMother.withoutCustomTerms() + }) + + cy.customMount( + withProviders( + <> + + + , + dataset + ) + ) + + cy.findByRole('button', { name: 'Cancel' }).click() + + cy.findByTestId('current-location').should( + 'have.text', + '/datasets?persistentId=some-persistent-id&version=1.0' + ) + }) }) describe('Restricted Files Tab Integration', () => { @@ -639,6 +703,43 @@ describe('EditDatasetTerms', () => { '/datasets?persistentId=some-persistent-id&version=DRAFT' ) }) + + it('navigates to the current dataset version after canceling restricted file terms edits', () => { + const dataset = DatasetMother.create({ + persistentId: 'some-persistent-id', + version: DatasetVersionMother.createReleased(), + license: mockLicenses[0], + termsOfUse: TermsOfUseMother.withoutCustomTerms({ + termsOfAccess: TermsOfAccessMother.create({ + fileAccessRequest: true, + termsOfAccessForRestrictedFiles: 'Existing access terms' + }) + }) + }) + + cy.customMount( + withProviders( + <> + + + , + dataset + ) + ) + + cy.findByRole('button', { name: 'Cancel' }).click() + + cy.findByTestId('current-location').should( + 'have.text', + '/datasets?persistentId=some-persistent-id&version=1.0' + ) + }) }) describe('Guestbook Tab Integration', () => { @@ -748,6 +849,68 @@ describe('EditDatasetTerms', () => { '/datasets?persistentId=some-persistent-id&version=DRAFT' ) }) + + it('navigates to the draft dataset version after removing a guestbook', () => { + const dataset = DatasetMother.create({ + persistentId: 'some-persistent-id', + version: DatasetVersionMother.createReleased(), + license: mockLicenses[0], + guestbookId: mockGuestbooks[0].id + }) + + cy.customMount( + withProviders( + <> + + + , + dataset + ) + ) + + cy.findByRole('button', { name: 'Clear Selection' }).click() + cy.findByRole('button', { name: 'Save Changes' }).click() + + cy.wrap(guestbookRepository.removeDatasetGuestbook).should('have.been.called') + cy.findByTestId('current-location').should( + 'have.text', + '/datasets?persistentId=some-persistent-id&version=DRAFT' + ) + }) + + it('navigates to the current draft dataset version after canceling guestbook edits', () => { + const dataset = DatasetMother.create({ + persistentId: 'some-persistent-id', + version: DatasetVersionMother.createDraftAsLatestVersion(), + license: mockLicenses[0], + guestbookId: mockGuestbooks[0].id + }) + + cy.customMount( + withProviders( + <> + + + , + dataset + ) + ) + + cy.findByRole('button', { name: 'Cancel' }).click() + + cy.findByTestId('current-location').should( + 'have.text', + '/datasets?persistentId=some-persistent-id&version=DRAFT' + ) + }) }) describe('Tab State Guard Branches', () => { From 88017e62b7aea7bd1144c76ff848c96c6092e855 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Thu, 9 Jul 2026 13:55:55 -0400 Subject: [PATCH 13/15] increase test coverage --- .../sections/dataset/DatasetProvider.spec.tsx | 26 ++++++++- .../sections/route-enum/Route.spec.ts | 54 +++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/tests/component/sections/dataset/DatasetProvider.spec.tsx b/tests/component/sections/dataset/DatasetProvider.spec.tsx index 9e7d4962b..95b1d34ae 100644 --- a/tests/component/sections/dataset/DatasetProvider.spec.tsx +++ b/tests/component/sections/dataset/DatasetProvider.spec.tsx @@ -99,19 +99,43 @@ describe('DatasetProvider', () => { }) it('stops loading if error happens', () => { + cy.stub(console, 'error').as('consoleError') datasetRepository.getByPersistentId = cy.stub().rejects(new Error('some error')) cy.mount( + searchParams={{ persistentId: dataset.persistentId }}> ) cy.findByText('Loading...').should('exist') + cy.wrap(datasetRepository.getByPersistentId).should('have.been.calledOnce') + cy.get('@consoleError').should( + 'have.been.calledWithMatch', + 'There was an error getting the dataset' + ) cy.findByText('Dataset Not Found').should('exist') cy.findByText('Loading...').should('not.exist') }) + + it('does not fetch the dataset while publishing', () => { + cy.mount( + + + + + + ) + + cy.wrap(datasetRepository.getByPersistentId).should('not.have.been.called') + cy.wrap(datasetRepository.getByPrivateUrlToken).should('not.have.been.called') + cy.findByText('Loading...').should('exist') + cy.findByText('Dataset Not Found').should('exist') + }) }) diff --git a/tests/component/sections/route-enum/Route.spec.ts b/tests/component/sections/route-enum/Route.spec.ts index 5c1b69ed5..825a5a79f 100644 --- a/tests/component/sections/route-enum/Route.spec.ts +++ b/tests/component/sections/route-enum/Route.spec.ts @@ -1,4 +1,6 @@ import { Route, RouteWithParams } from '@/sections/Route.enum' +import { EditFileMetadataReferrer } from '@/sections/edit-file-metadata/EditFileMetadata' +import { ReplaceFileReferrer } from '@/sections/replace-file/ReplaceFile' describe('Route.enum.ts', () => { describe('RouteWithParams', () => { @@ -18,5 +20,57 @@ describe('Route.enum.ts', () => { it('should return the correct route for CREATE_DATASET', () => { expect(RouteWithParams.CREATE_DATASET('123')).to.be.equal(`/datasets/123/create`) }) + + it('should return the correct route for EDIT_FEATURED_ITEMS', () => { + expect(RouteWithParams.EDIT_FEATURED_ITEMS('123')).to.be.equal( + `/collections/123/edit-featured-items` + ) + }) + + it('should return the correct route for collection templates', () => { + expect(RouteWithParams.COLLECTION_TEMPLATES('root')).to.be.equal('/root/templates') + expect(RouteWithParams.TEMPLATES_CREATE('root')).to.be.equal('/root/templates/create') + expect(RouteWithParams.TEMPLATES_EDIT_METADATA('root', 10)).to.be.equal( + '/root/templates/10/edit/metadata' + ) + expect(RouteWithParams.TEMPLATES_EDIT_TERMS('root', 'template-10')).to.be.equal( + '/root/templates/template-10/edit/terms' + ) + }) + + it('should return the correct route for EDIT_FILE_METADATA', () => { + expect( + RouteWithParams.EDIT_FILE_METADATA( + 'doi:10.5072/FK2/ABC', + 'draft', + 5, + EditFileMetadataReferrer.DATASET + ) + ).to.be.equal( + '/files/edit-metadata?id=5&persistentId=doi%3A10.5072%2FFK2%2FABC&datasetVersion=draft&referrer=dataset' + ) + }) + + it('should return the correct route for FILES_REPLACE', () => { + expect(RouteWithParams.FILES_REPLACE('doi:10.5072/FK2/ABC', 'draft', 5)).to.be.equal( + '/files/replace?id=5&persistentId=doi%3A10.5072%2FFK2%2FABC&datasetVersion=draft' + ) + + expect( + RouteWithParams.FILES_REPLACE('doi:10.5072/FK2/ABC', 'draft', 5, ReplaceFileReferrer.FILE) + ).to.be.equal( + '/files/replace?id=5&persistentId=doi%3A10.5072%2FFK2%2FABC&datasetVersion=draft&referrer=file' + ) + }) + + it('should return the correct route for FEATURED_ITEM', () => { + expect(RouteWithParams.FEATURED_ITEM('parent', 'item')).to.be.equal( + '/featured-item/parent/item' + ) + }) + + it('should return the correct route for ADVANCED_SEARCH', () => { + expect(RouteWithParams.ADVANCED_SEARCH('root')).to.be.equal('/collections/root/search') + }) }) }) From 1878f74ecf1a46b62612644b040d1c3019c39f25 Mon Sep 17 00:00:00 2001 From: Ellen Kraffmiller Date: Thu, 9 Jul 2026 14:43:15 -0400 Subject: [PATCH 14/15] fix flaky test --- .../e2e/sections/edit-dataset-terms/EditDatasetTerms.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e-integration/e2e/sections/edit-dataset-terms/EditDatasetTerms.spec.tsx b/tests/e2e-integration/e2e/sections/edit-dataset-terms/EditDatasetTerms.spec.tsx index f30c0f89a..a44fd151a 100644 --- a/tests/e2e-integration/e2e/sections/edit-dataset-terms/EditDatasetTerms.spec.tsx +++ b/tests/e2e-integration/e2e/sections/edit-dataset-terms/EditDatasetTerms.spec.tsx @@ -117,7 +117,7 @@ describe('Edit Dataset Terms', () => { cy.findByLabelText('Size of Collection').type('500 MB') cy.findByRole('button', { name: 'Save Changes' }).click() - cy.findByRole('button', { name: 'Cancel' }).click() + cy.findByText(/The terms for this dataset have been updated./i).should('exist') cy.findByRole('tab', { name: /^Terms(?: and Guestbook)?$/ }).click() cy.findByText(/Data can be accessed through the university data center/i).should('exist') From 214172cfc87b7e53fb267c73d6d7708f4e1c7cf7 Mon Sep 17 00:00:00 2001 From: Cheng Shi Date: Wed, 22 Jul 2026 13:13:58 -0400 Subject: [PATCH 15/15] fix: unit test --- .../FileEmbeddedExternalTool.spec.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/component/sections/file/file-embedded-external-tool/FileEmbeddedExternalTool.spec.tsx b/tests/component/sections/file/file-embedded-external-tool/FileEmbeddedExternalTool.spec.tsx index a3aec6f2d..0d4599940 100644 --- a/tests/component/sections/file/file-embedded-external-tool/FileEmbeddedExternalTool.spec.tsx +++ b/tests/component/sections/file/file-embedded-external-tool/FileEmbeddedExternalTool.spec.tsx @@ -168,11 +168,10 @@ describe('FileEmbeddedExternalTool', () => { cy.customMount( - @@ -218,11 +217,10 @@ describe('FileEmbeddedExternalTool', () => { cy.customMount( -