diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 030e967bd..a7db0d900 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,5 +1,6 @@ import type { Preview } from '@storybook/react' import { ThemeProvider } from '@iqss/dataverse-design-system' +import { MemoryRouter } from 'react-router-dom' const preview: Preview = { parameters: { @@ -14,7 +15,9 @@ const preview: Preview = { decorators: [ (Story) => ( <ThemeProvider> - <Story /> + <MemoryRouter> + <Story /> + </MemoryRouter> </ThemeProvider> ) ] diff --git a/merged-coverage/lcov.info b/merged-coverage/lcov.info index 9c8729e8c..8d7a2182a 100644 --- a/merged-coverage/lcov.info +++ b/merged-coverage/lcov.info @@ -7,7 +7,7 @@ DA:5,108 BRDA:1,0,0,108 BRDA:1,0,1,108 end_of_record -SF:src/sections/hello-dataverse/HelloDataverse.tsx +SF:src/sections/hello-dataverse/Home.tsx DA:5,76 DA:6,76 DA:8,38 diff --git a/public/locales/en/files.json b/public/locales/en/files.json index 98fc2b0bb..bbae885f1 100644 --- a/public/locales/en/files.json +++ b/public/locales/en/files.json @@ -8,9 +8,6 @@ "clearSelection": "Clear selection." }, "zipDownloadExceedsLimit": "The overall size of the files selected ({{selectionTotalSize}}) for download exceeds the zip limit of {{zipDownloadSizeLimit}}. Please unselect some files to continue.", - "pagination": { - "pageSize": "Files per page" - }, "tabularData": { "name": "Tabular Data", "variables": "Variables", diff --git a/public/locales/en/helloDataverse.json b/public/locales/en/helloDataverse.json deleted file mode 100644 index 8c947e521..000000000 --- a/public/locales/en/helloDataverse.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "title": "Hello Dataverse", - "linkText": "Dataverse", - "description": "Edit <1>src/sections/hello-dataverse/HelloDataverse.tsx</1> and save to reload.", - "altImage": "Dataverse logo" -} diff --git a/public/locales/en/home.json b/public/locales/en/home.json new file mode 100644 index 000000000..2da05e5f9 --- /dev/null +++ b/public/locales/en/home.json @@ -0,0 +1,7 @@ +{ + "title": "Root", + "noDatasetsMessage": { + "authenticated": "This dataverse currently has no datasets. You can add to it by using the Add Data button on this page.", + "anonymous": "This dataverse currently has no datasets. Please <1>log in</1> to see if you are able to add to it." + } +} diff --git a/public/locales/en/pagination.json b/public/locales/en/pagination.json new file mode 100644 index 000000000..e30599934 --- /dev/null +++ b/public/locales/en/pagination.json @@ -0,0 +1,4 @@ +{ + "results": "{{start}} to {{end}} of {{total}} {{item}}s", + "pageSize": "{{item}}s per page" +} diff --git a/src/Router.tsx b/src/Router.tsx index 38d016834..286a80f63 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -1,8 +1,8 @@ import { createBrowserRouter, RouterProvider } from 'react-router-dom' -import { HelloDataverse } from './sections/hello-dataverse/HelloDataverse' import { Layout } from './sections/layout/Layout' import { Route } from './sections/Route.enum' import { DatasetFactory } from './sections/dataset/DatasetFactory' +import { HomeFactory } from './sections/home/HomeFactory' const router = createBrowserRouter( [ @@ -12,7 +12,7 @@ const router = createBrowserRouter( children: [ { path: Route.HOME, - element: <HelloDataverse /> + element: HomeFactory.create() }, { path: `${Route.DATASETS}`, diff --git a/src/dataset/domain/models/Dataset.ts b/src/dataset/domain/models/Dataset.ts index caaaae045..657fb4eac 100644 --- a/src/dataset/domain/models/Dataset.ts +++ b/src/dataset/domain/models/Dataset.ts @@ -291,7 +291,7 @@ export class Dataset { public readonly privateUrl?: PrivateUrl ) {} - public getTitle(): string { + public get title(): string { return this.metadataBlocks[0].fields.title } diff --git a/src/dataset/domain/models/DatasetPaginationInfo.ts b/src/dataset/domain/models/DatasetPaginationInfo.ts new file mode 100644 index 000000000..e9d88e6f4 --- /dev/null +++ b/src/dataset/domain/models/DatasetPaginationInfo.ts @@ -0,0 +1,7 @@ +import { PaginationInfo } from '../../../shared/domain/models/PaginationInfo' + +export class DatasetPaginationInfo extends PaginationInfo<DatasetPaginationInfo> { + constructor(page = 1, pageSize = 10, totalItems = 0, itemName = 'Dataset') { + super(page, pageSize, totalItems, itemName) + } +} diff --git a/src/dataset/domain/models/DatasetPreview.ts b/src/dataset/domain/models/DatasetPreview.ts new file mode 100644 index 000000000..849ba4691 --- /dev/null +++ b/src/dataset/domain/models/DatasetPreview.ts @@ -0,0 +1,22 @@ +import { DatasetLabel, DatasetVersion } from './Dataset' + +export class DatasetPreview { + constructor( + public persistentId: string, + public title: string, + public version: DatasetVersion, + public citation: string, + public labels: DatasetLabel[], + public isDeaccessioned: boolean, + public releaseOrCreateDate: Date, + public description: string, + public thumbnail?: string + ) {} + + get abbreviatedDescription(): string { + if (this.description.length > 280) { + return `${this.description.substring(0, 280)}...` + } + return this.description + } +} diff --git a/src/dataset/domain/models/TotalDatasetsCount.ts b/src/dataset/domain/models/TotalDatasetsCount.ts new file mode 100644 index 000000000..acc302966 --- /dev/null +++ b/src/dataset/domain/models/TotalDatasetsCount.ts @@ -0,0 +1 @@ +export type TotalDatasetsCount = number diff --git a/src/dataset/domain/repositories/DatasetRepository.ts b/src/dataset/domain/repositories/DatasetRepository.ts index a836e867b..0cc96a13d 100644 --- a/src/dataset/domain/repositories/DatasetRepository.ts +++ b/src/dataset/domain/repositories/DatasetRepository.ts @@ -1,6 +1,11 @@ import { Dataset } from '../models/Dataset' +import { TotalDatasetsCount } from '../models/TotalDatasetsCount' +import { DatasetPaginationInfo } from '../models/DatasetPaginationInfo' +import { DatasetPreview } from '../models/DatasetPreview' export interface DatasetRepository { getByPersistentId: (persistentId: string, version?: string) => Promise<Dataset | undefined> getByPrivateUrlToken: (privateUrlToken: string) => Promise<Dataset | undefined> + getAll: (paginationInfo: DatasetPaginationInfo) => Promise<DatasetPreview[]> + getTotalDatasetsCount: () => Promise<TotalDatasetsCount> } diff --git a/src/dataset/domain/useCases/getDatasets.ts b/src/dataset/domain/useCases/getDatasets.ts new file mode 100644 index 000000000..e10f74a2c --- /dev/null +++ b/src/dataset/domain/useCases/getDatasets.ts @@ -0,0 +1,12 @@ +import { DatasetRepository } from '../repositories/DatasetRepository' +import { DatasetPaginationInfo } from '../models/DatasetPaginationInfo' +import { DatasetPreview } from '../models/DatasetPreview' + +export async function getDatasets( + datasetRepository: DatasetRepository, + paginationInfo: DatasetPaginationInfo +): Promise<DatasetPreview[]> { + return datasetRepository.getAll(paginationInfo).catch((error: Error) => { + throw new Error(error.message) + }) +} diff --git a/src/dataset/domain/useCases/getTotalDatasetsCount.ts b/src/dataset/domain/useCases/getTotalDatasetsCount.ts new file mode 100644 index 000000000..5ba855d06 --- /dev/null +++ b/src/dataset/domain/useCases/getTotalDatasetsCount.ts @@ -0,0 +1,10 @@ +import { DatasetRepository } from '../repositories/DatasetRepository' +import { TotalDatasetsCount } from '../models/TotalDatasetsCount' + +export async function getTotalDatasetsCount( + datasetRepository: DatasetRepository +): Promise<TotalDatasetsCount> { + return datasetRepository.getTotalDatasetsCount().catch((error: Error) => { + throw new Error(error.message) + }) +} diff --git a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts index c3773641b..b5ddf718c 100644 --- a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts +++ b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts @@ -16,9 +16,32 @@ import { FileDownloadSizeMode } from '@iqss/dataverse-client-javascript' import { JSDatasetMapper } from '../mappers/JSDatasetMapper' +import { TotalDatasetsCount } from '../../domain/models/TotalDatasetsCount' +import { DatasetPaginationInfo } from '../../domain/models/DatasetPaginationInfo' +import { DatasetPreview } from '../../domain/models/DatasetPreview' +import { DatasetPreviewMother } from '../../../../tests/component/dataset/domain/models/DatasetPreviewMother' const includeDeaccessioned = true export class DatasetJSDataverseRepository implements DatasetRepository { + // eslint-disable-next-line unused-imports/no-unused-vars + getAll(paginationInfo: DatasetPaginationInfo): Promise<DatasetPreview[]> { + // TODO - Implement using the js-dataverse-client + return new Promise((resolve) => { + setTimeout(() => { + resolve(DatasetPreviewMother.createManyRealistic(10)) + }, 1000) + }) + } + + getTotalDatasetsCount(): Promise<TotalDatasetsCount> { + // TODO - Implement using the js-dataverse-client + return new Promise((resolve) => { + setTimeout(() => { + resolve(200) + }, 1000) + }) + } + getByPersistentId( persistentId: string, version?: string, diff --git a/src/files/domain/models/FilePaginationInfo.ts b/src/files/domain/models/FilePaginationInfo.ts index fbef8d177..f2b4386af 100644 --- a/src/files/domain/models/FilePaginationInfo.ts +++ b/src/files/domain/models/FilePaginationInfo.ts @@ -1,52 +1,7 @@ -export class FilePaginationInfo { - constructor( - public readonly page: number = 1, - public readonly pageSize: number = 10, - public readonly totalFiles: number = 0 - ) {} +import { PaginationInfo } from '../../../shared/domain/models/PaginationInfo' - withTotal(total: number): FilePaginationInfo { - return new FilePaginationInfo(this.page, this.pageSize, total) - } - goToPage(page: number): FilePaginationInfo { - return new FilePaginationInfo(page, this.pageSize, this.totalFiles) - } - - goToPreviousPage(): FilePaginationInfo { - if (!this.previousPage) throw new Error('No previous page') - return this.goToPage(this.previousPage) - } - - goToNextPage(): FilePaginationInfo { - if (!this.nextPage) throw new Error('No next page') - return this.goToPage(this.nextPage) - } - - withPageSize(pageSize: number): FilePaginationInfo { - const getNewPage = (oldPageSize: number, newPageSize: number) => { - const newPage = Math.ceil(((this.page - 1) * oldPageSize + 1) / newPageSize) - return newPage > 0 ? newPage : 1 - } - return new FilePaginationInfo(getNewPage(this.pageSize, pageSize), pageSize, this.totalFiles) - } - - get totalPages(): number { - return Math.ceil(this.totalFiles / this.pageSize) - } - - get hasPreviousPage(): boolean { - return this.page > 1 - } - - get hasNextPage(): boolean { - return this.page < this.totalPages - } - - get previousPage(): number | undefined { - return this.hasPreviousPage ? this.page - 1 : undefined - } - - get nextPage(): number | undefined { - return this.hasNextPage ? this.page + 1 : undefined +export class FilePaginationInfo extends PaginationInfo<FilePaginationInfo> { + constructor(page = 1, pageSize = 10, totalItems = 0, itemName = 'File') { + super(page, pageSize, totalItems, itemName) } } diff --git a/src/files/domain/repositories/FileRepository.ts b/src/files/domain/repositories/FileRepository.ts index 9b861084d..278fc9502 100644 --- a/src/files/domain/repositories/FileRepository.ts +++ b/src/files/domain/repositories/FileRepository.ts @@ -1,9 +1,9 @@ import { File, FileDownloadMode } from '../models/File' import { FileCriteria } from '../models/FileCriteria' import { FilesCountInfo } from '../models/FilesCountInfo' -import { FilePaginationInfo } from '../models/FilePaginationInfo' import { FileUserPermissions } from '../models/FileUserPermissions' import { DatasetVersion } from '../../../dataset/domain/models/Dataset' +import { FilePaginationInfo } from '../models/FilePaginationInfo' export interface FileRepository { getAllByDatasetPersistentId: ( diff --git a/src/files/domain/useCases/getFilesByDatasetPersistentId.ts b/src/files/domain/useCases/getFilesByDatasetPersistentId.ts index 326633b7d..9370c7ebe 100644 --- a/src/files/domain/useCases/getFilesByDatasetPersistentId.ts +++ b/src/files/domain/useCases/getFilesByDatasetPersistentId.ts @@ -1,8 +1,8 @@ import { FileRepository } from '../repositories/FileRepository' import { File } from '../models/File' import { FileCriteria } from '../models/FileCriteria' -import { FilePaginationInfo } from '../models/FilePaginationInfo' import { DatasetVersion } from '../../../dataset/domain/models/Dataset' +import { FilePaginationInfo } from '../models/FilePaginationInfo' export async function getFilesByDatasetPersistentId( fileRepository: FileRepository, diff --git a/src/files/infrastructure/FileJSDataverseRepository.ts b/src/files/infrastructure/FileJSDataverseRepository.ts index 96affa553..64b46ce53 100644 --- a/src/files/infrastructure/FileJSDataverseRepository.ts +++ b/src/files/infrastructure/FileJSDataverseRepository.ts @@ -1,7 +1,6 @@ import { FileRepository } from '../domain/repositories/FileRepository' import { File, FileDownloadMode } from '../domain/models/File' import { FilesCountInfo } from '../domain/models/FilesCountInfo' -import { FilePaginationInfo } from '../domain/models/FilePaginationInfo' import { FileUserPermissions } from '../domain/models/FileUserPermissions' import { File as JSFile, @@ -19,6 +18,7 @@ import { FileCriteria } from '../domain/models/FileCriteria' import { DomainFileMapper } from './mappers/DomainFileMapper' import { JSFileMapper } from './mappers/JSFileMapper' import { DatasetVersion } from '../../dataset/domain/models/Dataset' +import { FilePaginationInfo } from '../domain/models/FilePaginationInfo' const includeDeaccessioned = true diff --git a/src/files/infrastructure/mappers/DomainFileMapper.ts b/src/files/infrastructure/mappers/DomainFileMapper.ts index 71082838d..9c8921102 100644 --- a/src/files/infrastructure/mappers/DomainFileMapper.ts +++ b/src/files/infrastructure/mappers/DomainFileMapper.ts @@ -1,4 +1,3 @@ -import { FilePaginationInfo } from '../../domain/models/FilePaginationInfo' import { FileAccessOption, FileCriteria, @@ -11,6 +10,7 @@ import { FileOrderCriteria as JSFileOrderCriteria } from '@iqss/dataverse-client-javascript' import { FileType } from '../../domain/models/File' +import { FilePaginationInfo } from '../../domain/models/FilePaginationInfo' export class DomainFileMapper { static toJSPagination(paginationInfo: FilePaginationInfo): { diff --git a/src/sections/dataset/Dataset.tsx b/src/sections/dataset/Dataset.tsx index 8095e0ae8..86e6b69f1 100644 --- a/src/sections/dataset/Dataset.tsx +++ b/src/sections/dataset/Dataset.tsx @@ -51,14 +51,14 @@ export function Dataset({ fileRepository }: DatasetProps) { </div> <header className={styles.header}> - <h1>{dataset.getTitle()}</h1> + <h1>{dataset.title}</h1> <DatasetLabels labels={dataset.labels} /> </header> <div className={styles.container}> <Row> <Col sm={9}> <DatasetCitation - title={dataset.getTitle()} + title={dataset.title} thumbnail={dataset.thumbnail} citation={dataset.citation} version={dataset.version} diff --git a/src/sections/dataset/dataset-citation/CitationThumbnail.tsx b/src/sections/dataset/dataset-citation/CitationThumbnail.tsx deleted file mode 100644 index 57cda7c95..000000000 --- a/src/sections/dataset/dataset-citation/CitationThumbnail.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { DatasetPublishingStatus } from '../../../dataset/domain/models/Dataset' -import styles from './DatasetCitation.module.scss' -import { Icon, IconName } from '@iqss/dataverse-design-system' - -interface CitationThumbnailProps { - thumbnail?: string - title: string - publishingStatus: DatasetPublishingStatus -} - -export function CitationThumbnail({ thumbnail, title, publishingStatus }: CitationThumbnailProps) { - if (thumbnail && publishingStatus !== DatasetPublishingStatus.DEACCESSIONED) { - return <img className={styles['preview-image']} src={thumbnail} alt={title} /> - } - - return ( - <div className={styles.icon}> - <Icon name={IconName.DATASET} /> - </div> - ) -} diff --git a/src/sections/dataset/dataset-citation/DatasetCitation.module.scss b/src/sections/dataset/dataset-citation/DatasetCitation.module.scss index 4e3ecc9ea..4dfa0ae50 100644 --- a/src/sections/dataset/dataset-citation/DatasetCitation.module.scss +++ b/src/sections/dataset/dataset-citation/DatasetCitation.module.scss @@ -17,20 +17,11 @@ min-height: 150px; border: 1px solid $dv-danger-color; } -.icon { - color: #428BCA; - font-size: 7.5em; - line-height: 1.1; -} - -.preview-image { - width: 100%; - max-width: 140px; - height: auto; - max-height: 140px; -} - .row { margin-right: -15px; margin-left: -15px; +} + +.thumbnail { + font-size: 7.5em; } \ No newline at end of file diff --git a/src/sections/dataset/dataset-citation/DatasetCitation.tsx b/src/sections/dataset/dataset-citation/DatasetCitation.tsx index 9b523c7c7..9f1450830 100644 --- a/src/sections/dataset/dataset-citation/DatasetCitation.tsx +++ b/src/sections/dataset/dataset-citation/DatasetCitation.tsx @@ -1,9 +1,10 @@ -import { Col, QuestionMarkTooltip, Row } from '@iqss/dataverse-design-system' +import { Col, Row } from '@iqss/dataverse-design-system' import styles from './DatasetCitation.module.scss' import { useTranslation } from 'react-i18next' import { DatasetPublishingStatus, DatasetVersion } from '../../../dataset/domain/models/Dataset' -import parse from 'html-react-parser' -import { CitationThumbnail } from './CitationThumbnail' +import { DatasetThumbnail } from '../dataset-thumbnail/DatasetThumbnail' +import { CitationDescription } from '../../shared/citation/CitationDescription' +import { DatasetCitationTooltip } from './DatasetCitationTooltip' interface DatasetCitationProps { thumbnail?: string @@ -23,16 +24,19 @@ export function DatasetCitation({ thumbnail, title, citation, version }: Dataset : styles.container }> <Row className={styles.row}> - <Col sm={2}> - <CitationThumbnail + <Col sm={2} className={styles.thumbnail}> + <DatasetThumbnail thumbnail={thumbnail} title={title} - publishingStatus={version.publishingStatus} + isDeaccessioned={version.publishingStatus === DatasetPublishingStatus.DEACCESSIONED} /> </Col> <Col> <Row> - <CitationDescription citation={citation} version={version} /> + <span className={styles.citation}> + <CitationDescription citation={citation} /> + <DatasetCitationTooltip status={version.publishingStatus} /> + </span> </Row> <Row> <div> @@ -52,35 +56,3 @@ export function DatasetCitation({ thumbnail, title, citation, version }: Dataset </> ) } - -function CitationDescription({ citation, version }: { citation: string; version: DatasetVersion }) { - const citationAsReactElement = parse(citation) - - return ( - <span className={styles.citation}> - {citationAsReactElement} - <CitationTooltip status={version.publishingStatus} /> - </span> - ) -} - -interface CitationDatasetStatusProps { - status: DatasetPublishingStatus -} - -function CitationTooltip({ status }: CitationDatasetStatusProps) { - const { t } = useTranslation('dataset') - - if (status !== DatasetPublishingStatus.RELEASED) { - return ( - <> - {' '} - <QuestionMarkTooltip - placement={'top'} - message={t(`citation.status.${status}.description`)} - /> - </> - ) - } - return <></> -} diff --git a/src/sections/dataset/dataset-citation/DatasetCitationTooltip.tsx b/src/sections/dataset/dataset-citation/DatasetCitationTooltip.tsx new file mode 100644 index 000000000..0ae1676b1 --- /dev/null +++ b/src/sections/dataset/dataset-citation/DatasetCitationTooltip.tsx @@ -0,0 +1,24 @@ +import { DatasetPublishingStatus } from '../../../dataset/domain/models/Dataset' +import { useTranslation } from 'react-i18next' +import { QuestionMarkTooltip } from '@iqss/dataverse-design-system' + +interface DatasetCitationTooltipProps { + status: DatasetPublishingStatus +} + +export function DatasetCitationTooltip({ status }: DatasetCitationTooltipProps) { + const { t } = useTranslation('dataset') + + if (status !== DatasetPublishingStatus.RELEASED) { + return ( + <> + {' '} + <QuestionMarkTooltip + placement={'top'} + message={t(`citation.status.${status}.description`)} + /> + </> + ) + } + return <></> +} diff --git a/src/sections/dataset/dataset-files/DatasetFiles.tsx b/src/sections/dataset/dataset-files/DatasetFiles.tsx index cccd061a5..afb96ec8b 100644 --- a/src/sections/dataset/dataset-files/DatasetFiles.tsx +++ b/src/sections/dataset/dataset-files/DatasetFiles.tsx @@ -4,9 +4,9 @@ import { FilesTable } from './files-table/FilesTable' import { FileCriteriaForm } from './file-criteria-form/FileCriteriaForm' import { FileCriteria } from '../../../files/domain/models/FileCriteria' import { useFiles } from './useFiles' -import { FilePaginationInfo } from '../../../files/domain/models/FilePaginationInfo' -import { FilesPagination } from './files-pagination/FilesPagination' +import { PaginationControls } from '../../shared/pagination/PaginationControls' import { DatasetVersion } from '../../../dataset/domain/models/Dataset' +import { FilePaginationInfo } from '../../../files/domain/models/FilePaginationInfo' interface DatasetFilesProps { filesRepository: FileRepository @@ -44,10 +44,8 @@ export function DatasetFiles({ filesTotalDownloadSize={filesTotalDownloadSize} criteria={criteria} /> - <FilesPagination - page={paginationInfo.page} - pageSize={paginationInfo.pageSize} - total={paginationInfo.totalFiles} + <PaginationControls + initialPaginationInfo={paginationInfo} onPaginationInfoChange={setPaginationInfo} /> </> diff --git a/src/sections/dataset/dataset-files/files-table/FilesTable.tsx b/src/sections/dataset/dataset-files/files-table/FilesTable.tsx index fc71ef801..b1988bba8 100644 --- a/src/sections/dataset/dataset-files/files-table/FilesTable.tsx +++ b/src/sections/dataset/dataset-files/files-table/FilesTable.tsx @@ -6,10 +6,10 @@ import { File } from '../../../../files/domain/models/File' import { RowSelectionMessage } from './row-selection/RowSelectionMessage' import { ZipDownloadLimitMessage } from './zip-download-limit-message/ZipDownloadLimitMessage' import { SpinnerSymbol } from './spinner-symbol/SpinnerSymbol' -import { FilePaginationInfo } from '../../../../files/domain/models/FilePaginationInfo' import { useEffect, useState } from 'react' import { FileSelection } from './row-selection/useFileSelection' import { FileCriteria } from '../../../../files/domain/models/FileCriteria' +import { FilePaginationInfo } from '../../../../files/domain/models/FilePaginationInfo' interface FilesTableProps { files: File[] @@ -56,7 +56,7 @@ export function FilesTable({ <RowSelectionMessage fileSelection={fileSelection} selectAllRows={selectAllFiles} - totalFilesCount={paginationInfo.totalFiles} + totalFilesCount={paginationInfo.totalItems} clearRowSelection={clearFileSelection} /> <ZipDownloadLimitMessage diff --git a/src/sections/dataset/dataset-files/files-table/FilesTableColumnsDefinition.tsx b/src/sections/dataset/dataset-files/files-table/FilesTableColumnsDefinition.tsx index bdf534f62..6c241eac0 100644 --- a/src/sections/dataset/dataset-files/files-table/FilesTableColumnsDefinition.tsx +++ b/src/sections/dataset/dataset-files/files-table/FilesTableColumnsDefinition.tsx @@ -5,8 +5,8 @@ import { FileInfoCell } from './file-info/file-info-cell/FileInfoCell' import { FileInfoHeader } from './file-info/FileInfoHeader' import { FileActionsHeader } from './file-actions/FileActionsHeader' import { FileActionsCell } from './file-actions/file-actions-cell/FileActionsCell' -import { FilePaginationInfo } from '../../../../files/domain/models/FilePaginationInfo' import { FileSelection } from './row-selection/useFileSelection' +import { FilePaginationInfo } from '../../../../files/domain/models/FilePaginationInfo' export const createColumnsDefinition = ( paginationInfo: FilePaginationInfo, diff --git a/src/sections/dataset/dataset-files/files-table/file-info/FileInfoHeader.tsx b/src/sections/dataset/dataset-files/files-table/file-info/FileInfoHeader.tsx index fd76cc997..b105aa5a4 100644 --- a/src/sections/dataset/dataset-files/files-table/file-info/FileInfoHeader.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-info/FileInfoHeader.tsx @@ -1,4 +1,5 @@ import styles from '../FilesTable.module.scss' +import { PaginationResultsInfo } from '../../../../shared/pagination/PaginationResultsInfo' import { FilePaginationInfo } from '../../../../../files/domain/models/FilePaginationInfo' interface FileInfoHeaderProps { @@ -6,16 +7,14 @@ interface FileInfoHeaderProps { } export function FileInfoHeader({ paginationInfo }: FileInfoHeaderProps) { - const fileCount = paginationInfo.totalFiles - const startIndex = (paginationInfo.page - 1) * paginationInfo.pageSize + 1 - const endIndex = Math.min(startIndex + paginationInfo.pageSize - 1, fileCount) + const fileCount = paginationInfo.totalItems if (fileCount === 0) { return <></> } return ( <span className={styles['file-info-header']}> - {`${startIndex} to ${endIndex} of ${fileCount} Files`} + <PaginationResultsInfo paginationInfo={paginationInfo} /> </span> ) } diff --git a/src/sections/dataset/dataset-files/files-table/file-info/file-info-cell/file-info-data/FileDate.tsx b/src/sections/dataset/dataset-files/files-table/file-info/file-info-cell/file-info-data/FileDate.tsx index 61ddb0c44..054ab4b89 100644 --- a/src/sections/dataset/dataset-files/files-table/file-info/file-info-cell/file-info-data/FileDate.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-info/file-info-cell/file-info-data/FileDate.tsx @@ -1,17 +1,13 @@ import { FileDate as FileDateModel } from '../../../../../../../files/domain/models/File' import { useTranslation } from 'react-i18next' +import { DateHelper } from '../../../../../../../shared/domain/helpers/DateHelper' export function FileDate({ date }: { date: FileDateModel }) { const { t } = useTranslation('files') return ( <div> <span> - {t(`table.date.${date.type}`)}{' '} - {date.date.toLocaleDateString(Intl.DateTimeFormat().resolvedOptions().locale, { - year: 'numeric', - month: 'short', - day: 'numeric' - })} + {t(`table.date.${date.type}`)} {DateHelper.toDisplayFormat(date.date)} </span> </div> ) diff --git a/src/sections/dataset/dataset-files/files-table/file-info/file-info-cell/file-info-data/FileEmbargoDate.tsx b/src/sections/dataset/dataset-files/files-table/file-info/file-info-cell/file-info-data/FileEmbargoDate.tsx index 4c61171b9..50db565fa 100644 --- a/src/sections/dataset/dataset-files/files-table/file-info/file-info-cell/file-info-data/FileEmbargoDate.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-info/file-info-cell/file-info-data/FileEmbargoDate.tsx @@ -1,5 +1,6 @@ import { FileEmbargo, FilePublishingStatus } from '../../../../../../../files/domain/models/File' import { useTranslation } from 'react-i18next' +import { DateHelper } from '../../../../../../../shared/domain/helpers/DateHelper' interface FileEmbargoDateProps { embargo: FileEmbargo | undefined @@ -17,11 +18,7 @@ export function FileEmbargoDate({ embargo, publishingStatus }: FileEmbargoDatePr <div> <span> {t(embargoTypeOfDate(embargo.isActive, publishingStatus))}{' '} - {embargo.dateAvailable.toLocaleDateString(Intl.DateTimeFormat().resolvedOptions().locale, { - year: 'numeric', - month: 'short', - day: 'numeric' - })} + {DateHelper.toDisplayFormat(embargo.dateAvailable)} </span> </div> ) diff --git a/src/sections/dataset/dataset-files/files-table/row-selection/useFileSelection.ts b/src/sections/dataset/dataset-files/files-table/row-selection/useFileSelection.ts index ede451ad0..6fe2a12eb 100644 --- a/src/sections/dataset/dataset-files/files-table/row-selection/useFileSelection.ts +++ b/src/sections/dataset/dataset-files/files-table/row-selection/useFileSelection.ts @@ -1,8 +1,8 @@ import { useEffect, useState } from 'react' -import { FilePaginationInfo } from '../../../../../files/domain/models/FilePaginationInfo' import { File } from '../../../../../files/domain/models/File' import { Row } from '@tanstack/react-table' import { RowSelection } from '../useFilesTable' +import { FilePaginationInfo } from '../../../../../files/domain/models/FilePaginationInfo' export type FileSelection = { [key: string]: File | undefined @@ -61,7 +61,7 @@ export function useFileSelection( const selectAllFiles = () => { setCurrentPageRowSelection(createRowSelection(paginationInfo.pageSize)) - const totalFilesFileSelection = createFileSelection(paginationInfo.totalFiles) + const totalFilesFileSelection = createFileSelection(paginationInfo.totalItems) const newFileSelection = { ...totalFilesFileSelection, ...fileSelection } setFileSelection(newFileSelection) } diff --git a/src/sections/dataset/dataset-files/files-table/useFilesTable.tsx b/src/sections/dataset/dataset-files/files-table/useFilesTable.tsx index b403e9782..0d4f35fd1 100644 --- a/src/sections/dataset/dataset-files/files-table/useFilesTable.tsx +++ b/src/sections/dataset/dataset-files/files-table/useFilesTable.tsx @@ -2,8 +2,8 @@ import { useEffect, useState } from 'react' import { File } from '../../../../files/domain/models/File' import { getCoreRowModel, Row, useReactTable } from '@tanstack/react-table' import { createColumnsDefinition } from './FilesTableColumnsDefinition' -import { FilePaginationInfo } from '../../../../files/domain/models/FilePaginationInfo' import { useFileSelection } from './row-selection/useFileSelection' +import { FilePaginationInfo } from '../../../../files/domain/models/FilePaginationInfo' export type RowSelection = { [key: string]: boolean diff --git a/src/sections/dataset/dataset-files/useFiles.tsx b/src/sections/dataset/dataset-files/useFiles.tsx index 538da0921..ce628deb3 100644 --- a/src/sections/dataset/dataset-files/useFiles.tsx +++ b/src/sections/dataset/dataset-files/useFiles.tsx @@ -5,11 +5,11 @@ import { getFilesByDatasetPersistentId } from '../../../files/domain/useCases/ge import { FileCriteria } from '../../../files/domain/models/FileCriteria' import { FilesCountInfo } from '../../../files/domain/models/FilesCountInfo' import { getFilesCountInfoByDatasetPersistentId } from '../../../files/domain/useCases/getFilesCountInfoByDatasetPersistentId' -import { FilePaginationInfo } from '../../../files/domain/models/FilePaginationInfo' import { useFilePermissions } from '../../file/file-permissions/FilePermissionsContext' import { FilePermission } from '../../../files/domain/models/FileUserPermissions' import { DatasetVersion } from '../../../dataset/domain/models/Dataset' import { getFilesTotalDownloadSize } from '../../../files/domain/useCases/getFilesTotalDownloadSize' +import { FilePaginationInfo } from '../../../files/domain/models/FilePaginationInfo' export function useFiles( filesRepository: FileRepository, @@ -33,7 +33,7 @@ export function useFiles( ) .then((filesCountInfo: FilesCountInfo) => { setFilesCountInfo(filesCountInfo) - if (filesCountInfo.total !== paginationInfo.totalFiles) { + if (filesCountInfo.total !== paginationInfo.totalItems) { onPaginationInfoChange(paginationInfo.withTotal(filesCountInfo.total)) } return filesCountInfo diff --git a/src/sections/dataset/dataset-icon/DatasetIcon.module.scss b/src/sections/dataset/dataset-icon/DatasetIcon.module.scss new file mode 100644 index 000000000..4025c0bbe --- /dev/null +++ b/src/sections/dataset/dataset-icon/DatasetIcon.module.scss @@ -0,0 +1,6 @@ +@import "node_modules/@iqss/dataverse-design-system/src/lib/assets/styles/design-tokens/colors.module"; + +.icon { + color: $dv-info-border-color; + line-height: 1.1; +} \ No newline at end of file diff --git a/src/sections/dataset/dataset-icon/DatasetIcon.tsx b/src/sections/dataset/dataset-icon/DatasetIcon.tsx new file mode 100644 index 000000000..ac68ba92b --- /dev/null +++ b/src/sections/dataset/dataset-icon/DatasetIcon.tsx @@ -0,0 +1,10 @@ +import styles from './DatasetIcon.module.scss' +import { Icon, IconName } from '@iqss/dataverse-design-system' + +export function DatasetIcon() { + return ( + <div className={styles.icon}> + <Icon name={IconName.DATASET} /> + </div> + ) +} diff --git a/src/sections/dataset/dataset-thumbnail/DatasetThumbnail.module.scss b/src/sections/dataset/dataset-thumbnail/DatasetThumbnail.module.scss new file mode 100644 index 000000000..a464f4dc4 --- /dev/null +++ b/src/sections/dataset/dataset-thumbnail/DatasetThumbnail.module.scss @@ -0,0 +1,6 @@ +.preview-image { + width: 100%; + max-width: 140px; + height: auto; + max-height: 140px; +} \ No newline at end of file diff --git a/src/sections/dataset/dataset-thumbnail/DatasetThumbnail.tsx b/src/sections/dataset/dataset-thumbnail/DatasetThumbnail.tsx new file mode 100644 index 000000000..628cdd8ac --- /dev/null +++ b/src/sections/dataset/dataset-thumbnail/DatasetThumbnail.tsx @@ -0,0 +1,16 @@ +import styles from './DatasetThumbnail.module.scss' +import { DatasetIcon } from '../dataset-icon/DatasetIcon' + +interface DatasetThumbnailProps { + thumbnail?: string + title: string + isDeaccessioned?: boolean +} + +export function DatasetThumbnail({ thumbnail, title, isDeaccessioned }: DatasetThumbnailProps) { + if (thumbnail && !isDeaccessioned) { + return <img className={styles['preview-image']} src={thumbnail} alt={title} /> + } + + return <DatasetIcon /> +} diff --git a/src/sections/hello-dataverse/HelloDataverse.module.scss b/src/sections/hello-dataverse/HelloDataverse.module.scss deleted file mode 100644 index 505b1cb9c..000000000 --- a/src/sections/hello-dataverse/HelloDataverse.module.scss +++ /dev/null @@ -1,26 +0,0 @@ -@import "node_modules/@iqss/dataverse-design-system/src/lib/assets/styles/design-tokens/colors.module"; -@import "src/sections/assets/variables"; - -.container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - min-height: $body-available-height; - font-size: calc(10px + 2vmin); - text-align: center; -} - -.title { - color: $dv-brand-color; -} - -.logo { - height: 40vmin; - pointer-events: none; -} - -.link { - color: $dv-link-color; -} - diff --git a/src/sections/hello-dataverse/HelloDataverse.tsx b/src/sections/hello-dataverse/HelloDataverse.tsx deleted file mode 100644 index e955d0e66..000000000 --- a/src/sections/hello-dataverse/HelloDataverse.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import logo from '../assets/logo.svg' -import styles from './HelloDataverse.module.scss' -import { Trans, useTranslation } from 'react-i18next' - -export function HelloDataverse() { - const { t } = useTranslation('helloDataverse') - - return ( - <section className={styles.container}> - <h2 className={styles.title}>{t('title')}</h2> - <img src={logo} className={styles.logo} alt={t('altImage')} /> - <p> - <Trans t={t} i18nKey="description" components={{ 1: <code /> }} /> - </p> - <a - className={styles.link} - href="https://dataverse.org" - target="_blank" - rel="noopener noreferrer"> - {t('linkText')} - </a> - </section> - ) -} diff --git a/src/sections/home/Home.tsx b/src/sections/home/Home.tsx new file mode 100644 index 000000000..803e2a1d6 --- /dev/null +++ b/src/sections/home/Home.tsx @@ -0,0 +1,20 @@ +import { Row } from '@iqss/dataverse-design-system' +import { useTranslation } from 'react-i18next' +import { DatasetRepository } from '../../dataset/domain/repositories/DatasetRepository' +import { DatasetsList } from './datasets-list/DatasetsList' +interface HomeProps { + datasetRepository: DatasetRepository +} + +export function Home({ datasetRepository }: HomeProps) { + const { t } = useTranslation('home') + + return ( + <Row> + <header> + <h1>{t('title')}</h1> + </header> + <DatasetsList datasetRepository={datasetRepository} /> + </Row> + ) +} diff --git a/src/sections/home/HomeFactory.tsx b/src/sections/home/HomeFactory.tsx new file mode 100644 index 000000000..f37a69caa --- /dev/null +++ b/src/sections/home/HomeFactory.tsx @@ -0,0 +1,10 @@ +import { ReactElement } from 'react' +import { Home } from './Home' +import { DatasetJSDataverseRepository } from '../../dataset/infrastructure/repositories/DatasetJSDataverseRepository' + +const datasetRepository = new DatasetJSDataverseRepository() +export class HomeFactory { + static create(): ReactElement { + return <Home datasetRepository={datasetRepository} /> + } +} diff --git a/src/sections/home/datasets-list/DatasetsList.module.scss b/src/sections/home/datasets-list/DatasetsList.module.scss new file mode 100644 index 000000000..15aff10a5 --- /dev/null +++ b/src/sections/home/datasets-list/DatasetsList.module.scss @@ -0,0 +1,13 @@ +@import "node_modules/@iqss/dataverse-design-system/src/lib/assets/styles/design-tokens/colors.module"; + +.container { + min-height: calc(100vh + 100px); + padding:15px; + border: 1px solid #ddd; + border-radius: 4px; +} + +.empty-message-container { + padding: .5em 1em; + background: $dv-warning-box-color; +} diff --git a/src/sections/home/datasets-list/DatasetsList.tsx b/src/sections/home/datasets-list/DatasetsList.tsx new file mode 100644 index 000000000..da13f06d3 --- /dev/null +++ b/src/sections/home/datasets-list/DatasetsList.tsx @@ -0,0 +1,53 @@ +import { useDatasets } from './useDatasets' +import styles from './DatasetsList.module.scss' +import { DatasetRepository } from '../../../dataset/domain/repositories/DatasetRepository' +import { useEffect, useState } from 'react' +import { PaginationResultsInfo } from '../../shared/pagination/PaginationResultsInfo' +import { PaginationControls } from '../../shared/pagination/PaginationControls' +import { DatasetPaginationInfo } from '../../../dataset/domain/models/DatasetPaginationInfo' +import { useLoading } from '../../loading/LoadingContext' +import { DatasetsListSkeleton } from './DatasetsListSkeleton' +import { NoDatasetsMessage } from './NoDatasetsMessage' +import { DatasetCard } from './dataset-card/DatasetCard' + +interface DatasetsListProps { + datasetRepository: DatasetRepository +} +const NO_DATASETS = 0 +export function DatasetsList({ datasetRepository }: DatasetsListProps) { + const { setIsLoading } = useLoading() + const [paginationInfo, setPaginationInfo] = useState<DatasetPaginationInfo>( + new DatasetPaginationInfo() + ) + const { datasets, isLoading } = useDatasets(datasetRepository, setPaginationInfo, paginationInfo) + + useEffect(() => { + setIsLoading(isLoading) + }, [isLoading]) + + if (isLoading) { + return <DatasetsListSkeleton /> + } + + return ( + <section className={styles.container}> + {datasets.length === NO_DATASETS ? ( + <NoDatasetsMessage /> + ) : ( + <> + <div> + <PaginationResultsInfo paginationInfo={paginationInfo} /> + </div> + {datasets.map((dataset) => ( + <DatasetCard dataset={dataset} key={dataset.persistentId} /> + ))} + <PaginationControls + onPaginationInfoChange={setPaginationInfo} + initialPaginationInfo={paginationInfo} + showPageSizeSelector={false} + /> + </> + )} + </section> + ) +} diff --git a/src/sections/home/datasets-list/DatasetsListSkeleton.tsx b/src/sections/home/datasets-list/DatasetsListSkeleton.tsx new file mode 100644 index 000000000..b07bc39d3 --- /dev/null +++ b/src/sections/home/datasets-list/DatasetsListSkeleton.tsx @@ -0,0 +1,26 @@ +import styles from './DatasetsList.module.scss' +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton' + +export function DatasetsListSkeleton() { + return ( + <SkeletonTheme> + <section className={styles.container} data-testid="datasets-list-skeleton"> + <div> + <Skeleton width="14%" /> + </div> + <article> + <Skeleton height="109px" style={{ marginBottom: 6 }} /> + <Skeleton height="109px" style={{ marginBottom: 6 }} /> + <Skeleton height="109px" style={{ marginBottom: 6 }} /> + <Skeleton height="109px" style={{ marginBottom: 6 }} /> + <Skeleton height="109px" style={{ marginBottom: 6 }} /> + <Skeleton height="109px" style={{ marginBottom: 6 }} /> + <Skeleton height="109px" style={{ marginBottom: 6 }} /> + <Skeleton height="109px" style={{ marginBottom: 6 }} /> + <Skeleton height="109px" style={{ marginBottom: 6 }} /> + <Skeleton height="109px" style={{ marginBottom: 6 }} /> + </article> + </section> + </SkeletonTheme> + ) +} diff --git a/src/sections/home/datasets-list/NoDatasetsMessage.tsx b/src/sections/home/datasets-list/NoDatasetsMessage.tsx new file mode 100644 index 000000000..a7ad3cfc0 --- /dev/null +++ b/src/sections/home/datasets-list/NoDatasetsMessage.tsx @@ -0,0 +1,24 @@ +import styles from './DatasetsList.module.scss' +import { Trans, useTranslation } from 'react-i18next' +import { useSession } from '../../session/SessionContext' +import { Route } from '../../Route.enum' + +export function NoDatasetsMessage() { + const { t } = useTranslation('home') + const { user } = useSession() + + return ( + <div className={styles['empty-message-container']}> + {user ? ( + <p>{t('noDatasetsMessage.authenticated')}</p> + ) : ( + <Trans i18nKey="noDatasetsMessage.anonymous"> + <p> + This dataverse currently has no datasets. Please <a href={Route.LOG_IN}>log in</a> to + see if you are able to add to it. + </p> + </Trans> + )} + </div> + ) +} diff --git a/src/sections/home/datasets-list/dataset-card/DatasetCard.module.scss b/src/sections/home/datasets-list/dataset-card/DatasetCard.module.scss new file mode 100644 index 000000000..92f7696b2 --- /dev/null +++ b/src/sections/home/datasets-list/dataset-card/DatasetCard.module.scss @@ -0,0 +1,71 @@ +@use 'sass:color'; +@import "node_modules/@iqss/dataverse-design-system/src/lib/assets/styles/design-tokens/colors.module"; +@import "node_modules/@iqss/dataverse-design-system/src/lib/assets/styles/design-tokens/typography.module"; + +.container { + margin: 6px 0; + padding: 4px 10px; + border: 1px solid $dv-info-border-color; +} + +.header { + display: flex; + justify-content: space-between; +} + +.title { + display: flex; + + > * { + margin-right: .5em; + } +} + +.icon { + margin-top: 2px; + font-size: 1.3em; + + > div >span { + margin-right: 0; + } +} + +.thumbnail { + width: 48px; + margin: 8px 12px 6px 0; + font-size: 2.8em; + + img { + vertical-align: top; + } +} + +.info { + display: flex; +} + +.description { + display: flex; + flex-direction: column; + width: 100%; + font-size: $dv-font-size-sm; +} + +.date { + color: $dv-subtext-color; + +} + +.citation-box { + margin-top: 4px; + margin-bottom: .5em; + padding: 4px; + background-color: color.adjust($dv-primary-color, $lightness: 51%) ; +} + +.citation-box-deaccessioned { + margin-top: 4px; + margin-bottom: .5em; + padding: 4px; + background-color: color.adjust($dv-danger-box-color, $lightness: 6%); +} \ No newline at end of file diff --git a/src/sections/home/datasets-list/dataset-card/DatasetCard.tsx b/src/sections/home/datasets-list/dataset-card/DatasetCard.tsx new file mode 100644 index 000000000..6fe1f807e --- /dev/null +++ b/src/sections/home/datasets-list/dataset-card/DatasetCard.tsx @@ -0,0 +1,21 @@ +import { DatasetPreview } from '../../../../dataset/domain/models/DatasetPreview' +import styles from './DatasetCard.module.scss' +import { DatasetCardHeader } from './DatasetCardHeader' +import { DatasetCardThumbnail } from './DatasetCardThumbnail' +import { DatasetCardInfo } from './DatasetCardInfo' + +interface DatasetCardProps { + dataset: DatasetPreview +} + +export function DatasetCard({ dataset }: DatasetCardProps) { + return ( + <article className={styles.container}> + <DatasetCardHeader dataset={dataset} /> + <div className={styles.info}> + <DatasetCardThumbnail dataset={dataset} /> + <DatasetCardInfo dataset={dataset} /> + </div> + </article> + ) +} diff --git a/src/sections/home/datasets-list/dataset-card/DatasetCardHeader.tsx b/src/sections/home/datasets-list/dataset-card/DatasetCardHeader.tsx new file mode 100644 index 000000000..9e927b2fc --- /dev/null +++ b/src/sections/home/datasets-list/dataset-card/DatasetCardHeader.tsx @@ -0,0 +1,25 @@ +import styles from './DatasetCard.module.scss' +import { LinkToPage } from '../../../shared/link-to-page/LinkToPage' +import { Route } from '../../../Route.enum' +import { DatasetLabels } from '../../../dataset/dataset-labels/DatasetLabels' +import { DatasetPreview } from '../../../../dataset/domain/models/DatasetPreview' +import { DatasetIcon } from '../../../dataset/dataset-icon/DatasetIcon' + +interface DatasetCardHeaderProps { + dataset: DatasetPreview +} +export function DatasetCardHeader({ dataset }: DatasetCardHeaderProps) { + return ( + <div className={styles.header}> + <div className={styles.title}> + <LinkToPage page={Route.DATASETS} searchParams={{ persistentId: dataset.persistentId }}> + {dataset.title} + </LinkToPage> + <DatasetLabels labels={dataset.labels} /> + </div> + <div className={styles.icon}> + <DatasetIcon /> + </div> + </div> + ) +} diff --git a/src/sections/home/datasets-list/dataset-card/DatasetCardInfo.tsx b/src/sections/home/datasets-list/dataset-card/DatasetCardInfo.tsx new file mode 100644 index 000000000..76a03a645 --- /dev/null +++ b/src/sections/home/datasets-list/dataset-card/DatasetCardInfo.tsx @@ -0,0 +1,23 @@ +import styles from './DatasetCard.module.scss' +import { DateHelper } from '../../../../shared/domain/helpers/DateHelper' +import { DatasetPreview } from '../../../../dataset/domain/models/DatasetPreview' +import { CitationDescription } from '../../../shared/citation/CitationDescription' + +interface DatasetCardInfoProps { + dataset: DatasetPreview +} + +export function DatasetCardInfo({ dataset }: DatasetCardInfoProps) { + return ( + <div className={styles.description}> + <span className={styles.date}>{DateHelper.toDisplayFormat(dataset.releaseOrCreateDate)}</span> + <span + className={ + dataset.isDeaccessioned ? styles['citation-box-deaccessioned'] : styles['citation-box'] + }> + <CitationDescription citation={dataset.citation} /> + </span> + <span>{dataset.abbreviatedDescription}</span> + </div> + ) +} diff --git a/src/sections/home/datasets-list/dataset-card/DatasetCardThumbnail.tsx b/src/sections/home/datasets-list/dataset-card/DatasetCardThumbnail.tsx new file mode 100644 index 000000000..cba65b215 --- /dev/null +++ b/src/sections/home/datasets-list/dataset-card/DatasetCardThumbnail.tsx @@ -0,0 +1,23 @@ +import styles from './DatasetCard.module.scss' +import { DatasetPreview } from '../../../../dataset/domain/models/DatasetPreview' +import { LinkToPage } from '../../../shared/link-to-page/LinkToPage' +import { Route } from '../../../Route.enum' +import { DatasetThumbnail } from '../../../dataset/dataset-thumbnail/DatasetThumbnail' + +interface DatasetCardThumbnailProps { + dataset: DatasetPreview +} + +export function DatasetCardThumbnail({ dataset }: DatasetCardThumbnailProps) { + return ( + <div className={styles.thumbnail}> + <LinkToPage page={Route.DATASETS} searchParams={{ persistentId: dataset.persistentId }}> + <DatasetThumbnail + title={dataset.title} + thumbnail={dataset.thumbnail} + isDeaccessioned={dataset.isDeaccessioned} + /> + </LinkToPage> + </div> + ) +} diff --git a/src/sections/home/datasets-list/useDatasets.tsx b/src/sections/home/datasets-list/useDatasets.tsx new file mode 100644 index 000000000..8bb900423 --- /dev/null +++ b/src/sections/home/datasets-list/useDatasets.tsx @@ -0,0 +1,65 @@ +import { useEffect, useState } from 'react' +import { DatasetRepository } from '../../../dataset/domain/repositories/DatasetRepository' +import { getDatasets } from '../../../dataset/domain/useCases/getDatasets' +import { getTotalDatasetsCount } from '../../../dataset/domain/useCases/getTotalDatasetsCount' +import { TotalDatasetsCount } from '../../../dataset/domain/models/TotalDatasetsCount' +import { DatasetPaginationInfo } from '../../../dataset/domain/models/DatasetPaginationInfo' +import { DatasetPreview } from '../../../dataset/domain/models/DatasetPreview' + +export function useDatasets( + datasetRepository: DatasetRepository, + onPaginationInfoChange: (paginationInfo: DatasetPaginationInfo) => void, + paginationInfo: DatasetPaginationInfo +) { + const [datasets, setDatasets] = useState<DatasetPreview[]>([]) + const [isLoading, setIsLoading] = useState<boolean>(true) + const [totalDatasetsCount, setTotalDatasetsCount] = useState<TotalDatasetsCount>() + + const fetchTotalDatasetsCount: () => Promise<TotalDatasetsCount> = () => { + return getTotalDatasetsCount(datasetRepository) + .then((totalDatasetsCount: TotalDatasetsCount) => { + setTotalDatasetsCount(totalDatasetsCount) + if (totalDatasetsCount !== paginationInfo.totalItems) { + onPaginationInfoChange(paginationInfo.withTotal(totalDatasetsCount)) + } + return totalDatasetsCount + }) + .catch(() => { + throw new Error('There was an error getting the datasets count info') + }) + } + const fetchDatasets = (totalDatasetsCount: TotalDatasetsCount) => { + if (typeof totalDatasetsCount !== 'undefined') { + if (totalDatasetsCount === 0) { + setIsLoading(false) + return + } + return getDatasets(datasetRepository, paginationInfo.withTotal(totalDatasetsCount)) + .then((datasets: DatasetPreview[]) => { + setDatasets(datasets) + setIsLoading(false) + return datasets + }) + .catch(() => { + throw new Error('There was an error getting the datasets') + }) + } + } + + useEffect(() => { + setIsLoading(true) + + fetchTotalDatasetsCount() + .then((totalDatasetsCount) => fetchDatasets(totalDatasetsCount)) + .catch(() => { + console.error('There was an error getting the datasets') + setIsLoading(false) + }) + }, [datasetRepository, paginationInfo.page]) + + return { + datasets, + totalDatasetsCount, + isLoading + } +} diff --git a/src/sections/layout/header/Header.tsx b/src/sections/layout/header/Header.tsx index e83b454be..7121bd1f1 100644 --- a/src/sections/layout/header/Header.tsx +++ b/src/sections/layout/header/Header.tsx @@ -3,22 +3,31 @@ import { useTranslation } from 'react-i18next' import { Navbar } from '@iqss/dataverse-design-system' import { Route } from '../../Route.enum' import { useSession } from '../../session/SessionContext' +import { useNavigate } from 'react-router-dom' +const currentPage = 0 export function Header() { const { t } = useTranslation('header') const { user, logout } = useSession() + const navigate = useNavigate() const baseRemoteUrl = import.meta.env.VITE_DATAVERSE_BACKEND_URL as string + const onLogoutClick = () => { + void logout().then(() => { + navigate(currentPage) + }) + } + return ( <Navbar brand={{ title: t('brandTitle'), - href: Route.HOME, + href: `/spa${Route.HOME}`, logoImgSrc: logo }}> {user ? ( <Navbar.Dropdown title={user.name} id="dropdown-user"> - <Navbar.Dropdown.Item href={Route.LOG_OUT} onClick={logout}> + <Navbar.Dropdown.Item href="#" onClick={onLogoutClick}> {t('logOut')} </Navbar.Dropdown.Item> </Navbar.Dropdown> diff --git a/src/sections/session/SessionContext.ts b/src/sections/session/SessionContext.ts index 4840d510a..5bafb77cf 100644 --- a/src/sections/session/SessionContext.ts +++ b/src/sections/session/SessionContext.ts @@ -4,12 +4,12 @@ import { User } from '../../users/domain/models/User' interface SessionContextProps { user: User | null setUser: (user: User) => void - logout: () => void + logout: () => Promise<void> } export const SessionContext = createContext<SessionContextProps>({ user: null, setUser: () => {}, - logout: () => {} + logout: () => Promise.resolve() }) export const useSession = () => useContext(SessionContext) diff --git a/src/sections/session/SessionProvider.tsx b/src/sections/session/SessionProvider.tsx index 56fa07d81..4fb95b74b 100644 --- a/src/sections/session/SessionProvider.tsx +++ b/src/sections/session/SessionProvider.tsx @@ -20,7 +20,7 @@ export function SessionProvider({ repository, children }: PropsWithChildren<Sess }, [repository]) const submitLogOut = () => { - logOut(repository) + return logOut(repository) .then(() => { setUser(null) }) diff --git a/src/sections/shared/citation/CitationDescription.tsx b/src/sections/shared/citation/CitationDescription.tsx new file mode 100644 index 000000000..eb2730bd2 --- /dev/null +++ b/src/sections/shared/citation/CitationDescription.tsx @@ -0,0 +1,11 @@ +import parse from 'html-react-parser' + +interface CitationDescriptionProps { + citation: string +} + +export function CitationDescription({ citation }: CitationDescriptionProps) { + const citationAsReactElement = parse(citation) + + return <span>{citationAsReactElement}</span> +} diff --git a/src/sections/shared/link-to-page/LinkToPage.tsx b/src/sections/shared/link-to-page/LinkToPage.tsx new file mode 100644 index 000000000..dd38a28af --- /dev/null +++ b/src/sections/shared/link-to-page/LinkToPage.tsx @@ -0,0 +1,20 @@ +import { Link } from 'react-router-dom' +import { PropsWithChildren } from 'react' +import { Route } from '../../Route.enum' + +interface LinkToPageProps { + page: Route + searchParams?: Record<string, string> +} + +export function LinkToPage({ children, page, searchParams }: PropsWithChildren<LinkToPageProps>) { + const searchParamsString: string = searchParams ? '?' + encodeSearchParamsToURI(searchParams) : '' + + return <Link to={`${page}${searchParamsString}`}>{children}</Link> +} + +const encodeSearchParamsToURI = (searchParams: Record<string, string>) => { + return Object.entries(searchParams) + .map(([key, value]) => `${key}=${value}`) + .join('&') +} diff --git a/src/sections/dataset/dataset-files/files-pagination/PageNumbersButtons.tsx b/src/sections/shared/pagination/PageNumbersButtons.tsx similarity index 100% rename from src/sections/dataset/dataset-files/files-pagination/PageNumbersButtons.tsx rename to src/sections/shared/pagination/PageNumbersButtons.tsx diff --git a/src/sections/dataset/dataset-files/files-pagination/PageNumbersButtonsWithEllipsis.tsx b/src/sections/shared/pagination/PageNumbersButtonsWithEllipsis.tsx similarity index 100% rename from src/sections/dataset/dataset-files/files-pagination/PageNumbersButtonsWithEllipsis.tsx rename to src/sections/shared/pagination/PageNumbersButtonsWithEllipsis.tsx diff --git a/src/sections/dataset/dataset-files/files-pagination/PageSizeSelector.tsx b/src/sections/shared/pagination/PageSizeSelector.tsx similarity index 80% rename from src/sections/dataset/dataset-files/files-pagination/PageSizeSelector.tsx rename to src/sections/shared/pagination/PageSizeSelector.tsx index a5acbf945..2378453df 100644 --- a/src/sections/dataset/dataset-files/files-pagination/PageSizeSelector.tsx +++ b/src/sections/shared/pagination/PageSizeSelector.tsx @@ -1,20 +1,24 @@ -import styles from './FilesPagination.module.scss' +import styles from './Pagination.module.scss' import { useTranslation } from 'react-i18next' export function PageSizeSelector({ + itemName, pageSize, setPageSize }: { + itemName: string pageSize: number setPageSize: (pageSize: number) => void }) { - const { t } = useTranslation('files') + const { t } = useTranslation('pagination') const availableSizes = [10, 25, 50] return ( <div className={styles['size-selector-container']}> <label htmlFor="files-per-page-selector" className={styles['size-selector-container__text']}> - {t('table.pagination.pageSize')} + {t('pageSize', { + item: itemName + })} </label> <select id="files-per-page-selector" diff --git a/src/sections/dataset/dataset-files/files-pagination/FilesPagination.module.scss b/src/sections/shared/pagination/Pagination.module.scss similarity index 55% rename from src/sections/dataset/dataset-files/files-pagination/FilesPagination.module.scss rename to src/sections/shared/pagination/Pagination.module.scss index f75206577..c5b97fb62 100644 --- a/src/sections/dataset/dataset-files/files-pagination/FilesPagination.module.scss +++ b/src/sections/shared/pagination/Pagination.module.scss @@ -1,3 +1,5 @@ +@import "node_modules/@iqss/dataverse-design-system/src/lib/assets/styles/design-tokens/typography.module"; + .row { justify-content: center; } @@ -15,4 +17,9 @@ &__text { margin-right: 14px; } +} + +.results { + margin-right: 4px; + font-weight: $dv-font-weight-bold; } \ No newline at end of file diff --git a/src/sections/dataset/dataset-files/files-pagination/FilesPagination.tsx b/src/sections/shared/pagination/PaginationControls.tsx similarity index 62% rename from src/sections/dataset/dataset-files/files-pagination/FilesPagination.tsx rename to src/sections/shared/pagination/PaginationControls.tsx index 7a6c3c976..834b19842 100644 --- a/src/sections/dataset/dataset-files/files-pagination/FilesPagination.tsx +++ b/src/sections/shared/pagination/PaginationControls.tsx @@ -1,25 +1,27 @@ import { Col, Pagination, Row } from '@iqss/dataverse-design-system' import { PageNumbersButtonsWithEllipsis } from './PageNumbersButtonsWithEllipsis' import { PageSizeSelector } from './PageSizeSelector' -import styles from './FilesPagination.module.scss' -import { FilePaginationInfo } from '../../../../files/domain/models/FilePaginationInfo' +import styles from './Pagination.module.scss' +import { PaginationInfo } from '../../../shared/domain/models/PaginationInfo' import { useEffect, useState } from 'react' +import { FilePaginationInfo } from '../../../files/domain/models/FilePaginationInfo' +import { DatasetPaginationInfo } from '../../../dataset/domain/models/DatasetPaginationInfo' -interface FilesPaginationProps { - onPaginationInfoChange: (paginationInfo: FilePaginationInfo) => void - page: number - pageSize: number - total: number +interface PaginationProps { + onPaginationInfoChange: ( + paginationInfo: PaginationInfo<DatasetPaginationInfo | FilePaginationInfo> + ) => void + initialPaginationInfo: PaginationInfo<DatasetPaginationInfo | FilePaginationInfo> + showPageSizeSelector?: boolean } const NO_PAGES = 0 -export function FilesPagination({ +export function PaginationControls({ onPaginationInfoChange, - page, - pageSize, - total -}: FilesPaginationProps) { - const [paginationInfo, setPaginationInfo] = useState<FilePaginationInfo>( - new FilePaginationInfo(page, pageSize, total) + initialPaginationInfo, + showPageSizeSelector = true +}: PaginationProps) { + const [paginationInfo, setPaginationInfo] = useState<DatasetPaginationInfo | FilePaginationInfo>( + initialPaginationInfo ) const goToPage = (newPage: number) => { setPaginationInfo(paginationInfo.goToPage(newPage)) @@ -39,8 +41,8 @@ export function FilesPagination({ }, [paginationInfo.pageSize, paginationInfo.page]) useEffect(() => { - setPaginationInfo(paginationInfo.withTotal(total)) - }, [total]) + setPaginationInfo(paginationInfo.withTotal(initialPaginationInfo.totalItems)) + }, [initialPaginationInfo.totalItems]) if (paginationInfo.totalPages === NO_PAGES) { return <></> @@ -72,7 +74,13 @@ export function FilesPagination({ disabled={!paginationInfo.hasNextPage} /> </Pagination> - <PageSizeSelector pageSize={paginationInfo.pageSize} setPageSize={setPageSize} /> + {showPageSizeSelector && ( + <PageSizeSelector + itemName={paginationInfo.itemName} + pageSize={paginationInfo.pageSize} + setPageSize={setPageSize} + /> + )} </div> </Col> </Row> diff --git a/src/sections/shared/pagination/PaginationResultsInfo.tsx b/src/sections/shared/pagination/PaginationResultsInfo.tsx new file mode 100644 index 000000000..df57ad701 --- /dev/null +++ b/src/sections/shared/pagination/PaginationResultsInfo.tsx @@ -0,0 +1,23 @@ +import styles from './Pagination.module.scss' +import { PaginationInfo } from '../../../shared/domain/models/PaginationInfo' +import { useTranslation } from 'react-i18next' +import { DatasetPaginationInfo } from '../../../dataset/domain/models/DatasetPaginationInfo' +import { FilePaginationInfo } from '../../../files/domain/models/FilePaginationInfo' + +interface PaginationResultsInfoProps { + paginationInfo: PaginationInfo<DatasetPaginationInfo | FilePaginationInfo> +} + +export function PaginationResultsInfo({ paginationInfo }: PaginationResultsInfoProps) { + const { t } = useTranslation('pagination') + return ( + <span className={styles.results}> + {t('results', { + start: paginationInfo.pageStartItem, + end: paginationInfo.pageEndItem, + total: paginationInfo.totalItems, + item: paginationInfo.itemName + })} + </span> + ) +} diff --git a/src/shared/domain/helpers/DateHelper.ts b/src/shared/domain/helpers/DateHelper.ts new file mode 100644 index 000000000..021ec2a68 --- /dev/null +++ b/src/shared/domain/helpers/DateHelper.ts @@ -0,0 +1,9 @@ +export class DateHelper { + static toDisplayFormat(date: Date): string { + return date.toLocaleDateString(Intl.DateTimeFormat().resolvedOptions().locale, { + year: 'numeric', + month: 'short', + day: 'numeric' + }) + } +} diff --git a/src/shared/domain/models/PaginationInfo.ts b/src/shared/domain/models/PaginationInfo.ts new file mode 100644 index 000000000..c21822b7c --- /dev/null +++ b/src/shared/domain/models/PaginationInfo.ts @@ -0,0 +1,72 @@ +export class PaginationInfo<T extends PaginationInfo<T>> { + constructor( + public readonly page: number = 1, + public readonly pageSize: number = 10, + public readonly totalItems: number = 0, + public readonly itemName: string = 'Item' + ) {} + + get offset(): number { + return (this.page - 1) * this.pageSize + } + + get pageStartItem(): number { + return (this.page - 1) * this.pageSize + 1 + } + + get pageEndItem(): number { + return Math.min(this.pageStartItem + this.pageSize - 1, this.totalItems) + } + + withTotal(total: number): T { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return new (this.constructor as new (...args: any[]) => T)(this.page, this.pageSize, total) + } + goToPage(page: number): T { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return new (this.constructor as new (...args: any[]) => T)(page, this.pageSize, this.totalItems) + } + + goToPreviousPage(): T { + if (!this.previousPage) throw new Error('No previous page') + return this.goToPage(this.previousPage) + } + + goToNextPage(): T { + if (!this.nextPage) throw new Error('No next page') + return this.goToPage(this.nextPage) + } + + withPageSize(pageSize: number): T { + const getNewPage = (oldPageSize: number, newPageSize: number) => { + const newPage = Math.ceil(((this.page - 1) * oldPageSize + 1) / newPageSize) + return newPage > 0 ? newPage : 1 + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return new (this.constructor as new (...args: any[]) => T)( + getNewPage(this.pageSize, pageSize), + pageSize, + this.totalItems + ) + } + + get totalPages(): number { + return Math.ceil(this.totalItems / this.pageSize) + } + + get hasPreviousPage(): boolean { + return this.page > 1 + } + + get hasNextPage(): boolean { + return this.page < this.totalPages + } + + get previousPage(): number | undefined { + return this.hasPreviousPage ? this.page - 1 : undefined + } + + get nextPage(): number | undefined { + return this.hasNextPage ? this.page + 1 : undefined + } +} diff --git a/src/stories/WithLayout.tsx b/src/stories/WithLayout.tsx index 2dc1dcc0d..34a5af36d 100644 --- a/src/stories/WithLayout.tsx +++ b/src/stories/WithLayout.tsx @@ -1,16 +1,14 @@ import { StoryFn } from '@storybook/react' -import { MemoryRouter as Router, Routes, Route } from 'react-router-dom' +import { Routes, Route } from 'react-router-dom' import { Layout } from '../sections/layout/Layout' import { LoadingProvider } from '../sections/loading/LoadingProvider' export const WithLayout = (Story: StoryFn) => ( <LoadingProvider> - <Router> - <Routes> - <Route element={<Layout />}> - <Route path="/*" element={<Story />} /> - </Route> - </Routes> - </Router> + <Routes> + <Route element={<Layout />}> + <Route path="/*" element={<Story />} /> + </Route> + </Routes> </LoadingProvider> ) diff --git a/src/stories/WithLoggedInUser.tsx b/src/stories/WithLoggedInUser.tsx index 75a5878a4..233c46e6f 100644 --- a/src/stories/WithLoggedInUser.tsx +++ b/src/stories/WithLoggedInUser.tsx @@ -5,7 +5,7 @@ import { UserMother } from '../../tests/component/users/domain/models/UserMother export const WithLoggedInUser = (Story: StoryFn) => { return ( <SessionContext.Provider - value={{ user: UserMother.create(), logout: () => {}, setUser: () => {} }}> + value={{ user: UserMother.create(), logout: () => Promise.resolve(), setUser: () => {} }}> <Story /> </SessionContext.Provider> ) diff --git a/src/stories/dataset/DatasetLoadingMockRepository.ts b/src/stories/dataset/DatasetLoadingMockRepository.ts new file mode 100644 index 000000000..b7051afc7 --- /dev/null +++ b/src/stories/dataset/DatasetLoadingMockRepository.ts @@ -0,0 +1,10 @@ +import { DatasetMockRepository } from './DatasetMockRepository' +import { DatasetPaginationInfo } from '../../dataset/domain/models/DatasetPaginationInfo' +import { DatasetPreview } from '../../dataset/domain/models/DatasetPreview' + +export class DatasetLoadingMockRepository extends DatasetMockRepository { + // eslint-disable-next-line unused-imports/no-unused-vars + getAll(paginationInfo: DatasetPaginationInfo): Promise<DatasetPreview[]> { + return new Promise(() => {}) + } +} diff --git a/src/stories/dataset/DatasetMockRepository.ts b/src/stories/dataset/DatasetMockRepository.ts new file mode 100644 index 000000000..9ffa083ed --- /dev/null +++ b/src/stories/dataset/DatasetMockRepository.ts @@ -0,0 +1,49 @@ +import { Dataset } from '../../dataset/domain/models/Dataset' +import { DatasetRepository } from '../../dataset/domain/repositories/DatasetRepository' +import { DatasetMother } from '../../../tests/component/dataset/domain/models/DatasetMother' +import { TotalDatasetsCount } from '../../dataset/domain/models/TotalDatasetsCount' +import { DatasetPaginationInfo } from '../../dataset/domain/models/DatasetPaginationInfo' +import { DatasetPreview } from '../../dataset/domain/models/DatasetPreview' +import { DatasetPreviewMother } from '../../../tests/component/dataset/domain/models/DatasetPreviewMother' + +export class DatasetMockRepository implements DatasetRepository { + // eslint-disable-next-line unused-imports/no-unused-vars + getAll(paginationInfo: DatasetPaginationInfo): Promise<DatasetPreview[]> { + return new Promise((resolve) => { + setTimeout(() => { + resolve(DatasetPreviewMother.createManyRealistic(paginationInfo.pageSize)) + }, 1000) + }) + } + + getTotalDatasetsCount(): Promise<TotalDatasetsCount> { + return new Promise((resolve) => { + setTimeout(() => { + resolve(200) + }, 1000) + }) + } + + getByPersistentId( + // eslint-disable-next-line unused-imports/no-unused-vars + persistentId: string, + // eslint-disable-next-line unused-imports/no-unused-vars + version?: string | undefined + ): Promise<Dataset | undefined> { + return new Promise((resolve) => { + setTimeout(() => { + resolve(DatasetMother.createRealistic()) + }, 1000) + }) + } + getByPrivateUrlToken( + // eslint-disable-next-line unused-imports/no-unused-vars + privateUrlToken: string + ): Promise<Dataset | undefined> { + return new Promise((resolve) => { + setTimeout(() => { + resolve(DatasetMother.createRealistic()) + }, 1000) + }) + } +} diff --git a/src/stories/dataset/NoDatasetsMockRepository.ts b/src/stories/dataset/NoDatasetsMockRepository.ts new file mode 100644 index 000000000..b5fa7b41c --- /dev/null +++ b/src/stories/dataset/NoDatasetsMockRepository.ts @@ -0,0 +1,22 @@ +import { DatasetMockRepository } from './DatasetMockRepository' +import { DatasetPaginationInfo } from '../../dataset/domain/models/DatasetPaginationInfo' +import { DatasetPreview } from '../../dataset/domain/models/DatasetPreview' +import { TotalDatasetsCount } from '../../dataset/domain/models/TotalDatasetsCount' +export class NoDatasetsMockRepository extends DatasetMockRepository { + // eslint-disable-next-line unused-imports/no-unused-vars + getAll(paginationInfo: DatasetPaginationInfo): Promise<DatasetPreview[]> { + return new Promise((resolve) => { + setTimeout(() => { + resolve([]) + }, 1000) + }) + } + + getTotalDatasetsCount(): Promise<TotalDatasetsCount> { + return new Promise((resolve) => { + setTimeout(() => { + resolve(0) + }, 1000) + }) + } +} diff --git a/src/stories/dataset/WithDataset.tsx b/src/stories/dataset/WithDataset.tsx index 6c1df0ddf..b559c9532 100644 --- a/src/stories/dataset/WithDataset.tsx +++ b/src/stories/dataset/WithDataset.tsx @@ -1,23 +1,10 @@ import { StoryFn } from '@storybook/react' import { DatasetProvider } from '../../sections/dataset/DatasetProvider' import { DatasetRepository } from '../../dataset/domain/repositories/DatasetRepository' -import { Dataset } from '../../dataset/domain/models/Dataset' -import { DatasetMother } from '../../../tests/component/dataset/domain/models/DatasetMother' +import { DatasetMockRepository } from './DatasetMockRepository' export const WithDataset = (Story: StoryFn) => { - const datasetRepository = {} as DatasetRepository - datasetRepository.getByPersistentId = ( - // eslint-disable-next-line unused-imports/no-unused-vars - persistentId: string, - // eslint-disable-next-line unused-imports/no-unused-vars - version?: string | undefined - ): Promise<Dataset | undefined> => { - return new Promise((resolve) => { - setTimeout(() => { - resolve(DatasetMother.createRealistic()) - }, 1000) - }) - } + const datasetRepository: DatasetRepository = new DatasetMockRepository() return ( <DatasetProvider repository={datasetRepository} diff --git a/src/stories/dataset/dataset-citation/DatasetCitation.stories.tsx b/src/stories/dataset/dataset-citation/DatasetCitation.stories.tsx index cd7d511d4..0a52d5264 100644 --- a/src/stories/dataset/dataset-citation/DatasetCitation.stories.tsx +++ b/src/stories/dataset/dataset-citation/DatasetCitation.stories.tsx @@ -22,7 +22,7 @@ export const Default: Story = { <br></br> <br></br> <DatasetCitation - title={dataset.getTitle()} + title={dataset.title} citation={dataset.citation} version={dataset.version} /> @@ -39,7 +39,7 @@ export const WithThumbnail: Story = { <br></br> <br></br> <DatasetCitation - title={dataset.getTitle()} + title={dataset.title} thumbnail={dataset.thumbnail} citation={dataset.citation} version={dataset.version} @@ -71,7 +71,7 @@ export const DraftVersion: Story = { <br></br> <br></br> <DatasetCitation - title={dataset.getTitle()} + title={dataset.title} citation={dataset.citation} version={dataset.version} /> @@ -104,7 +104,7 @@ export const Deaccessioned: Story = { <br></br> <br></br> <DatasetCitation - title={dataset.getTitle()} + title={dataset.title} citation={dataset.citation} version={dataset.version} /> @@ -128,7 +128,7 @@ export const Anonymized: Story = { <br></br> <br></br> <DatasetCitation - title={dataset.getTitle()} + title={dataset.title} citation={dataset.citation} version={dataset.version} /> diff --git a/src/stories/dataset/dataset-files/files-pagination/FilesPagination.stories.tsx b/src/stories/dataset/dataset-files/files-pagination/FilesPagination.stories.tsx deleted file mode 100644 index b090683ab..000000000 --- a/src/stories/dataset/dataset-files/files-pagination/FilesPagination.stories.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Meta, StoryObj } from '@storybook/react' -import { WithI18next } from '../../../WithI18next' -import { FilesPagination } from '../../../../sections/dataset/dataset-files/files-pagination/FilesPagination' - -const meta: Meta<typeof FilesPagination> = { - title: 'Sections/Dataset Page/DatasetFiles/FilesPagination', - component: FilesPagination, - decorators: [WithI18next] -} - -export default meta -type Story = StoryObj<typeof FilesPagination> - -const emptyFunction = () => {} -export const Default: Story = { - render: () => ( - <FilesPagination page={10} pageSize={10} total={200} onPaginationInfoChange={emptyFunction} /> - ) -} - -export const NoEllipsis: Story = { - render: () => ( - <FilesPagination page={1} pageSize={10} total={100} onPaginationInfoChange={emptyFunction} /> - ) -} diff --git a/src/stories/files/FileMockRepository.ts b/src/stories/files/FileMockRepository.ts index 317facbc4..020d15199 100644 --- a/src/stories/files/FileMockRepository.ts +++ b/src/stories/files/FileMockRepository.ts @@ -3,11 +3,11 @@ import { FilesMockData } from './FileMockData' import { File, FileDownloadMode } from '../../files/domain/models/File' import { FilesCountInfo } from '../../files/domain/models/FilesCountInfo' import { FilesCountInfoMother } from '../../../tests/component/files/domain/models/FilesCountInfoMother' -import { FilePaginationInfo } from '../../files/domain/models/FilePaginationInfo' import { FileUserPermissionsMother } from '../../../tests/component/files/domain/models/FileUserPermissionsMother' import { FileUserPermissions } from '../../files/domain/models/FileUserPermissions' import { DatasetVersion } from '../../dataset/domain/models/Dataset' import { FileCriteria } from '../../files/domain/models/FileCriteria' +import { FilePaginationInfo } from '../../files/domain/models/FilePaginationInfo' import { FileMother } from '../../../tests/component/files/domain/models/FileMother' export class FileMockRepository implements FileRepository { diff --git a/src/stories/hello-dataverse/HelloDataverse.stories.tsx b/src/stories/hello-dataverse/HelloDataverse.stories.tsx deleted file mode 100644 index f95572055..000000000 --- a/src/stories/hello-dataverse/HelloDataverse.stories.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react' -import { HelloDataverse } from '../../sections/hello-dataverse/HelloDataverse' -import { WithI18next } from '../WithI18next' -import { WithLayout } from '../WithLayout' - -const meta: Meta<typeof HelloDataverse> = { - title: 'Pages/Hello Dataverse', - component: HelloDataverse, - decorators: [WithI18next, WithLayout] -} - -export default meta -type Story = StoryObj<typeof HelloDataverse> - -export const Default: Story = { - render: () => <HelloDataverse /> -} diff --git a/src/stories/home/Home.stories.tsx b/src/stories/home/Home.stories.tsx new file mode 100644 index 000000000..800bb977e --- /dev/null +++ b/src/stories/home/Home.stories.tsx @@ -0,0 +1,28 @@ +import type { Meta, StoryObj } from '@storybook/react' +import { Home } from '../../sections/home/Home' +import { WithI18next } from '../WithI18next' +import { WithLayout } from '../WithLayout' +import { DatasetMockRepository } from '../dataset/DatasetMockRepository' +import { DatasetLoadingMockRepository } from '../dataset/DatasetLoadingMockRepository' +import { NoDatasetsMockRepository } from '../dataset/NoDatasetsMockRepository' + +const meta: Meta<typeof Home> = { + title: 'Pages/Home', + component: Home, + decorators: [WithI18next, WithLayout] +} + +export default meta +type Story = StoryObj<typeof Home> + +export const Default: Story = { + render: () => <Home datasetRepository={new DatasetMockRepository()} /> +} + +export const Loading: Story = { + render: () => <Home datasetRepository={new DatasetLoadingMockRepository()} /> +} + +export const NoResults: Story = { + render: () => <Home datasetRepository={new NoDatasetsMockRepository()} /> +} diff --git a/src/stories/home/datasets-list/DatasetCard.stories.tsx b/src/stories/home/datasets-list/DatasetCard.stories.tsx new file mode 100644 index 000000000..26f6af0da --- /dev/null +++ b/src/stories/home/datasets-list/DatasetCard.stories.tsx @@ -0,0 +1,21 @@ +import { Meta, StoryObj } from '@storybook/react' +import { WithI18next } from '../../WithI18next' +import { DatasetCard } from '../../../sections/home/datasets-list/dataset-card/DatasetCard' +import { DatasetPreviewMother } from '../../../../tests/component/dataset/domain/models/DatasetPreviewMother' + +const meta: Meta<typeof DatasetCard> = { + title: 'Sections/Home/DatasetCard', + component: DatasetCard, + decorators: [WithI18next] +} + +export default meta +type Story = StoryObj<typeof DatasetCard> + +export const Default: Story = { + render: () => <DatasetCard dataset={DatasetPreviewMother.createDraft()} /> +} + +export const Deaccessioned: Story = { + render: () => <DatasetCard dataset={DatasetPreviewMother.createDeaccessioned()} /> +} diff --git a/src/stories/home/datasets-list/DatasetsList.stories.tsx b/src/stories/home/datasets-list/DatasetsList.stories.tsx new file mode 100644 index 000000000..51287336f --- /dev/null +++ b/src/stories/home/datasets-list/DatasetsList.stories.tsx @@ -0,0 +1,27 @@ +import { Meta, StoryObj } from '@storybook/react' +import { WithI18next } from '../../WithI18next' +import { DatasetMockRepository } from '../../dataset/DatasetMockRepository' +import { DatasetsList } from '../../../sections/home/datasets-list/DatasetsList' +import { DatasetLoadingMockRepository } from '../../dataset/DatasetLoadingMockRepository' +import { NoDatasetsMockRepository } from '../../dataset/NoDatasetsMockRepository' + +const meta: Meta<typeof DatasetsList> = { + title: 'Sections/Home/DatasetsList', + component: DatasetsList, + decorators: [WithI18next] +} + +export default meta +type Story = StoryObj<typeof DatasetsList> + +export const Default: Story = { + render: () => <DatasetsList datasetRepository={new DatasetMockRepository()} /> +} + +export const Loading: Story = { + render: () => <DatasetsList datasetRepository={new DatasetLoadingMockRepository()} /> +} + +export const NoResults: Story = { + render: () => <DatasetsList datasetRepository={new NoDatasetsMockRepository()} /> +} diff --git a/src/stories/home/datasets-list/NoDatasetsMessage.stories.tsx b/src/stories/home/datasets-list/NoDatasetsMessage.stories.tsx new file mode 100644 index 000000000..d81a48e90 --- /dev/null +++ b/src/stories/home/datasets-list/NoDatasetsMessage.stories.tsx @@ -0,0 +1,23 @@ +import { Meta, StoryObj } from '@storybook/react' +import { Home } from '../../../sections/home/Home' +import { WithI18next } from '../../WithI18next' +import { NoDatasetsMessage } from '../../../sections/home/datasets-list/NoDatasetsMessage' +import { WithLoggedInUser } from '../../WithLoggedInUser' + +const meta: Meta<typeof Home> = { + title: 'Sections/Home/NoDatasetsMessage', + component: Home, + decorators: [WithI18next] +} + +export default meta +type Story = StoryObj<typeof Home> + +export const AnonymousUser: Story = { + render: () => <NoDatasetsMessage /> +} + +export const AuthenticatedUser: Story = { + decorators: [WithLoggedInUser], + render: () => <NoDatasetsMessage /> +} diff --git a/src/stories/shared/pagination/PaginationControls.stories.tsx b/src/stories/shared/pagination/PaginationControls.stories.tsx new file mode 100644 index 000000000..a482f80d2 --- /dev/null +++ b/src/stories/shared/pagination/PaginationControls.stories.tsx @@ -0,0 +1,38 @@ +import { Meta, StoryObj } from '@storybook/react' +import { WithI18next } from '../../WithI18next' +import { PaginationControls } from '../../../sections/shared/pagination/PaginationControls' +import { PaginationInfo } from '../../../shared/domain/models/PaginationInfo' +import { DatasetPaginationInfo } from '../../../dataset/domain/models/DatasetPaginationInfo' +import { FilePaginationInfo } from '../../../files/domain/models/FilePaginationInfo' + +const meta: Meta<typeof PaginationControls> = { + title: 'Sections/Shared/Pagination/PaginationControls', + component: PaginationControls, + decorators: [WithI18next] +} + +export default meta +type Story = StoryObj<typeof PaginationControls> + +const emptyFunction = () => {} +export const Default: Story = { + render: () => ( + <PaginationControls + initialPaginationInfo={ + new PaginationInfo<DatasetPaginationInfo | FilePaginationInfo>(10, 10, 200) + } + onPaginationInfoChange={emptyFunction} + /> + ) +} + +export const NoEllipsis: Story = { + render: () => ( + <PaginationControls + initialPaginationInfo={ + new PaginationInfo<DatasetPaginationInfo | FilePaginationInfo>(1, 10, 100) + } + onPaginationInfoChange={emptyFunction} + /> + ) +} diff --git a/tests/component/dataset/domain/models/DatasetMother.ts b/tests/component/dataset/domain/models/DatasetMother.ts index 635785501..e20b2c496 100644 --- a/tests/component/dataset/domain/models/DatasetMother.ts +++ b/tests/component/dataset/domain/models/DatasetMother.ts @@ -2,6 +2,7 @@ import { faker } from '@faker-js/faker' import { ANONYMIZED_FIELD_VALUE, Dataset, + DatasetLabel, DatasetDownloadUrls, DatasetLabelSemanticMeaning, DatasetLabelValue, @@ -195,6 +196,45 @@ export class DatasetLockMother { } } +export class DatasetLabelsMother { + static create(): DatasetLabel[] { + return [{ value: 'Version 1.0', semanticMeaning: DatasetLabelSemanticMeaning.FILE }] + } + + static createDraft(): DatasetLabel[] { + return [ + { + value: DatasetLabelValue.UNPUBLISHED, + semanticMeaning: DatasetLabelSemanticMeaning.WARNING + }, + { value: DatasetLabelValue.DRAFT, semanticMeaning: DatasetLabelSemanticMeaning.DATASET } + ] + } + + static createDeaccessioned(): DatasetLabel[] { + return [ + { + value: DatasetLabelValue.DEACCESSIONED, + semanticMeaning: DatasetLabelSemanticMeaning.DANGER + } + ] + } +} + +export class DatasetCitationMother { + static create(): string { + return 'Finch, Fiona, 2023, "Darwin\'s Finches", <a href="https://doi.org/10.5072/FK2/0YFWKL" target="_blank">https://doi.org/10.5072/FK2/0YFWKL</a>, Root, V1' + } + + static createDraft(): string { + return 'Finch, Fiona, 2023, "Darwin\'s Finches", <a href="https://doi.org/10.5072/FK2/0YFWKL" target="_blank">https://doi.org/10.5072/FK2/0YFWKL</a>, Root, DRAFT VERSION' + } + + static createDeaccessioned(): string { + return 'Finch, Fiona, 2023, "Darwin\'s Finches", <a href="https://doi.org/10.5072/FK2/0YFWKL" target="_blank">https://doi.org/10.5072/FK2/0YFWKL</a>, Root, V1, DEACCESSIONED VERSION' + } +} + export class DatasetFileDownloadSizeMother { static create(props?: Partial<FileDownloadSize>): FileDownloadSize { return new FileDownloadSize( @@ -235,6 +275,10 @@ export class DatasetMother { return undefined } + static createMany(count: number): Dataset[] { + return Array.from({ length: count }, () => this.create()) + } + static create(props?: Partial<Dataset>): Dataset { const dataset = { persistentId: faker.datatype.uuid(), @@ -247,24 +291,7 @@ export class DatasetMother { uri: 'https://creativecommons.org/publicdomain/zero/1.0/', iconUri: 'https://licensebuttons.net/p/zero/1.0/88x31.png' }, - labels: [ - { - value: DatasetLabelValue.IN_REVIEW, - semanticMeaning: faker.helpers.arrayElement(Object.values(DatasetLabelSemanticMeaning)) - }, - { - value: DatasetLabelValue.EMBARGOED, - semanticMeaning: faker.helpers.arrayElement(Object.values(DatasetLabelSemanticMeaning)) - }, - { - value: DatasetLabelValue.UNPUBLISHED, - semanticMeaning: faker.helpers.arrayElement(Object.values(DatasetLabelSemanticMeaning)) - }, - { - value: `Version ${faker.lorem.word()}`, - semanticMeaning: faker.helpers.arrayElement(Object.values(DatasetLabelSemanticMeaning)) - } - ], + labels: DatasetLabelsMother.create(), summaryFields: [ { name: MetadataBlockName.CITATION, diff --git a/tests/component/dataset/domain/models/DatasetPreviewMother.ts b/tests/component/dataset/domain/models/DatasetPreviewMother.ts new file mode 100644 index 000000000..83e080ff9 --- /dev/null +++ b/tests/component/dataset/domain/models/DatasetPreviewMother.ts @@ -0,0 +1,68 @@ +import { faker } from '@faker-js/faker' +import { DatasetPreview } from '../../../../../src/dataset/domain/models/DatasetPreview' +import { DatasetCitationMother, DatasetLabelsMother, DatasetVersionMother } from './DatasetMother' + +export class DatasetPreviewMother { + static createMany(count: number): DatasetPreview[] { + return Array.from({ length: count }, () => this.create()) + } + + static createManyRealistic(count: number): DatasetPreview[] { + return Array.from({ length: count }, () => this.createRealistic()) + } + + static create(props?: Partial<DatasetPreview>): DatasetPreview { + const datasetPreview = { + persistentId: faker.datatype.uuid(), + title: faker.lorem.sentence(), + labels: DatasetLabelsMother.create(), + isDeaccessioned: faker.datatype.boolean(), + thumbnail: faker.datatype.boolean() ? faker.image.imageUrl() : undefined, + releaseOrCreateDate: faker.date.past(), + version: DatasetVersionMother.create(), + citation: DatasetCitationMother.create(), + description: faker.lorem.paragraph(), + ...props + } + + return new DatasetPreview( + datasetPreview.persistentId, + datasetPreview.title, + datasetPreview.version, + datasetPreview.citation, + datasetPreview.labels, + datasetPreview.isDeaccessioned, + datasetPreview.releaseOrCreateDate, + datasetPreview.description, + datasetPreview.thumbnail + ) + } + + static createRealistic(): DatasetPreview { + return faker.datatype.boolean() ? this.createDraft() : this.createDeaccessioned() + } + + static createDraft(): DatasetPreview { + return this.create({ + isDeaccessioned: false, + labels: DatasetLabelsMother.createDraft(), + citation: DatasetCitationMother.createDraft() + }) + } + + static createWithThumbnail(): DatasetPreview { + return this.create({ thumbnail: faker.image.imageUrl(), isDeaccessioned: false }) + } + + static createWithNoThumbnail(): DatasetPreview { + return this.create({ thumbnail: undefined, isDeaccessioned: false }) + } + + static createDeaccessioned(): DatasetPreview { + return this.create({ + isDeaccessioned: true, + labels: DatasetLabelsMother.createDeaccessioned(), + citation: DatasetCitationMother.createDeaccessioned() + }) + } +} diff --git a/tests/component/sections/dataset/Dataset.spec.tsx b/tests/component/sections/dataset/Dataset.spec.tsx index 70b899057..783479fed 100644 --- a/tests/component/sections/dataset/Dataset.spec.tsx +++ b/tests/component/sections/dataset/Dataset.spec.tsx @@ -41,7 +41,7 @@ describe('Dataset', () => { mountWithDataset(<Dataset fileRepository={fileRepository} />, testDataset) cy.findByTestId('dataset-skeleton').should('exist') - cy.findByText(testDataset.getTitle()).should('not.exist') + cy.findByText(testDataset.title).should('not.exist') }) it('renders page not found when dataset is null', () => { @@ -57,7 +57,7 @@ describe('Dataset', () => { mountWithDataset(<Dataset fileRepository={fileRepository} />, testDataset) - cy.findAllByText(testDataset.getTitle()).should('exist') + cy.findAllByText(testDataset.title).should('exist') testDataset.labels.forEach((label) => { cy.findAllByText(label.value).should('exist') @@ -69,7 +69,7 @@ describe('Dataset', () => { mountWithDataset(<Dataset fileRepository={fileRepository} />, testDataset) - cy.findAllByText(testDataset.getTitle()).should('exist') + cy.findAllByText(testDataset.title).should('exist') const metadataTab = cy.findByRole('tab', { name: 'Metadata' }) metadataTab.should('exist') diff --git a/tests/component/sections/dataset/DatasetProvider.spec.tsx b/tests/component/sections/dataset/DatasetProvider.spec.tsx index 03684cf44..e396bba45 100644 --- a/tests/component/sections/dataset/DatasetProvider.spec.tsx +++ b/tests/component/sections/dataset/DatasetProvider.spec.tsx @@ -9,7 +9,7 @@ function TestComponent() { return ( <div> - {dataset ? <span>{dataset.getTitle()}</span> : <span>Dataset Not Found</span>} + {dataset ? <span>{dataset.title}</span> : <span>Dataset Not Found</span>} {isLoading && <div>Loading...</div>} </div> ) @@ -39,7 +39,7 @@ describe('DatasetProvider', () => { cy.findByText('Loading...').should('exist') cy.wrap(datasetRepository.getByPersistentId).should('be.calledOnceWith', dataset.persistentId) - cy.findByText(dataset.getTitle()).should('exist') + cy.findByText(dataset.title).should('exist') cy.findByText('Loading...').should('not.exist') }) @@ -60,7 +60,7 @@ describe('DatasetProvider', () => { dataset.persistentId, 'draft' ) - cy.findByText(dataset.getTitle()).should('exist') + cy.findByText(dataset.title).should('exist') cy.findByText('Loading...').should('not.exist') }) @@ -80,7 +80,7 @@ describe('DatasetProvider', () => { 'be.calledOnce', 'some-private-url-token' ) - cy.findByText(dataset.getTitle()).should('exist') + cy.findByText(dataset.title).should('exist') cy.findByText('Loading...').should('not.exist') }) diff --git a/tests/component/sections/dataset/dataset-citation/CitationThumbnail.spec.tsx b/tests/component/sections/dataset/dataset-citation/CitationThumbnail.spec.tsx deleted file mode 100644 index c73ab2359..000000000 --- a/tests/component/sections/dataset/dataset-citation/CitationThumbnail.spec.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { DatasetPublishingStatus } from '../../../../../src/dataset/domain/models/Dataset' -import { CitationThumbnail } from '../../../../../src/sections/dataset/dataset-citation/CitationThumbnail' - -describe('CitationThumbnail', () => { - it('renders the dataset icon when there is no thumbnail', () => { - cy.customMount( - <CitationThumbnail - title="Dataset title" - publishingStatus={DatasetPublishingStatus.RELEASED} - /> - ) - - cy.findByLabelText('icon-dataset').should('exist') - }) - - it('renders the dataset icon when the dataset is deaccessioned', () => { - cy.customMount( - <CitationThumbnail - title="Dataset title" - thumbnail="https://example.com/image.png" - publishingStatus={DatasetPublishingStatus.DEACCESSIONED} - /> - ) - - cy.findByLabelText('icon-dataset').should('exist') - }) - - it('renders the dataset thumbnail when there is one and the dataset is not deaccessioned', () => { - cy.customMount( - <CitationThumbnail - thumbnail="https://example.com/image.png" - title="Dataset title" - publishingStatus={DatasetPublishingStatus.RELEASED} - /> - ) - - cy.findByRole('img', { name: 'Dataset title' }).should('exist') - }) -}) diff --git a/tests/component/sections/dataset/dataset-files/DatasetFiles.spec.tsx b/tests/component/sections/dataset/dataset-files/DatasetFiles.spec.tsx index 77c287336..58a6ec23d 100644 --- a/tests/component/sections/dataset/dataset-files/DatasetFiles.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/DatasetFiles.spec.tsx @@ -8,7 +8,6 @@ import { FileTag } from '../../../../../src/files/domain/models/FileCriteria' import { FilesCountInfoMother } from '../../../files/domain/models/FilesCountInfoMother' -import { FilePaginationInfo } from '../../../../../src/files/domain/models/FilePaginationInfo' import { FileSize, FileSizeUnit, FileType } from '../../../../../src/files/domain/models/File' import styles from '../../../../../src/sections/dataset/dataset-files/files-table/FilesTable.module.scss' import { DatasetMother } from '../../../dataset/domain/models/DatasetMother' @@ -16,6 +15,7 @@ import { SettingMother } from '../../../settings/domain/models/SettingMother' import { ZipDownloadLimit } from '../../../../../src/settings/domain/models/ZipDownloadLimit' import { SettingsProvider } from '../../../../../src/sections/settings/SettingsProvider' import { SettingRepository } from '../../../../../src/settings/domain/repositories/SettingRepository' +import { FilePaginationInfo } from '../../../../../src/files/domain/models/FilePaginationInfo' const testFiles = FileMother.createMany(10) const datasetPersistentId = 'test-dataset-persistent-id' @@ -42,7 +42,7 @@ const testFilesCountInfo = FilesCountInfoMother.create({ { tag: new FileTag('code'), count: 10 } ] }) -const filePaginationInfo = new FilePaginationInfo(1, 10, 200) +const paginationInfo: FilePaginationInfo = new FilePaginationInfo(1, 10, 200) const settingsRepository = {} as SettingRepository describe('DatasetFiles', () => { beforeEach(() => { @@ -417,7 +417,7 @@ describe('DatasetFiles', () => { 'be.calledWith', datasetPersistentId, datasetVersion, - filePaginationInfo, + paginationInfo, new FileCriteria().withSortBy(FileSortByOption.NAME_AZ) ) }) @@ -437,7 +437,7 @@ describe('DatasetFiles', () => { 'be.calledWith', datasetPersistentId, datasetVersion, - filePaginationInfo, + paginationInfo, new FileCriteria().withFilterByType('image/png') ) }) @@ -457,7 +457,7 @@ describe('DatasetFiles', () => { 'be.calledWith', datasetPersistentId, datasetVersion, - filePaginationInfo, + paginationInfo, new FileCriteria().withFilterByAccess(FileAccessOption.PUBLIC) ) }) @@ -477,7 +477,7 @@ describe('DatasetFiles', () => { 'be.calledWith', datasetPersistentId, datasetVersion, - filePaginationInfo, + paginationInfo, new FileCriteria().withFilterByTag('document') ) }) @@ -496,7 +496,7 @@ describe('DatasetFiles', () => { 'be.calledWith', datasetPersistentId, datasetVersion, - filePaginationInfo, + paginationInfo, new FileCriteria().withSearchText('test') ) }) diff --git a/tests/component/sections/dataset/dataset-files/files-table/FilesTable.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/FilesTable.spec.tsx index b56ea78d6..cc0d50c3a 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/FilesTable.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/FilesTable.spec.tsx @@ -8,8 +8,8 @@ import { SettingMother } from '../../../../settings/domain/models/SettingMother' import { ZipDownloadLimit } from '../../../../../../src/settings/domain/models/ZipDownloadLimit' import { SettingsContext } from '../../../../../../src/sections/settings/SettingsContext' import styles from '../../../../../../src/sections/dataset/dataset-files/files-table/FilesTable.module.scss' -import { FilePaginationInfo } from '../../../../../../src/files/domain/models/FilePaginationInfo' import { FileCriteria } from '../../../../../../src/files/domain/models/FileCriteria' +import { FilePaginationInfo } from '../../../../../../src/files/domain/models/FilePaginationInfo' const testFiles = FileMother.createMany(10) const paginationInfo = new FilePaginationInfo(1, 10, 200) diff --git a/tests/component/sections/dataset/dataset-files/files-table/files-info/file-info-cell/file-info-data/FileDate.spec.tsx b/tests/component/sections/dataset/dataset-files/files-table/files-info/file-info-cell/file-info-data/FileDate.spec.tsx index 851029fa5..9f605cc54 100644 --- a/tests/component/sections/dataset/dataset-files/files-table/files-info/file-info-cell/file-info-data/FileDate.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/files-table/files-info/file-info-cell/file-info-data/FileDate.spec.tsx @@ -1,16 +1,13 @@ import { FileDate } from '../../../../../../../../../src/sections/dataset/dataset-files/files-table/file-info/file-info-cell/file-info-data/FileDate' import { FileDateType } from '../../../../../../../../../src/files/domain/models/File' +import { DateHelper } from '../../../../../../../../../src/shared/domain/helpers/DateHelper' describe('FileDate', () => { it('renders the date', () => { const fileDate = new Date('2023-09-18') const date = { type: FileDateType.PUBLISHED, date: fileDate } cy.customMount(<FileDate date={date} />) - const dateString = fileDate.toLocaleDateString(Intl.DateTimeFormat().resolvedOptions().locale, { - year: 'numeric', - month: 'short', - day: 'numeric' - }) + const dateString = DateHelper.toDisplayFormat(fileDate) cy.findByText(`Published ` + dateString).should('exist') }) }) diff --git a/tests/component/sections/dataset/dataset-files/useFiles.spec.tsx b/tests/component/sections/dataset/dataset-files/useFiles.spec.tsx index 9f9d9a5d0..6e958064e 100644 --- a/tests/component/sections/dataset/dataset-files/useFiles.spec.tsx +++ b/tests/component/sections/dataset/dataset-files/useFiles.spec.tsx @@ -5,12 +5,12 @@ import { useFiles } from '../../../../../src/sections/dataset/dataset-files/useF import { FileUserPermissionsMother } from '../../../files/domain/models/FileUserPermissionsMother' import { FilePermissionsProvider } from '../../../../../src/sections/file/file-permissions/FilePermissionsProvider' import { useState } from 'react' -import { FilePaginationInfo } from '../../../../../src/files/domain/models/FilePaginationInfo' import { DatasetPublishingStatus, DatasetVersion } from '../../../../../src/dataset/domain/models/Dataset' import { FileCriteria, FileSortByOption } from '../../../../../src/files/domain/models/FileCriteria' +import { FilePaginationInfo } from '../../../../../src/files/domain/models/FilePaginationInfo' const files = FileMother.createMany(100) const filesCountInfo = FilesCountInfoMother.create({ total: 100 }) @@ -48,7 +48,7 @@ const FilesTableTestComponent = ({ datasetPersistentId }: { datasetPersistentId: }}> Sort by name Z-A </button> - <div>Files count: {paginationInfo.totalFiles}</div> + <div>Files count: {paginationInfo.totalItems}</div> <div>Files total download size: {filesTotalDownloadSize}</div> <table> <tbody> diff --git a/tests/component/sections/dataset/dataset-icon/DatasetIcon.spec.tsx b/tests/component/sections/dataset/dataset-icon/DatasetIcon.spec.tsx new file mode 100644 index 000000000..163759a50 --- /dev/null +++ b/tests/component/sections/dataset/dataset-icon/DatasetIcon.spec.tsx @@ -0,0 +1,9 @@ +import { DatasetIcon } from '../../../../../src/sections/dataset/dataset-icon/DatasetIcon' + +describe('DatasetIcon', () => { + it('renders the dataset icon', () => { + cy.customMount(<DatasetIcon />) + + cy.findByLabelText('icon-dataset').should('exist') + }) +}) diff --git a/tests/component/sections/dataset/dataset-thumbanil/DatasetThumbnail.spec.tsx b/tests/component/sections/dataset/dataset-thumbanil/DatasetThumbnail.spec.tsx new file mode 100644 index 000000000..1bb47523e --- /dev/null +++ b/tests/component/sections/dataset/dataset-thumbanil/DatasetThumbnail.spec.tsx @@ -0,0 +1,29 @@ +import { DatasetThumbnail } from '../../../../../src/sections/dataset/dataset-thumbnail/DatasetThumbnail' + +describe('DatasetThumbnail', () => { + it('renders the dataset icon when there is no thumbnail', () => { + cy.customMount(<DatasetThumbnail title="Dataset title" />) + + cy.findByLabelText('icon-dataset').should('exist') + }) + + it('renders the dataset icon when the dataset is deaccessioned', () => { + cy.customMount( + <DatasetThumbnail + title="Dataset title" + thumbnail="https://example.com/image.png" + isDeaccessioned + /> + ) + + cy.findByLabelText('icon-dataset').should('exist') + }) + + it('renders the dataset thumbnail when there is one and the dataset is not deaccessioned', () => { + cy.customMount( + <DatasetThumbnail thumbnail="https://example.com/image.png" title="Dataset title" /> + ) + + cy.findByRole('img', { name: 'Dataset title' }).should('exist') + }) +}) diff --git a/tests/component/sections/hello-dataverse/HelloDataverse.spec.tsx b/tests/component/sections/hello-dataverse/HelloDataverse.spec.tsx deleted file mode 100644 index 7e1668691..000000000 --- a/tests/component/sections/hello-dataverse/HelloDataverse.spec.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { HelloDataverse } from '../../../../src/sections/hello-dataverse/HelloDataverse' - -describe('HelloDataverse page', () => { - it('renders hello dataverse title', () => { - cy.customMount(<HelloDataverse />) - cy.findByRole('heading').should('contain.text', 'Hello Dataverse') - }) -}) diff --git a/tests/component/sections/home/Home.spec.tsx b/tests/component/sections/home/Home.spec.tsx new file mode 100644 index 000000000..52fe1d34a --- /dev/null +++ b/tests/component/sections/home/Home.spec.tsx @@ -0,0 +1,28 @@ +import { Home } from '../../../../src/sections/home/Home' +import { DatasetRepository } from '../../../../src/dataset/domain/repositories/DatasetRepository' +import { DatasetPreviewMother } from '../../dataset/domain/models/DatasetPreviewMother' + +const datasetRepository: DatasetRepository = {} as DatasetRepository +const totalDatasetsCount = 10 +const datasets = DatasetPreviewMother.createMany(totalDatasetsCount) +describe('Home page', () => { + beforeEach(() => { + datasetRepository.getAll = cy.stub().resolves(datasets) + datasetRepository.getTotalDatasetsCount = cy.stub().resolves(totalDatasetsCount) + }) + + it('renders Root title', () => { + cy.customMount(<Home datasetRepository={datasetRepository} />) + cy.findByRole('heading').should('contain.text', 'Root') + }) + + it('renders the datasets list', () => { + cy.customMount(<Home datasetRepository={datasetRepository} />) + + cy.wrap(datasetRepository.getAll).should('be.calledOnce') + + datasets.forEach((dataset) => { + cy.findByText(dataset.title).should('exist') + }) + }) +}) diff --git a/tests/component/sections/home/datasets-list/DatasetsList.spec.tsx b/tests/component/sections/home/datasets-list/DatasetsList.spec.tsx new file mode 100644 index 000000000..e126ce245 --- /dev/null +++ b/tests/component/sections/home/datasets-list/DatasetsList.spec.tsx @@ -0,0 +1,58 @@ +import { DatasetRepository } from '../../../../../src/dataset/domain/repositories/DatasetRepository' +import { DatasetsList } from '../../../../../src/sections/home/datasets-list/DatasetsList' +import { DatasetPaginationInfo } from '../../../../../src/dataset/domain/models/DatasetPaginationInfo' +import { DatasetPreviewMother } from '../../../dataset/domain/models/DatasetPreviewMother' + +const datasetRepository: DatasetRepository = {} as DatasetRepository +const totalDatasetsCount = 200 +const datasets = DatasetPreviewMother.createMany(totalDatasetsCount) +describe('Datasets List', () => { + beforeEach(() => { + datasetRepository.getAll = cy.stub().resolves(datasets) + datasetRepository.getTotalDatasetsCount = cy.stub().resolves(totalDatasetsCount) + }) + + it('renders skeleton while loading', () => { + cy.customMount(<DatasetsList datasetRepository={datasetRepository} />) + + cy.findByTestId('datasets-list-skeleton').should('exist') + datasets.forEach((dataset) => { + cy.findByRole('link', { name: dataset.title }).should('not.exist') + }) + }) + + it('renders no datasets message when there are no datasets', () => { + datasetRepository.getAll = cy.stub().resolves([]) + cy.customMount(<DatasetsList datasetRepository={datasetRepository} />) + + cy.findByText(/This dataverse currently has no datasets./).should('exist') + }) + + it('renders the datasets list', () => { + cy.customMount(<DatasetsList datasetRepository={datasetRepository} />) + + cy.wrap(datasetRepository.getAll).should( + 'be.calledOnceWith', + new DatasetPaginationInfo(1, 10, totalDatasetsCount) + ) + + cy.findByText('1 to 10 of 200 Datasets').should('exist') + datasets.forEach((dataset) => { + cy.findByText(dataset.title) + .should('exist') + .should('have.attr', 'href', `/datasets?persistentId=${dataset.persistentId}`) + }) + }) + + it('renders the datasets list with the correct header on a page different than the first one ', () => { + cy.customMount(<DatasetsList datasetRepository={datasetRepository} />) + + cy.findByRole('button', { name: '6' }).click() + + cy.wrap(datasetRepository.getAll).should( + 'be.calledWith', + new DatasetPaginationInfo(1, 10, totalDatasetsCount).goToPage(6) + ) + cy.findByText('51 to 60 of 200 Datasets').should('exist') + }) +}) diff --git a/tests/component/sections/home/datasets-list/NoDatasetsMessage.spec.tsx b/tests/component/sections/home/datasets-list/NoDatasetsMessage.spec.tsx new file mode 100644 index 000000000..c8b580bea --- /dev/null +++ b/tests/component/sections/home/datasets-list/NoDatasetsMessage.spec.tsx @@ -0,0 +1,20 @@ +import { NoDatasetsMessage } from '../../../../../src/sections/home/datasets-list/NoDatasetsMessage' + +describe('No Datasets Message', () => { + it('renders the message for anonymous user', () => { + cy.customMount(<NoDatasetsMessage />) + cy.findByText(/This dataverse currently has no datasets. Please /).should('exist') + cy.findByRole('link', { name: 'log in' }).should( + 'have.attr', + 'href', + '/loginpage.xhtml?redirectPage=%2Fdataverse.xhtml' + ) + }) + + it('renders the message for authenticated user', () => { + cy.mountAuthenticated(<NoDatasetsMessage />) + cy.findByText( + 'This dataverse currently has no datasets. You can add to it by using the Add Data button on this page.' + ).should('exist') + }) +}) diff --git a/tests/component/sections/home/datasets-list/dataset-card/DatasetCard.spec.tsx b/tests/component/sections/home/datasets-list/dataset-card/DatasetCard.spec.tsx new file mode 100644 index 000000000..8b66c37d1 --- /dev/null +++ b/tests/component/sections/home/datasets-list/dataset-card/DatasetCard.spec.tsx @@ -0,0 +1,21 @@ +import { DatasetPreviewMother } from '../../../../dataset/domain/models/DatasetPreviewMother' +import { DatasetCard } from '../../../../../../src/sections/home/datasets-list/dataset-card/DatasetCard' +import { DateHelper } from '../../../../../../src/shared/domain/helpers/DateHelper' +import styles from '../../../../../../src/sections/home/datasets-list/dataset-card/DatasetCard.module.scss' + +describe('DatasetCard', () => { + it('should render the card', () => { + const dataset = DatasetPreviewMother.createWithThumbnail() + cy.customMount(<DatasetCard dataset={dataset} />) + + cy.findByText(dataset.title).should('exist') + + cy.findByRole('img', { name: dataset.title }).should('exist') + cy.findByText(DateHelper.toDisplayFormat(dataset.releaseOrCreateDate)).should('exist') + cy.findByText(/Finch, Fiona, 2023, "Darwin's Finches"/) + .should('exist') + .parent() + .should('have.class', styles['citation-box']) + cy.findByText(dataset.abbreviatedDescription).should('exist') + }) +}) diff --git a/tests/component/sections/home/datasets-list/dataset-card/DatasetCardHeader.spec.tsx b/tests/component/sections/home/datasets-list/dataset-card/DatasetCardHeader.spec.tsx new file mode 100644 index 000000000..1e7773dfc --- /dev/null +++ b/tests/component/sections/home/datasets-list/dataset-card/DatasetCardHeader.spec.tsx @@ -0,0 +1,17 @@ +import { DatasetCardHeader } from '../../../../../../src/sections/home/datasets-list/dataset-card/DatasetCardHeader' +import { DatasetPreviewMother } from '../../../../dataset/domain/models/DatasetPreviewMother' + +describe('DatasetCardHeader', () => { + it('should render the header', () => { + const dataset = DatasetPreviewMother.create() + cy.customMount(<DatasetCardHeader dataset={dataset} />) + + cy.findByText(dataset.title) + .should('exist') + .should('have.attr', 'href', `/datasets?persistentId=${dataset.persistentId}`) + dataset.labels.forEach((label) => { + cy.findByText(label.value).should('exist') + }) + cy.findByLabelText('icon-dataset').should('exist') + }) +}) diff --git a/tests/component/sections/home/datasets-list/dataset-card/DatasetCardInfo.spec.tsx b/tests/component/sections/home/datasets-list/dataset-card/DatasetCardInfo.spec.tsx new file mode 100644 index 000000000..5563fe412 --- /dev/null +++ b/tests/component/sections/home/datasets-list/dataset-card/DatasetCardInfo.spec.tsx @@ -0,0 +1,28 @@ +import { DatasetPreviewMother } from '../../../../dataset/domain/models/DatasetPreviewMother' +import { DateHelper } from '../../../../../../src/shared/domain/helpers/DateHelper' +import styles from '../../../../../../src/sections/home/datasets-list/dataset-card/DatasetCard.module.scss' +import { DatasetCardInfo } from '../../../../../../src/sections/home/datasets-list/dataset-card/DatasetCardInfo' + +describe('DatasetCardInfo', () => { + it('should render the dataset info', () => { + const dataset = DatasetPreviewMother.createDraft() + cy.customMount(<DatasetCardInfo dataset={dataset} />) + + cy.findByText(DateHelper.toDisplayFormat(dataset.releaseOrCreateDate)).should('exist') + cy.findByText(/Finch, Fiona, 2023, "Darwin's Finches"/) + .should('exist') + .parent() + .should('have.class', styles['citation-box']) + cy.findByText(dataset.abbreviatedDescription).should('exist') + }) + + it('should render the citation with the deaccessioned background if the dataset is deaccessioned', () => { + const dataset = DatasetPreviewMother.createDeaccessioned() + cy.customMount(<DatasetCardInfo dataset={dataset} />) + + cy.findByText(/Finch, Fiona, 2023, "Darwin's Finches"/) + .should('exist') + .parent() + .should('have.class', styles['citation-box-deaccessioned']) + }) +}) diff --git a/tests/component/sections/home/datasets-list/dataset-card/DatasetCardThumbnail.spec.tsx b/tests/component/sections/home/datasets-list/dataset-card/DatasetCardThumbnail.spec.tsx new file mode 100644 index 000000000..e261e32db --- /dev/null +++ b/tests/component/sections/home/datasets-list/dataset-card/DatasetCardThumbnail.spec.tsx @@ -0,0 +1,25 @@ +import { DatasetPreviewMother } from '../../../../dataset/domain/models/DatasetPreviewMother' +import { DatasetCardThumbnail } from '../../../../../../src/sections/home/datasets-list/dataset-card/DatasetCardThumbnail' + +describe('DatasetCardThumbnail', () => { + it('should render the thumbnail', () => { + const dataset = DatasetPreviewMother.createWithThumbnail() + cy.customMount(<DatasetCardThumbnail dataset={dataset} />) + + cy.findByRole('img', { name: dataset.title }) + .should('exist') + .parent('a') + .should('have.attr', 'href', `/datasets?persistentId=${dataset.persistentId}`) + }) + + it('should render the placeholder if the dataset has no thumbnail', () => { + const dataset = DatasetPreviewMother.createWithNoThumbnail() + cy.customMount(<DatasetCardThumbnail dataset={dataset} />) + + cy.findByRole('img', { name: 'icon-dataset' }) + .should('exist') + .parent() + .parent('a') + .should('have.attr', 'href', `/datasets?persistentId=${dataset.persistentId}`) + }) +}) diff --git a/tests/component/sections/layout/header/Header.spec.tsx b/tests/component/sections/layout/header/Header.spec.tsx index a3c208624..9db242389 100644 --- a/tests/component/sections/layout/header/Header.spec.tsx +++ b/tests/component/sections/layout/header/Header.spec.tsx @@ -11,6 +11,17 @@ describe('Header component', () => { userRepository.removeAuthenticated = cy.stub().resolves() }) + it('displays the brand', () => { + cy.customMount( + <SessionProvider repository={userRepository}> + <Header /> + </SessionProvider> + ) + + cy.findByRole('link', { name: /Dataverse/ }).should('exist') + cy.findByRole('link').should('have.attr', 'href', '/spa/') + }) + it('displays the user name when the user is logged in', () => { cy.customMount( <SessionProvider repository={userRepository}> diff --git a/tests/component/sections/session/useSession.spec.tsx b/tests/component/sections/session/useSession.spec.tsx index bd5f678b4..a1f15f1d0 100644 --- a/tests/component/sections/session/useSession.spec.tsx +++ b/tests/component/sections/session/useSession.spec.tsx @@ -32,11 +32,14 @@ describe('useSession', () => { it('should unset user after calling logOut on repository', () => { function TestComponent() { const { user, logout } = useSession() + const onLogoutClick = () => { + void logout() + } return ( <div> {user ? <span>{user.name}</span> : <></>} - <Button onClick={logout}>Log Out</Button> + <Button onClick={onLogoutClick}>Log Out</Button> </div> ) } diff --git a/tests/component/sections/shared/citation/CitationDescription.spec.tsx b/tests/component/sections/shared/citation/CitationDescription.spec.tsx new file mode 100644 index 000000000..ce5b1a57e --- /dev/null +++ b/tests/component/sections/shared/citation/CitationDescription.spec.tsx @@ -0,0 +1,12 @@ +import { CitationDescription } from '../../../../../src/sections/shared/citation/CitationDescription' + +describe('CitationDescription', () => { + it('renders the citation', () => { + const citation = + 'Finch, Fiona, 2023, "Darwin\'s Finches", <a href="https://doi.org/10.5072/FK2/0YFWKL" target="_blank">https://doi.org/10.5072/FK2/0YFWKL</a>, Root, V1' + cy.customMount(<CitationDescription citation={citation} />) + + cy.findByText(/Finch, Fiona, 2023, "Darwin's Finches",/).should('exist') + cy.findByRole('link', { name: 'https://doi.org/10.5072/FK2/0YFWKL' }).should('exist') + }) +}) diff --git a/tests/component/sections/shared/link-to-page/LinkToPage.spec.tsx b/tests/component/sections/shared/link-to-page/LinkToPage.spec.tsx new file mode 100644 index 000000000..25d166231 --- /dev/null +++ b/tests/component/sections/shared/link-to-page/LinkToPage.spec.tsx @@ -0,0 +1,14 @@ +import { LinkToPage } from '../../../../../src/sections/shared/link-to-page/LinkToPage' +import { Route } from '../../../../../src/sections/Route.enum' + +describe('LinkToPage', () => { + it('renders a link to the page with the given search params', () => { + cy.customMount(<LinkToPage page={Route.DATASETS} searchParams={{ foo: 'bar' }} />) + cy.findByRole('link').should('have.attr', 'href', '/datasets?foo=bar') + }) + + it('renders a link to the page without search params', () => { + cy.customMount(<LinkToPage page={Route.DATASETS} />) + cy.findByRole('link').should('have.attr', 'href', '/datasets') + }) +}) diff --git a/tests/component/sections/dataset/dataset-files/files-pagination/PageNumbersButtonsWithEllipsis.spec.tsx b/tests/component/sections/shared/pagination/PageNumbersButtonsWithEllipsis.spec.tsx similarity index 95% rename from tests/component/sections/dataset/dataset-files/files-pagination/PageNumbersButtonsWithEllipsis.spec.tsx rename to tests/component/sections/shared/pagination/PageNumbersButtonsWithEllipsis.spec.tsx index 0b4fffc39..eb644b64b 100644 --- a/tests/component/sections/dataset/dataset-files/files-pagination/PageNumbersButtonsWithEllipsis.spec.tsx +++ b/tests/component/sections/shared/pagination/PageNumbersButtonsWithEllipsis.spec.tsx @@ -1,4 +1,4 @@ -import { PageNumbersButtonsWithEllipsis } from '../../../../../../src/sections/dataset/dataset-files/files-pagination/PageNumbersButtonsWithEllipsis' +import { PageNumbersButtonsWithEllipsis } from '../../../../../src/sections/shared/pagination/PageNumbersButtonsWithEllipsis' let goToPage: (pageIndex: number) => void const selectedPageIndex = 3 diff --git a/tests/component/sections/dataset/dataset-files/files-pagination/FilesPagination.spec.tsx b/tests/component/sections/shared/pagination/PaginationControls.spec.tsx similarity index 63% rename from tests/component/sections/dataset/dataset-files/files-pagination/FilesPagination.spec.tsx rename to tests/component/sections/shared/pagination/PaginationControls.spec.tsx index df2a50ce6..4ca16340c 100644 --- a/tests/component/sections/dataset/dataset-files/files-pagination/FilesPagination.spec.tsx +++ b/tests/component/sections/shared/pagination/PaginationControls.spec.tsx @@ -1,23 +1,27 @@ -import { FilesPagination } from '../../../../../../src/sections/dataset/dataset-files/files-pagination/FilesPagination' -import { FilePaginationInfo } from '../../../../../../src/files/domain/models/FilePaginationInfo' +import { PaginationControls } from '../../../../../src/sections/shared/pagination/PaginationControls' +import { PaginationInfo } from '../../../../../src/shared/domain/models/PaginationInfo' +import { FilePaginationInfo } from '../../../../../src/files/domain/models/FilePaginationInfo' +import { DatasetPaginationInfo } from '../../../../../src/dataset/domain/models/DatasetPaginationInfo' -let paginationInfo: FilePaginationInfo +let paginationInfo: PaginationInfo<FilePaginationInfo | DatasetPaginationInfo> const page = 3 const pageSize = 10 const total = 200 -describe('FilesPagination', () => { +describe('PaginationControls', () => { beforeEach(() => { cy.viewport(1000, 1000) - paginationInfo = new FilePaginationInfo(page, pageSize, total) + paginationInfo = new PaginationInfo<FilePaginationInfo | DatasetPaginationInfo>( + page, + pageSize, + total + ) }) it('clicking on the first page button calls goToPage 1', () => { const onPaginationInfoChange = cy.stub().as('onPaginationInfoChange') cy.customMount( - <FilesPagination - page={page} - pageSize={pageSize} - total={total} + <PaginationControls + initialPaginationInfo={paginationInfo} onPaginationInfoChange={onPaginationInfoChange} /> ) @@ -29,10 +33,8 @@ describe('FilesPagination', () => { it('clicking on the previous page button calls goToPreviousPage', () => { const onPaginationInfoChange = cy.stub().as('onPaginationInfoChange') cy.customMount( - <FilesPagination - page={page} - pageSize={pageSize} - total={total} + <PaginationControls + initialPaginationInfo={paginationInfo} onPaginationInfoChange={onPaginationInfoChange} /> ) @@ -47,10 +49,8 @@ describe('FilesPagination', () => { it('clicking on a page button calls goToPage with the correct number', () => { const onPaginationInfoChange = cy.stub().as('onPaginationInfoChange') cy.customMount( - <FilesPagination - page={page} - pageSize={pageSize} - total={total} + <PaginationControls + initialPaginationInfo={paginationInfo} onPaginationInfoChange={onPaginationInfoChange} /> ) @@ -62,10 +62,8 @@ describe('FilesPagination', () => { it('clicking on the next page button calls goToNextPage', () => { const onPaginationInfoChange = cy.stub().as('onPaginationInfoChange') cy.customMount( - <FilesPagination - page={page} - pageSize={pageSize} - total={total} + <PaginationControls + initialPaginationInfo={paginationInfo} onPaginationInfoChange={onPaginationInfoChange} /> ) @@ -77,10 +75,8 @@ describe('FilesPagination', () => { it('clicking on the last page button calls setPageIndex with the last index', () => { const onPaginationInfoChange = cy.stub().as('onPaginationInfoChange') cy.customMount( - <FilesPagination - page={page} - pageSize={pageSize} - total={total} + <PaginationControls + initialPaginationInfo={paginationInfo} onPaginationInfoChange={onPaginationInfoChange} /> ) @@ -92,15 +88,13 @@ describe('FilesPagination', () => { it('selecting a page size calls setPageSize with the selected value', () => { const onPaginationInfoChange = cy.stub().as('onPaginationInfoChange') cy.customMount( - <FilesPagination - page={page} - pageSize={pageSize} - total={total} + <PaginationControls + initialPaginationInfo={paginationInfo} onPaginationInfoChange={onPaginationInfoChange} /> ) - cy.findByLabelText('Files per page').select('50') + cy.findByLabelText('Items per page').select('50') cy.wrap(onPaginationInfoChange).should('have.been.calledWith', paginationInfo.withPageSize(50)) cy.findByRole('button', { name: 'Last' }).click() @@ -109,4 +103,17 @@ describe('FilesPagination', () => { paginationInfo.withPageSize(50).goToPage(4) ) }) + + it('does not show the page size selector if the prop is false', () => { + const onPaginationInfoChange = cy.stub().as('onPaginationInfoChange') + cy.customMount( + <PaginationControls + initialPaginationInfo={paginationInfo.withTotal(10)} + onPaginationInfoChange={onPaginationInfoChange} + showPageSizeSelector={false} + /> + ) + + cy.findByLabelText('Items per page').should('not.exist') + }) }) diff --git a/tests/e2e-integration/e2e/sections/hello-dataverse/HelloDataverse.spec.ts b/tests/e2e-integration/e2e/sections/home/Home.spec.ts similarity index 59% rename from tests/e2e-integration/e2e/sections/hello-dataverse/HelloDataverse.spec.ts rename to tests/e2e-integration/e2e/sections/home/Home.spec.ts index 4e364a2ea..1f85bc181 100644 --- a/tests/e2e-integration/e2e/sections/hello-dataverse/HelloDataverse.spec.ts +++ b/tests/e2e-integration/e2e/sections/home/Home.spec.ts @@ -1,23 +1,23 @@ -describe('Hello Dataverse', () => { +describe('Home Page', () => { it('successfully loads', () => { cy.visit('/spa') - cy.findAllByText(/Hello Dataverse/i).should('exist') + cy.findAllByText(/Root/i).should('exist') }) it('log in Dataverse Admin user', () => { cy.loginAsAdmin('/spa') - cy.findAllByText(/Hello Dataverse/i).should('exist') + cy.findAllByText(/Root/i).should('exist') cy.findByText(/Dataverse Admin/i).should('exist') }) it('log out Dataverse Admin user', () => { cy.loginAsAdmin('/spa') - cy.findAllByText(/Hello Dataverse/i).should('exist') + cy.findAllByText(/Root/i).should('exist') cy.findByText(/Dataverse Admin/i).click() - cy.findByRole('link', { name: /Log Out/i }).click() + cy.findByRole('button', { name: /Log Out/i }).click() cy.findByText(/Dataverse Admin/i).should('not.exist') }) }) diff --git a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts index 4916d056b..5d0336a02 100644 --- a/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts +++ b/tests/e2e-integration/integration/datasets/DatasetJSDataverseRepository.spec.ts @@ -121,7 +121,7 @@ describe('Dataset JSDataverse Repository', () => { } const datasetExpected = datasetData(dataset.persistentId, dataset.version.id) - expect(dataset.getTitle()).to.deep.equal(datasetExpected.title) + expect(dataset.title).to.deep.equal(datasetExpected.title) expect(dataset.citation).to.deep.equal(datasetExpected.citation) expect(dataset.labels).to.deep.equal(datasetExpected.labels) expect(dataset.license).to.deep.equal(datasetExpected.license) @@ -162,7 +162,7 @@ describe('Dataset JSDataverse Repository', () => { 0 ) const expectedPublicationDate = getCurrentDateInYYYYMMDDFormat() - expect(dataset.getTitle()).to.deep.equal(datasetExpected.title) + expect(dataset.title).to.deep.equal(datasetExpected.title) expect(dataset.version).to.deep.equal(newVersion) expect(dataset.metadataBlocks[0].fields.publicationDate).to.deep.equal( expectedPublicationDate @@ -200,7 +200,7 @@ describe('Dataset JSDataverse Repository', () => { 0 ) const expectedPublicationDate = getCurrentDateInYYYYMMDDFormat() - expect(dataset.getTitle()).to.deep.equal(datasetExpected.title) + expect(dataset.title).to.deep.equal(datasetExpected.title) expect(dataset.version).to.deep.equal(newVersion) expect(dataset.metadataBlocks[0].fields.publicationDate).to.deep.equal( expectedPublicationDate @@ -221,7 +221,7 @@ describe('Dataset JSDataverse Repository', () => { } const datasetExpected = datasetData(dataset.persistentId, dataset.version.id) - expect(dataset.getTitle()).to.deep.equal(datasetExpected.title) + expect(dataset.title).to.deep.equal(datasetExpected.title) expect(dataset.version).to.deep.equal(datasetExpected.version) }) }) @@ -236,7 +236,7 @@ describe('Dataset JSDataverse Repository', () => { } const datasetExpected = datasetData(dataset.persistentId, dataset.version.id) - expect(dataset.getTitle()).to.deep.equal(datasetExpected.title) + expect(dataset.title).to.deep.equal(datasetExpected.title) expect(dataset.version).to.deep.equal(datasetExpected.version) expect(dataset.permissions).to.deep.equal(datasetExpected.permissions) }) @@ -278,7 +278,7 @@ describe('Dataset JSDataverse Repository', () => { } const datasetExpected = datasetData(dataset.persistentId, dataset.version.id) - expect(dataset.getTitle()).to.deep.equal(datasetExpected.title) + expect(dataset.title).to.deep.equal(datasetExpected.title) }) }) it('gets the dataset by persistentId when is locked', async () => { @@ -291,7 +291,7 @@ describe('Dataset JSDataverse Repository', () => { } const datasetExpected = datasetData(dataset.persistentId, dataset.version.id) - expect(dataset.getTitle()).to.deep.equal(datasetExpected.title) + expect(dataset.title).to.deep.equal(datasetExpected.title) expect(dataset.locks).to.deep.equal([ { userPersistentId: 'dataverseAdmin', diff --git a/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts b/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts index df8d92afe..d079f9e2a 100644 --- a/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts +++ b/tests/e2e-integration/integration/files/FileJSDataverseRepository.spec.ts @@ -18,7 +18,6 @@ import { DatasetPublishingStatus, DatasetVersion } from '../../../../src/dataset/domain/models/Dataset' -import { FilePaginationInfo } from '../../../../src/files/domain/models/FilePaginationInfo' import { FileAccessOption, FileCriteria, @@ -28,6 +27,7 @@ import { import { DatasetHelper } from '../../shared/datasets/DatasetHelper' import { FileData, FileHelper } from '../../shared/files/FileHelper' import { FilesCountInfo } from '../../../../src/files/domain/models/FilesCountInfo' +import { FilePaginationInfo } from '../../../../src/files/domain/models/FilePaginationInfo' chai.use(chaiAsPromised) const expect = chai.expect diff --git a/tests/support/commands.tsx b/tests/support/commands.tsx index 92229d209..d0f69cdf9 100644 --- a/tests/support/commands.tsx +++ b/tests/support/commands.tsx @@ -45,14 +45,17 @@ import { I18nextProvider } from 'react-i18next' import i18next from '../../src/i18n' import { UserRepository } from '../../src/users/domain/repositories/UserRepository' import { SessionProvider } from '../../src/sections/session/SessionProvider' +import { MemoryRouter } from 'react-router-dom' // Define your custom mount function Cypress.Commands.add('customMount', (component: ReactNode) => { return cy.mount( - <ThemeProvider> - <I18nextProvider i18n={i18next}>{component}</I18nextProvider> - </ThemeProvider> + <MemoryRouter> + <ThemeProvider> + <I18nextProvider i18n={i18next}>{component}</I18nextProvider> + </ThemeProvider> + </MemoryRouter> ) })