diff --git a/web/client/plugins/ResourcesCatalog/ResourcesGrid.jsx b/web/client/plugins/ResourcesCatalog/ResourcesGrid.jsx index 9c9f75b1c6..3d4f273cb6 100644 --- a/web/client/plugins/ResourcesCatalog/ResourcesGrid.jsx +++ b/web/client/plugins/ResourcesCatalog/ResourcesGrid.jsx @@ -184,12 +184,10 @@ function ResourcesGrid({ requestResources={requestResources} configuredItems={configuredItems} metadata={metadata} - registry={{ - getResourceStatus, - formatHref: handleFormatHref, - getResourceTypesInfo, - getResourceId - }} + getResourceStatus={getResourceStatus} + formatHref={handleFormatHref} + getResourceTypesInfo={getResourceTypesInfo} + getResourceId={getResourceId} /> ); } diff --git a/web/client/plugins/ResourcesCatalog/components/DetailsHeader.jsx b/web/client/plugins/ResourcesCatalog/components/DetailsHeader.jsx index 52c59325d4..dda7540fb7 100644 --- a/web/client/plugins/ResourcesCatalog/components/DetailsHeader.jsx +++ b/web/client/plugins/ResourcesCatalog/components/DetailsHeader.jsx @@ -36,7 +36,7 @@ function DetailsHeader({ return ( <> -
+
- + {!loading ? : }{' '} diff --git a/web/client/plugins/ResourcesCatalog/components/FilterAccordion.jsx b/web/client/plugins/ResourcesCatalog/components/FilterAccordion.jsx index 0dc4e82474..dffec5d8b5 100644 --- a/web/client/plugins/ResourcesCatalog/components/FilterAccordion.jsx +++ b/web/client/plugins/ResourcesCatalog/components/FilterAccordion.jsx @@ -44,7 +44,7 @@ const AccordionTitle = ({ }; /** - * Accordion component + * FilterAccordion component * @prop {string} title of the accordion * @prop {string} titleId translation path of the title on the accordion * @prop {string} noItemsMsgId default message when no items present @@ -53,9 +53,7 @@ const AccordionTitle = ({ * @prop {function} loadItems function to fetch accordion items * @prop {array} items accordion items available without the need to fetch * @prop {string} query string - * @prop {boolean} defaultExpanded flag to expand the accordion on load by default - * @prop {boolean} expanded flag to keep accordion stay expanded and disable toggle function on the accordion (Note: Not on accordion items) - */ +*/ const FilterAccordion = ({ title, titleId, @@ -96,7 +94,7 @@ const FilterAccordion = ({ }, [isExpanded, JSON.stringify(query)]); return ( - + + diff --git a/web/client/plugins/ResourcesCatalog/components/MenuDropdownList.jsx b/web/client/plugins/ResourcesCatalog/components/MenuDropdownList.jsx index 86b8e78492..140d2662b0 100644 --- a/web/client/plugins/ResourcesCatalog/components/MenuDropdownList.jsx +++ b/web/client/plugins/ResourcesCatalog/components/MenuDropdownList.jsx @@ -70,7 +70,7 @@ const itemsList = (items) => (items && items.map((item, idx) => { const MenuDropdownList = ({ id, - items, + items = [], label, labelId, toggleStyle, diff --git a/web/client/plugins/ResourcesCatalog/components/MenuItem.jsx b/web/client/plugins/ResourcesCatalog/components/MenuItem.jsx index 83acce5e06..e72c4c3ad7 100644 --- a/web/client/plugins/ResourcesCatalog/components/MenuItem.jsx +++ b/web/client/plugins/ResourcesCatalog/components/MenuItem.jsx @@ -42,7 +42,7 @@ const isValidBadgeValue = (badge) => !!badge || badge === 0; const MenuItem = ({ item, menuItemsProps, containerNode, tabIndex, classItem = '', size, alignRight, variant, resourceName }) => { - const { formatHref, query } = menuItemsProps; + const { formatHref, query } = menuItemsProps || {}; const { id, type, @@ -63,7 +63,7 @@ const MenuItem = ({ item, menuItemsProps, containerNode, tabIndex, classItem = ' square, tooltipId, src - } = item; + } = item || {}; const btnClassName = `btn${variant && ` btn-${variant}` || ''}${size && ` btn-${size}` || ''}${className ? ` ${className}` : ''} _border-transparent`; const labelNode = labelId ? <Message msgId={labelId} msgParams={{ resourceName }} /> : label; diff --git a/web/client/plugins/ResourcesCatalog/components/Permissions.jsx b/web/client/plugins/ResourcesCatalog/components/Permissions.jsx index 4d8ecef0ef..074a3654e0 100644 --- a/web/client/plugins/ResourcesCatalog/components/Permissions.jsx +++ b/web/client/plugins/ResourcesCatalog/components/Permissions.jsx @@ -138,7 +138,7 @@ function Permissions({ const hasFiltrablePermissions = !!permissionsEntires.filter((item) => item.permissions !== 'owner' && !item.is_superuser)?.length; return ( - <div className="_relative _padding-tb-sm"> + <div className="ms-permissions _relative _padding-tb-sm"> {showGroupsPermissions ? <div className="ms-secondary-colors _padding-lr-sm"> <FlexBox component="ul" column gap="sm" classNames={['_padding-tb-sm']} > {permissionsEntires diff --git a/web/client/plugins/ResourcesCatalog/components/PermissionsAddEntriesPanel.jsx b/web/client/plugins/ResourcesCatalog/components/PermissionsAddEntriesPanel.jsx index 56ab8540ec..004f1f8159 100644 --- a/web/client/plugins/ResourcesCatalog/components/PermissionsAddEntriesPanel.jsx +++ b/web/client/plugins/ResourcesCatalog/components/PermissionsAddEntriesPanel.jsx @@ -108,7 +108,7 @@ function PermissionsAddEntriesPanel({ <FlexBox.Fill flexBox column - classNames={['_absolute', '_fill']} + classNames={['ms-permissions-add-entries-panel', '_absolute', '_fill']} > <FlexBox centerChildrenVertically gap="sm" classNames={['_padding-sm']}> <InputControl diff --git a/web/client/plugins/ResourcesCatalog/components/PermissionsRow.jsx b/web/client/plugins/ResourcesCatalog/components/PermissionsRow.jsx index 86e2561b05..68c88cdf97 100644 --- a/web/client/plugins/ResourcesCatalog/components/PermissionsRow.jsx +++ b/web/client/plugins/ResourcesCatalog/components/PermissionsRow.jsx @@ -40,7 +40,7 @@ function PermissionsRow({ ) : (<Text>{valueOption?.labelId ? <Message msgId={valueOption?.labelId} /> : null}</Text>); return ( - <FlexBox centerChildrenVertically gap="sm"> + <FlexBox className="ms-permissions-row" centerChildrenVertically gap="sm"> <FlexBox.Fill flexBox gap="sm"> {(!hideIcon && (type || avatar)) && <Text> {avatar diff --git a/web/client/plugins/ResourcesCatalog/components/ResourceCard.jsx b/web/client/plugins/ResourcesCatalog/components/ResourceCard.jsx index 016ddfa5c3..1c25f58416 100644 --- a/web/client/plugins/ResourcesCatalog/components/ResourceCard.jsx +++ b/web/client/plugins/ResourcesCatalog/components/ResourceCard.jsx @@ -200,7 +200,7 @@ const ResourceCardGridBody = ({ statusItems, options, thumbnailUrl, - registry + getResourceId }) => { const headerEntry = metadata.find(entry => entry.target === 'header'); @@ -283,7 +283,7 @@ const ResourceCardGridBody = ({ viewerUrl={viewerUrl} options={options} readOnly={readOnly} - registry={registry} + getResourceId={getResourceId} className="_absolute _margin-sm _corner-tr" /> ) @@ -305,7 +305,7 @@ const ResourceCardListBody = ({ options: optionsProp, buttons, columns, - registry + getResourceId }) => { const options = [ ...(buttons || []), @@ -345,7 +345,7 @@ const ResourceCardListBody = ({ viewerUrl={viewerUrl} options={options} readOnly={readOnly} - registry={registry} + getResourceId={getResourceId} /> ) : null} @@ -369,19 +369,16 @@ const ResourceCard = forwardRef(({ loading, downloading, statusItems, - registry, buttons = [], component, query, metadata = [], - columns = [] + columns = [], + getResourceTypesInfo = () => ({}), + formatHref, + getResourceId }, ref) => { - const { - formatHref, - getResourceTypesInfo - } = registry; - const resource = data; const { icon, @@ -416,7 +413,7 @@ const ResourceCard = forwardRef(({ options={options} columns={columns} thumbnailUrl={thumbnailUrl} - registry={registry} + getResourceId={getResourceId} /> : null} </CardComponent> ); diff --git a/web/client/plugins/ResourcesCatalog/components/ResourceCardActionButtons.jsx b/web/client/plugins/ResourcesCatalog/components/ResourceCardActionButtons.jsx index f4650a5509..d19f4aa017 100644 --- a/web/client/plugins/ResourcesCatalog/components/ResourceCardActionButtons.jsx +++ b/web/client/plugins/ResourcesCatalog/components/ResourceCardActionButtons.jsx @@ -32,12 +32,10 @@ function ResourceCardActionButtons({ viewerUrl, resource, className, - registry, + getResourceId = () => '', ...props }) { - const { getResourceId } = registry; - const containerNode = useRef(); const dropdownClassName = 'ms-card-dropdown'; const dropdownNode = containerNode?.current?.querySelector(`.${dropdownClassName}`); diff --git a/web/client/plugins/ResourcesCatalog/components/ResourcesContainer.jsx b/web/client/plugins/ResourcesCatalog/components/ResourcesContainer.jsx index bc42383ec6..1fd00495a3 100644 --- a/web/client/plugins/ResourcesCatalog/components/ResourcesContainer.jsx +++ b/web/client/plugins/ResourcesCatalog/components/ResourcesContainer.jsx @@ -24,20 +24,19 @@ const ResourcesContainer = (props) => { footer, cardLayoutStyle, loading, - getMainMessageId, - registry, + getMainMessageId = () => '', onSelect, theme = 'main', cardButtons, cardComponent, query, columns, - metadata - } = props; - const { + metadata, getResourceStatus, + formatHref, + getResourceTypesInfo, getResourceId - } = registry; + } = props; const messageId = getMainMessageId(props); return ( <div @@ -78,7 +77,10 @@ const ResourcesContainer = (props) => { readOnly={isProcessing} downloading={isDownloading} statusItems={statusItems} - registry={registry} + getResourceStatus={getResourceStatus} + formatHref={formatHref} + getResourceTypesInfo={getResourceTypesInfo} + getResourceId={getResourceId} onClick={onSelect} query={query} columns={columns} diff --git a/web/client/plugins/ResourcesCatalog/components/ResourcesMenu.jsx b/web/client/plugins/ResourcesCatalog/components/ResourcesMenu.jsx index 5d7fe65095..b6711e30a9 100644 --- a/web/client/plugins/ResourcesCatalog/components/ResourcesMenu.jsx +++ b/web/client/plugins/ResourcesCatalog/components/ResourcesMenu.jsx @@ -120,7 +120,7 @@ const ResourcesMenu = forwardRef(({ setCardLayoutStyle, orderConfig, query, - registry, + formatHref, titleId, theme = 'main', menuItemsLeft = [], @@ -136,10 +136,6 @@ const ResourcesMenu = forwardRef(({ align: orderAlign = 'right' } = orderConfig || {}; - const { - formatHref - } = registry; - const selectedSort = orderOptions.find(({ value }) => query?.sort === value); function handleToggleCardLayoutStyle() { setCardLayoutStyle(cardLayoutStyle === 'grid' ? 'list' : 'grid'); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/ALink-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/ALink-test.jsx new file mode 100644 index 0000000000..0f83a39eaf --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/ALink-test.jsx @@ -0,0 +1,45 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import ALink from '../ALink'; + +describe('ALink component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should not render with default', () => { + ReactDOM.render(<ALink />, document.getElementById('container')); + const container = document.getElementById('container'); + expect(container.children.length).toBe(0); + }); + it('should apply the link (a) tag if href is provided', () => { + ReactDOM.render(<ALink href="#" className="link"><span className="child"></span></ALink>, document.getElementById('container')); + const container = document.getElementById('container'); + expect(container.children[0].getAttribute('class')).toBe('link'); + }); + it('should not apply the link (a) tag if href is not provided', () => { + ReactDOM.render(<ALink className="link"><span className="child"></span></ALink>, document.getElementById('container')); + const container = document.getElementById('container'); + expect(container.children[0].getAttribute('class')).toBe('child'); + }); + it('should not apply the link (a) tag if href is provided and readOnly is true', () => { + ReactDOM.render(<ALink href="#" readOnly className="link"><span className="child"></span></ALink>, document.getElementById('container')); + const container = document.getElementById('container'); + expect(container.children[0].getAttribute('class')).toBe('child'); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/Button-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/Button-test.jsx new file mode 100644 index 0000000000..508e0dd06f --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/Button-test.jsx @@ -0,0 +1,47 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import Button from '../Button'; + +describe('Button component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<Button><span className="child" /></Button>, document.getElementById('container')); + const button = document.querySelector('.btn'); + expect(button).toBeTruthy(); + expect(document.querySelector('.child')).toBeTruthy(); + }); + it('should apply custom classes based on props', () => { + ReactDOM.render(<Button + variant="primary" + size="md" + borderTransparent + ><span className="child" /></Button>, document.getElementById('container')); + const button = document.querySelector('.btn'); + expect(button).toBeTruthy(); + expect(button.getAttribute('class')).toBe(' _border-transparent btn btn-md btn-primary'); + }); + it('should render a square button', () => { + ReactDOM.render(<Button square ><span className="child" /></Button>, document.getElementById('container')); + const button = document.querySelector('.btn'); + expect(button).toBeTruthy(); + expect(button.getAttribute('class')).toBe('square-button-md btn btn-default'); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/ConfirmDialog-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/ConfirmDialog-test.jsx new file mode 100644 index 0000000000..f8f83800c6 --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/ConfirmDialog-test.jsx @@ -0,0 +1,62 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import ConfirmDialog from '../ConfirmDialog'; +import { Simulate } from 'react-dom/test-utils'; + +describe('ConfirmDialog component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should not render with default', () => { + ReactDOM.render(<ConfirmDialog />, document.getElementById('container')); + const dialog = document.querySelector('[role=dialog]'); + expect(dialog).toBeFalsy(); + }); + it('should render the modal when show is true', () => { + ReactDOM.render(<ConfirmDialog show />, document.getElementById('container')); + const dialog = document.querySelector('[role=dialog]'); + expect(dialog).toBeTruthy(); + }); + it('should trigger onConfirm', (done) => { + ReactDOM.render(<ConfirmDialog + show + onConfirm={() => { + done(); + }} + />, document.getElementById('container')); + const dialog = document.querySelector('[role=dialog]'); + expect(dialog).toBeTruthy(); + const buttons = dialog.querySelectorAll('.btn'); + expect(buttons.length).toBe(2); + Simulate.click(buttons[1]); + }); + it('should trigger onCancel', (done) => { + ReactDOM.render(<ConfirmDialog + show + onCancel={() => { + done(); + }} + />, document.getElementById('container')); + const dialog = document.querySelector('[role=dialog]'); + expect(dialog).toBeTruthy(); + const buttons = dialog.querySelectorAll('.btn'); + expect(buttons.length).toBe(2); + Simulate.click(buttons[0]); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/DetailsHeader-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/DetailsHeader-test.jsx new file mode 100644 index 0000000000..2eebff004c --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/DetailsHeader-test.jsx @@ -0,0 +1,87 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import DetailsHeader from '../DetailsHeader'; +import { Simulate } from 'react-dom/test-utils'; + +describe('DetailsHeader component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<DetailsHeader />, document.getElementById('container')); + const detailsHeader = document.querySelector('.ms-details-header'); + expect(detailsHeader).toBeTruthy(); + }); + it('should display the resource information', () => { + ReactDOM.render(<DetailsHeader + getResourceTypesInfo={(resource) => { + return { + icon: { + glyph: 'map', + type: 'glyphicon' + }, + title: resource.name, + thumbnailUrl: resource.attributes.thumbnail + }; + }} + resource={{ + name: 'Resource Title', + attributes: { + thumbnail: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==' + } + }}/>, document.getElementById('container')); + const detailsHeader = document.querySelector('.ms-details-header-info'); + expect(detailsHeader).toBeTruthy(); + const texts = detailsHeader.querySelectorAll('.ms-text'); + expect(texts[0].innerHTML).toBe('<span class="glyphicon glyphicon-map"></span> Resource Title'); + const imgs = document.querySelectorAll('img'); + expect(imgs.length).toBe(1); + expect(imgs[0].getAttribute('src')).toBe('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=='); + }); + it('should trigger onClose', (done) => { + ReactDOM.render(<DetailsHeader + onClose={() => { + done(); + }} + />, document.getElementById('container')); + const detailsHeader = document.querySelector('.ms-details-header'); + expect(detailsHeader).toBeTruthy(); + const buttons = document.querySelectorAll('button'); + expect(buttons.length).toBe(1); + Simulate.click(buttons[0]); + }); + it('should trigger onChangeThumbnail', (done) => { + ReactDOM.render(<DetailsHeader + editing + onChangeThumbnail={() => { + done(); + }} + />, document.getElementById('container')); + const detailsHeader = document.querySelector('.ms-details-header'); + expect(detailsHeader).toBeTruthy(); + const input = document.querySelectorAll('input'); + expect(input.length).toBe(1); + fetch('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==') + .then(res => res.blob()) + .then(blob => { + const file = new File([blob], "image", { type: "image/png" }); + Simulate.change(input[0], { target: { files: [file] } }); + }); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/DetailsInfo-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/DetailsInfo-test.jsx new file mode 100644 index 0000000000..2ca90c4b55 --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/DetailsInfo-test.jsx @@ -0,0 +1,134 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import DetailsInfo from '../DetailsInfo'; +import { waitFor } from '@testing-library/react'; +import { Simulate } from 'react-dom/test-utils'; + +describe('DetailsInfo component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<DetailsInfo />, document.getElementById('container')); + const detailsInfo = document.querySelector('.ms-details-info'); + expect(detailsInfo).toBeTruthy(); + }); + it('should render tabs items', (done) => { + ReactDOM.render(<DetailsInfo + tabs={[ + { + type: 'tab', + id: 'info', + labelId: 'Info', + items: [ + { + type: 'text', + labelId: 'Name', + value: 'Resource Name' + } + ] + } + ]} + />, document.getElementById('container')); + const detailsInfo = document.querySelector('.ms-details-info'); + expect(detailsInfo).toBeTruthy(); + waitFor(() => document.querySelector('.ms-details-info-fields')) + .then(() => { + const detailsInfoFields = document.querySelectorAll('.ms-details-info-fields'); + expect(detailsInfoFields.length).toBe(1); + expect(detailsInfoFields[0].innerText).toBe('Name\nResource Name'); + done(); + }) + .catch(done); + }); + it('should allow editing of editable fields and trigger onChange (text)', (done) => { + ReactDOM.render(<DetailsInfo + editing + tabs={[ + { + type: 'tab', + id: 'info', + labelId: 'Info', + items: [ + { + type: 'text', + editable: true, + labelId: 'Name', + path: 'name', + value: 'Resource Name' + } + ] + } + ]} + onChange={(value) => { + try { + expect(value).toEqual({ name: 'Resource' }); + } catch (e) { + done(e); + } + done(); + }} + />, document.getElementById('container')); + const detailsInfo = document.querySelector('.ms-details-info'); + expect(detailsInfo).toBeTruthy(); + waitFor(() => document.querySelector('.ms-details-info-fields')) + .then(() => { + const input = document.querySelector('input'); + Simulate.change(input, { target: { value: 'Resource' }}); + }) + .catch(done); + }); + it('should allow editing of editable fields and trigger onChange (boolean)', (done) => { + ReactDOM.render(<DetailsInfo + editing + tabs={[ + { + type: 'tab', + id: 'info', + labelId: 'Info', + items: [ + { + type: 'boolean', + editable: true, + labelId: 'Advertised', + path: 'advertised', + value: false + } + ] + } + ]} + onChange={(value) => { + try { + expect(value).toEqual({ advertised: true }); + } catch (e) { + done(e); + } + done(); + }} + />, document.getElementById('container')); + const detailsInfo = document.querySelector('.ms-details-info'); + expect(detailsInfo).toBeTruthy(); + waitFor(() => document.querySelector('.ms-details-info-fields')) + .then(() => { + const input = document.querySelector('input'); + Simulate.change(input, { target: { checked: true }}); + }) + .catch(done); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/DetailsThumbnail-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/DetailsThumbnail-test.jsx new file mode 100644 index 0000000000..add5d83a7c --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/DetailsThumbnail-test.jsx @@ -0,0 +1,84 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import DetailsThumbnail from '../DetailsThumbnail'; +import { Simulate } from 'react-dom/test-utils'; + +describe('DetailsThumbnail component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<DetailsThumbnail />, document.getElementById('container')); + const detailsThumbnail = document.querySelector('.ms-details-thumbnail'); + expect(detailsThumbnail).toBeTruthy(); + }); + it('should render an image while not editing', () => { + ReactDOM.render(<DetailsThumbnail + thumbnail={'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=='} + />, document.getElementById('container')); + const detailsThumbnail = document.querySelector('.ms-details-thumbnail'); + expect(detailsThumbnail).toBeTruthy(); + const img = detailsThumbnail.querySelector('img'); + expect(img.getAttribute('src')).toBe('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=='); + }); + it('should render the thumbnail component while editing', (done) => { + ReactDOM.render(<DetailsThumbnail + editing + onChange={(value) => { + try { + expect(value.indexOf('data:image/png;base64')).toBe(0); + } catch (e) { + done(e); + } + done(); + }} + />, document.getElementById('container')); + const detailsThumbnail = document.querySelector('.ms-details-thumbnail'); + expect(detailsThumbnail).toBeTruthy(); + const buttons = detailsThumbnail.querySelectorAll('button'); + expect(buttons.length).toBe(2); + const input = detailsThumbnail.querySelectorAll('input'); + expect(input.length).toBe(1); + fetch('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==') + .then(res => res.blob()) + .then(blob => { + const file = new File([blob], "image", { type: "image/png" }); + Simulate.change(input[0], { target: { files: [file] } }); + }); + }); + it('should clear the thumbnail when clicking on the trash button', (done) => { + ReactDOM.render(<DetailsThumbnail + editing + thumbnail={'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=='} + onChange={(value) => { + try { + expect(value).toBe(''); + } catch (e) { + done(e); + } + done(); + }} + />, document.getElementById('container')); + const detailsThumbnail = document.querySelector('.ms-details-thumbnail'); + expect(detailsThumbnail).toBeTruthy(); + const buttons = detailsThumbnail.querySelectorAll('button'); + expect(buttons.length).toBe(2); + Simulate.click(buttons[1]); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/FilterAccordion-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/FilterAccordion-test.jsx new file mode 100644 index 0000000000..32819f8193 --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/FilterAccordion-test.jsx @@ -0,0 +1,72 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import FilterAccordion from '../FilterAccordion'; +import { waitFor } from '@testing-library/react'; +import { Simulate } from 'react-dom/test-utils'; + +describe('FilterAccordion component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + localStorage.removeItem('accordionsExpanded'); + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<FilterAccordion />, document.getElementById('container')); + const filterAccordion = document.querySelector('.ms-filter-accordion'); + expect(filterAccordion).toBeTruthy(); + }); + it('should expand and show the items', () => { + ReactDOM.render(<FilterAccordion + items={[ + { name: 'Item 1' }, + { name: 'Item 2' } + ]} + content={(items) => { + return <ul>{items.map((item, idx) => <li key={idx}>{item.name}</li>)}</ul>; + }} + />, document.getElementById('container')); + const filterAccordion = document.querySelector('.ms-filter-accordion'); + expect(filterAccordion).toBeTruthy(); + expect(filterAccordion.querySelectorAll('li').length).toBe(0); + Simulate.click(document.querySelector('button')); + expect(filterAccordion.querySelectorAll('li').length).toBe(2); + }); + it('should expand and show loaded items', (done) => { + ReactDOM.render(<FilterAccordion + loadItems={() => { + return Promise.resolve({ + items: [ + { name: 'Item 1' }, + { name: 'Item 2' } + ] + }); + }} + content={(items) => { + return <ul>{items.map((item, idx) => <li key={idx}>{item.name}</li>)}</ul>; + }} + />, document.getElementById('container')); + const filterAccordion = document.querySelector('.ms-filter-accordion'); + expect(filterAccordion).toBeTruthy(); + Simulate.click(document.querySelector('button')); + waitFor(() => expect(filterAccordion.querySelectorAll('li').length).toBe(2)) + .then(() => { + done(); + }) + .catch(done); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/FilterByExtent-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/FilterByExtent-test.jsx new file mode 100644 index 0000000000..785a3108df --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/FilterByExtent-test.jsx @@ -0,0 +1,54 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import FilterByExtent from '../FilterByExtent'; +import { waitFor } from '@testing-library/react'; + +describe('FilterByExtent component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', (done) => { + ReactDOM.render(<FilterByExtent />, document.getElementById('container')); + const filterByExtent = document.querySelector('.ms-filter-by-extent'); + expect(filterByExtent).toBeTruthy(); + waitFor(() => expect(document.querySelector('#ms-filter-by-extent-map')).toBeTruthy()) + .then(() => { + const mapContainerNode = document.querySelector('.ms-filter-by-extent-map'); + expect(mapContainerNode.style.pointerEvents).toBe('none'); + expect(mapContainerNode.style.opacity).toBe('0.4'); + done(); + }) + .catch(done); + }); + it('should enable map when extent is provided', (done) => { + ReactDOM.render(<FilterByExtent + extent="-10,-10,10,10" + />, document.getElementById('container')); + const filterByExtent = document.querySelector('.ms-filter-by-extent'); + expect(filterByExtent).toBeTruthy(); + waitFor(() => expect(document.querySelector('#ms-filter-by-extent-map')).toBeTruthy()) + .then(() => { + const mapContainerNode = document.querySelector('.ms-filter-by-extent-map'); + expect(mapContainerNode.style.pointerEvents).toBe('auto'); + expect(mapContainerNode.style.opacity).toBe('1'); + done(); + }) + .catch(done); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/FilterDateRange-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/FilterDateRange-test.jsx new file mode 100644 index 0000000000..add3ce20f5 --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/FilterDateRange-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import FilterDateRange from '../FilterDateRange'; + +describe('FilterDateRange component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<FilterDateRange />, document.getElementById('container')); + const formGroups = document.querySelectorAll('.form-group'); + expect(formGroups.length).toBe(2); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/FilterGroup-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/FilterGroup-test.jsx new file mode 100644 index 0000000000..2bcd35ae87 --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/FilterGroup-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import FilterGroup from '../FilterGroup'; + +describe('FilterGroup component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<FilterGroup />, document.getElementById('container')); + const filterGroup = document.querySelector('.ms-filter-group'); + expect(filterGroup).toBeTruthy(); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/FilterItems-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/FilterItems-test.jsx new file mode 100644 index 0000000000..2ed7563448 --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/FilterItems-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import FilterItems from '../FilterItems'; + +describe('FilterItems component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should not render with default', () => { + ReactDOM.render(<FilterItems />, document.getElementById('container')); + const container = document.getElementById('container'); + expect(container.children.length).toBe(0); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/FiltersForm-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/FiltersForm-test.jsx new file mode 100644 index 0000000000..ee0c57db0c --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/FiltersForm-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import FiltersForm from '../FiltersForm'; + +describe('FilterForm component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<FiltersForm />, document.getElementById('container')); + const filtersForm = document.querySelector('.ms-filters-form'); + expect(filtersForm).toBeTruthy(); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/FlexBox-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/FlexBox-test.jsx new file mode 100644 index 0000000000..532639a074 --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/FlexBox-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import FlexBox from '../FlexBox'; + +describe('FlexBox component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<FlexBox />, document.getElementById('container')); + const filtersForm = document.querySelector('.ms-flex-box'); + expect(filtersForm).toBeTruthy(); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/Icon-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/Icon-test.jsx new file mode 100644 index 0000000000..70bec96e36 --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/Icon-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import Icon from '../Icon'; + +describe('Icon component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should not render with default', () => { + ReactDOM.render(<Icon />, document.getElementById('container')); + const container = document.getElementById('container'); + expect(container.children.length).toBe(0); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/InputControl-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/InputControl-test.jsx new file mode 100644 index 0000000000..7acd9cc675 --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/InputControl-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import InputControl from '../InputControl'; + +describe('InputControl component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<InputControl />, document.getElementById('container')); + const inputControl = document.querySelector('.form-control'); + expect(inputControl).toBeTruthy(); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/Menu-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/Menu-test.jsx new file mode 100644 index 0000000000..504b56440f --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/Menu-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import Menu from '../Menu'; + +describe('Menu component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<Menu />, document.getElementById('container')); + const menu = document.querySelector('.ms-flex-box'); + expect(menu).toBeTruthy(); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/MenuDropdownList-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/MenuDropdownList-test.jsx new file mode 100644 index 0000000000..332926fbf1 --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/MenuDropdownList-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import MenuDropdownList from '../MenuDropdownList'; + +describe('MenuDropdownList component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<MenuDropdownList />, document.getElementById('container')); + const dropdown = document.querySelector('.dropdown'); + expect(dropdown).toBeTruthy(); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/MenuItem-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/MenuItem-test.jsx new file mode 100644 index 0000000000..1ecbe0830b --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/MenuItem-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import MenuItem from '../MenuItem'; + +describe('MenuItem component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should not render with default', () => { + ReactDOM.render(<MenuItem />, document.getElementById('container')); + const container = document.getElementById('container'); + expect(container.children.length).toBe(0); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/MenuNavLink-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/MenuNavLink-test.jsx new file mode 100644 index 0000000000..3520c3efc1 --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/MenuNavLink-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import MenuNavLink from '../MenuNavLink'; + +describe('MenuNavLink component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<MenuNavLink />, document.getElementById('container')); + const menuNavLink = document.querySelector('a'); + expect(menuNavLink).toBeTruthy(); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/PaginationCustom-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/PaginationCustom-test.jsx new file mode 100644 index 0000000000..14b0e09070 --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/PaginationCustom-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import PaginationCustom from '../PaginationCustom'; + +describe('PaginationCustom component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<PaginationCustom />, document.getElementById('container')); + const paginationCustom = document.querySelector('.custom'); + expect(paginationCustom).toBeTruthy(); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/Permissions-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/Permissions-test.jsx new file mode 100644 index 0000000000..dca480f69c --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/Permissions-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import Permissions from '../Permissions'; + +describe('Permissions component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<Permissions />, document.getElementById('container')); + const permissions = document.querySelector('.ms-permissions'); + expect(permissions).toBeTruthy(); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/PermissionsAddEntriesPanel-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/PermissionsAddEntriesPanel-test.jsx new file mode 100644 index 0000000000..7c92a0e68d --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/PermissionsAddEntriesPanel-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import PermissionsAddEntriesPanel from '../PermissionsAddEntriesPanel'; + +describe('PermissionsAddEntriesPanel component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<PermissionsAddEntriesPanel />, document.getElementById('container')); + const permissionsAddEntriesPanel = document.querySelector('.ms-permissions-add-entries-panel'); + expect(permissionsAddEntriesPanel).toBeTruthy(); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/PermissionsRow-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/PermissionsRow-test.jsx new file mode 100644 index 0000000000..747a8bce06 --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/PermissionsRow-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import PermissionsRow from '../PermissionsRow'; + +describe('PermissionsRow component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<PermissionsRow />, document.getElementById('container')); + const permissionsRow = document.querySelector('.ms-permissions-row'); + expect(permissionsRow).toBeTruthy(); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/ResourceCard-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/ResourceCard-test.jsx new file mode 100644 index 0000000000..96edbf3811 --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/ResourceCard-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import ResourceCard from '../ResourceCard'; + +describe('ResourceCard component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<ResourceCard />, document.getElementById('container')); + const resourceCard = document.querySelector('.ms-resource-card'); + expect(resourceCard).toBeTruthy(); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/ResourceCardActionButtons-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/ResourceCardActionButtons-test.jsx new file mode 100644 index 0000000000..d83c108d0f --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/ResourceCardActionButtons-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import ResourceCardActionButtons from '../ResourceCardActionButtons'; + +describe('ResourceCardActionButtons component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<ResourceCardActionButtons />, document.getElementById('container')); + const resourceCardActionButtons = document.querySelector('.ms-resource-card-action-buttons'); + expect(resourceCardActionButtons).toBeTruthy(); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/ResourceStatus-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/ResourceStatus-test.jsx new file mode 100644 index 0000000000..c2bc35c39b --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/ResourceStatus-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import ResourceStatus from '../ResourceStatus'; + +describe('ResourceStatus component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should not render with default', () => { + ReactDOM.render(<ResourceStatus />, document.getElementById('container')); + const container = document.getElementById('container'); + expect(container.children.length).toBe(0); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/ResourcesContainer-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/ResourcesContainer-test.jsx new file mode 100644 index 0000000000..4e76f1efe3 --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/ResourcesContainer-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import ResourcesContainer from '../ResourcesContainer'; + +describe('ResourcesContainer component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<ResourcesContainer />, document.getElementById('container')); + const resourcesContainer = document.querySelector('.ms-resources-container'); + expect(resourcesContainer).toBeTruthy(); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/ResourcesMenu-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/ResourcesMenu-test.jsx new file mode 100644 index 0000000000..2963e4de45 --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/ResourcesMenu-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import ResourcesMenu from '../ResourcesMenu'; + +describe('ResourcesMenu component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<ResourcesMenu />, document.getElementById('container')); + const resourcesMenu = document.querySelector('.ms-resources-menu'); + expect(resourcesMenu).toBeTruthy(); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/ResourcesPanelWrapper-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/ResourcesPanelWrapper-test.jsx new file mode 100644 index 0000000000..ddae86d169 --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/ResourcesPanelWrapper-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import ResourcesPanelWrapper from '../ResourcesPanelWrapper'; + +describe('ResourcesPanelWrapper component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should not render with default', () => { + ReactDOM.render(<ResourcesPanelWrapper />, document.getElementById('container')); + const container = document.getElementById('container'); + expect(container.children.length).toBe(0); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/SelectInfiniteScroll-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/SelectInfiniteScroll-test.jsx new file mode 100644 index 0000000000..bd4106f606 --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/SelectInfiniteScroll-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import SelectInfiniteScroll from '../SelectInfiniteScroll'; + +describe('SelectInfiniteScroll component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<SelectInfiniteScroll />, document.getElementById('container')); + const selectInfiniteScroll = document.querySelector('.Select'); + expect(selectInfiniteScroll).toBeTruthy(); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/Spinner-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/Spinner-test.jsx new file mode 100644 index 0000000000..a8661bb446 --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/Spinner-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import Spinner from '../Spinner'; + +describe('Spinner component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<Spinner />, document.getElementById('container')); + const spinner = document.querySelector('.ms-spinner'); + expect(spinner).toBeTruthy(); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/Tabs-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/Tabs-test.jsx new file mode 100644 index 0000000000..e6f2b1607c --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/Tabs-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import Tabs from '../Tabs'; + +describe('Tabs component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<Tabs />, document.getElementById('container')); + const tabs = document.querySelector('.ms-tabs'); + expect(tabs).toBeTruthy(); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/TargetSelectorPortal-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/TargetSelectorPortal-test.jsx new file mode 100644 index 0000000000..c4b8ebe609 --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/TargetSelectorPortal-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import TargetSelectorPortal from '../TargetSelectorPortal'; + +describe('TargetSelectorPortal component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should not render with default', () => { + ReactDOM.render(<TargetSelectorPortal />, document.getElementById('container')); + const container = document.getElementById('container'); + expect(container.children.length).toBe(0); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/Text-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/Text-test.jsx new file mode 100644 index 0000000000..f0dd47e319 --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/Text-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import Text from '../Text'; + +describe('Text component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should render with default', () => { + ReactDOM.render(<Text />, document.getElementById('container')); + const text = document.querySelector('.ms-text'); + expect(text).toBeTruthy(); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/components/__tests__/ZoomTo-test.jsx b/web/client/plugins/ResourcesCatalog/components/__tests__/ZoomTo-test.jsx new file mode 100644 index 0000000000..03fb1ec948 --- /dev/null +++ b/web/client/plugins/ResourcesCatalog/components/__tests__/ZoomTo-test.jsx @@ -0,0 +1,30 @@ + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import expect from 'expect'; +import ZoomTo from '../ZoomTo'; + +describe('ZoomTo component', () => { + beforeEach((done) => { + document.body.innerHTML = '<div id="container"></div>'; + setTimeout(done); + }); + afterEach((done) => { + ReactDOM.unmountComponentAtNode(document.getElementById('container')); + document.body.innerHTML = ''; + setTimeout(done); + }); + it('should not render with default', () => { + ReactDOM.render(<ZoomTo />, document.getElementById('container')); + const container = document.getElementById('container'); + expect(container.children.length).toBe(0); + }); +}); diff --git a/web/client/plugins/ResourcesCatalog/containers/ResourcesGrid.jsx b/web/client/plugins/ResourcesCatalog/containers/ResourcesGrid.jsx index 031113100f..a8e5c85300 100644 --- a/web/client/plugins/ResourcesCatalog/containers/ResourcesGrid.jsx +++ b/web/client/plugins/ResourcesCatalog/containers/ResourcesGrid.jsx @@ -87,7 +87,6 @@ function ResourcesGrid({ resources, isFirstRequest, requestResources, - registry, titleId, queryPage, page: pageProp, @@ -96,7 +95,11 @@ function ResourcesGrid({ getMainMessageId = defaultGetMainMessageId, search, onResetSearch, - hideWithNoResults + hideWithNoResults, + getResourceStatus, + formatHref, + getResourceTypesInfo, + getResourceId }) { const { query } = url.parse(location.search, true); @@ -162,10 +165,6 @@ function ResourcesGrid({ const metadata = isArray(metadataProp) ? metadataProp : metadataProp[cardLayoutStyle]; - const { - getResourceId - } = registry; - return ( <TargetSelectorPortal targetSelector={targetSelector}> <div className={`ms-resources-grid${panel ? ' _panel' : ''}`} style={hideWithNoResults && !resources.length ? { display: 'none' } : { }}> @@ -197,11 +196,14 @@ function ResourcesGrid({ position: 'sticky', top: stickyTop }} - registry={registry} query={query} metadata={metadata} columns={columns} setColumns={setColumns} + getResourceStatus={getResourceStatus} + formatHref={formatHref} + getResourceTypesInfo={getResourceTypesInfo} + getResourceId={getResourceId} /> } footer={ @@ -232,7 +234,10 @@ function ResourcesGrid({ cardComponent={cardComponent} isCardActive={res => getResourceId(res) === getResourceId(selectedResource)} getMainMessageId={getMainMessageId} - registry={registry} + getResourceStatus={getResourceStatus} + formatHref={formatHref} + getResourceTypesInfo={getResourceTypesInfo} + getResourceId={getResourceId} /> </div> </TargetSelectorPortal>