diff --git a/packages/ui-tests/stories/catalog/PropertiesModal.stories.tsx b/packages/ui-tests/stories/catalog/PropertiesModal.stories.tsx index 29e27f01f..de36a3e2d 100644 --- a/packages/ui-tests/stories/catalog/PropertiesModal.stories.tsx +++ b/packages/ui-tests/stories/catalog/PropertiesModal.stories.tsx @@ -1,4 +1,11 @@ import { ITile, PropertiesModal } from '@kaoto/kaoto'; +import { + CatalogLoaderProvider, + CatalogSchemaLoader, + CatalogTilesProvider, + RuntimeProvider, + SchemasLoaderProvider, +} from '@kaoto/kaoto/testing'; import { Meta, StoryFn } from '@storybook/react'; import { useState } from 'react'; import aggregate from '../../cypress/fixtures/aggregate.json'; @@ -6,9 +13,22 @@ import cronSource from '../../cypress/fixtures/cronSource.json'; import activeMq from '../../cypress/fixtures/activeMq.json'; import box from '../../cypress/fixtures/box.json'; +const ContextDecorator = (Story: StoryFn) => ( + + + + + + + + + +); + export default { title: 'Components/PropertiesModal', component: PropertiesModal, + decorators: [ContextDecorator], } as Meta; const Template: StoryFn = (args) => { diff --git a/packages/ui/src/camel-utils/camel-to-tabs.adapter.test.ts b/packages/ui/src/camel-utils/camel-to-tabs.adapter.test.ts index 914294710..1ffc1fcfa 100644 --- a/packages/ui/src/camel-utils/camel-to-tabs.adapter.test.ts +++ b/packages/ui/src/camel-utils/camel-to-tabs.adapter.test.ts @@ -143,6 +143,11 @@ describe('camelComponentToTab', () => { expect(tab).toHaveLength(2); }); + + it('should return empty tab when component definition is undefined', () => { + const tab = transformCamelComponentIntoTab(undefined); + expect(tab).toHaveLength(0); + }); }); describe('camelProcessorToTab', () => { @@ -187,6 +192,11 @@ describe('camelProcessorToTab', () => { const tab = transformCamelProcessorComponentIntoTab(processDef); expect(tab).toHaveLength(0); }); + + it('should return empty tab when processor definition is undefined', () => { + const tab = transformCamelProcessorComponentIntoTab(undefined); + expect(tab).toHaveLength(0); + }); }); describe('kameletToTab', () => { @@ -236,4 +246,9 @@ describe('kameletToTab', () => { const tab = transformKameletComponentIntoTab(kameletDef); expect(tab).toHaveLength(0); }); + + it('should return empty tab when kamelet definition is undefined', () => { + const tab = transformKameletComponentIntoTab(undefined); + expect(tab).toHaveLength(0); + }); }); diff --git a/packages/ui/src/camel-utils/camel-to-tabs.adapter.ts b/packages/ui/src/camel-utils/camel-to-tabs.adapter.ts index 7ee832ba4..2a8dd3ecd 100644 --- a/packages/ui/src/camel-utils/camel-to-tabs.adapter.ts +++ b/packages/ui/src/camel-utils/camel-to-tabs.adapter.ts @@ -1,5 +1,6 @@ import { IPropertiesTab, IPropertiesTable } from '../components/PropertiesModal'; import { ICamelComponentDefinition, ICamelProcessorDefinition, IKameletDefinition } from '../models'; +import { isDefined } from '../utils'; import { IPropertiesTableFilter, camelComponentApisToTable, @@ -46,9 +47,12 @@ const transformPropertiesIntoTab = ( }; }; -export const transformCamelComponentIntoTab = (componentDef: ICamelComponentDefinition): IPropertiesTab[] => { - const finalTabs: IPropertiesTab[] = []; +export const transformCamelComponentIntoTab = ( + componentDef: ICamelComponentDefinition | undefined, +): IPropertiesTab[] => { + if (!isDefined(componentDef)) return []; + const finalTabs: IPropertiesTab[] = []; let tab = transformPropertiesIntoTab( [ { @@ -116,7 +120,11 @@ export const transformCamelComponentIntoTab = (componentDef: ICamelComponentDefi return finalTabs; }; -export const transformCamelProcessorComponentIntoTab = (processorDef: ICamelProcessorDefinition): IPropertiesTab[] => { +export const transformCamelProcessorComponentIntoTab = ( + processorDef: ICamelProcessorDefinition | undefined, +): IPropertiesTab[] => { + if (!isDefined(processorDef)) return []; + const finalTabs: IPropertiesTab[] = []; const tab = transformPropertiesIntoTab( [ @@ -131,7 +139,9 @@ export const transformCamelProcessorComponentIntoTab = (processorDef: ICamelProc return finalTabs; }; -export const transformKameletComponentIntoTab = (kameletDef: IKameletDefinition): IPropertiesTab[] => { +export const transformKameletComponentIntoTab = (kameletDef: IKameletDefinition | undefined): IPropertiesTab[] => { + if (!isDefined(kameletDef)) return []; + const finalTabs: IPropertiesTab[] = []; const tab = transformPropertiesIntoTab( [ diff --git a/packages/ui/src/camel-utils/camel-to-tile.adapter.ts b/packages/ui/src/camel-utils/camel-to-tile.adapter.ts index 13f5b1dec..235f7e13a 100644 --- a/packages/ui/src/camel-utils/camel-to-tile.adapter.ts +++ b/packages/ui/src/camel-utils/camel-to-tile.adapter.ts @@ -30,7 +30,6 @@ export const camelComponentToTile = (componentDef: ICamelComponentDefinition): I tags, provider, version, - rawObject: componentDef, }; }; @@ -45,7 +44,6 @@ export const camelProcessorToTile = (processorDef: ICamelProcessorDefinition): I description, headerTags: ['Processor'], tags, - rawObject: processorDef, }; }; @@ -73,6 +71,5 @@ export const kameletToTile = (kameletDef: IKameletDefinition): ITile => { headerTags, tags, version, - rawObject: kameletDef, }; }; diff --git a/packages/ui/src/components/Catalog/BaseCatalog.test.tsx b/packages/ui/src/components/Catalog/BaseCatalog.test.tsx new file mode 100644 index 000000000..39ff3c5e7 --- /dev/null +++ b/packages/ui/src/components/Catalog/BaseCatalog.test.tsx @@ -0,0 +1,108 @@ +import { act, fireEvent, render, screen } from '@testing-library/react'; +import { CatalogLayout } from './Catalog.models'; +import { BaseCatalog } from './BaseCatalog'; +import { longTileList } from '../../stubs'; + +describe('BaseCatalog', () => { + it('renders correctly with Gallery Layout', () => { + const { container } = render( + , + ); + + expect(container).toMatchSnapshot(); + }); + + it('renders correctly with List Layout', () => { + const { container } = render( + , + ); + + expect(container).toMatchSnapshot(); + }); + + it('Render BaseCatalog with 60 tiles, 2 pages with 50 tiles on the 1st page and 10 tiles on the 2nd page', async () => { + render( + , + ); + + expect(screen.getByRole('spinbutton', { name: 'Current page' })).toHaveValue(1); + + expect( + screen.getAllByRole('listitem').filter((li) => li.classList.contains('catalog-data-list-item')), + ).toHaveLength(50); + + const nextPageButton = screen.getByRole('button', { name: 'Go to next page' }); + act(() => { + fireEvent.click(nextPageButton); + }); + expect(screen.getByRole('spinbutton', { name: 'Current page' })).toHaveValue(2); + + expect( + screen.getAllByRole('listitem').filter((li) => li.classList.contains('catalog-data-list-item')), + ).toHaveLength(10); + + expect(screen.getByRole('button', { name: 'Go to next page' })).toBeDisabled(); + }); + + it('Render BaseCatalog with 60 tiles, change per page setting to 20', async () => { + render( + , + ); + + expect(screen.getByRole('spinbutton', { name: 'Current page' })).toHaveValue(1); + + const pageSetting = screen.getAllByRole('button').filter((btn) => btn.id === 'catalog-pagination-top-toggle'); + act(() => { + fireEvent.click(pageSetting[0]); + }); + + act(() => { + fireEvent.click(screen.getByText('20 per page')); + }); + + expect( + screen.getAllByRole('listitem').filter((li) => li.classList.contains('catalog-data-list-item')), + ).toHaveLength(20); + + const nextPageButton = screen.getByRole('button', { name: 'Go to next page' }); + act(() => { + fireEvent.click(nextPageButton); + }); + expect(screen.getByRole('spinbutton', { name: 'Current page' })).toHaveValue(2); + + expect( + screen.getAllByRole('listitem').filter((li) => li.classList.contains('catalog-data-list-item')), + ).toHaveLength(20); + + act(() => { + fireEvent.click(nextPageButton); + }); + expect(screen.getByRole('spinbutton', { name: 'Current page' })).toHaveValue(3); + + expect( + screen.getAllByRole('listitem').filter((li) => li.classList.contains('catalog-data-list-item')), + ).toHaveLength(20); + + expect(screen.getByRole('button', { name: 'Go to next page' })).toBeDisabled(); + }); +}); diff --git a/packages/ui/src/components/Catalog/BaseCatalog.tsx b/packages/ui/src/components/Catalog/BaseCatalog.tsx index 0eef5c255..1a702caaf 100644 --- a/packages/ui/src/components/Catalog/BaseCatalog.tsx +++ b/packages/ui/src/components/Catalog/BaseCatalog.tsx @@ -1,5 +1,5 @@ -import { DataList, Gallery, Title } from '@patternfly/react-core'; -import { FunctionComponent, useCallback, useEffect, useRef } from 'react'; +import { DataList, Gallery, Pagination } from '@patternfly/react-core'; +import { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'; import './BaseCatalog.scss'; import { CatalogLayout, ITile } from './Catalog.models'; import { CatalogDataListItem } from './DataListItem'; @@ -15,6 +15,10 @@ interface BaseCatalogProps { export const BaseCatalog: FunctionComponent = (props) => { const catalogBodyRef = useRef(null); + const itemCount = props.tiles?.length; + const [page, setPage] = useState(1); + const [perPage, setPerPage] = useState(50); + const onTileClick = useCallback( (tile: ITile) => { props.onTileClick?.(tile); @@ -22,6 +26,17 @@ export const BaseCatalog: FunctionComponent = (props) => { [props], ); + const startIndex = (page - 1) * perPage < 0 ? 0 : (page - 1) * perPage; + const endIndex = page * perPage; + useEffect(() => { + // Handling the scenario where the item count is less the page selected. + if (startIndex + 1 > itemCount) { + setPage(Math.ceil(itemCount / perPage)); + } else if (page === 0 && itemCount > 0) setPage(1); + + catalogBodyRef.current!.scrollTop = 0; + }, [props.tiles]); + const onSelectDataListItem = useCallback( (_event: React.MouseEvent | React.KeyboardEvent, id: string) => { const tile = props.tiles.find((tile) => tile.name + '-' + tile.type === id); @@ -30,26 +45,43 @@ export const BaseCatalog: FunctionComponent = (props) => { [onTileClick, props.tiles], ); - useEffect(() => { - catalogBodyRef.current!.scrollTop = 0; - }, [props.tiles]); + const onSetPage = (_event: React.MouseEvent | React.KeyboardEvent | MouseEvent, newPage: number) => { + setPage(newPage); + }; + + const onPerPageSelect = ( + _event: React.MouseEvent | React.KeyboardEvent | MouseEvent, + newPerPage: number, + newPage: number, + ) => { + setPerPage(newPerPage); + setPage(newPage); + }; + + const paginatedCards = props.tiles?.slice(startIndex, endIndex); return ( <> - - Showing {props.tiles?.length} elements - +
{props.catalogLayout == CatalogLayout.List && ( - {props.tiles?.map((tile) => ( + {paginatedCards.map((tile) => ( ))} )} {props.catalogLayout == CatalogLayout.Gallery && ( - {props.tiles?.map((tile) => ( + {paginatedCards.map((tile) => ( ))} diff --git a/packages/ui/src/components/Catalog/Catalog.models.ts b/packages/ui/src/components/Catalog/Catalog.models.ts index 32a1ee56e..1d6325c97 100644 --- a/packages/ui/src/components/Catalog/Catalog.models.ts +++ b/packages/ui/src/components/Catalog/Catalog.models.ts @@ -7,8 +7,6 @@ export interface ITile { tags: string[]; version?: string; provider?: string; - /** @deprecated Please relay on name property instead */ - rawObject?: unknown; } export type TileFilter = (item: ITile) => boolean; diff --git a/packages/ui/src/components/Catalog/DataListItem.test.tsx b/packages/ui/src/components/Catalog/DataListItem.test.tsx index bc0d71afd..e6d7a6ea6 100644 --- a/packages/ui/src/components/Catalog/DataListItem.test.tsx +++ b/packages/ui/src/components/Catalog/DataListItem.test.tsx @@ -11,7 +11,6 @@ describe('DataListItem', () => { tags: ['tag1', 'tag2'], headerTags: ['header-tag1', 'header-tag2'], version: '1.0', - rawObject: {}, }; it('renders correctly', () => { diff --git a/packages/ui/src/components/Catalog/Tile.test.tsx b/packages/ui/src/components/Catalog/Tile.test.tsx index 29cd446dd..110ae57b8 100644 --- a/packages/ui/src/components/Catalog/Tile.test.tsx +++ b/packages/ui/src/components/Catalog/Tile.test.tsx @@ -10,7 +10,6 @@ describe('Tile', () => { description: 'tile-description', tags: ['tag1', 'tag2'], headerTags: ['header-tag1', 'header-tag2'], - rawObject: {}, }; it('renders correctly', () => { diff --git a/packages/ui/src/components/Catalog/__snapshots__/BaseCatalog.test.tsx.snap b/packages/ui/src/components/Catalog/__snapshots__/BaseCatalog.test.tsx.snap new file mode 100644 index 000000000..5bb0f4125 --- /dev/null +++ b/packages/ui/src/components/Catalog/__snapshots__/BaseCatalog.test.tsx.snap @@ -0,0 +1,11115 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`BaseCatalog renders correctly with Gallery Layout 1`] = ` +
+
+
+ + 1 + - + 50 + + + of + + + 60 + + +
+
+ +
+ +
+
+