diff --git a/.env.example b/.env.example index 65f1d913b..e2210073a 100644 --- a/.env.example +++ b/.env.example @@ -1 +1 @@ -VITE_DATAVERSE_BACKEND_URL=http://localhost:8080 \ No newline at end of file +VITE_DATAVERSE_BACKEND_URL=http://localhost:8000 \ No newline at end of file diff --git a/README.md b/README.md index 2a4998123..5b26f890b 100644 --- a/README.md +++ b/README.md @@ -288,6 +288,11 @@ The new SPA does not use Solr as the API endpoint it uses performs all queries o The decision of this change is made on the assumption that Solr may not be required in the context of files tab search, whose search facets are reduced compared to other in-application searches. Therefore, if we find evidence that the assumption is incorrect, we will work on extending the search capabilities to support Solr. +#### Dataverses/Datasets list + +The original JSF Dataverses/Datasets list on the home page uses normal paging buttons at the bottom of the list. +We have implemented infinite scrolling in this list, replacing the normal paging buttons, but the goal would be to be able to toggle between normal paging and infinite scrolling via a toggle setting or button. + ## Publishing the Design System The Design System is published to the npm Package Registry. To publish a new version, follow these steps: diff --git a/package-lock.json b/package-lock.json index 8d433d18a..68815a200 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@types/react": "18.0.27", "@types/react-dom": "18.0.10", "bootstrap": "5.2.3", + "classnames": "2.5.1", "html-react-parser": "3.0.16", "i18next": "22.4.9", "i18next-browser-languagedetector": "7.0.1", @@ -28,6 +29,7 @@ "react-bootstrap": "2.7.2", "react-bootstrap-icons": "1.10.3", "react-i18next": "12.1.5", + "react-infinite-scroll-hook": "4.1.1", "react-loader-spinner": "5.3.4", "react-markdown": "8.0.7", "react-router-dom": "6.8.1", @@ -19369,9 +19371,9 @@ } }, "node_modules/classnames": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", - "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" }, "node_modules/clean-stack": { "version": "2.2.0", @@ -35728,6 +35730,20 @@ } } }, + "node_modules/react-infinite-scroll-hook": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/react-infinite-scroll-hook/-/react-infinite-scroll-hook-4.1.1.tgz", + "integrity": "sha512-1bu2572rF3DtjFMhIOzoasLMdYW0vMWxROtl99M5FYGSxm84Ro4aNBZW6ivgE45ofus4Ymo7jIS0Be3zcuLk8g==", + "dependencies": { + "react-intersection-observer-hook": "^2.1.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/react-inspector": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/react-inspector/-/react-inspector-6.0.2.tgz", @@ -35737,6 +35753,14 @@ "react": "^16.8.4 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-intersection-observer-hook": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/react-intersection-observer-hook/-/react-intersection-observer-hook-2.1.1.tgz", + "integrity": "sha512-MeFGpYtcfHB9v6oGqQuHAbSwaWBpd7yZ4wMIeVtboWRdGusAF4V+/8QQ0OKZ36Ez19grYnoDVhRUCjtwI2ZVaw==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", diff --git a/package.json b/package.json index 452d57ec2..4df39b469 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@types/react": "18.0.27", "@types/react-dom": "18.0.10", "bootstrap": "5.2.3", + "classnames": "2.5.1", "html-react-parser": "3.0.16", "i18next": "22.4.9", "i18next-browser-languagedetector": "7.0.1", @@ -32,6 +33,7 @@ "react-bootstrap": "2.7.2", "react-bootstrap-icons": "1.10.3", "react-i18next": "12.1.5", + "react-infinite-scroll-hook": "4.1.1", "react-loader-spinner": "5.3.4", "react-markdown": "8.0.7", "react-router-dom": "6.8.1", @@ -61,6 +63,7 @@ "test:coverage-merge": "lcov-result-merger '**/*/lcov.info' 'merged-coverage/lcov.info' && perl -pi -e 's/src\\/lib/packages\\/design-system\\/src\\/lib/g' merged-coverage/lcov.info", "git:add": "git add .", "storybook": "concurrently 'storybook dev -p 6006 && open \"http://localhost:6006\"' 'cd packages/design-system && npm run storybook'", + "storybook:windows": "concurrently \"storybook dev -p 6006\" \"npm run storybook -w packages/design-system\"", "build-storybook": "storybook build", "test:storybook": "test-storybook", "test:storybook-all": "concurrently 'test-storybook' 'cd packages/design-system && npm run test:storybook'", diff --git a/public/locales/en/pagination.json b/public/locales/en/pagination.json index 99a994c6e..512a4797d 100644 --- a/public/locales/en/pagination.json +++ b/public/locales/en/pagination.json @@ -1,5 +1,10 @@ { "results_one": "1 {{item}}", "results_other": "{{start}} to {{end}} of {{count}} {{item}}s", + "accumulated": { + "one": "{{count}} {{item}}", + "lessThanPageSize": "{{count}} {{item}}s", + "moreThanPageSize": "{{accumulated}} of {{count}} {{item}}s seen" + }, "pageSize": "{{item}}s per page" } diff --git a/src/dataset/domain/models/DatasetsWithCount.ts b/src/dataset/domain/models/DatasetsWithCount.ts new file mode 100644 index 000000000..9194f4b62 --- /dev/null +++ b/src/dataset/domain/models/DatasetsWithCount.ts @@ -0,0 +1,7 @@ +import { DatasetPreview } from '../../domain/models/DatasetPreview' +import { TotalDatasetsCount } from './TotalDatasetsCount' + +export interface DatasetsWithCount { + datasetPreviews: DatasetPreview[] + totalCount: TotalDatasetsCount +} diff --git a/src/dataset/domain/repositories/DatasetRepository.ts b/src/dataset/domain/repositories/DatasetRepository.ts index 23efec3a3..12a528619 100644 --- a/src/dataset/domain/repositories/DatasetRepository.ts +++ b/src/dataset/domain/repositories/DatasetRepository.ts @@ -3,12 +3,17 @@ import { TotalDatasetsCount } from '../models/TotalDatasetsCount' import { DatasetPaginationInfo } from '../models/DatasetPaginationInfo' import { DatasetPreview } from '../models/DatasetPreview' import { DatasetFormFields } from '../models/DatasetFormFields' +import { DatasetsWithCount } from '../models/DatasetsWithCount' export interface DatasetRepository { getByPersistentId: (persistentId: string, version?: string) => Promise getByPrivateUrlToken: (privateUrlToken: string) => Promise getAll: (collectionId: string, paginationInfo: DatasetPaginationInfo) => Promise getTotalDatasetsCount: (collectionId: string) => Promise + getAllWithCount: ( + collectionId: string, + paginationInfo: DatasetPaginationInfo + ) => Promise // Created as placeholder for https://github.com/IQSS/dataverse-frontend/pull/251 createDataset: (fields: DatasetFormFields) => Promise } diff --git a/src/dataset/domain/useCases/getDatasetsWithCount.ts b/src/dataset/domain/useCases/getDatasetsWithCount.ts new file mode 100644 index 000000000..fc9f8396e --- /dev/null +++ b/src/dataset/domain/useCases/getDatasetsWithCount.ts @@ -0,0 +1,13 @@ +import { DatasetRepository } from '../repositories/DatasetRepository' +import { DatasetPaginationInfo } from '../models/DatasetPaginationInfo' +import { DatasetsWithCount } from '../models/DatasetsWithCount' + +export async function getDatasetsWithCount( + datasetRepository: DatasetRepository, + collectionId: string, + paginationInfo: DatasetPaginationInfo +): Promise { + return datasetRepository.getAllWithCount(collectionId, paginationInfo).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 c8be79b3c..82dbbc887 100644 --- a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts +++ b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts @@ -24,6 +24,7 @@ import { DatasetPaginationInfo } from '../../domain/models/DatasetPaginationInfo import { DatasetPreview } from '../../domain/models/DatasetPreview' import { JSDatasetPreviewMapper } from '../mappers/JSDatasetPreviewMapper' import { DatasetFormFields } from '../../domain/models/DatasetFormFields' +import { DatasetsWithCount } from '../../domain/models/DatasetsWithCount' const includeDeaccessioned = true @@ -47,6 +48,24 @@ export class DatasetJSDataverseRepository implements DatasetRepository { }) } + getAllWithCount( + collectionId: string, + paginationInfo: DatasetPaginationInfo + ): Promise { + return getAllDatasetPreviews + .execute(paginationInfo.pageSize, paginationInfo.offset, collectionId) + .then((subset: DatasetPreviewSubset) => { + const datasetPreviewsMapped = subset.datasetPreviews.map( + (datasetPreview: JSDatasetPreview) => + JSDatasetPreviewMapper.toDatasetPreview(datasetPreview) + ) + return { + datasetPreviews: datasetPreviewsMapped, + totalCount: subset.totalDatasetCount + } + }) + } + getByPersistentId( persistentId: string, version?: string, diff --git a/src/sections/collection/Collection.tsx b/src/sections/collection/Collection.tsx index ef236728b..9a6469514 100644 --- a/src/sections/collection/Collection.tsx +++ b/src/sections/collection/Collection.tsx @@ -1,6 +1,7 @@ import { Row } from '@iqss/dataverse-design-system' import { DatasetRepository } from '../../dataset/domain/repositories/DatasetRepository' import { DatasetsList } from './datasets-list/DatasetsList' +import { DatasetsListWithInfiniteScroll } from './datasets-list/DatasetsListWithInfiniteScroll' import { BreadcrumbsGenerator } from '../shared/hierarchy/BreadcrumbsGenerator' import { DvObjectType, @@ -14,6 +15,7 @@ interface CollectionProps { datasetRepository: DatasetRepository id: string page?: number + infiniteScrollEnabled?: boolean } const rootNode = new UpwardHierarchyNode( 'Root', @@ -24,7 +26,12 @@ const rootNode = new UpwardHierarchyNode( undefined ) -export function Collection({ datasetRepository, id, page }: CollectionProps) { +export function Collection({ + datasetRepository, + id, + page, + infiniteScrollEnabled = false +}: CollectionProps) { const { user } = useSession() return ( @@ -49,7 +56,11 @@ export function Collection({ datasetRepository, id, page }: CollectionProps) { )} - + {infiniteScrollEnabled ? ( + + ) : ( + + )} ) } diff --git a/src/sections/collection/CollectionFactory.tsx b/src/sections/collection/CollectionFactory.tsx index eaa01da22..9939edb7e 100644 --- a/src/sections/collection/CollectionFactory.tsx +++ b/src/sections/collection/CollectionFactory.tsx @@ -2,6 +2,7 @@ import { ReactElement } from 'react' import { Collection } from './Collection' import { DatasetJSDataverseRepository } from '../../dataset/infrastructure/repositories/DatasetJSDataverseRepository' import { useSearchParams } from 'react-router-dom' +import { INFINITE_SCROLL_ENABLED } from './config' const datasetRepository = new DatasetJSDataverseRepository() export class CollectionFactory { @@ -15,5 +16,12 @@ function CollectionWithSearchParams() { const page = searchParams.get('page') ? parseInt(searchParams.get('page') as string) : undefined const id = searchParams.get('id') ? (searchParams.get('id') as string) : 'root' - return + return ( + + ) } diff --git a/src/sections/collection/config.ts b/src/sections/collection/config.ts new file mode 100644 index 000000000..9aea21dc2 --- /dev/null +++ b/src/sections/collection/config.ts @@ -0,0 +1 @@ +export const INFINITE_SCROLL_ENABLED = true diff --git a/src/sections/collection/datasets-list/DatasetsList.module.scss b/src/sections/collection/datasets-list/DatasetsList.module.scss index 15aff10a5..3bf17dfb6 100644 --- a/src/sections/collection/datasets-list/DatasetsList.module.scss +++ b/src/sections/collection/datasets-list/DatasetsList.module.scss @@ -1,13 +1,50 @@ -@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/colors.module'; .container { min-height: calc(100vh + 100px); - padding:15px; + padding: 15px; border: 1px solid #ddd; border-radius: 4px; } .empty-message-container { - padding: .5em 1em; + padding: 0.5em 1em; background: $dv-warning-box-color; } + +// Infinite scroll enabled +.scrollable-container { + --inline-padding: 1rem; + + height: 650px; + max-height: 650px; + padding-inline: var(--inline-padding); + overflow-x: hidden; + overflow-y: auto; + border: 1px solid #ddd; + border-radius: 4px; + + @media screen and (max-width: 768px) { + --inline-padding: 8px; + } + + @media screen and (min-width: 1280px) { + height: 60vh; + max-height: 60vh; + } + + &--empty-or-error { + padding-block: 1rem; + } + + .sticky-pagination-results { + position: sticky; + top: 0; + z-index: 10; + width: calc(100% + (var(--inline-padding) * 2)); + padding: 1rem var(--inline-padding) 0.5rem; + background-color: var(--bs-white); + box-shadow: 0 0 10px 0 rgba(0 0 0 / 30%); + transform: translateX(calc(var(--inline-padding) * -1)); + } +} diff --git a/src/sections/collection/datasets-list/DatasetsListWithInfiniteScroll.tsx b/src/sections/collection/datasets-list/DatasetsListWithInfiniteScroll.tsx new file mode 100644 index 000000000..dd962ddeb --- /dev/null +++ b/src/sections/collection/datasets-list/DatasetsListWithInfiniteScroll.tsx @@ -0,0 +1,110 @@ +import { useEffect, useState } from 'react' +import useInfiniteScroll from 'react-infinite-scroll-hook' +import cn from 'classnames' +import { SkeletonTheme } from 'react-loading-skeleton' +import { DatasetRepository } from '../../../dataset/domain/repositories/DatasetRepository' +import { PaginationResultsInfo } from '../../shared/pagination/PaginationResultsInfo' +import { DatasetPaginationInfo } from '../../../dataset/domain/models/DatasetPaginationInfo' +import { useLoading } from '../../loading/LoadingContext' +import { NoDatasetsMessage } from './NoDatasetsMessage' +import { DatasetCard } from './dataset-card/DatasetCard' +import { InitialLoadingSkeleton, LoadingSkeleton } from './DatasetsListWithInfiniteScrollSkeletons' +import { ErrorDatasetsMessage } from './ErrorDatasetsMessage' +import { NO_DATASETS, useLoadDatasets } from './useLoadDatasets' +import styles from './DatasetsList.module.scss' +import 'react-loading-skeleton/dist/skeleton.css' + +interface DatasetsListWithInfiniteScrollProps { + datasetRepository: DatasetRepository + collectionId: string +} + +const PAGE_SIZE = 10 +const INITIAL_PAGE = 1 + +export function DatasetsListWithInfiniteScroll({ + datasetRepository, + collectionId +}: DatasetsListWithInfiniteScrollProps) { + const { setIsLoading } = useLoading() + const [paginationInfo, setPaginationInfo] = useState( + new DatasetPaginationInfo(INITIAL_PAGE) + ) + const { + isLoading, + accumulatedDatasets, + totalAvailable, + hasNextPage, + error, + loadMore, + isEmptyDatasets, + areDatasetsAvailable, + accumulatedCount + } = useLoadDatasets(datasetRepository, collectionId, paginationInfo) + + const [sentryRef, { rootRef }] = useInfiniteScroll({ + loading: isLoading, + hasNextPage: hasNextPage, + onLoadMore: loadMore as VoidFunction, + disabled: !!error, + rootMargin: '0px 0px 250px 0px' + }) + + useEffect(() => { + setIsLoading(isLoading) + }, [isLoading]) + + useEffect(() => { + const updatePaginationTotalItems = () => { + if (totalAvailable && totalAvailable !== paginationInfo.totalItems) { + setPaginationInfo(paginationInfo.withTotal(totalAvailable)) + } + } + + updatePaginationTotalItems() + }, [totalAvailable, paginationInfo]) + + useEffect(() => { + const updatePaginationPageNumber = () => { + setPaginationInfo((currentPagination) => + currentPagination.goToPage(accumulatedCount / PAGE_SIZE + 1) + ) + } + + updatePaginationPageNumber() + }, [accumulatedCount]) + + return ( +
+ {isEmptyDatasets && } + + {error && } + + {areDatasetsAvailable && ( + <> +
+ +
+ {accumulatedDatasets.map((dataset) => ( + + ))} + + )} + + {hasNextPage && !error && !isEmptyDatasets && ( +
+ + {accumulatedCount === NO_DATASETS && } + + +
+ )} +
+ ) +} diff --git a/src/sections/collection/datasets-list/DatasetsListWithInfiniteScrollSkeletons.tsx b/src/sections/collection/datasets-list/DatasetsListWithInfiniteScrollSkeletons.tsx new file mode 100644 index 000000000..56a97bec6 --- /dev/null +++ b/src/sections/collection/datasets-list/DatasetsListWithInfiniteScrollSkeletons.tsx @@ -0,0 +1,27 @@ +import styles from './DatasetsList.module.scss' +import Skeleton from 'react-loading-skeleton' + +export const InitialLoadingSkeleton = () => ( + <> +
+ +
+ + + + + + + + +) + +export const LoadingSkeleton = () => ( + <> + + + + +) diff --git a/src/sections/collection/datasets-list/ErrorDatasetsMessage.tsx b/src/sections/collection/datasets-list/ErrorDatasetsMessage.tsx new file mode 100644 index 000000000..a12bb88a5 --- /dev/null +++ b/src/sections/collection/datasets-list/ErrorDatasetsMessage.tsx @@ -0,0 +1,11 @@ +import { Alert } from '@iqss/dataverse-design-system' + +interface ErrorDatasetsMessageProps { + errorMessage: string +} + +export const ErrorDatasetsMessage = ({ errorMessage }: ErrorDatasetsMessageProps) => ( + + {errorMessage} + +) diff --git a/src/sections/collection/datasets-list/useLoadDatasets.tsx b/src/sections/collection/datasets-list/useLoadDatasets.tsx new file mode 100644 index 000000000..314fb999f --- /dev/null +++ b/src/sections/collection/datasets-list/useLoadDatasets.tsx @@ -0,0 +1,77 @@ +import { useMemo, useState } from 'react' +import { getDatasetsWithCount } from '../../../dataset/domain/useCases/getDatasetsWithCount' +import { DatasetPreview } from '../../../dataset/domain/models/DatasetPreview' +import { DatasetRepository } from '../../../dataset/domain/repositories/DatasetRepository' +import { DatasetPaginationInfo } from '../../../dataset/domain/models/DatasetPaginationInfo' +import { DatasetsWithCount } from '../../../dataset/domain/models/DatasetsWithCount' + +export const NO_DATASETS = 0 + +async function loadNextDatasets( + datasetRepository: DatasetRepository, + collectionId: string, + paginationInfo: DatasetPaginationInfo +): Promise { + return getDatasetsWithCount(datasetRepository, collectionId, paginationInfo).catch((_err) => { + throw new Error('Something went wrong getting the datasets') + }) +} + +export function useLoadDatasets( + datasetRepository: DatasetRepository, + collectionId: string, + paginationInfo: DatasetPaginationInfo +) { + const [isLoading, setLoading] = useState(false) + const [accumulatedDatasets, setAccumulatedDatasets] = useState([]) + const [hasNextPage, setHasNextPage] = useState(true) + const [totalAvailable, setTotalAvailable] = useState(undefined) + const [error, setError] = useState(null) + + const isEmptyDatasets = useMemo(() => totalAvailable === NO_DATASETS, [totalAvailable]) + const areDatasetsAvailable = useMemo(() => { + return typeof totalAvailable === 'number' && totalAvailable > NO_DATASETS && !error + }, [totalAvailable, error]) + const accumulatedCount = useMemo(() => accumulatedDatasets.length, [accumulatedDatasets]) + + const loadMore = async () => { + setLoading(true) + try { + const { datasetPreviews, totalCount } = await loadNextDatasets( + datasetRepository, + collectionId, + paginationInfo + ) + const isNextPage = paginationInfo.page * paginationInfo.pageSize < totalCount + + setAccumulatedDatasets((current) => [...current, ...datasetPreviews]) + setTotalAvailable(totalCount) + + setHasNextPage(isNextPage) + + if (!isNextPage) { + setLoading(false) + } + } catch (err) { + const errorMessage = + err instanceof Error && err.message + ? err.message + : 'Something went wrong getting the datasets' + setError(errorMessage) + } finally { + setLoading(false) + } + } + + return { + isLoading, + accumulatedDatasets, + totalAvailable, + hasNextPage, + error, + loadMore, + isEmptyDatasets, + areDatasetsAvailable, + accumulatedCount + } +} diff --git a/src/sections/shared/pagination/PaginationResultsInfo.tsx b/src/sections/shared/pagination/PaginationResultsInfo.tsx index abe711776..a23e2ccd5 100644 --- a/src/sections/shared/pagination/PaginationResultsInfo.tsx +++ b/src/sections/shared/pagination/PaginationResultsInfo.tsx @@ -1,3 +1,4 @@ +import { useCallback } from 'react' import styles from './Pagination.module.scss' import { PaginationInfo } from '../../../shared/pagination/domain/models/PaginationInfo' import { useTranslation } from 'react-i18next' @@ -6,18 +7,36 @@ import { FilePaginationInfo } from '../../../files/domain/models/FilePaginationI interface PaginationResultsInfoProps { paginationInfo: PaginationInfo + accumulated?: number } -export function PaginationResultsInfo({ paginationInfo }: PaginationResultsInfoProps) { +export function PaginationResultsInfo({ paginationInfo, accumulated }: PaginationResultsInfoProps) { const { t } = useTranslation('pagination') + + const defineLocale = useCallback( + (accumulated: number) => + accumulated === 1 + ? 'accumulated.one' + : accumulated < paginationInfo.pageSize + ? 'accumulated.lessThanPageSize' + : 'accumulated.moreThanPageSize', + [accumulated, paginationInfo.pageSize] + ) + return ( - {t('results', { - start: paginationInfo.pageStartItem, - end: paginationInfo.pageEndItem, - item: paginationInfo.itemName, - count: paginationInfo.totalItems - })} + {typeof accumulated === 'number' + ? t(defineLocale(accumulated), { + accumulated: accumulated, + count: paginationInfo.totalItems, + item: paginationInfo.itemName + }) + : t('results', { + start: paginationInfo.pageStartItem, + end: paginationInfo.pageEndItem, + item: paginationInfo.itemName, + count: paginationInfo.totalItems + })} ) } diff --git a/src/settings/infrastructure/SettingJSDataverseRepository.ts b/src/settings/infrastructure/SettingJSDataverseRepository.ts index cb257bc32..16c7df1de 100644 --- a/src/settings/infrastructure/SettingJSDataverseRepository.ts +++ b/src/settings/infrastructure/SettingJSDataverseRepository.ts @@ -4,7 +4,6 @@ import { ZipDownloadLimit } from '../domain/models/ZipDownloadLimit' import { FileSizeUnit } from '../../files/domain/models/FileMetadata' export class SettingJSDataverseRepository implements SettingRepository { - // eslint-disable-next-line unused-imports/no-unused-vars getByName(name: SettingName): Promise> { // TODO - implement using js-dataverse return new Promise((resolve) => { diff --git a/src/stories/WithSettings.tsx b/src/stories/WithSettings.tsx index 95ac7b136..a696da115 100644 --- a/src/stories/WithSettings.tsx +++ b/src/stories/WithSettings.tsx @@ -7,7 +7,6 @@ import { FileSizeUnit } from '../files/domain/models/FileMetadata' const zipDownloadLimitMock = new ZipDownloadLimit(1, FileSizeUnit.BYTES) export const WithSettings = (Story: StoryFn) => { - // eslint-disable-next-line unused-imports/no-unused-vars function getSettingByName(name: SettingName): Promise> { switch (name) { case SettingName.ZIP_DOWNLOAD_LIMIT: diff --git a/src/stories/collection/Collection.stories.tsx b/src/stories/collection/Collection.stories.tsx index a8e3573f9..70f8d8326 100644 --- a/src/stories/collection/Collection.stories.tsx +++ b/src/stories/collection/Collection.stories.tsx @@ -24,6 +24,16 @@ export const Default: Story = { render: () => } +export const InfiniteScrollingEnabled: Story = { + render: () => ( + + ) +} + export const Loading: Story = { render: () => ( diff --git a/src/stories/collection/datasets-list/DatasetsListWithInfiniteScroll.stories.tsx b/src/stories/collection/datasets-list/DatasetsListWithInfiniteScroll.stories.tsx new file mode 100644 index 000000000..88980694a --- /dev/null +++ b/src/stories/collection/datasets-list/DatasetsListWithInfiniteScroll.stories.tsx @@ -0,0 +1,52 @@ +import { Meta, StoryObj } from '@storybook/react' +import { WithI18next } from '../../WithI18next' +import { DatasetMockRepository } from '../../dataset/DatasetMockRepository' +import { DatasetsListWithInfiniteScroll } from '../../../sections/collection/datasets-list/DatasetsListWithInfiniteScroll' +import { DatasetLoadingMockRepository } from '../../dataset/DatasetLoadingMockRepository' +import { NoDatasetsMockRepository } from '../../dataset/NoDatasetsMockRepository' +import { DatasetErrorMockRepository } from '../../dataset/DatasetErrorMockRepository' + +const meta: Meta = { + title: 'Sections/Collection/DatasetsListWithInfiniteScroll', + component: DatasetsListWithInfiniteScroll, + decorators: [WithI18next] +} + +export default meta +type Story = StoryObj + +export const Default: Story = { + render: () => ( + + ) +} + +export const Loading: Story = { + render: () => ( + + ) +} + +export const NoResults: Story = { + render: () => ( + + ) +} + +export const Error: Story = { + render: () => ( + + ) +} diff --git a/src/stories/dataset/DatasetErrorMockRepository.ts b/src/stories/dataset/DatasetErrorMockRepository.ts new file mode 100644 index 000000000..7e8fe2688 --- /dev/null +++ b/src/stories/dataset/DatasetErrorMockRepository.ts @@ -0,0 +1,65 @@ +import { Dataset } from '../../dataset/domain/models/Dataset' +import { DatasetMockRepository } from './DatasetMockRepository' +import { TotalDatasetsCount } from '../../dataset/domain/models/TotalDatasetsCount' +import { DatasetPaginationInfo } from '../../dataset/domain/models/DatasetPaginationInfo' +import { DatasetPreview } from '../../dataset/domain/models/DatasetPreview' +import { DatasetFormFields } from '../../dataset/domain/models/DatasetFormFields' +import { DatasetsWithCount } from '../../dataset/domain/models/DatasetsWithCount' + +export class DatasetErrorMockRepository implements DatasetMockRepository { + getAll(_collectionId: string, _paginationInfo: DatasetPaginationInfo): Promise { + return new Promise((_resolve, reject) => { + setTimeout(() => { + reject('Error thrown from mock') + }, 1000) + }) + } + + getTotalDatasetsCount(_collectionId: string): Promise { + return new Promise((_resolve, reject) => { + setTimeout(() => { + reject('Error thrown from mock') + }, 1000) + }) + } + + getAllWithCount: ( + collectionId: string, + paginationInfo: DatasetPaginationInfo + ) => Promise = ( + _collectionId: string, + _paginationInfo: DatasetPaginationInfo + ) => { + return new Promise((_resolve, reject) => { + setTimeout(() => { + reject('Error thrown from mock') + }, 1000) + }) + } + + getByPersistentId( + _persistentId: string, + _version?: string | undefined + ): Promise { + return new Promise((_resolve, reject) => { + setTimeout(() => { + reject('Error thrown from mock') + }, 1000) + }) + } + getByPrivateUrlToken(_privateUrlToken: string): Promise { + return new Promise((_resolve, reject) => { + setTimeout(() => { + reject('Error thrown from mock') + }, 1000) + }) + } + + createDataset(_fields: DatasetFormFields): Promise { + return new Promise((_resolve, reject) => { + setTimeout(() => { + reject('Error thrown from mock') + }, 1000) + }) + } +} diff --git a/src/stories/dataset/DatasetLoadingMockRepository.ts b/src/stories/dataset/DatasetLoadingMockRepository.ts index 3a14f4a0a..6b0635966 100644 --- a/src/stories/dataset/DatasetLoadingMockRepository.ts +++ b/src/stories/dataset/DatasetLoadingMockRepository.ts @@ -1,10 +1,20 @@ import { DatasetMockRepository } from './DatasetMockRepository' import { DatasetPaginationInfo } from '../../dataset/domain/models/DatasetPaginationInfo' import { DatasetPreview } from '../../dataset/domain/models/DatasetPreview' +import { DatasetsWithCount } from '../../dataset/domain/models/DatasetsWithCount' export class DatasetLoadingMockRepository extends DatasetMockRepository { - // eslint-disable-next-line unused-imports/no-unused-vars - getAll(collectionId: string, paginationInfo: DatasetPaginationInfo): Promise { + getAll(_collectionId: string, _paginationInfo: DatasetPaginationInfo): Promise { + return new Promise(() => {}) + } + + getDatasetsWithCount: ( + collectionId: string, + paginationInfo: DatasetPaginationInfo + ) => Promise = ( + _collectionId: string, + _paginationInfo: DatasetPaginationInfo + ) => { return new Promise(() => {}) } } diff --git a/src/stories/dataset/DatasetMockRepository.ts b/src/stories/dataset/DatasetMockRepository.ts index 71bde4bec..62b6a64d5 100644 --- a/src/stories/dataset/DatasetMockRepository.ts +++ b/src/stories/dataset/DatasetMockRepository.ts @@ -6,18 +6,18 @@ import { DatasetPaginationInfo } from '../../dataset/domain/models/DatasetPagina import { DatasetPreview } from '../../dataset/domain/models/DatasetPreview' import { DatasetPreviewMother } from '../../../tests/component/dataset/domain/models/DatasetPreviewMother' import { DatasetFormFields } from '../../dataset/domain/models/DatasetFormFields' +import { DatasetsWithCount } from '../../dataset/domain/models/DatasetsWithCount' import { FakerHelper } from '../../../tests/component/shared/FakerHelper' export class DatasetMockRepository implements DatasetRepository { - // eslint-disable-next-line unused-imports/no-unused-vars - getAll(collectionId: string, paginationInfo: DatasetPaginationInfo): Promise { + getAll(_collectionId: string, paginationInfo: DatasetPaginationInfo): Promise { return new Promise((resolve) => { setTimeout(() => { resolve(DatasetPreviewMother.createManyRealistic(paginationInfo.pageSize)) }, FakerHelper.loadingTimout()) }) } - // eslint-disable-next-line unused-imports/no-unused-vars - getTotalDatasetsCount(collectionId: string): Promise { + + getTotalDatasetsCount(_collectionId: string): Promise { return new Promise((resolve) => { setTimeout(() => { resolve(200) @@ -25,11 +25,26 @@ export class DatasetMockRepository implements DatasetRepository { }) } + getAllWithCount: ( + collectionId: string, + paginationInfo: DatasetPaginationInfo + ) => Promise = ( + _collectionId: string, + paginationInfo: DatasetPaginationInfo + ) => { + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + datasetPreviews: DatasetPreviewMother.createManyRealistic(paginationInfo.pageSize), + totalCount: 200 + }) + }, FakerHelper.loadingTimout()) + }) + } + getByPersistentId( - // eslint-disable-next-line unused-imports/no-unused-vars - persistentId: string, - // eslint-disable-next-line unused-imports/no-unused-vars - version?: string | undefined + _persistentId: string, + _version?: string | undefined ): Promise { return new Promise((resolve) => { setTimeout(() => { @@ -37,10 +52,7 @@ export class DatasetMockRepository implements DatasetRepository { }, FakerHelper.loadingTimout()) }) } - getByPrivateUrlToken( - // eslint-disable-next-line unused-imports/no-unused-vars - privateUrlToken: string - ): Promise { + getByPrivateUrlToken(_privateUrlToken: string): Promise { return new Promise((resolve) => { setTimeout(() => { resolve(DatasetMother.createRealistic()) diff --git a/src/stories/dataset/NoDatasetsMockRepository.ts b/src/stories/dataset/NoDatasetsMockRepository.ts index b794139cc..66efc7c1e 100644 --- a/src/stories/dataset/NoDatasetsMockRepository.ts +++ b/src/stories/dataset/NoDatasetsMockRepository.ts @@ -2,21 +2,35 @@ 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' +import { DatasetsWithCount } from '../../dataset/domain/models/DatasetsWithCount' export class NoDatasetsMockRepository extends DatasetMockRepository { - // eslint-disable-next-line unused-imports/no-unused-vars - getAll(collectionId: string, paginationInfo: DatasetPaginationInfo): Promise { + getAll(_collectionId: string, _paginationInfo: DatasetPaginationInfo): Promise { return new Promise((resolve) => { setTimeout(() => { resolve([]) }, 1000) }) } - // eslint-disable-next-line unused-imports/no-unused-vars - getTotalDatasetsCount(collectionId: string): Promise { + + getTotalDatasetsCount(_collectionId: string): Promise { return new Promise((resolve) => { setTimeout(() => { resolve(0) }, 1000) }) } + + getAllWithCount: ( + collectionId: string, + paginationInfo: DatasetPaginationInfo + ) => Promise = ( + _collectionId: string, + _paginationInfo: DatasetPaginationInfo + ) => { + return new Promise((resolve) => { + setTimeout(() => { + resolve({ datasetPreviews: [], totalCount: 0 }) + }, 1000) + }) + } } diff --git a/src/stories/dataset/WithDatasetAllPermissionsGranted.tsx b/src/stories/dataset/WithDatasetAllPermissionsGranted.tsx index 56eb8544d..8f989d0c5 100644 --- a/src/stories/dataset/WithDatasetAllPermissionsGranted.tsx +++ b/src/stories/dataset/WithDatasetAllPermissionsGranted.tsx @@ -11,10 +11,8 @@ import { FakerHelper } from '../../../tests/component/shared/FakerHelper' export const WithDatasetAllPermissionsGranted = (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 + _persistentId: string, + _version?: string | undefined ): Promise => { return new Promise((resolve) => { setTimeout(() => { diff --git a/src/stories/dataset/WithDatasetDraftAsOwner.tsx b/src/stories/dataset/WithDatasetDraftAsOwner.tsx index 1fedcddfd..0eee1be50 100644 --- a/src/stories/dataset/WithDatasetDraftAsOwner.tsx +++ b/src/stories/dataset/WithDatasetDraftAsOwner.tsx @@ -13,8 +13,7 @@ export const WithDatasetDraftAsOwner = (Story: StoryFn) => { const datasetRepository = {} as DatasetRepository datasetRepository.getByPersistentId = ( persistentId: string, - // eslint-disable-next-line unused-imports/no-unused-vars - version?: string | undefined + _version?: string | undefined ): Promise => { return new Promise((resolve) => { setTimeout(() => { diff --git a/src/stories/dataset/WithDatasetLoading.tsx b/src/stories/dataset/WithDatasetLoading.tsx index 654917e3a..f3d69b11b 100644 --- a/src/stories/dataset/WithDatasetLoading.tsx +++ b/src/stories/dataset/WithDatasetLoading.tsx @@ -6,10 +6,8 @@ import { DatasetProvider } from '../../sections/dataset/DatasetProvider' export const WithDatasetLoading = (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 + _persistentId: string, + _version?: string | undefined ): Promise => { return new Promise(() => {}) } diff --git a/src/stories/dataset/WithDatasetLockedFromEdits.tsx b/src/stories/dataset/WithDatasetLockedFromEdits.tsx index 8c3e7ec24..8f1ca4744 100644 --- a/src/stories/dataset/WithDatasetLockedFromEdits.tsx +++ b/src/stories/dataset/WithDatasetLockedFromEdits.tsx @@ -13,8 +13,7 @@ export const WithDatasetLockedFromEdits = (Story: StoryFn) => { const datasetRepository = {} as DatasetRepository datasetRepository.getByPersistentId = ( persistentId: string, - // eslint-disable-next-line unused-imports/no-unused-vars - version?: string | undefined + _version?: string | undefined ): Promise => { return new Promise((resolve) => { setTimeout(() => { diff --git a/src/stories/dataset/WithDatasetNotFound.tsx b/src/stories/dataset/WithDatasetNotFound.tsx index 66eb9c7d0..fdf2ce321 100644 --- a/src/stories/dataset/WithDatasetNotFound.tsx +++ b/src/stories/dataset/WithDatasetNotFound.tsx @@ -6,10 +6,8 @@ import { DatasetProvider } from '../../sections/dataset/DatasetProvider' export const WithDatasetNotFound = (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 + _persistentId: string, + _version?: string | undefined ): Promise => { return new Promise((resolve) => { setTimeout(() => { diff --git a/src/stories/dataset/WithDatasetPrivateUrl.tsx b/src/stories/dataset/WithDatasetPrivateUrl.tsx index 357425b50..698e4b7ce 100644 --- a/src/stories/dataset/WithDatasetPrivateUrl.tsx +++ b/src/stories/dataset/WithDatasetPrivateUrl.tsx @@ -8,8 +8,7 @@ import { FakerHelper } from '../../../tests/component/shared/FakerHelper' export const WithDatasetPrivateUrl = (Story: StoryFn) => { const datasetRepository = {} as DatasetRepository datasetRepository.getByPrivateUrlToken = ( - // eslint-disable-next-line unused-imports/no-unused-vars - privateUrlToken: string + _privateUrlToken: string ): Promise => { return new Promise((resolve) => { setTimeout(() => { diff --git a/src/stories/dataset/WithDeaccessionedDataset.tsx b/src/stories/dataset/WithDeaccessionedDataset.tsx index 83283182f..2cfffd464 100644 --- a/src/stories/dataset/WithDeaccessionedDataset.tsx +++ b/src/stories/dataset/WithDeaccessionedDataset.tsx @@ -11,10 +11,8 @@ import { FakerHelper } from '../../../tests/component/shared/FakerHelper' export const WithDeaccessionedDataset = (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 + _persistentId: string, + _version?: string | undefined ): Promise => { return new Promise((resolve) => { setTimeout(() => { diff --git a/src/stories/file/FileMockLoadingRepository.ts b/src/stories/file/FileMockLoadingRepository.ts index fd823764f..f9c345af4 100644 --- a/src/stories/file/FileMockLoadingRepository.ts +++ b/src/stories/file/FileMockLoadingRepository.ts @@ -9,10 +9,8 @@ import { FakerHelper } from '../../../tests/component/shared/FakerHelper' export class FileMockLoadingRepository extends FileMockRepository implements FileRepository { getAllByDatasetPersistentId( - // eslint-disable-next-line unused-imports/no-unused-vars - datasetPersistentId: string, - // eslint-disable-next-line unused-imports/no-unused-vars - datasetVersion: DatasetVersion + _datasetPersistentId: string, + _datasetVersion: DatasetVersion ): Promise { return new Promise(() => { setTimeout(() => { @@ -22,12 +20,9 @@ export class FileMockLoadingRepository extends FileMockRepository implements Fil } getFilesCountInfoByDatasetPersistentId( - // eslint-disable-next-line unused-imports/no-unused-vars - datasetPersistentId: string, - // eslint-disable-next-line unused-imports/no-unused-vars - datasetVersionNumber: DatasetVersionNumber, - // eslint-disable-next-line unused-imports/no-unused-vars - criteria?: FileCriteria + _datasetPersistentId: string, + _datasetVersionNumber: DatasetVersionNumber, + _criteria?: FileCriteria ): Promise { return new Promise(() => { setTimeout(() => { @@ -36,8 +31,7 @@ export class FileMockLoadingRepository extends FileMockRepository implements Fil }) } - // eslint-disable-next-line unused-imports/no-unused-vars - getById(id: number): Promise { + getById(_id: number): Promise { return new Promise(() => { setTimeout(() => { // Do nothing diff --git a/src/stories/file/FileMockNoDataRepository.ts b/src/stories/file/FileMockNoDataRepository.ts index bfd993666..c51bd83b5 100644 --- a/src/stories/file/FileMockNoDataRepository.ts +++ b/src/stories/file/FileMockNoDataRepository.ts @@ -9,10 +9,8 @@ import { File } from '../../files/domain/models/File' export class FileMockNoDataRepository extends FileMockRepository implements FileRepository { getAllByDatasetPersistentId( - // eslint-disable-next-line unused-imports/no-unused-vars - datasetPersistentId: string, - // eslint-disable-next-line unused-imports/no-unused-vars - datasetVersion: DatasetVersion + _datasetPersistentId: string, + _datasetVersion: DatasetVersion ): Promise { return new Promise((resolve) => { setTimeout(() => { @@ -22,10 +20,8 @@ export class FileMockNoDataRepository extends FileMockRepository implements File } getFilesCountInfoByDatasetPersistentId( - // eslint-disable-next-line unused-imports/no-unused-vars - datasetPersistentId: string, - // eslint-disable-next-line unused-imports/no-unused-vars - datasetVersionNumber: DatasetVersionNumber + _datasetPersistentId: string, + _datasetVersionNumber: DatasetVersionNumber ): Promise { return new Promise((resolve) => { setTimeout(() => { @@ -35,12 +31,9 @@ export class FileMockNoDataRepository extends FileMockRepository implements File } getFilesTotalDownloadSizeByDatasetPersistentId( - // eslint-disable-next-line unused-imports/no-unused-vars - datasetPersistentId: string, - // eslint-disable-next-line unused-imports/no-unused-vars - datasetVersionNumber: DatasetVersionNumber, - // eslint-disable-next-line unused-imports/no-unused-vars - criteria?: FileCriteria + _datasetPersistentId: string, + _datasetVersionNumber: DatasetVersionNumber, + _criteria?: FileCriteria ): Promise { return new Promise((resolve) => { setTimeout(() => { @@ -49,8 +42,7 @@ export class FileMockNoDataRepository extends FileMockRepository implements File }) } - // eslint-disable-next-line unused-imports/no-unused-vars - getById(id: number): Promise { + getById(_id: number): Promise { return new Promise((resolve) => { setTimeout(() => { resolve(undefined) diff --git a/src/stories/file/FileMockNoFiltersRepository.ts b/src/stories/file/FileMockNoFiltersRepository.ts index 04ccd7574..7d48e7558 100644 --- a/src/stories/file/FileMockNoFiltersRepository.ts +++ b/src/stories/file/FileMockNoFiltersRepository.ts @@ -10,10 +10,8 @@ import { FakerHelper } from '../../../tests/component/shared/FakerHelper' export class FileMockNoFiltersRepository extends FileMockRepository implements FileRepository { getAllByDatasetPersistentId( - // eslint-disable-next-line unused-imports/no-unused-vars - datasetPersistentId: string, - // eslint-disable-next-line unused-imports/no-unused-vars - datasetVersion: DatasetVersion + _datasetPersistentId: string, + _datasetVersion: DatasetVersion ): Promise { return new Promise((resolve) => { setTimeout(() => { @@ -23,12 +21,9 @@ export class FileMockNoFiltersRepository extends FileMockRepository implements F } getFilesCountInfoByDatasetPersistentId( - // eslint-disable-next-line unused-imports/no-unused-vars - datasetPersistentId: string, - // eslint-disable-next-line unused-imports/no-unused-vars - datasetVersionNumber: DatasetVersionNumber, - // eslint-disable-next-line unused-imports/no-unused-vars - criteria?: FileCriteria + _datasetPersistentId: string, + _datasetVersionNumber: DatasetVersionNumber, + _criteria?: FileCriteria ): Promise { return new Promise((resolve) => { setTimeout(() => { diff --git a/src/stories/file/FileMockRepository.ts b/src/stories/file/FileMockRepository.ts index 688410c96..1aa466014 100644 --- a/src/stories/file/FileMockRepository.ts +++ b/src/stories/file/FileMockRepository.ts @@ -15,10 +15,9 @@ import { FakerHelper } from '../../../tests/component/shared/FakerHelper' export class FileMockRepository implements FileRepository { constructor(public readonly fileMock?: File) {} - // eslint-disable-next-line unused-imports/no-unused-vars getAllByDatasetPersistentId( - datasetPersistentId: string, - datasetVersion: DatasetVersion, + _datasetPersistentId: string, + _datasetVersion: DatasetVersion, paginationInfo?: FilePaginationInfo ): Promise { return new Promise((resolve) => { @@ -29,10 +28,8 @@ export class FileMockRepository implements FileRepository { } getFilesCountInfoByDatasetPersistentId( - // eslint-disable-next-line unused-imports/no-unused-vars - datasetPersistentId: string, - // eslint-disable-next-line unused-imports/no-unused-vars - datasetVersionNumber: DatasetVersionNumber + _datasetPersistentId: string, + _datasetVersionNumber: DatasetVersionNumber ): Promise { return new Promise((resolve) => { setTimeout(() => { @@ -42,12 +39,9 @@ export class FileMockRepository implements FileRepository { } getFilesTotalDownloadSizeByDatasetPersistentId( - // eslint-disable-next-line unused-imports/no-unused-vars - datasetPersistentId: string, - // eslint-disable-next-line unused-imports/no-unused-vars - datasetVersionNumber: DatasetVersionNumber, - // eslint-disable-next-line unused-imports/no-unused-vars - criteria?: FileCriteria + _datasetPersistentId: string, + _datasetVersionNumber: DatasetVersionNumber, + _criteria?: FileCriteria ): Promise { return new Promise((resolve) => { setTimeout(() => { @@ -56,10 +50,7 @@ export class FileMockRepository implements FileRepository { }) } - // eslint-disable-next-line unused-imports/no-unused-vars - - // eslint-disable-next-line unused-imports/no-unused-vars - getById(id: number): Promise { + getById(_id: number): Promise { return new Promise((resolve) => { setTimeout(() => { resolve(this.fileMock ?? FileMother.createRealistic()) @@ -67,13 +58,11 @@ export class FileMockRepository implements FileRepository { }) } - // eslint-disable-next-line unused-imports/no-unused-vars - getMultipleFileDownloadUrl(ids: number[], downloadMode: FileDownloadMode): string { + getMultipleFileDownloadUrl(_ids: number[], _downloadMode: FileDownloadMode): string { return FileMetadataMother.createDownloadUrl() } - // eslint-disable-next-line unused-imports/no-unused-vars - getFileDownloadUrl(id: number, downloadMode: FileDownloadMode): string { + getFileDownloadUrl(_id: number, _downloadMode: FileDownloadMode): string { return FileMetadataMother.createDownloadUrl() } } diff --git a/tests/component/sections/collection/Collection.spec.tsx b/tests/component/sections/collection/Collection.spec.tsx index 7ad0828d8..7cd271ae0 100644 --- a/tests/component/sections/collection/Collection.spec.tsx +++ b/tests/component/sections/collection/Collection.spec.tsx @@ -60,4 +60,23 @@ describe('Collection page', () => { cy.findByText('41 to 50 of 200 Datasets').should('exist') }) + + it('renders the datasets list with infinite scrolling enabled', () => { + const first10Elements = datasets.slice(0, 10) + + datasetRepository.getAllWithCount = cy.stub().resolves({ + datasetPreviews: first10Elements, + totalCount: totalDatasetsCount + }) + + cy.customMount( + + ) + + cy.findByText('10 of 200 Datasets seen').should('exist') + + first10Elements.forEach((dataset) => { + cy.findByText(dataset.version.title).should('exist') + }) + }) }) diff --git a/tests/component/sections/collection/datasets-list/DatasetsListWithInfiniteScroll.spec.tsx b/tests/component/sections/collection/datasets-list/DatasetsListWithInfiniteScroll.spec.tsx new file mode 100644 index 000000000..2f23cc6bd --- /dev/null +++ b/tests/component/sections/collection/datasets-list/DatasetsListWithInfiniteScroll.spec.tsx @@ -0,0 +1,97 @@ +import { DatasetRepository } from '../../../../../src/dataset/domain/repositories/DatasetRepository' +import { DatasetsListWithInfiniteScroll } from '../../../../../src/sections/collection/datasets-list/DatasetsListWithInfiniteScroll' +import { DatasetPreviewMother } from '../../../dataset/domain/models/DatasetPreviewMother' + +const datasetRepository: DatasetRepository = {} as DatasetRepository +const totalDatasetsCount = 200 +const datasets = DatasetPreviewMother.createMany(totalDatasetsCount) +const first10Elements = datasets.slice(0, 10) +const only4DatasetsCount = 4 + +describe('Datasets List with Infinite Scroll', () => { + beforeEach(() => { + datasetRepository.getAllWithCount = cy.stub().resolves({ + datasetPreviews: first10Elements, + totalCount: totalDatasetsCount + }) + }) + it('renders skeleton while loading', () => { + cy.customMount( + + ) + + cy.findByTestId('datasets-list-infinite-scroll-skeleton').should('exist') + datasets.forEach((dataset) => { + cy.findByRole('link', { name: dataset.version.title }).should('not.exist') + }) + }) + + it('renders no datasets message when there are no datasets', () => { + datasetRepository.getAllWithCount = cy.stub().resolves({ + datasetPreviews: [], + totalCount: 0 + }) + + cy.customMount( + + ) + + cy.findByText(/This dataverse currently has no datasets./).should('exist') + }) + + it('renders the first 10 datasets', () => { + cy.customMount( + + ) + + cy.findByText('10 of 200 Datasets seen').should('exist') + first10Elements.forEach((dataset) => { + cy.findByText(dataset.version.title) + .should('exist') + .should('have.attr', 'href', `/datasets?persistentId=${dataset.persistentId}`) + }) + }) + + it('renders the first 10 datasets with more to load, showing the bottom loading skeleton but not the header skeleton', () => { + cy.customMount( + + ) + + first10Elements.forEach((dataset) => { + cy.findByText(dataset.version.title) + .should('exist') + .should('have.attr', 'href', `/datasets?persistentId=${dataset.persistentId}`) + }) + cy.findByTestId('datasets-list-infinite-scroll-skeleton-header').should('not.exist') + cy.findByTestId('datasets-list-infinite-scroll-skeleton').should('exist') + }) + + it('renders 4 datasets with no more to load, correct results in header, and no bottom skeleton loader', () => { + const first4Elements = datasets.slice(0, only4DatasetsCount) + datasetRepository.getAllWithCount = cy.stub().resolves({ + datasetPreviews: first4Elements, + totalCount: only4DatasetsCount + }) + cy.customMount( + + ) + + cy.findByText(`${only4DatasetsCount} Datasets`).should('exist') + first4Elements.forEach((dataset) => { + cy.findByText(dataset.version.title) + .should('exist') + .should('have.attr', 'href', `/datasets?persistentId=${dataset.persistentId}`) + }) + cy.findByTestId('datasets-list-infinite-scroll-skeleton').should('not.exist') + }) + + it('renders error message when there is an error', () => { + datasetRepository.getAllWithCount = cy.stub().rejects(new Error('some error')) + + cy.customMount( + + ) + + cy.findByText('Error').should('exist') + }) +}) diff --git a/tests/component/sections/shared/pagination/PaginationResultsInfo.spec.tsx b/tests/component/sections/shared/pagination/PaginationResultsInfo.spec.tsx index da9ee8214..efb20be00 100644 --- a/tests/component/sections/shared/pagination/PaginationResultsInfo.spec.tsx +++ b/tests/component/sections/shared/pagination/PaginationResultsInfo.spec.tsx @@ -23,4 +23,34 @@ describe('PaginationResultsInfo', () => { cy.findByText('1 Item').should('exist') }) + + it('shows the correct results when accumulated prop passed and only one result', () => { + cy.customMount( + (1, 10, 1)} + accumulated={1} + /> + ) + cy.findByText('1 Item').should('exist') + }) + + it('shows the correct results when accumulated prop passed and results are more than one and less than page size', () => { + cy.customMount( + (1, 10, 6)} + accumulated={6} + /> + ) + cy.findByText('6 Items').should('exist') + }) + + it('shows the correct results when accumulated prop passed and results are more than page size', () => { + cy.customMount( + (1, 10, 15)} + accumulated={10} + /> + ) + cy.findByText('10 of 15 Items seen').should('exist') + }) }) diff --git a/tests/e2e-integration/e2e/sections/collection/Collection.spec.ts b/tests/e2e-integration/e2e/sections/collection/Collection.spec.ts index 89f3bc973..42116bae1 100644 --- a/tests/e2e-integration/e2e/sections/collection/Collection.spec.ts +++ b/tests/e2e-integration/e2e/sections/collection/Collection.spec.ts @@ -108,61 +108,63 @@ describe('Collection Page', () => { }) }) - it('displays the correct page of the datasets list when passing the page query param', () => { - cy.wrap( - DatasetHelper.destroyAll().then(() => DatasetHelper.createMany(12)), - { timeout: 10000 } - ).then(() => { - cy.visit('/spa?page=2') - - cy.findAllByText(/Root/i).should('exist') - cy.findByText(/Dataverse Admin/i).should('exist') - - cy.findByText('11 to 12 of 12 Datasets').should('exist') + describe.skip('Currently skipping all tests as we are only rendering an infinite scrollable container. Please refactor these tests if a toggle button is added to switch between pagination and infinite scroll.', () => { + it.skip('displays the correct page of the datasets list when passing the page query param', () => { + cy.wrap( + DatasetHelper.destroyAll().then(() => DatasetHelper.createMany(12)), + { timeout: 10000 } + ).then(() => { + cy.visit('/spa?page=2') + + cy.findAllByText(/Root/i).should('exist') + cy.findByText(/Dataverse Admin/i).should('exist') + + cy.findByText('11 to 12 of 12 Datasets').should('exist') + }) }) - }) - it('updates the query param when updateQueryParam is true', () => { - cy.wrap( - DatasetHelper.destroyAll().then(() => DatasetHelper.createMany(12)), - { timeout: 10000 } - ).then(() => { - cy.visit('/spa') + it.skip('updates the query param when updateQueryParam is true', () => { + cy.wrap( + DatasetHelper.destroyAll().then(() => DatasetHelper.createMany(12)), + { timeout: 10000 } + ).then(() => { + cy.visit('/spa') - cy.findAllByText(/Root/i).should('exist') - cy.findByText(/Dataverse Admin/i).should('exist') + cy.findAllByText(/Root/i).should('exist') + cy.findByText(/Dataverse Admin/i).should('exist') - cy.findByRole('button', { name: 'Next' }).click() - cy.findByText('11 to 12 of 12 Datasets').should('exist') - cy.location('search').should('eq', '?page=2') + cy.findByRole('button', { name: 'Next' }).click() + cy.findByText('11 to 12 of 12 Datasets').should('exist') + cy.location('search').should('eq', '?page=2') + }) }) - }) - it('correctly changes the pages when using the back and forward buttons from the browser after using some page number button', () => { - cy.wrap( - DatasetHelper.destroyAll().then(() => DatasetHelper.createMany(12)), - { timeout: 10000 } - ).then(() => { - cy.visit('/spa?page=2') + it.skip('correctly changes the pages when using the back and forward buttons from the browser after using some page number button', () => { + cy.wrap( + DatasetHelper.destroyAll().then(() => DatasetHelper.createMany(12)), + { timeout: 10000 } + ).then(() => { + cy.visit('/spa?page=2') - cy.findAllByText(/Root/i).should('exist') - cy.findByText(/Dataverse Admin/i).should('exist') - cy.findByText('11 to 12 of 12 Datasets').should('exist') + cy.findAllByText(/Root/i).should('exist') + cy.findByText(/Dataverse Admin/i).should('exist') + cy.findByText('11 to 12 of 12 Datasets').should('exist') - cy.wait(1000) + cy.wait(1000) - cy.findByRole('button', { name: '1' }).click({ force: true }) - cy.findByText('1 to 10 of 12 Datasets').should('exist') + cy.findByRole('button', { name: '1' }).click({ force: true }) + cy.findByText('1 to 10 of 12 Datasets').should('exist') - cy.wait(1000) + cy.wait(1000) - cy.go('back') - cy.findByText('11 to 12 of 12 Datasets').should('exist') + cy.go('back') + cy.findByText('11 to 12 of 12 Datasets').should('exist') - cy.wait(1000) + cy.wait(1000) - cy.go('forward') - cy.findByText('1 to 10 of 12 Datasets').should('exist') + cy.go('forward') + cy.findByText('1 to 10 of 12 Datasets').should('exist') + }) }) }) @@ -172,4 +174,23 @@ describe('Collection Page', () => { cy.findAllByText(/Subcollection/i).should('exist') cy.findByText(/Dataverse Admin/i).should('exist') }) + + it('12 Datasets - displays first 10 datasets, scroll to the bottom and displays the remaining 2 datasets', () => { + cy.wrap( + DatasetHelper.destroyAll().then(() => DatasetHelper.createMany(12)), + { timeout: 10_000 } + ).then(() => { + cy.wait(1_500) + cy.visit('/spa') + + cy.findAllByText(/Root/i).should('exist') + cy.findByText(/Dataverse Admin/i).should('exist') + + cy.findByText('10 of 12 Datasets seen').should('exist') + + cy.get('[data-testid="scrollable-container"]').scrollTo('bottom', { ensureScrollable: false }) + cy.wait(1_500) + cy.findByText('12 of 12 Datasets seen').should('exist') + }) + }) })