diff --git a/.github/workflows/build-npm-release.yml b/.github/workflows/build-npm-release.yml index 659d222bf..7a93a2ef0 100644 --- a/.github/workflows/build-npm-release.yml +++ b/.github/workflows/build-npm-release.yml @@ -87,8 +87,12 @@ jobs: run: yarn lint continue-on-error: true + - name: Get number of CPU cores + id: cpu-cores + uses: SimenB/github-actions-cpu-cores@v1 + - name: Run yarn test - run: xvfb-run --server-args="-screen 0 1024x768x24" yarn test $YARN_TEST_OPTIONS + run: xvfb-run --server-args="-screen 0 1024x768x24" yarn test $YARN_TEST_OPTIONS --max-workers ${{ steps.cpu-cores.outputs.count }} - name: Run yarn formatjs-compile if : ${{ env.COMPILE_TRANSLATION_FILES == 'true' }} @@ -222,6 +226,7 @@ jobs: - name: Exclude some CI-generated artifacts in package run: | + echo "artifacts" >> .npmignore echo ".github" >> .npmignore echo ".scannerwork" >> .npmignore cat .npmignore diff --git a/.github/workflows/build-npm.yml b/.github/workflows/build-npm.yml index 8dc9ef46a..ac084bd68 100644 --- a/.github/workflows/build-npm.yml +++ b/.github/workflows/build-npm.yml @@ -69,8 +69,12 @@ jobs: run: yarn lint continue-on-error: true + - name: Get number of CPU cores + id: cpu-cores + uses: SimenB/github-actions-cpu-cores@v1 + - name: Run yarn test - run: xvfb-run --server-args="-screen 0 1024x768x24" yarn test $YARN_TEST_OPTIONS + run: xvfb-run --server-args="-screen 0 1024x768x24" yarn test $YARN_TEST_OPTIONS --max-workers ${{ steps.cpu-cores.outputs.count }} - name: Run yarn formatjs-compile if: ${{ env.COMPILE_TRANSLATION_FILES == 'true' }} @@ -168,6 +172,7 @@ jobs: - name: Exclude some CI-generated artifacts in package if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' }} run: | + echo "artifacts" >> .npmignore echo ".github" >> .npmignore echo ".scannerwork" >> .npmignore cat .npmignore diff --git a/CHANGELOG.md b/CHANGELOG.md index 784fa38f5..31992a792 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change history for ui-inventory -## 9.5.0 IN PROGRESS +## 10.0.0 IN PROGRESS * Added a new option in the "Actions" dropdown within the Inventory app search page for "+New MARC Bib Record". Refs UIIN-2356. * Change the url for the "New MARC Bib Record" page. Refs UIIN-2380. * Avoid private paths in stripes-core imports. Refs UIIN-2367. @@ -11,7 +11,56 @@ * Holdings Create/Edit screens: Replace custom RepeatableField with component from stripes. Refs UIIN-2398. * Holdings Create/Edit screens: Repeatable field trashcan is not aligned with the data row. Fixes UIIN-2373. * Holdings view source: Print button not visible with "View MARC holdings record" permission. Refs UIIN-2405. - +* The 'Missing' and 'Withdrawn' options in the actions menu are absent after clicking on the 'Actions' button for item status "In process". Fixes UIIN-2338. +* Item Create/Edit screens: Replace custom RepeatableField with component from stripes. Refs UIIN-2397. +* Item Create/Edit screens: Repeatable field trashcan is not aligned with the data row. Fixes UIIN-2374. +* Chronology not displayed in receiving history. Fixes UIIN-2411. +* Also support `circulation` `14.0`. Refs UIIN-2412. +* Instance/Holdings/Item notes, administrative notes character limit to 32K. Refs UIIN-2354. +* Import testing-library deps from `@folio/jest-config-stripes`. Refs UIIN-2427. +* Bump zustand to v4. Refs UIIN-2353. +* Fix the `records-editor/records` request by adding `_actionType`. Refs UIIN-2431. +* Navigating away and back to item-edit or holdings-edit screen throws NPE. Fixes UIIN-2112. +* ISRI: Adjust jobProfiles GET request to fetch profiles by ids. Refs UIIN-2428. +* Sorting of profiles is not executed in alphabetical order in the Z39.50 target profiles View. Fixes UIIN-2424. +* Added sort options in Actions for Instance/Holdings/Item. Refs UIIN-2357. +* Statistical code empty field error. Refs UIIN-2420. +* Hide the `Actions` button for the view page if there are no permissions. Refs UIIN-2360. +* When duplicating an item, the circulation history is duplicated when it should not be. Fixes UIIN-2419. +* Don't reset browse query when on Browse route and click Browse segment. Fixes UIIN-2434. +* Prevent double-escaping of query when Browsing. Fixes UIIN-2435. +* When adding items, cursor is in the barcode field as default. Refs UIIN-2205. +* Quick export from instance detail view. Refs UIIN-2430. +* Escape quotes to search for Subjects and Contributor. Refs UIIN-2445. +* Fix Search and Browse navigation and determining if the button is active. Refs UIIN-2444. +* Scrolling within Inventory facets. Refs UIIN-2377. +* When removing the dead icons in the Item record, then the displayed text looks inconsistent. Fixes UIIN-1392. +* Adjust the sentence case for some Inventory action options. Refs UIIN-2436. +* Adjust the Instance Edit screen header. Refs UIIN-2437. +* Inventory: Retain Search/Browse query/options/filter and facet selections UNLESS user resets/clear selections. Refs UIIN-2433. +* Statistical Code dropdown "contains" type ahead functionality needed (Instance/Holdings/Items). Refs UIIN-2466. +* Fix problem with a large number of items on a single holdings record. Refs UIIN-2478. +* Decrease rerenders for TargetProfileDetail component to avoid errors when view the target profile. Fixes UIIN-2467. +* leverage cookie-based authentication in all API requests. Refs UIIN-2282. +* Restrict modifying and deleting system call number types. Refs UIIN-2385. +* Prevent editing of shared settings from outside "Consortium manager". Refs UIIN-2482. +* Add new browse options to limit browse by call number type. Fixes UIIN-2467. +* Implement Advanced search modal. Refs UIIN-1920. + +## [9.4.8](https://github.com/folio-org/ui-inventory/tree/v9.4.8) (2023-06-30) +[Full Changelog](https://github.com/folio-org/ui-inventory/compare/v9.4.7...v9.4.8) + +* Fix useBoundWithHoldings for 100+ items. Fixes UIIN-2478. + +## [9.4.7](https://github.com/folio-org/ui-inventory/tree/v9.4.7) (2023-06-27) +[Full Changelog](https://github.com/folio-org/ui-inventory/compare/v9.4.6...v9.4.7) + +* When duplicating an item, the circulation history is duplicated when it should not be. Fixes UIIN-2469. + +## [9.4.6](https://github.com/folio-org/ui-inventory/tree/v9.4.6) (2023-06-19) +[Full Changelog](https://github.com/folio-org/ui-inventory/compare/v9.4.5...v9.4.6) + +* Rename `hrid` qindex for item to avoid collisions with holdings and instances. Fixes UIIN-2443. ## [9.4.5](https://github.com/folio-org/ui-inventory/tree/v9.4.5) (2023-04-03) [Full Changelog](https://github.com/folio-org/ui-inventory/compare/v9.4.4...v9.4.5) @@ -128,6 +177,14 @@ * Moved the print button from QuickMarkView, into the ViewSource. Due to the folio-org/ui-quick-marc#468. Refs UIIN-2324. * Change title for the print popup. Refs UIIN-2329. * Move @testing-library/* to dev-deps. Refs UIIN-2309. +* Rename `hrid` qindex for item to avoid collisions with holdings and instances. Fixes UIIN-2443. +* Reset `` when modal is closed. Fixes UIIN-2401. + +## [9.2.9](https://github.com/folio-org/ui-inventory/tree/v9.2.9) (2023-06-27) +[Full Changelog](https://github.com/folio-org/ui-inventory/compare/v9.2.8...v9.2.9) + +* Use correct reference to item resource. Fixes UIIN-2418. +* When duplicating an item, the circulation history is duplicated when it should not be. Fixes UIIN-2470. ## [9.2.0](https://github.com/folio-org/ui-inventory/tree/v9.2.0) (2022-10-27) [Full Changelog](https://github.com/folio-org/ui-inventory/compare/v9.1.0...v9.2.0) diff --git a/package.json b/package.json index 1d5c31e6d..1db228383 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@folio/inventory", - "version": "9.4.5", + "version": "10.0.0", "description": "Inventory manager", "repository": "folio-org/ui-inventory", "publishConfig": { @@ -51,7 +51,7 @@ "alternative-title-types": "1.0", "call-number-types": "1.0", "browse": "0.6 1.0", - "circulation": "9.0 10.0 11.0 12.0 13.0", + "circulation": "9.0 10.0 11.0 12.0 13.0 14.0", "classification-types": "1.1", "configuration": "2.0", "contributor-name-types": "1.2", @@ -836,7 +836,7 @@ "lint": "eslint .", "test": "yarn run test:unit", "test:jest": "jest", - "test:unit": "jest --silent --ci --coverage", + "test:unit": "jest --ci --coverage", "test:unit:watch": "jest --watch --coverage", "test:e2e": "stripes test karma", "test:e2e:ci": "stripes test karma --karma.singleRun --karma.browsers ChromeDocker --karma.reporters mocha junit --coverage", @@ -880,10 +880,11 @@ "react-query": "^3.6.0", "react-router-dom": "^5.0.1", "regenerator-runtime": "^0.13.3", - "sinon": "^7.0.0" + "sinon": "^7.0.0", + "zustand": "^4.1.1" }, "dependencies": { - "@folio/quick-marc": "^6.0.0", + "@folio/quick-marc": "^7.0.0", "@folio/stripes-acq-components": "^4.0.0", "file-saver": "^2.0.0", "final-form": "^4.18.2", @@ -900,8 +901,7 @@ "react-final-form-arrays": "^3.1.0", "react-final-form-listeners": "^1.0.2", "react-router-prop-types": "^1.0.4", - "redux-form": "^8.3.7", - "zustand": "^3.7.2" + "redux-form": "^8.3.7" }, "peerDependencies": { "@folio/stripes": "^8.0.0", @@ -909,7 +909,8 @@ "react-intl": "^5.8.0", "react-query": "^3.6.0", "react-router": "^5.0.1", - "react-router-dom": "^5.0.1" + "react-router-dom": "^5.0.1", + "zustand": "^4.1.1" }, "optionalDependencies": { "@folio/plugin-create-inventory-records": "^3.0.0", diff --git a/src/Holding/DuplicateHolding/DuplicateHolding.test.js b/src/Holding/DuplicateHolding/DuplicateHolding.test.js index 75daf6836..f9d84cc72 100644 --- a/src/Holding/DuplicateHolding/DuplicateHolding.test.js +++ b/src/Holding/DuplicateHolding/DuplicateHolding.test.js @@ -2,7 +2,7 @@ import '../../../test/jest/__mock__'; import { MemoryRouter } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from 'react-query'; -import { render, screen } from '@testing-library/react'; +import { render, screen } from '@folio/jest-config-stripes/testing-library/react'; import { instance } from '../../../test/fixtures/instance'; import { diff --git a/src/Holding/EditHolding/EditHolding.test.js b/src/Holding/EditHolding/EditHolding.test.js index 2a3a4f701..34494fc91 100644 --- a/src/Holding/EditHolding/EditHolding.test.js +++ b/src/Holding/EditHolding/EditHolding.test.js @@ -2,7 +2,7 @@ import '../../../test/jest/__mock__'; import { MemoryRouter } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from 'react-query'; -import { render, screen } from '@testing-library/react'; +import { render, screen } from '@folio/jest-config-stripes/testing-library/react'; import { instance } from '../../../test/fixtures/instance'; import { diff --git a/src/Holding/ViewHolding/HoldingAquisitions/HoldingAquisitions.test.js b/src/Holding/ViewHolding/HoldingAquisitions/HoldingAquisitions.test.js index 275bde416..650ef1dc5 100644 --- a/src/Holding/ViewHolding/HoldingAquisitions/HoldingAquisitions.test.js +++ b/src/Holding/ViewHolding/HoldingAquisitions/HoldingAquisitions.test.js @@ -1,7 +1,7 @@ import React from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; -import { screen } from '@testing-library/react'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; import '../../../../test/jest/__mock__'; import renderWithIntl from '../../../../test/jest/helpers/renderWithIntl'; diff --git a/src/Holding/ViewHolding/HoldingAquisitions/useHoldingOrderLines.test.js b/src/Holding/ViewHolding/HoldingAquisitions/useHoldingOrderLines.test.js index b8f23db88..f18e0508d 100644 --- a/src/Holding/ViewHolding/HoldingAquisitions/useHoldingOrderLines.test.js +++ b/src/Holding/ViewHolding/HoldingAquisitions/useHoldingOrderLines.test.js @@ -3,7 +3,7 @@ import { QueryClient, QueryClientProvider, } from 'react-query'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@folio/jest-config-stripes/testing-library/react-hooks'; import '../../../../test/jest/__mock__'; diff --git a/src/Holding/ViewHolding/HoldingBoundWith/HoldingBoundWith.test.js b/src/Holding/ViewHolding/HoldingBoundWith/HoldingBoundWith.test.js index f80aeb355..4b9387890 100644 --- a/src/Holding/ViewHolding/HoldingBoundWith/HoldingBoundWith.test.js +++ b/src/Holding/ViewHolding/HoldingBoundWith/HoldingBoundWith.test.js @@ -1,7 +1,7 @@ import React from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; -import { screen } from '@testing-library/react'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; import '../../../../test/jest/__mock__'; import renderWithIntl from '../../../../test/jest/helpers/renderWithIntl'; diff --git a/src/Holding/ViewHolding/HoldingBoundWith/useBoundWithHoldings.js b/src/Holding/ViewHolding/HoldingBoundWith/useBoundWithHoldings.js index 820dbae45..448627f80 100644 --- a/src/Holding/ViewHolding/HoldingBoundWith/useBoundWithHoldings.js +++ b/src/Holding/ViewHolding/HoldingBoundWith/useBoundWithHoldings.js @@ -1,23 +1,24 @@ -import { useQuery } from 'react-query'; - -import { useOkapiKy, useNamespace } from '@folio/stripes/core'; +import useChunkedCQLFetch from '../../../hooks/useChunkedCQLFetch'; const useBoundWithHoldings = (boundWithItems) => { - const ky = useOkapiKy(); - const [namespace] = useNamespace({ key: 'boundWithHoldings' }); + let holdingsRecordIds = boundWithItems?.map(x => x.holdingsRecordId); - const holdingRecordIds = boundWithItems?.map(x => x.holdingsRecordId); - const queryIds = holdingRecordIds.join(' or '); + // De-dup the list of holdingsRecordIds for efficiency + holdingsRecordIds = [...new Set(holdingsRecordIds)]; - const { data, isLoading } = useQuery( - [namespace, queryIds], - () => ky.get(`holdings-storage/holdings?query=id=(${queryIds})`).json(), - { enabled: Boolean(queryIds) } - ); + const { items: holdingsRecords, isLoading } = useChunkedCQLFetch({ + ids: holdingsRecordIds, + endpoint: 'holdings-storage/holdings', + reduceFunction: (holdingQueries) => ( + holdingQueries.reduce((acc, curr) => { + return [...acc, ...(curr?.data?.holdingsRecords ?? [])]; + }, []) + ) + }); return { isLoading, - boundWithHoldings: data?.holdingsRecords || [], + boundWithHoldings: holdingsRecords, }; }; diff --git a/src/Holding/ViewHolding/HoldingBoundWith/useBoundWithHoldings.test.js b/src/Holding/ViewHolding/HoldingBoundWith/useBoundWithHoldings.test.js index 627e676a8..115123b49 100644 --- a/src/Holding/ViewHolding/HoldingBoundWith/useBoundWithHoldings.test.js +++ b/src/Holding/ViewHolding/HoldingBoundWith/useBoundWithHoldings.test.js @@ -3,7 +3,7 @@ import { QueryClient, QueryClientProvider, } from 'react-query'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@folio/jest-config-stripes/testing-library/react-hooks'; import '../../../../test/jest/__mock__'; diff --git a/src/Holding/ViewHolding/HoldingBoundWith/useBoundWithItems.test.js b/src/Holding/ViewHolding/HoldingBoundWith/useBoundWithItems.test.js index dd40e190c..d6b7edf12 100644 --- a/src/Holding/ViewHolding/HoldingBoundWith/useBoundWithItems.test.js +++ b/src/Holding/ViewHolding/HoldingBoundWith/useBoundWithItems.test.js @@ -3,7 +3,7 @@ import { QueryClient, QueryClientProvider, } from 'react-query'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@folio/jest-config-stripes/testing-library/react-hooks'; import '../../../../test/jest/__mock__'; diff --git a/src/Holding/ViewHolding/HoldingReceivingHistory/HoldingReceivingHistory.js b/src/Holding/ViewHolding/HoldingReceivingHistory/HoldingReceivingHistory.js index fde86634b..9e889a310 100644 --- a/src/Holding/ViewHolding/HoldingReceivingHistory/HoldingReceivingHistory.js +++ b/src/Holding/ViewHolding/HoldingReceivingHistory/HoldingReceivingHistory.js @@ -20,15 +20,17 @@ const columnMapping = { 'caption': , 'copyNumber': , 'enumeration': , + 'chronology': , 'receivedDate': , 'comment': , 'source': , }; -const visibleColumns = ['caption', 'copyNumber', 'enumeration', 'receivedDate', 'comment', 'source']; +const visibleColumns = ['caption', 'copyNumber', 'enumeration', 'chronology', 'receivedDate', 'comment', 'source']; const columnFormatter = { 'caption': i => i.caption || , 'copyNumber': i => i.copyNumber || , 'enumeration': i => i.enumeration || , + 'chronology': i => i.chronology || , 'receivedDate': i => (i.receivedDate ? : ), 'comment': i => i.comment || , 'source': i => , @@ -36,6 +38,7 @@ const columnFormatter = { const sorters = { 'caption': ({ caption }) => caption, 'copyNumber': ({ copyNumber }) => copyNumber, + 'chronology': ({ chronology }) => chronology, 'enumeration': ({ enumeration }) => enumeration, 'receivedDate': ({ receivedDate }) => receivedDate, 'source': ({ source }) => source, diff --git a/src/Holding/ViewHolding/HoldingReceivingHistory/HoldingReceivingHistory.test.js b/src/Holding/ViewHolding/HoldingReceivingHistory/HoldingReceivingHistory.test.js index bdbf0b755..bd5e3d019 100644 --- a/src/Holding/ViewHolding/HoldingReceivingHistory/HoldingReceivingHistory.test.js +++ b/src/Holding/ViewHolding/HoldingReceivingHistory/HoldingReceivingHistory.test.js @@ -1,7 +1,7 @@ import React from 'react'; -import user from '@testing-library/user-event'; -import { screen } from '@testing-library/react'; +import user from '@folio/jest-config-stripes/testing-library/user-event'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; import '../../../../test/jest/__mock__'; import renderWithIntl from '../../../../test/jest/helpers/renderWithIntl'; @@ -28,6 +28,7 @@ describe('HoldingReceivingHistory', () => { renderHoldingReceivingHistory({ id: 'holdingUid' }); expect(screen.getByText(receivingHistory[0].enumeration)).toBeInTheDocument(); + expect(screen.getByText(receivingHistory[0].chronology)).toBeInTheDocument(); }); it('should apply sort by a column', () => { diff --git a/src/Holding/ViewHolding/HoldingReceivingHistory/useReceivingHistory.test.js b/src/Holding/ViewHolding/HoldingReceivingHistory/useReceivingHistory.test.js index cbf46c0a1..8d5fb9627 100644 --- a/src/Holding/ViewHolding/HoldingReceivingHistory/useReceivingHistory.test.js +++ b/src/Holding/ViewHolding/HoldingReceivingHistory/useReceivingHistory.test.js @@ -3,7 +3,7 @@ import { QueryClient, QueryClientProvider, } from 'react-query'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@folio/jest-config-stripes/testing-library/react-hooks'; import '../../../../test/jest/__mock__'; diff --git a/src/Instance/DnDContext.test.js b/src/Instance/DnDContext.test.js index 3730b208e..e89837e76 100644 --- a/src/Instance/DnDContext.test.js +++ b/src/Instance/DnDContext.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { render } from '@testing-library/react'; +import { render } from '@folio/jest-config-stripes/testing-library/react'; import DnDContext from './DnDContext'; describe('DnDContext', () => { diff --git a/src/Instance/HoldingsList/Holding/Holding.test.js b/src/Instance/HoldingsList/Holding/Holding.test.js index 9266a9e94..a14f961a7 100644 --- a/src/Instance/HoldingsList/Holding/Holding.test.js +++ b/src/Instance/HoldingsList/Holding/Holding.test.js @@ -1,7 +1,7 @@ import React from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; -import { screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; import '../../../../test/jest/__mock__'; diff --git a/src/Instance/HoldingsList/Holding/HoldingAccordion.test.js b/src/Instance/HoldingsList/Holding/HoldingAccordion.test.js index 8bc920612..7d48b1275 100644 --- a/src/Instance/HoldingsList/Holding/HoldingAccordion.test.js +++ b/src/Instance/HoldingsList/Holding/HoldingAccordion.test.js @@ -1,9 +1,9 @@ import React from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; import { noop } from 'lodash'; -import userEvent from '@testing-library/user-event'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; import { act } from 'react-dom/test-utils'; -import { screen, waitFor } from '@testing-library/react'; +import { screen, waitFor } from '@folio/jest-config-stripes/testing-library/react'; import '../../../../test/jest/__mock__'; diff --git a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.test.js b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.test.js index 447cfe463..f9163f63d 100644 --- a/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.test.js +++ b/src/Instance/HoldingsList/Holding/HoldingButtonsGroup.test.js @@ -1,7 +1,7 @@ import React from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; -import { screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; import '../../../../test/jest/__mock__'; diff --git a/src/Instance/HoldingsList/Holding/HoldingContainer.test.js b/src/Instance/HoldingsList/Holding/HoldingContainer.test.js index 9477582b9..217fc2e34 100644 --- a/src/Instance/HoldingsList/Holding/HoldingContainer.test.js +++ b/src/Instance/HoldingsList/Holding/HoldingContainer.test.js @@ -1,6 +1,6 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; +import { render, screen } from '@folio/jest-config-stripes/testing-library/react'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; import '../../../../test/jest/__mock__'; import { IntlProvider } from 'react-intl'; import { MemoryRouter } from 'react-router-dom'; diff --git a/src/Instance/HoldingsList/Holding/MoveToDropdown/MoveToDropdown.test.js b/src/Instance/HoldingsList/Holding/MoveToDropdown/MoveToDropdown.test.js index 6c3e565b4..40e86d30b 100644 --- a/src/Instance/HoldingsList/Holding/MoveToDropdown/MoveToDropdown.test.js +++ b/src/Instance/HoldingsList/Holding/MoveToDropdown/MoveToDropdown.test.js @@ -1,6 +1,6 @@ import React from 'react'; -import { screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; import { Router } from 'react-router'; import { createMemoryHistory } from 'history'; import { DataContext } from '../../../../contexts'; diff --git a/src/Instance/HoldingsList/HoldingsList.test.js b/src/Instance/HoldingsList/HoldingsList.test.js index 0e4d4d6c3..4610e9ad9 100644 --- a/src/Instance/HoldingsList/HoldingsList.test.js +++ b/src/Instance/HoldingsList/HoldingsList.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { screen, render } from '@testing-library/react'; +import { screen, render } from '@folio/jest-config-stripes/testing-library/react'; import '../../../test/jest/__mock__'; import { MemoryRouter } from 'react-router-dom'; import HoldingsList from './HoldingsList'; diff --git a/src/Instance/HoldingsList/HoldingsListContainer.test.js b/src/Instance/HoldingsList/HoldingsListContainer.test.js index 869aecc09..8c2eedd4d 100644 --- a/src/Instance/HoldingsList/HoldingsListContainer.test.js +++ b/src/Instance/HoldingsList/HoldingsListContainer.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@folio/jest-config-stripes/testing-library/react'; import '../../../test/jest/__mock__'; import { MemoryRouter } from 'react-router-dom'; import { useInstanceHoldingsQuery } from '../../providers'; diff --git a/src/Instance/InstanceDetails/InstanceAcquisition/InstanceAcquisition.test.js b/src/Instance/InstanceDetails/InstanceAcquisition/InstanceAcquisition.test.js index 557158ea3..0a978027b 100644 --- a/src/Instance/InstanceDetails/InstanceAcquisition/InstanceAcquisition.test.js +++ b/src/Instance/InstanceDetails/InstanceAcquisition/InstanceAcquisition.test.js @@ -1,7 +1,7 @@ import React from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; -import { screen } from '@testing-library/react'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; import '../../../../test/jest/__mock__'; import renderWithIntl from '../../../../test/jest/helpers/renderWithIntl'; diff --git a/src/Instance/InstanceDetails/InstanceAcquisition/useInstanceAcquisition.test.js b/src/Instance/InstanceDetails/InstanceAcquisition/useInstanceAcquisition.test.js index 90c667d73..aea768013 100644 --- a/src/Instance/InstanceDetails/InstanceAcquisition/useInstanceAcquisition.test.js +++ b/src/Instance/InstanceDetails/InstanceAcquisition/useInstanceAcquisition.test.js @@ -1,6 +1,6 @@ import React from 'react'; import { QueryClient, QueryClientProvider } from 'react-query'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@folio/jest-config-stripes/testing-library/react-hooks'; import '../../../../test/jest/__mock__'; diff --git a/src/Instance/InstanceDetails/InstanceClassificationView/InstanceClassificationView.test.js b/src/Instance/InstanceDetails/InstanceClassificationView/InstanceClassificationView.test.js index 6c78543f5..bddee95b6 100644 --- a/src/Instance/InstanceDetails/InstanceClassificationView/InstanceClassificationView.test.js +++ b/src/Instance/InstanceDetails/InstanceClassificationView/InstanceClassificationView.test.js @@ -1,7 +1,7 @@ import React from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; -import userEvent from '@testing-library/user-event'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; import '../../../../test/jest/__mock__'; import renderWithIntl from '../../../../test/jest/helpers/renderWithIntl'; diff --git a/src/Instance/InstanceDetails/InstanceContributorsView/InstanceContributorsView.test.js b/src/Instance/InstanceDetails/InstanceContributorsView/InstanceContributorsView.test.js index 13d1f3599..6067c1ee3 100644 --- a/src/Instance/InstanceDetails/InstanceContributorsView/InstanceContributorsView.test.js +++ b/src/Instance/InstanceDetails/InstanceContributorsView/InstanceContributorsView.test.js @@ -1,8 +1,8 @@ import React from 'react'; import { Router } from 'react-router-dom'; import { createMemoryHistory } from 'history'; -import { screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; import '../../../../test/jest/__mock__'; diff --git a/src/Instance/InstanceDetails/InstanceDescriptiveView/InstanceDescriptiveView.test.js b/src/Instance/InstanceDetails/InstanceDescriptiveView/InstanceDescriptiveView.test.js index d363f7153..1f7dcf2d9 100644 --- a/src/Instance/InstanceDetails/InstanceDescriptiveView/InstanceDescriptiveView.test.js +++ b/src/Instance/InstanceDetails/InstanceDescriptiveView/InstanceDescriptiveView.test.js @@ -1,7 +1,7 @@ import React from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; -import userEvent from '@testing-library/user-event'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; import '../../../../test/jest/__mock__'; import renderWithIntl from '../../../../test/jest/helpers/renderWithIntl'; diff --git a/src/Instance/InstanceDetails/InstanceDescriptiveView/utils.test.js b/src/Instance/InstanceDetails/InstanceDescriptiveView/utils.test.js index ba6bf094a..caf7640cb 100644 --- a/src/Instance/InstanceDetails/InstanceDescriptiveView/utils.test.js +++ b/src/Instance/InstanceDetails/InstanceDescriptiveView/utils.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen } from '@folio/jest-config-stripes/testing-library/react'; import '../../../../test/jest/__mock__'; import { formatLanguages } from './utils'; diff --git a/src/Instance/InstanceDetails/InstanceDetails.test.js b/src/Instance/InstanceDetails/InstanceDetails.test.js new file mode 100644 index 000000000..7c08c8cb7 --- /dev/null +++ b/src/Instance/InstanceDetails/InstanceDetails.test.js @@ -0,0 +1,240 @@ +import React from 'react'; +import '../../../test/jest/__mock__'; +import { QueryClient, QueryClientProvider } from 'react-query'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; +import { MemoryRouter } from 'react-router-dom'; +import { DataContext } from '../../contexts'; +import { renderWithIntl, translationsProperties } from '../../../test/jest/helpers'; +import InstanceDetails from './InstanceDetails'; + +jest.mock('../../components/ViewSource/ViewSource', () => jest.fn().mockReturnValue('ViewSource')); +jest.mock('../InstanceDetails/InstanceTitle/InstanceTitle', () => jest.fn().mockReturnValue('InstanceTitle')); +jest.mock('../InstanceDetails/InstanceContributorsView/InstanceContributorsView', () => jest.fn().mockReturnValue('InstanceContributorsView')); +jest.mock('../InstanceDetails/InstanceSubjectView/InstanceSubjectView', () => jest.fn().mockReturnValue('InstanceSubjectView')); +jest.mock('../InstanceDetails/InstanceTitleData/AlternativeTitlesList', () => jest.fn().mockReturnValue('AlternativeTitlesList')); +jest.mock('../InstanceDetails/InstanceTitleData/TitleSeriesStatements', () => jest.fn().mockReturnValue('TitleSeriesStatements')); +jest.mock('../InstanceDetails/ControllableDetail/ControllableDetail', () => jest.fn().mockReturnValue('ControllableDetail')); +jest.mock('../InstanceDetails/SubInstanceGroup/SubInstanceGroup', () => jest.fn().mockReturnValue('SubInstanceGroup')); +jest.mock('../InstanceDetails/InstanceAcquisition/InstanceAcquisition', () => jest.fn().mockReturnValue('InstanceAcquisition')); +jest.mock('../InstanceDetails/InstanceTitleData/InstanceTitleData', () => jest.fn().mockReturnValue('InstanceTitleData')); + +jest.mock('@folio/stripes-core', () => ({ + ...jest.requireActual('@folio/stripes-core'), + TitleManager: ({ children }) => <>{children} +})); + +const instance = { + title: 'Test Title', + contributors: [], + identifiers: [], + instanceTypeId: '1234', + instanceFormatIds: [], + physicalDescriptions: [], + languages: [], + publication: [], + notes: [], + staffSuppress: false, + discoverySuppress: false, +}; + +const mockReferenceData = { + titleTypes:[ + { id: '1', name: 'Type 1' }, + { id: '2', name: 'Type 2' }, + ], + instanceTypes: [ + { id: '1', name: 'Book' }, + { id: '2', name: 'E-book' }, + ] +}; + +const queryClient = new QueryClient(); + +const actionMenu = jest.fn(); +const onClose = jest.fn(); +const tagsEnabled = true; +describe('InstanceDetails', () => { + it('renders the InstanceDetails component', () => { + renderWithIntl( + + + + , + + + , + translationsProperties + ); + expect(screen.getByText('InstanceTitle')).toBeInTheDocument(); + expect(screen.getByText('Add holdings')).toBeInTheDocument(); + expect(screen.getByText('Administrative data')).toBeInTheDocument(); + expect(screen.getByText('Instance HRID')).toBeInTheDocument(); + expect(screen.getByText('Source')).toBeInTheDocument(); + expect(screen.getByText('Cataloged date')).toBeInTheDocument(); + expect(screen.getByText('Instance status term')).toBeInTheDocument(); + expect(screen.getByText('status updated -')).toBeInTheDocument(); + expect(screen.getByText('Instance status code')).toBeInTheDocument(); + expect(screen.getByText('Instance status source')).toBeInTheDocument(); + expect(screen.getByText('Mode of issuance')).toBeInTheDocument(); + expect(screen.getByText('Statistical code type')).toBeInTheDocument(); + expect(screen.getByText('Statistical code')).toBeInTheDocument(); + expect(screen.getByText('Format category')).toBeInTheDocument(); + expect(screen.getByText('Format term')).toBeInTheDocument(); + expect(screen.getByText('Format code')).toBeInTheDocument(); + expect(screen.getByText('Format source')).toBeInTheDocument(); + expect(screen.getByText('Language')).toBeInTheDocument(); + expect(screen.getByText('Publication frequency')).toBeInTheDocument(); + expect(screen.getByText('Publication range')).toBeInTheDocument(); + expect(screen.getByText('Instance notes')).toBeInTheDocument(); + expect(screen.getByText('Staff only')).toBeInTheDocument(); + expect(screen.getByText('Note')).toBeInTheDocument(); + expect(screen.getByText('Electronic access')).toBeInTheDocument(); + expect(screen.getByText('URL relationship')).toBeInTheDocument(); + expect(screen.getByText('URI')).toBeInTheDocument(); + expect(screen.getByText('Link text')).toBeInTheDocument(); + expect(screen.getByText('Materials specified')).toBeInTheDocument(); + expect(screen.getByText('URL public note')).toBeInTheDocument(); + expect(screen.getByText('Classification identifier type')).toBeInTheDocument(); + expect(screen.getByText('Instance relationship (analytics and bound-with)')).toBeInTheDocument(); + }); + + it('should show a correct Warning message banner when staff suppressed', () => { + const staffSuppressedInstance = { + ...instance, + staffSuppress: true, + }; + renderWithIntl( + + + + , + + + , + translationsProperties + ); + + expect(screen.getByText('Warning: Instance is marked staff suppressed')).toBeInTheDocument(); + expect(screen.getByText('Staff suppressed')).toBeInTheDocument(); + }); + + it('should show a correct Warning message banner when discovery suppressed', () => { + const discoverySuppressedInstance = { + ...instance, + discoverySuppress: true, + }; + renderWithIntl( + + + + , + + + , + translationsProperties + ); + + expect(screen.getByText('Warning: Instance is marked suppressed from discovery')).toBeInTheDocument(); + expect(screen.getByText('Suppressed from discovery')).toBeInTheDocument(); + }); + it('should show a correct Warning message banner when both staff and discovery suppressed', () => { + const bothSuppressedInstance = { + ...instance, + staffSuppress: true, + discoverySuppress: true, + }; + renderWithIntl( + + + + , + + + , + translationsProperties + ); + + expect(screen.getByText('Warning: Instance is marked suppressed from discovery and staff suppressed')).toBeInTheDocument(); + }); + + it('expands and collapses the accordion sections', () => { + renderWithIntl( + + + + , + + + , + translationsProperties + ); + + const expandAllButtons = screen.getByText('Expand all'); + const firstAccordionSection = screen.getByRole('button', { name: /Administrative data/i }); + const secondAccordionSection = screen.getByRole('button', { name: /Instance notes/i }); + const thirdAccordionSection = screen.getByRole('button', { name: /Electronic access/i }); + const fourthAccordionSection = screen.getByRole('button', { name: /Classification/i }); + expect(firstAccordionSection.getAttribute('aria-expanded')).toBe('false'); + expect(secondAccordionSection.getAttribute('aria-expanded')).toBe('false'); + expect(thirdAccordionSection.getAttribute('aria-expanded')).toBe('false'); + expect(fourthAccordionSection.getAttribute('aria-expanded')).toBe('false'); + userEvent.click(expandAllButtons); + expect(firstAccordionSection.getAttribute('aria-expanded')).toBe('true'); + expect(secondAccordionSection.getAttribute('aria-expanded')).toBe('true'); + expect(thirdAccordionSection.getAttribute('aria-expanded')).toBe('true'); + expect(fourthAccordionSection.getAttribute('aria-expanded')).toBe('true'); + const collapseAllButtons = screen.getByText('Collapse all'); + userEvent.click(collapseAllButtons); + expect(firstAccordionSection.getAttribute('aria-expanded')).toBe('false'); + expect(secondAccordionSection.getAttribute('aria-expanded')).toBe('false'); + expect(thirdAccordionSection.getAttribute('aria-expanded')).toBe('false'); + expect(fourthAccordionSection.getAttribute('aria-expanded')).toBe('false'); + }); + + it('renders tags button if tagsEnabled is true', () => { + renderWithIntl( + + + + , + + + , + translationsProperties + ); + const button = screen.getAllByRole('button', { id: 'clickable-show-tags' }); + userEvent.click(button[1]); + expect(button[1]).toBeEnabled(); + }); +}); diff --git a/src/Instance/InstanceDetails/InstanceElecAccessView/InstanceElecAccessView.test.js b/src/Instance/InstanceDetails/InstanceElecAccessView/InstanceElecAccessView.test.js index 551eaf19b..260e60b19 100644 --- a/src/Instance/InstanceDetails/InstanceElecAccessView/InstanceElecAccessView.test.js +++ b/src/Instance/InstanceDetails/InstanceElecAccessView/InstanceElecAccessView.test.js @@ -1,7 +1,7 @@ import React from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; -import userEvent from '@testing-library/user-event'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; import '../../../../test/jest/__mock__'; import renderWithIntl from '../../../../test/jest/helpers/renderWithIntl'; diff --git a/src/Instance/InstanceDetails/InstanceIdentifiersView/InstanceIdentifiersView.test.js b/src/Instance/InstanceDetails/InstanceIdentifiersView/InstanceIdentifiersView.test.js index 0325d63aa..d9f6e62cd 100644 --- a/src/Instance/InstanceDetails/InstanceIdentifiersView/InstanceIdentifiersView.test.js +++ b/src/Instance/InstanceDetails/InstanceIdentifiersView/InstanceIdentifiersView.test.js @@ -1,7 +1,7 @@ import React from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; -import userEvent from '@testing-library/user-event'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; import '../../../../test/jest/__mock__'; import renderWithIntl from '../../../../test/jest/helpers/renderWithIntl'; diff --git a/src/Instance/InstanceDetails/InstanceNotesView/InstanceNotesView.test.js b/src/Instance/InstanceDetails/InstanceNotesView/InstanceNotesView.test.js index 3b3c35fb3..7ff944ad5 100644 --- a/src/Instance/InstanceDetails/InstanceNotesView/InstanceNotesView.test.js +++ b/src/Instance/InstanceDetails/InstanceNotesView/InstanceNotesView.test.js @@ -1,7 +1,7 @@ import React from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; -import userEvent from '@testing-library/user-event'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; import '../../../../test/jest/__mock__'; import renderWithIntl from '../../../../test/jest/helpers/renderWithIntl'; diff --git a/src/Instance/InstanceDetails/InstanceSubjectView/InstanceSubjectView.test.js b/src/Instance/InstanceDetails/InstanceSubjectView/InstanceSubjectView.test.js index a78bea316..faf41775e 100644 --- a/src/Instance/InstanceDetails/InstanceSubjectView/InstanceSubjectView.test.js +++ b/src/Instance/InstanceDetails/InstanceSubjectView/InstanceSubjectView.test.js @@ -4,7 +4,7 @@ import '../../../../test/jest/__mock__'; import { Router } from 'react-router'; import { createMemoryHistory } from 'history'; -import userEvent from '@testing-library/user-event'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; import { DataContext } from '../../../contexts'; diff --git a/src/Instance/InstanceDetails/InstanceTitleData/InstanceTitleData.test.js b/src/Instance/InstanceDetails/InstanceTitleData/InstanceTitleData.test.js index 05b89fb0b..c68511e9b 100644 --- a/src/Instance/InstanceDetails/InstanceTitleData/InstanceTitleData.test.js +++ b/src/Instance/InstanceDetails/InstanceTitleData/InstanceTitleData.test.js @@ -1,7 +1,7 @@ import React from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; -import userEvent from '@testing-library/user-event'; -import { screen } from '@testing-library/react'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; import '../../../../test/jest/__mock__'; import renderWithIntl from '../../../../test/jest/helpers/renderWithIntl'; import InstanceTitleData from './InstanceTitleData'; diff --git a/src/Instance/InstanceDetails/InstanceTitleData/TitleSeriesStatements.test.js b/src/Instance/InstanceDetails/InstanceTitleData/TitleSeriesStatements.test.js new file mode 100644 index 000000000..a70832daa --- /dev/null +++ b/src/Instance/InstanceDetails/InstanceTitleData/TitleSeriesStatements.test.js @@ -0,0 +1,40 @@ +import React from 'react'; +import { BrowserRouter as Router } from 'react-router-dom'; + +import { screen } from '@folio/jest-config-stripes/testing-library/react'; + +import '../../../../test/jest/__mock__'; +import renderWithIntl from '../../../../test/jest/helpers/renderWithIntl'; + +import TitleSeriesStatements from './TitleSeriesStatements'; + +const seriesStatements = ['Statement 1', 'Statement 2']; + +jest.mock('../ControllableDetail', () => ({ + ControllableDetail: jest.fn().mockReturnValue('ControllableDetail'), +})); + +const props = { + seriesStatements, + segment: 'segments', + source: 'source-test' +}; + +const renderTitleSeriesStatements = () => ( + renderWithIntl( + + + + ) +); + +describe('TitleSeriesStatements', () => { + it('Should renders correctly', () => { + const { getByRole } = renderTitleSeriesStatements(); + const list = getByRole('grid'); + expect(list).toBeInTheDocument(); + expect(list).toHaveAttribute('id', 'list-series-statement'); + expect(screen.getByText(/ui-inventory.seriesStatement/i)).toBeInTheDocument(); + expect(screen.getAllByText(/ControllableDetail/i)).toBeTruthy(); + }); +}); diff --git a/src/Instance/InstanceDetails/SubInstanceGroup/SubInstanceGroup.test.js b/src/Instance/InstanceDetails/SubInstanceGroup/SubInstanceGroup.test.js index dd3c25f50..6b84646b6 100644 --- a/src/Instance/InstanceDetails/SubInstanceGroup/SubInstanceGroup.test.js +++ b/src/Instance/InstanceDetails/SubInstanceGroup/SubInstanceGroup.test.js @@ -1,4 +1,4 @@ -import { screen } from '@testing-library/react'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; import React from 'react'; import '../../../../test/jest/__mock__'; import { renderWithIntl, translationsProperties } from '../../../../test/jest/helpers'; diff --git a/src/Instance/InstanceDetails/SubInstanceList/SubInstanceList.test.js b/src/Instance/InstanceDetails/SubInstanceList/SubInstanceList.test.js new file mode 100644 index 000000000..e355c0c78 --- /dev/null +++ b/src/Instance/InstanceDetails/SubInstanceList/SubInstanceList.test.js @@ -0,0 +1,92 @@ +import React from 'react'; +import { QueryClient, QueryClientProvider } from 'react-query'; +import '../../../../test/jest/__mock__'; +import { Router } from 'react-router'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; +import { createMemoryHistory } from 'history'; +import { renderWithIntl, translationsProperties } from '../../../../test/jest/helpers'; +import useLoadSubInstances from '../../../hooks/useLoadSubInstances'; + +import SubInstanceList from './SubInstanceList'; + +const mockuseLoadSubInstancesVaues = [ + { + id: '1', + title: 'Test Title 1', + hrid: 'TestHrid1', + publication: [{ publisher: 'Test Publisher 1', dateOfPublication: '04-17-2023' }], + identifiers: [ + { identifierTypeId: 'ISBN', value: '369369' }, + ] + }, + { + id: '2', + title: 'Test Title 2', + hrid: 'TestHrid2', + publication: [{ publisher: 'Test Publisher 2', dateOfPublication: '04-17-2023' }], + identifiers: [ + { identifierTypeId: 'ISSN', value: '789789' }, + ], + } +]; + +const history = createMemoryHistory(); +const defaultProps = { + id: 'TestID', + label: 'LabelTest', + titles: [{}], + titleKey: 'id', +}; + +const queryClient = new QueryClient(); +const renderSubInstanceList = (props) => renderWithIntl( + + + + + , + translationsProperties +); + + +jest.mock('../../../hooks/useReferenceData', () => jest.fn().mockReturnValue({ + identifierTypesById : { + 'ISBN': { name : 'ISBN' }, + 'ISSN': { name : 'ISSN' } + } +})); +jest.mock('../../../hooks/useLoadSubInstances', () => jest.fn()); + + +describe('render SubInstanceList', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + it('NoValue should display when no title is present', () => { + useLoadSubInstances.mockReturnValue([{}]); + const { getAllByText } = renderSubInstanceList(defaultProps); + expect(getAllByText(/No value set/i).length).toBe(4); + }); + it('No link to be present when tittleKey is empty', () => { + useLoadSubInstances.mockReturnValue(mockuseLoadSubInstancesVaues); + const { container, getByText } = renderSubInstanceList({ ...defaultProps, titleKey: '' }); + expect(getByText('Test Title 1')).toBeInTheDocument(); + expect(getByText('Test Title 1')).not.toHaveAttribute('href'); + expect(getByText('Test Title 2')).toBeInTheDocument(); + expect(getByText('Test Title 2')).not.toHaveAttribute('href'); + expect(container.getElementsByTagName('a').length).toBe(0); + }); + it('Link to a title to be present when title and tittleKey is present', () => { + useLoadSubInstances.mockReturnValue(mockuseLoadSubInstancesVaues); + const { container, getByText } = renderSubInstanceList(defaultProps); + expect(getByText('Test Title 1')).toHaveAttribute('href', '/inventory/view/1'); + expect(getByText('Test Title 2')).toHaveAttribute('href', '/inventory/view/2'); + expect(container.getElementsByTagName('a').length).toBe(2); + }); + it('Pagination message to be render on clicking next button', () => { + useLoadSubInstances.mockReturnValue(mockuseLoadSubInstancesVaues); + const { container, getByRole } = renderSubInstanceList(defaultProps); + userEvent.click(getByRole('button', { name: 'Next' })); + expect(container.getElementsByClassName('sr-only').length).toBe(1); + }); +}); diff --git a/src/Instance/InstanceDetails/utils.js b/src/Instance/InstanceDetails/utils.js index ee3c75a05..1d9b3bbbb 100644 --- a/src/Instance/InstanceDetails/utils.js +++ b/src/Instance/InstanceDetails/utils.js @@ -11,9 +11,10 @@ export const getPublishingInfo = instance => { const publication = instance.publication && instance.publication[0]; if (publication) { + const publisherStr = publication.publisher ? ` • ${publication.publisher}` : ''; const publishDateStr = publication.dateOfPublication ? ` • ${publication.dateOfPublication}` : ''; - return `${publication.publisher ?? ''}${publishDateStr}`; + return `${publisherStr}${publishDateStr}`; } return undefined; diff --git a/src/Instance/InstanceDetails/utils.test.js b/src/Instance/InstanceDetails/utils.test.js index cc6c7b9d6..c2f5148af 100644 --- a/src/Instance/InstanceDetails/utils.test.js +++ b/src/Instance/InstanceDetails/utils.test.js @@ -12,7 +12,7 @@ describe('getPublishingInfo', () => { }, ], }; - expect(getPublishingInfo(instanceProps)).toEqual('Publisher • 2022-01-01'); + expect(getPublishingInfo(instanceProps)).toEqual(' • Publisher • 2022-01-01'); }); it('returns expected string when publication object exists without dateOfPublication', () => { const instanceProps = { @@ -22,7 +22,17 @@ describe('getPublishingInfo', () => { }, ], }; - expect(getPublishingInfo(instanceProps)).toEqual('Publisher'); + expect(getPublishingInfo(instanceProps)).toEqual(' • Publisher'); + }); + it('returns expected string when publication object exists without publisher', () => { + const instanceProps = { + publication: [ + { + dateOfPublication: '2022', + }, + ], + }; + expect(getPublishingInfo(instanceProps)).toEqual(' • 2022'); }); it('returns undefined when publication object does not exist', () => { const instanceProps = {}; diff --git a/src/Instance/InstanceEdit/InstanceEdit.test.js b/src/Instance/InstanceEdit/InstanceEdit.test.js index dc6b9142c..a5da4f317 100644 --- a/src/Instance/InstanceEdit/InstanceEdit.test.js +++ b/src/Instance/InstanceEdit/InstanceEdit.test.js @@ -1,9 +1,9 @@ import React from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; import { noop } from 'lodash'; -import userEvent from '@testing-library/user-event'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; import { act } from 'react-dom/test-utils'; -import { screen } from '@testing-library/react'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; import { QueryClient, QueryClientProvider } from 'react-query'; import '../../../test/jest/__mock__'; diff --git a/src/Instance/InstanceEdit/InstanceField/InstanceField.test.js b/src/Instance/InstanceEdit/InstanceField/InstanceField.test.js index b66eaf2e7..d4b4fb991 100644 --- a/src/Instance/InstanceEdit/InstanceField/InstanceField.test.js +++ b/src/Instance/InstanceEdit/InstanceField/InstanceField.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { screen } from '@testing-library/react'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; import { noop, keyBy } from 'lodash'; import { BrowserRouter as Router } from 'react-router-dom'; import { act } from 'react-dom/test-utils'; diff --git a/src/Instance/InstanceMarc/InstanceMarcContainer.test.js b/src/Instance/InstanceMarc/InstanceMarcContainer.test.js index 45225da38..41d442238 100644 --- a/src/Instance/InstanceMarc/InstanceMarcContainer.test.js +++ b/src/Instance/InstanceMarc/InstanceMarcContainer.test.js @@ -1,7 +1,7 @@ import React from 'react'; import '../../../test/jest/__mock__'; import { MemoryRouter } from 'react-router-dom'; -import { render, screen } from '@testing-library/react'; +import { render, screen } from '@folio/jest-config-stripes/testing-library/react'; import InstanceMarcContainer from './InstanceMarcContainer'; diff --git a/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.test.js b/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.test.js index a5dbe38af..5a90a2e41 100644 --- a/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.test.js +++ b/src/Instance/InstanceMovement/HoldingMovementList/HoldingsListMovement.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { screen } from '@testing-library/react'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { Router } from 'react-router'; import { createMemoryHistory } from 'history'; diff --git a/src/Instance/InstanceMovement/InstanceMovement.test.js b/src/Instance/InstanceMovement/InstanceMovement.test.js index aeb89250d..41262c282 100644 --- a/src/Instance/InstanceMovement/InstanceMovement.test.js +++ b/src/Instance/InstanceMovement/InstanceMovement.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { screen } from '@testing-library/react'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; import { QueryClient, QueryClientProvider } from 'react-query'; import { DataContext } from '../../contexts'; import '../../../test/jest/__mock__'; diff --git a/src/Instance/InstanceMovement/InstanceMovementContainer.js b/src/Instance/InstanceMovement/InstanceMovementContainer.js index 1595e92bb..8b00d2bce 100644 --- a/src/Instance/InstanceMovement/InstanceMovementContainer.js +++ b/src/Instance/InstanceMovement/InstanceMovementContainer.js @@ -77,6 +77,7 @@ const InstanceMovementContainer = ({ parsedRecordId, fields: changeHridForMarcHoldings(fields, instanceTo.hrid), relatedRecordVersion: ++holdingsById[marcHoldingsId]._version, + _actionType: 'edit', }); }); }); diff --git a/src/Instance/InstanceMovement/InstanceMovementContainer.test.js b/src/Instance/InstanceMovement/InstanceMovementContainer.test.js index 2bc70d4a9..197b26507 100644 --- a/src/Instance/InstanceMovement/InstanceMovementContainer.test.js +++ b/src/Instance/InstanceMovement/InstanceMovementContainer.test.js @@ -1,8 +1,8 @@ import '../../../test/jest/__mock__'; import React from 'react'; -import { screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; import { MemoryRouter } from 'react-router-dom'; import InstanceMovementContainer from './InstanceMovementContainer'; @@ -188,6 +188,7 @@ describe('Given InstanceMovementContainer', () => { content: 'instance-hrid-2', }], relatedRecordVersion: 2, + _actionType: 'edit', }); }); diff --git a/src/Instance/InstanceMovement/InstanceMovementDetails/InstanceMovementDetails.test.js b/src/Instance/InstanceMovement/InstanceMovementDetails/InstanceMovementDetails.test.js index 84702505d..d31c859f8 100644 --- a/src/Instance/InstanceMovement/InstanceMovementDetails/InstanceMovementDetails.test.js +++ b/src/Instance/InstanceMovement/InstanceMovementDetails/InstanceMovementDetails.test.js @@ -1,6 +1,6 @@ import React from 'react'; -import { screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; import { QueryClient, QueryClientProvider } from 'react-query'; import '../../../../test/jest/__mock__'; @@ -73,7 +73,7 @@ describe('InstanceMovementDetails', () => { }); it('render Action Menu', () => { renderInstanceMovementDetails(); - const actionMenu = screen.getByText(/Action Menu/i) + const actionMenu = screen.getByText(/Action Menu/i); expect(actionMenu).toBeInTheDocument(); }); it('render HoldingsListContainer', () => { diff --git a/src/Instance/InstanceMovement/InstanceMovementDetails/InstanceMovementDetailsActions.test.js b/src/Instance/InstanceMovement/InstanceMovementDetails/InstanceMovementDetailsActions.test.js index 72d8a7a2e..1f94323d4 100644 --- a/src/Instance/InstanceMovement/InstanceMovementDetails/InstanceMovementDetailsActions.test.js +++ b/src/Instance/InstanceMovement/InstanceMovementDetails/InstanceMovementDetailsActions.test.js @@ -1,8 +1,8 @@ import '../../../../test/jest/__mock__'; import React from 'react'; -import { screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; import { MemoryRouter } from 'react-router-dom'; import InstanceMovementDetailsActions from './InstanceMovementDetailsActions'; import { diff --git a/src/Instance/InstanceMovement/InstanceMovementDetails/InstanceMovementDetailsContainer.test.js b/src/Instance/InstanceMovement/InstanceMovementDetails/InstanceMovementDetailsContainer.test.js index d2471fba0..3e65d58a9 100644 --- a/src/Instance/InstanceMovement/InstanceMovementDetails/InstanceMovementDetailsContainer.test.js +++ b/src/Instance/InstanceMovement/InstanceMovementDetails/InstanceMovementDetailsContainer.test.js @@ -1,6 +1,6 @@ import '../../../../test/jest/__mock__'; import { MemoryRouter } from 'react-router-dom'; -import { render, screen } from '@testing-library/react'; +import { render, screen } from '@folio/jest-config-stripes/testing-library/react'; import InstanceMovementDetailsContainer from './InstanceMovementDetailsContainer'; jest.mock('./InstanceMovementDetails', () => jest.fn().mockReturnValue('InstanceMovementDetails')); diff --git a/src/Instance/ItemsList/tests/ItemsList.test.js b/src/Instance/ItemsList/tests/ItemsList.test.js index 546f8aff2..157d0deb4 100644 --- a/src/Instance/ItemsList/tests/ItemsList.test.js +++ b/src/Instance/ItemsList/tests/ItemsList.test.js @@ -2,7 +2,7 @@ import React from 'react'; import { QueryClient, QueryClientProvider } from 'react-query'; import { BrowserRouter as Router } from 'react-router-dom'; import { act } from 'react-dom/test-utils'; -import { screen } from '@testing-library/react'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; import '../../../../test/jest/__mock__'; diff --git a/src/Instance/Move/ConfirmationModal.test.js b/src/Instance/Move/ConfirmationModal.test.js index 74b6d46fd..0e6e48353 100644 --- a/src/Instance/Move/ConfirmationModal.test.js +++ b/src/Instance/Move/ConfirmationModal.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { screen } from '@testing-library/react'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; import '../../../test/jest/__mock__'; import { renderWithIntl, translationsProperties } from '../../../test/jest/helpers'; import { ConfirmationModal } from './ConfirmationModal'; diff --git a/src/Instance/MoveItemsContext/Confirmation.test.js b/src/Instance/MoveItemsContext/Confirmation.test.js index 895f12f55..dd1df6c78 100644 --- a/src/Instance/MoveItemsContext/Confirmation.test.js +++ b/src/Instance/MoveItemsContext/Confirmation.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { screen } from '@testing-library/react'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; import '../../../test/jest/__mock__'; import { renderWithIntl, translationsProperties } from '../../../test/jest/helpers'; import { Confirmation } from './Confirmation'; diff --git a/src/Instance/MoveItemsContext/useNoKeyboardSensors.test.js b/src/Instance/MoveItemsContext/useNoKeyboardSensors.test.js index cae5b5532..2e6daa262 100644 --- a/src/Instance/MoveItemsContext/useNoKeyboardSensors.test.js +++ b/src/Instance/MoveItemsContext/useNoKeyboardSensors.test.js @@ -1,5 +1,5 @@ -import { fireEvent } from '@testing-library/dom'; -import { renderHook, act } from '@testing-library/react-hooks'; +import { fireEvent } from '@folio/jest-config-stripes/testing-library/dom'; +import { renderHook, act } from '@folio/jest-config-stripes/testing-library/react-hooks'; import useNoKeyboardSensors from './useNoKeyboardSensors'; const mockApi = { diff --git a/src/Instance/ViewRequests/ViewRequests.test.js b/src/Instance/ViewRequests/ViewRequests.test.js new file mode 100644 index 000000000..8d227c5b8 --- /dev/null +++ b/src/Instance/ViewRequests/ViewRequests.test.js @@ -0,0 +1,88 @@ +import React from 'react'; +import { BrowserRouter as Router } from 'react-router-dom'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; + +import '../../../test/jest/__mock__'; +import renderWithIntl from '../../../test/jest/helpers/renderWithIntl'; + +import ViewRequests from './ViewRequests'; + +const items = [ + { + barcode: '1234', + status: { name: 'available' }, + id: '1', + holdingsRecordId: '2', + materialType: { name: 'book' }, + temporaryLoanType: { name: 'temporary' }, + effectiveLocation: { name: 'Main Library' }, + enumeration: 'vol 1', + chronology: '2001' + }, +]; +const requestsMap = new Map([['1', '1']]); +const loansMap = new Map([['1', [{ id: '1', dueDate: '2023-03-31T20:00:00.000Z' }]]]); +const instanceId = '1'; +const instance = { title: 'Book Title', publication: [{ dateOfPublication: '2022' }] }; + +const onCloseViewRequests = jest.fn(); + +const ViewRequestsSetup = () => ( + + + +); + +describe('ViewRequests', () => { + beforeEach(() => { + renderWithIntl(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + it('should render paneTitles', () => { + expect(screen.getByText(/ui-inventory.instanceRecordRequestsTitle/i)).toBeInTheDocument(); + expect(screen.getByText(/ui-inventory.instanceRecordRequestsSubtitle/i)).toBeInTheDocument(); + }); + it('should render the correct column headers', () => { + expect(screen.getByText(/ui-inventory.item.barcode/i)).toBeInTheDocument(); + expect(screen.getByText(/ui-inventory.status/i)).toBeInTheDocument(); + expect(screen.getByText(/ui-inventory.item.availability.dueDate/i)).toBeInTheDocument(); + expect(screen.getByText(/ui-inventory.item.requestQueue/i)).toBeInTheDocument(); + expect(screen.getByText(/ui-inventory.effectiveLocationShort/i)).toBeInTheDocument(); + expect(screen.getByText(/ui-inventory.loanType/i)).toBeInTheDocument(); + expect(screen.getByText(/ui-inventory.enumeration/i)).toBeInTheDocument(); + expect(screen.getByText(/ui-inventory.chronology/i)).toBeInTheDocument(); + expect(screen.getByText(/ui-inventory.materialType/i)).toBeInTheDocument(); + }); + it('should render the correct data for each item', () => { + expect(screen.getByText(/1234/i)).toBeInTheDocument(); + expect(screen.getByText(/available/i)).toBeInTheDocument(); + expect(screen.getByText('3/31/2023, 8:00 PM')).toBeInTheDocument(); + expect(screen.getByText('1')).toBeInTheDocument(); + expect(screen.getByText(/Main Library/i)).toBeInTheDocument(); + expect(screen.getByText(/temporary/i)).toBeInTheDocument(); + expect(screen.getByText(/vol 1/i)).toBeInTheDocument(); + expect(screen.getByText(/2001/i)).toBeInTheDocument(); + expect(screen.getByText(/book/i)).toBeInTheDocument(); + }); + it('should click barcodeButton', () => { + const barcodeButton = screen.getByRole('button', { name: 'ui-inventory.item.barcode' }); + expect(barcodeButton).toBeInTheDocument(); + userEvent.click(barcodeButton); + }); + it('should click requestQueue', () => { + const requestQueueButton = screen.getByRole('button', { name: 'ui-inventory.item.requestQueue' }); + expect(requestQueueButton).toBeInTheDocument(); + userEvent.click(requestQueueButton); + }); +}); diff --git a/src/Instance/ViewRequests/useFetchItems.test.js b/src/Instance/ViewRequests/useFetchItems.test.js index e3ee9d3e3..2c93136f8 100644 --- a/src/Instance/ViewRequests/useFetchItems.test.js +++ b/src/Instance/ViewRequests/useFetchItems.test.js @@ -1,4 +1,4 @@ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@folio/jest-config-stripes/testing-library/react-hooks'; import useFetchItems from './useFetchItems'; import { batchFetchItems } from './utils'; diff --git a/src/Instance/ViewRequests/utils.test.js b/src/Instance/ViewRequests/utils.test.js new file mode 100644 index 000000000..9c0897443 --- /dev/null +++ b/src/Instance/ViewRequests/utils.test.js @@ -0,0 +1,163 @@ +import { + getRecordsInBatch, + buildQueryByHoldingsIds, + buildQueryByItemsIds, + batchFetch, + batchFetchItems, + batchFetchRequests, +} from './utils'; + +import '../../../test/jest/__mock__'; + +describe('getRecordsInBatch', () => { + const mockMutator = { + GET: jest.fn(() => Promise.resolve()), + reset: jest.fn(), + }; + it('should return an empty array if no records are found', async () => { + mockMutator.GET.mockReturnValueOnce(Promise.resolve({ totalRecords: 0 })); + const result = await getRecordsInBatch(mockMutator, {}); + expect(result).toEqual([]); + }); + it('should return all records in one batch if there are fewer records than the limit', async () => { + const records = [{ id: '1' }, { id: '2' }, { id: '3' }]; + mockMutator.GET.mockReturnValueOnce(Promise.resolve({ totalRecords: records.length })); + mockMutator.GET.mockImplementationOnce(({ params }) => { + const offset = params.offset || 0; + const recordsChunk = records.slice(offset, offset + params.limit); + return Promise.resolve(recordsChunk); + }); + const result = await getRecordsInBatch(mockMutator, {}); + expect(result).toEqual(records); + }); + it('should return all records in multiple batches if there are more records than the limit', async () => { + const records = [{ id: '1' }, { id: '2' }, { id: '3' }, { id: '4' }, { id: '5' }, { id: '6' }, { id: '7' }, { id: '8' }]; + mockMutator.GET.mockReturnValueOnce(Promise.resolve({ totalRecords: records.length })); + mockMutator.GET.mockImplementation(({ params }) => { + const offset = params.offset || 0; + const recordsChunk = records.slice(offset, offset + params.limit); + return Promise.resolve(recordsChunk); + }); + const result = await getRecordsInBatch(mockMutator, {}); + expect(result).toEqual(records); + }); +}); + +describe('buildQueryByHoldingsIds', () => { + it('should return empty string when given an empty array', () => { + const query = buildQueryByHoldingsIds([]); + expect(query).toBe(''); + }); + it('should return a query string when given an array of holdings', () => { + const holdings = [ + { id: 1 }, + { id: 2 }, + { id: 3 }, + ]; + const query = buildQueryByHoldingsIds(holdings); + expect(query).toBe('holdingsRecordId==1 or holdingsRecordId==2 or holdingsRecordId==3'); + }); + it('should handle a single holding in the array', () => { + const holdings = [ + { id: 1 }, + ]; + const query = buildQueryByHoldingsIds(holdings); + expect(query).toBe('holdingsRecordId==1'); + }); +}); + +describe('buildQueryByItemsIds', () => { + it('returns empty string for empty input', () => { + const result = buildQueryByItemsIds([]); + expect(result).toEqual(''); + }); + it('returns correct query for single item', () => { + const result = buildQueryByItemsIds([{ id: 123 }]); + expect(result).toEqual('itemId==123'); + }); + it('returns correct query for multiple items', () => { + const result = buildQueryByItemsIds([{ id: 123 }, { id: 456 }, { id: 789 }]); + expect(result).toEqual('itemId==123 or itemId==456 or itemId==789'); + }); + it('returns empty string for input without ids', () => { + const result = buildQueryByItemsIds([{ foo: 'bar' }]); + expect(result).toEqual('itemId==undefined'); + }); +}); + +describe('batchFetch', () => { + const mutator = { + reset: jest.fn(), + GET: jest.fn(), + }; + beforeEach(() => { + mutator.reset.mockClear(); + mutator.GET.mockClear(); + }); + it('should return empty array when no records are provided', async () => { + const records = []; + const buildQuery = jest.fn(); + const response = await batchFetch(mutator, records, buildQuery); + expect(response).toEqual([]); + expect(mutator.reset).toHaveBeenCalledTimes(1); + expect(mutator.GET).toHaveBeenCalledTimes(0); + }); + it('should return empty array when query is empty', async () => { + const records = [{ id: 1 }]; + const buildQuery = jest.fn(() => ''); + const response = await batchFetch(mutator, records, buildQuery); + expect(response).toEqual([]); + expect(mutator.reset).toHaveBeenCalledTimes(1); + expect(mutator.GET).toHaveBeenCalledTimes(0); + }); + it('should return records when mutator.GET returns data', async () => { + const records = [{ id: 1 }, { id: 2 }]; + const buildQuery = buildQueryByHoldingsIds; + const data = []; + mutator.GET.mockResolvedValueOnce({ data }); + const response = await batchFetch(mutator, records, buildQuery); + expect(response).toEqual(data); + expect(mutator.reset).toHaveBeenCalledTimes(1); + expect(mutator.GET).toHaveBeenCalledTimes(1); + expect(mutator.GET).toHaveBeenCalledWith({ + params: { + limit: 0, + query: '(holdingsRecordId==1 or holdingsRecordId==2)', + }, + records: undefined, + }); + }); +}); + +describe('batchFetchItems', () => { + const mockMutator = { + GET: jest.fn(() => Promise.resolve()), + reset: jest.fn(), + }; + it('returns an empty array if holdings are empty', async () => { + const result = await batchFetchItems(mockMutator, []); + expect(result).toEqual([]); + }); +}); + +describe('batchFetchRequests', () => { + let mutator; + beforeEach(() => { + mutator = { + GET: jest.fn(), + reset: jest.fn(), + }; + }); + it('should return empty array if items array is empty', async () => { + const result = await batchFetchRequests(mutator, []); + expect(result).toEqual([]); + }); + it('should flatten the response arrays and return the concatenated records', async () => { + const records = [{ id: 1 }, { id: 2 }]; + mutator.GET.mockReturnValueOnce(Promise.resolve({ totalRecords: 2 })); + mutator.GET.mockReturnValueOnce(Promise.resolve(records)); + const items = [{ id: 1 }, { id: 2 }]; + const result = await batchFetchRequests(mutator, items); + expect(result).toEqual(records); + }); +}); diff --git a/src/Item/CreateItem/CreateItem.test.js b/src/Item/CreateItem/CreateItem.test.js index 9f7d27d7a..8761679ce 100644 --- a/src/Item/CreateItem/CreateItem.test.js +++ b/src/Item/CreateItem/CreateItem.test.js @@ -1,7 +1,7 @@ import '../../../test/jest/__mock__'; import { MemoryRouter } from 'react-router-dom'; -import { render, screen } from '@testing-library/react'; +import { render, screen } from '@folio/jest-config-stripes/testing-library/react'; import { QueryClient, QueryClientProvider } from 'react-query'; import { instance } from '../../../test/fixtures/instance'; diff --git a/src/Item/DuplicateItem/DuplicateItem.js b/src/Item/DuplicateItem/DuplicateItem.js index 6634256a5..47f19556a 100644 --- a/src/Item/DuplicateItem/DuplicateItem.js +++ b/src/Item/DuplicateItem/DuplicateItem.js @@ -26,6 +26,10 @@ import { useItemMutation, } from '../hooks'; +import { itemStatusesMap } from '../../constants'; + +const OMITTED_INITIAL_FIELDS = ['id', 'hrid', 'barcode', 'lastCheckIn']; + const DuplicateItem = ({ referenceData, instanceId, @@ -42,9 +46,9 @@ const DuplicateItem = ({ const stripes = useStripes(); const initialValues = useMemo(() => { - const duplicatedItem = omit(item, ['id', 'hrid', 'barcode']); + const duplicatedItem = omit(item, OMITTED_INITIAL_FIELDS); - duplicatedItem.status = { name: 'Available' }; + duplicatedItem.status = { name: itemStatusesMap.AVAILABLE }; return duplicatedItem; }, [item]); diff --git a/src/Item/DuplicateItem/DuplicateItem.test.js b/src/Item/DuplicateItem/DuplicateItem.test.js index d9ace5ac2..d25d821b1 100644 --- a/src/Item/DuplicateItem/DuplicateItem.test.js +++ b/src/Item/DuplicateItem/DuplicateItem.test.js @@ -1,7 +1,7 @@ import '../../../test/jest/__mock__'; import { MemoryRouter } from 'react-router-dom'; -import { render, screen } from '@testing-library/react'; +import { render, screen } from '@folio/jest-config-stripes/testing-library/react'; import { QueryClient, QueryClientProvider } from 'react-query'; import { instance } from '../../../test/fixtures/instance'; diff --git a/src/Item/EditItem/EditItem.test.js b/src/Item/EditItem/EditItem.test.js index 68ee3a9c3..0b1320585 100644 --- a/src/Item/EditItem/EditItem.test.js +++ b/src/Item/EditItem/EditItem.test.js @@ -1,7 +1,7 @@ import '../../../test/jest/__mock__'; import { MemoryRouter } from 'react-router-dom'; -import { render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@folio/jest-config-stripes/testing-library/react'; import { QueryClient, QueryClientProvider } from 'react-query'; import { instance } from '../../../test/fixtures/instance'; diff --git a/src/Item/ViewItem/ItemAcquisition/ItemAcquisition.test.js b/src/Item/ViewItem/ItemAcquisition/ItemAcquisition.test.js index bea654cea..bc1cdf87c 100644 --- a/src/Item/ViewItem/ItemAcquisition/ItemAcquisition.test.js +++ b/src/Item/ViewItem/ItemAcquisition/ItemAcquisition.test.js @@ -1,7 +1,7 @@ import React from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; -import { screen } from '@testing-library/react'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; import '../../../../test/jest/__mock__'; import renderWithIntl from '../../../../test/jest/helpers/renderWithIntl'; diff --git a/src/Item/ViewItem/ItemAcquisition/useItemAcquisition.test.js b/src/Item/ViewItem/ItemAcquisition/useItemAcquisition.test.js index 1363afb8b..ea1901880 100644 --- a/src/Item/ViewItem/ItemAcquisition/useItemAcquisition.test.js +++ b/src/Item/ViewItem/ItemAcquisition/useItemAcquisition.test.js @@ -1,6 +1,6 @@ import React from 'react'; import { QueryClient, QueryClientProvider } from 'react-query'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@folio/jest-config-stripes/testing-library/react-hooks'; import '../../../../test/jest/__mock__'; diff --git a/src/Item/hooks/useBoundWithsMutation/useBoundWithsMutation.test.js b/src/Item/hooks/useBoundWithsMutation/useBoundWithsMutation.test.js index b0ddcea49..5b24e22a9 100644 --- a/src/Item/hooks/useBoundWithsMutation/useBoundWithsMutation.test.js +++ b/src/Item/hooks/useBoundWithsMutation/useBoundWithsMutation.test.js @@ -2,7 +2,7 @@ import '../../../../test/jest/__mock__'; import React from 'react'; import { QueryClient, QueryClientProvider } from 'react-query'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@folio/jest-config-stripes/testing-library/react-hooks'; import { useOkapiKy } from '@folio/stripes/core'; diff --git a/src/Item/hooks/useItem/useItem.test.js b/src/Item/hooks/useItem/useItem.test.js index 9a25a6b17..e42c9f8ea 100644 --- a/src/Item/hooks/useItem/useItem.test.js +++ b/src/Item/hooks/useItem/useItem.test.js @@ -2,7 +2,7 @@ import '../../../../test/jest/__mock__'; import React from 'react'; import { QueryClient, QueryClientProvider } from 'react-query'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@folio/jest-config-stripes/testing-library/react-hooks'; import { useOkapiKy } from '@folio/stripes/core'; diff --git a/src/Item/hooks/useItemMutation/useItemMutation.test.js b/src/Item/hooks/useItemMutation/useItemMutation.test.js index 0fd7b2226..5ea7fb8d7 100644 --- a/src/Item/hooks/useItemMutation/useItemMutation.test.js +++ b/src/Item/hooks/useItemMutation/useItemMutation.test.js @@ -2,7 +2,7 @@ import '../../../../test/jest/__mock__'; import React from 'react'; import { QueryClient, QueryClientProvider } from 'react-query'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@folio/jest-config-stripes/testing-library/react-hooks'; import { useOkapiKy } from '@folio/stripes/core'; diff --git a/src/RemoteStorageService/Check.test.js b/src/RemoteStorageService/Check.test.js new file mode 100644 index 000000000..3ae6ae524 --- /dev/null +++ b/src/RemoteStorageService/Check.test.js @@ -0,0 +1,57 @@ +import { renderHook } from '@folio/jest-config-stripes/testing-library/react-hooks'; +import '../../test/jest/__mock__'; +import { useHoldings } from '../providers'; +import { useByLocation, useByHoldings } from './Check'; + +jest.mock('../providers', () => ({ + useHoldings: jest.fn(), +})); + +describe('useByLocation', () => { + it('return true if fromLocationId is in remoteMap and toLocationId is not', () => { + const { result } = renderHook(() => useByLocation()); + expect(result.current({ fromLocationId: 'holdings-id-1', toLocationId: 'holdings-id-3' })).toBe(true); + }); + it('return false if fromLocationId is not in remoteMap', () => { + const { result } = renderHook(() => useByLocation()); + expect(result.current({ fromLocationId: 'holdings-id-4', toLocationId: 'holdings-id-2' })).toBe(false); + }); + it('return false if toLocationId is in remoteMap', () => { + const { result } = renderHook(() => useByLocation()); + expect(result.current({ fromLocationId: 'holdings-id-1', toLocationId: 'holdings-id-2' })).toBe(false); + }); +}); + +describe('useByHoldings', () => { + beforeEach(() => { + useHoldings.mockReturnValue({ holdingsById: { + 'holdings-id-1': { permanentLocationId: 'holdings-id-1' }, + 'holdings-id-2': { permanentLocationId: 'holdings-id-2' }, + } }); + }); + afterEach(() => { + jest.clearAllMocks(); + }); + it('return true if from location is in remote storage and to location is not', () => { + const { result } = renderHook(() => useByHoldings()); + expect(result.current({ fromHoldingsId: 'holdings-id-1', toHoldingsId: 'holdings-id-3' })).toBe(true); + }); + it('return false if holdingsById is undefined', () => { + useHoldings.mockReturnValueOnce({ holdingsById: undefined }); + const { result } = renderHook(() => useByHoldings()); + expect(result.current({ fromHoldingsId: 'holdings-id-1', toHoldingsId: 'holdings-id-2' })).toBe(false); + }); + it('return false if from location is not in remote storage', () => { + const { result } = renderHook(() => useByHoldings()); + expect(result.current({ fromHoldingsId: 'holdings-id-3', toHoldingsId: 'holdings-id-2' })).toBe(false); + }); + it('return false if to location is in remote storage', () => { + useHoldings.mockReturnValueOnce({ holdingsById: { + 'holdings-id-1': { permanentLocationId: 'holdings-id-1' }, + 'holdings-id-2': { permanentLocationId: 'holdings-id-2' }, + } }); + const { result } = renderHook(() => useByHoldings()); + expect(result.current({ fromHoldingsId: 'holdings-id-1', toHoldingsId: 'holdings-id-2' })).toBe(false); + }); +}); + diff --git a/src/ViewHoldingsRecord.test.js b/src/ViewHoldingsRecord.test.js index b17c450ad..3f069ad10 100644 --- a/src/ViewHoldingsRecord.test.js +++ b/src/ViewHoldingsRecord.test.js @@ -1,25 +1,27 @@ import '../test/jest/__mock__'; - +import React from 'react'; import { QueryClient, QueryClientProvider } from 'react-query'; import { MemoryRouter } from 'react-router-dom'; -import user from '@testing-library/user-event'; -import { screen, waitFor } from '@testing-library/react'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; +import { screen, waitFor } from '@folio/jest-config-stripes/testing-library/react'; import { renderWithIntl, translationsProperties } from '../test/jest/helpers'; import ViewHoldingsRecord from './ViewHoldingsRecord'; + jest.mock('./withLocation', () => jest.fn(c => c)); -jest.mock('@folio/stripes/components', () => ({ - ...jest.requireActual('@folio/stripes/components'), - LoadingView: () => 'LoadingView', -})); + +const spyOncollapseAllSections = jest.spyOn(require('@folio/stripes/components'), 'collapseAllSections'); +const spyOnexpandAllSections = jest.spyOn(require('@folio/stripes/components'), 'expandAllSections'); + +const mockData = jest.fn().mockResolvedValue({ id: 'testId' }); const defaultProps = { id: 'id', goTo: jest.fn(), holdingsrecordid: 'holdingId', referenceTables: { - holdingsSources: [{ id: 'sourceId' }], + holdingsSources: [{ id: 'sourceId', name: 'MARC' }], locationsById: { inactiveLocation: { name: 'Location 1', isActive: false }, }, @@ -37,7 +39,7 @@ const defaultProps = { }, mutator: { instances1: { - GET: jest.fn(() => Promise.resolve({ id: 'instanceId' })), + GET: jest.fn(() => Promise.resolve({ id: 'instanceId', source: 'testSource' })), reset: jest.fn(() => Promise.resolve()), }, holdingsRecords: { @@ -48,7 +50,7 @@ const defaultProps = { reset: jest.fn(() => Promise.resolve()), }, marcRecord: { - GET: jest.fn(() => Promise.resolve({ id: 'marcRecordId' })), + GET: mockData, DELETE: jest.fn(() => Promise.resolve()), }, marcRecordId: { @@ -63,6 +65,7 @@ const defaultProps = { }, location: { search: '/', + pathname: 'pathname' }, }; @@ -82,53 +85,98 @@ const renderViewHoldingsRecord = (props = {}) => renderWithIntl( describe('ViewHoldingsRecord actions', () => { beforeEach(() => { - defaultProps.history.push.mockClear(); + jest.clearAllMocks(); }); it('should render Loading when awaiting resource', () => { const { getByText } = renderViewHoldingsRecord({ referenceTables: {} }); - expect(getByText('LoadingView')).toBeDefined(); }); it('should close view holding page', async () => { renderViewHoldingsRecord(); - - await waitFor(() => { - const closeBtn = screen.getAllByRole('button')[0]; - - user.click(closeBtn); - - expect(defaultProps.history.push).toHaveBeenCalled(); - }); + userEvent.click(await screen.findByRole('button', { name: 'confirm' })); + expect(defaultProps.history.push).toBeCalled(); }); it('should translate to edit holding form page', async () => { renderViewHoldingsRecord(); - const editHoldingBtn = await screen.findByTestId('edit-holding-btn'); - - user.click(editHoldingBtn); - + userEvent.click(editHoldingBtn); expect(defaultProps.history.push).toHaveBeenCalled(); }); it('should translate to duplicate holding form page', async () => { renderViewHoldingsRecord(); - const duplicatHoldingBtn = await screen.findByTestId('duplicate-holding-btn'); - - user.click(duplicatHoldingBtn); - + userEvent.click(duplicatHoldingBtn); expect(defaultProps.history.push).toHaveBeenCalled(); }); it('should display "inactive" by an inactive temporary location', async () => { renderViewHoldingsRecord(); - await waitFor(() => { const tempLocation = document.querySelector('*[data-test-id=temporary-location]').innerHTML; expect(tempLocation).toContain('Inactive'); }); }); + it('handleViewSource should be called when View Source button', async () => { + renderViewHoldingsRecord(); + userEvent.click(await screen.findByText('Actions')); + const viewSourceBtn = screen.getByRole('button', { name: 'View source' }); + await waitFor(() => { + expect(viewSourceBtn).not.toHaveAttribute('disabled'); + }); + userEvent.click(viewSourceBtn); + expect(defaultProps.goTo).toBeCalled(); + }); + it('"handleEditInQuickMarc" function should be called when "Edit in quickMARC" button', async () => { + renderViewHoldingsRecord(); + userEvent.click(await screen.findByText('Actions')); + const quickMarcbtn = screen.getByRole('button', { name: 'Edit in quickMARC' }); + await waitFor(() => { + expect(quickMarcbtn).not.toHaveAttribute('disabled'); + }); + userEvent.click(quickMarcbtn); + expect(defaultProps.goTo).toBeCalled(); + }); + describe('Tests for shortcut of HasCommand', () => { + it('"onCopyHolding" function to be triggered on clicking "duplicateRecord" button', async () => { + const data = { + pathname: `/inventory/copy/${defaultProps.id}/${defaultProps.holdingsrecordid}`, + search: defaultProps.location.search, + state: { backPathname: defaultProps.location.pathname }, + }; + renderViewHoldingsRecord(); + userEvent.click(await screen.findByRole('button', { name: 'duplicateRecord' })); + expect(defaultProps.history.push).toBeCalledWith(data); + }); + it('"onEditHolding" function to be triggered on clicking edit button', async () => { + const data = { + pathname: `/inventory/edit/${defaultProps.id}/${defaultProps.holdingsrecordid}`, + search: defaultProps.location.search, + state: { backPathname: defaultProps.location.pathname }, + }; + renderViewHoldingsRecord(); + userEvent.click(await screen.findByRole('button', { name: 'edit' })); + expect(defaultProps.history.push).toBeCalledWith(data); + }); + it('"goTo" function to be triggered on clicking duplicateRecord button', async () => { + renderViewHoldingsRecord(); + userEvent.click(await screen.findByRole('button', { name: 'search' })); + expect(defaultProps.goTo).toBeCalledWith('/inventory'); + }); + it('collapseAllSections triggered on clicking collapseAllSections button', async () => { + renderViewHoldingsRecord(); + userEvent.click(await screen.findByRole('button', { name: 'collapseAllSections' })); + expect(spyOncollapseAllSections).toBeCalled(); + }); + it('expandAllSections triggered on clicking expandAllSections button', async () => { + renderViewHoldingsRecord(); + userEvent.click(await screen.findByRole('button', { name: 'expandAllSections' })); + await waitFor(() => { + expect(spyOnexpandAllSections).toBeCalled(); + }); + }); + }); }); diff --git a/src/ViewInstance.js b/src/ViewInstance.js index 89a4d0ba7..689357922 100644 --- a/src/ViewInstance.js +++ b/src/ViewInstance.js @@ -58,6 +58,7 @@ import { import ImportRecordModal from './components/ImportRecordModal'; import NewInstanceRequestButton from './components/ViewInstance/MenuSection/NewInstanceRequestButton'; import RequestsReorderButton from './components/ViewInstance/MenuSection/RequestsReorderButton'; +import { IdReportGenerator } from './reports'; const quickMarcPages = { editInstance: 'edit-bib', @@ -124,6 +125,13 @@ class ViewInstance extends React.Component { accumulate: true, throwErrors: false, }, + quickExport: { + type: 'okapi', + fetch: false, + path: 'data-export/quick-export', + throwErrors: false, + clientGeneratePk: false, + }, instanceRelationshipTypes: { type: 'okapi', records: 'instanceRelationshipTypes', @@ -158,6 +166,7 @@ class ViewInstance extends React.Component { isCopyrightModalOpened: false, isNewOrderModalOpen: false, afterCreate: false, + instancesQuickExportInProgress: false, }; this.instanceId = null; this.cViewHoldingsRecord = this.props.stripes.connect(ViewHoldingsRecord); @@ -349,6 +358,34 @@ class ViewInstance extends React.Component { goTo(`${location.pathname.replace('/view/', '/viewsource/')}${location.search}`); }; + + triggerQuickExport = async () => { + const { instancesQuickExportInProgress } = this.state; + const { match } = this.props; + + if (instancesQuickExportInProgress) return; + + this.setState({ instancesQuickExportInProgress: true }); + + try { + const instanceIds = [match.params.id]; + + await this.props.mutator.quickExport.POST({ + uuids: instanceIds, + type: 'uuid', + recordType: 'INSTANCE' + }); + new IdReportGenerator('QuickInstanceExport').toCSV(instanceIds); + } catch (error) { + this.calloutRef.current.sendCallout({ + type: 'error', + message: , + }); + } finally { + this.setState({ instancesQuickExportInProgress: false }); + } + }; + handleImportRecordModalSubmit = (args) => { this.setState({ isImportRecordModalOpened: false }); this.props.mutator.query.update({ @@ -402,7 +439,7 @@ class ViewInstance extends React.Component { return null; }; - createActionMenuGetter = instance => ({ onToggle }) => { + createActionMenuGetter = (instance, data) => ({ onToggle }) => { const { onCopy, stripes, @@ -420,13 +457,44 @@ class ViewInstance extends React.Component { const isSourceMARC = get(instance, ['source'], '') === 'MARC'; const canEditInstance = stripes.hasPerm('ui-inventory.instance.edit'); const canCreateInstance = stripes.hasPerm('ui-inventory.instance.create'); + const canCreateRequest = stripes.hasPerm('ui-requests.create'); const canMoveItems = stripes.hasPerm('ui-inventory.item.move'); const canCreateMARCHoldings = stripes.hasPerm('ui-quick-marc.quick-marc-holdings-editor.create'); const canMoveHoldings = stripes.hasPerm('ui-inventory.holdings.move'); const canEditMARCRecord = stripes.hasPerm('ui-quick-marc.quick-marc-editor.all'); const canDeriveMARCRecord = stripes.hasPerm('ui-quick-marc.quick-marc-editor.duplicate'); - const hasReorderPermissions = stripes.hasPerm('ui-requests.create') || stripes.hasPerm('ui-requests.edit') || stripes.hasPerm('ui-requests.all'); + const hasReorderPermissions = canCreateRequest || stripes.hasPerm('ui-requests.edit') || stripes.hasPerm('ui-requests.all'); const canViewMARCSource = stripes.hasPerm('ui-quick-marc.quick-marc-editor.view'); + const canViewInstance = stripes.hasPerm('ui-inventory.instance.view'); + const canViewSource = canViewMARCSource && canViewInstance; + const canImport = stripes.hasInterface('copycat-imports') && stripes.hasPerm('copycat.profiles.collection.get'); + const canCreateOrder = stripes.hasInterface('orders') && stripes.hasPerm('ui-inventory.instance.createOrder'); + const canReorder = stripes.hasPerm('ui-requests.reorderQueue'); + const numberOfRequests = instanceRequests.other?.totalRecords; + const canReorderRequests = titleLevelRequestsFeatureEnabled && hasReorderPermissions && numberOfRequests && canReorder; + const canViewRequests = !titleLevelRequestsFeatureEnabled; + const canCreateNewRequest = titleLevelRequestsFeatureEnabled && canCreateRequest; + const identifier = this.getIdentifiers(data); + + const buildOnClickHandler = onClickHandler => { + return () => { + onToggle(); + + onClickHandler(this.context.sendCallout); + }; + }; + + const showInventoryMenuSection = ( + canEditInstance + || canViewSource + || (!openedFromBrowse && (canMoveItems || canMoveHoldings)) + || canImport + || canCreateInstance + || canCreateOrder + || canReorderRequests + || canViewRequests + || canCreateNewRequest + ); const showQuickMarcMenuSection = isSourceMARC && (canCreateMARCHoldings || canEditMARCRecord || canDeriveMARCRecord); @@ -434,88 +502,85 @@ class ViewInstance extends React.Component { return null; } + // the `identifier` is responsible for displaying the plugin `copyright-permissions-checker` + if (!showInventoryMenuSection && !showQuickMarcMenuSection && !identifier) { + return null; + } + return ( <> - - - - - - { - canViewMARCSource && ( - - - - ) - } - - { - !openedFromBrowse && ( - <> - { - canMoveItems && ( - - ) - } - - { - (canMoveItems || canMoveHoldings) && ( - - ) - } - - ) - } - - - + {showInventoryMenuSection && ( + + {canEditInstance && ( + + )} + {canViewSource && ( + + )} + { + !openedFromBrowse && ( + <> + { + canMoveItems && ( + + ) + } + { + (canMoveItems || canMoveHoldings) && ( + + ) + } + + ) + } + {canImport && ( - - - - + )} + {canCreateInstance && ( + + )} - - - - + {canCreateOrder && ( - - - - { - titleLevelRequestsFeatureEnabled - ? ( - - ) - : ( - - ) - } - - - + )} + { + titleLevelRequestsFeatureEnabled + ? ( + + ) + : ( + + ) + } + + + )} { showQuickMarcMenuSection && ( @@ -647,31 +715,27 @@ class ViewInstance extends React.Component { ) } - - {data => ( - ( - - )} - /> + ( + )} - + /> ); }; @@ -743,91 +807,93 @@ class ViewInstance extends React.Component { } return ( - <> - - - } - paneSubtitle={ - + {data => ( + + + } + paneSubtitle={ + + } + onClose={onClose} + actionMenu={this.createActionMenuGetter(instance, data)} + instance={instance} + tagsEnabled={tagsEnabled} + ref={this.accordionStatusRef} + > + { + (!holdingsrecordid && !itemid) ? + ( + + + + ) + : + null + } + + + + + {this.state.afterCreate && + } /> } - onClose={onClose} - actionMenu={this.createActionMenuGetter(instance)} - instance={instance} - tagsEnabled={tagsEnabled} - ref={this.accordionStatusRef} - > + { - (!holdingsrecordid && !itemid) ? - ( - - - - ) - : - null + this.state.findInstancePluginOpened && ( + + ) } - - + + + + + - {this.state.afterCreate && - } + - } - { - this.state.findInstancePluginOpened && ( - - ) - } - - - - - - - - - - - + + )} + ); } } @@ -859,6 +925,9 @@ ViewInstance.propTypes = { marcRecord: PropTypes.shape({ GET: PropTypes.func.isRequired, }), + quickExport: PropTypes.shape({ + POST: PropTypes.func.isRequired, + }), query: PropTypes.object.isRequired, movableItems: PropTypes.object.isRequired, instanceRequests: PropTypes.shape({ @@ -884,6 +953,7 @@ ViewInstance.propTypes = { }).isRequired, stripes: PropTypes.shape({ connect: PropTypes.func.isRequired, + hasInterface: PropTypes.func.isRequired, hasPerm: PropTypes.func.isRequired, locale: PropTypes.string.isRequired, logger: PropTypes.object.isRequired, diff --git a/src/ViewInstance.test.js b/src/ViewInstance.test.js index 27b89af18..d8adaf452 100644 --- a/src/ViewInstance.test.js +++ b/src/ViewInstance.test.js @@ -1,64 +1,165 @@ -import { screen } from '@testing-library/react'; -import { QueryClient, QueryClientProvider } from 'react-query'; -import { MemoryRouter } from 'react-router-dom'; - import '../test/jest/__mock__'; - +import React from 'react'; +import { screen, waitFor } from '@folio/jest-config-stripes/testing-library/react'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; +import { QueryClient, QueryClientProvider } from 'react-query'; +import { Router } from 'react-router-dom'; +import { createMemoryHistory } from 'history'; import { instances } from '../test/fixtures/instances'; -import { renderWithIntl, translationsProperties } from '../test/jest/helpers'; import { DataContext } from './contexts'; import StripesConnectedInstance from './ConnectedInstance/StripesConnectedInstance'; +import { renderWithIntl, translationsProperties } from '../test/jest/helpers'; import ViewInstance from './ViewInstance'; +const spyOncollapseAllSections = jest.spyOn(require('@folio/stripes/components'), 'collapseAllSections'); + +const spyOnexpandAllSections = jest.spyOn(require('@folio/stripes/components'), 'expandAllSections'); + +jest.mock('@folio/stripes-core', () => ({ + ...jest.requireActual('@folio/stripes-core'), + TitleManager: ({ children }) => <>{children} +})); + +jest.mock('./components/ImportRecordModal/ImportRecordModal', () => (props) => { + const { isOpen, handleSubmit, handleCancel } = props; + if (isOpen) { + const args = { + externalIdentifierType: 'typeTest', + externalIdentifier: 'externalIdentifier', + selectedJobProfileId: 'profileId' + }; + const container = +
+
ImportRecordModal
+ + +
; + return container; + } + return null; +}); + +jest.mock('./components/InstancePlugin/InstancePlugin', () => ({ onSelect, onClose }) => { + return ( +
+
InstancePlugin
+ + +
+ ); +}); + jest.mock('./RemoteStorageService/Check', () => ({ ...jest.requireActual('./RemoteStorageService/Check'), useByLocation: jest.fn(() => false), useByHoldings: jest.fn(() => false), })); +const location = { + pathname: '/testPathName', + search: '?filters=test1&query=test2&sort=test3&qindex=test', + hash: '' +}; +const mockPush = jest.fn(); +const mockReplace = jest.fn(); +const history = createMemoryHistory(); +history.location = location; +history.push = mockPush; +history.replace = mockReplace; const instance = { ...instances[0], + subjects:['Information society--Political aspects'], parentInstances: [], childInstances: [], }; jest .spyOn(StripesConnectedInstance.prototype, 'instance') - .mockImplementation(() => instance); + .mockImplementation(() => instance) + .mockImplementationOnce(() => {}); -const defaultProps = { +const goToMock = jest.fn(); +const mockReset = jest.fn(); +const updateMock = jest.fn(); +const mockonClose = jest.fn(); +const mockData = jest.fn().mockResolvedValue(true); +const defaultProp = { selectedInstance: instance, - paneWidth: '44%', - openedFromBrowse: false, - onCopy: jest.fn(), - onClose: jest.fn(), + goTo: goToMock, + match: { + path: '/inventory/view', + params: { + id: 'testId' + }, + }, + intl: { + formatMessage: jest.fn(), + }, mutator: { allInstanceItems: { - GET: jest.fn(() => Promise.resolve([])), - reset: jest.fn(), - }, - configs: { - GET: jest.fn(() => Promise.resolve([])), - reset: jest.fn(), + reset: mockReset }, holdings: { - POST: jest.fn(() => Promise.resolve({})), - reset: jest.fn(), - }, - instanceRequests: { - GET: jest.fn(() => Promise.resolve({})), - reset: jest.fn(), + POST: jest.fn(), }, marcRecord: { - GET: jest.fn(() => Promise.resolve([])), - reset: jest.fn(), + GET: mockData, + }, + quickExport:{ + POST: jest.fn(), + }, + query: { + update: updateMock, }, movableItems: { GET: jest.fn(() => Promise.resolve([])), reset: jest.fn(), }, - query: {}, + instanceRequests: { + GET: jest.fn(() => Promise.resolve([])), + reset: jest.fn() + } }, + onClose: mockonClose, + onCopy: jest.fn(), + openedFromBrowse: false, + paneWidth: '55%', + resources: { + allInstanceItems: { + reset: mockReset + }, + allInstanceHoldings: {}, + locations: {}, + configs: { + hasLoaded: true, + records: [ + { + value: 'testParse' + }, + ] + }, + instanceRequests: { + other: { + totalRecords: 10, + }, + records: [ + { + id: 'testIdRecord1' + } + ], + }, + }, + stripes: { + connect: jest.fn(), + hasInterface: jest.fn().mockReturnValue(true), + hasPerm: jest.fn().mockReturnValue(true), + locale: 'Testlocale', + logger: { + log: jest.fn() + }, + }, + tagsEnabled: true, + updateLocation: jest.fn(), }; const referenceData = { @@ -80,40 +181,198 @@ const queryClient = new QueryClient(); const renderViewInstance = (props = {}) => renderWithIntl( - - + , translationsProperties ); describe('ViewInstance', () => { beforeEach(() => { - // defaultProps.history.push.mockClear(); + jest.clearAllMocks(); + }); + it('should display action menu items', () => { + renderViewInstance(); + expect(screen.getByText('Move items within an instance')).toBeInTheDocument(); + expect(screen.getByText('Move holdings/items to another instance')).toBeInTheDocument(); + }); + it('should NOT display \'move\' action menu items when instance was opened from Browse page', () => { + renderViewInstance({ openedFromBrowse: true }); + expect(screen.queryByText('Move items within an instance')).not.toBeInTheDocument(); + expect(screen.queryByText('Move holdings/items to another instance')).not.toBeInTheDocument(); }); + describe('Action Menu', () => { + it('should not be displayed', () => { + renderViewInstance({ + stripes: { + ...defaultProp.stripes, + hasInterface: jest.fn().mockReturnValue(false), + hasPerm: jest.fn().mockReturnValue(false), + }, + resources: { + ...defaultProp.resources, + configs: { + ...defaultProp.resources.configs, + records: [{ value: JSON.stringify({ titleLevelRequestsFeatureEnabled: true }) }], + }, + }, + }); - describe('Action menu', () => { - it('should display action menu items', async () => { + expect(screen.queryByRole('button', { name: 'Actions' })).not.toBeInTheDocument(); + }); + it('"onClickEditInstance" should be called when the user clicks the "Edit instance" button', () => { renderViewInstance(); - - expect(screen.getByText('Move items within an instance')).toBeInTheDocument(); - expect(screen.getByText('Move holdings/items to another instance')).toBeInTheDocument(); + userEvent.click(screen.getByRole('button', { name: 'Actions' })); + userEvent.click(screen.getByRole('button', { name: 'Edit instance' })); + expect(mockPush).toBeCalled(); }); - - it('should NOT display \'move\' action menu items when instance was opened from Browse page', async () => { - renderViewInstance({ openedFromBrowse: true }); - - expect(screen.queryByText('Move items within an instance')).not.toBeInTheDocument(); - expect(screen.queryByText('Move holdings/items to another instance')).not.toBeInTheDocument(); + it('"onClickViewRequests" should be called when the user clicks the "View requests" button', () => { + renderViewInstance(); + userEvent.click(screen.getByRole('button', { name: 'Actions' })); + userEvent.click(screen.getByRole('button', { name: 'View requests' })); + expect(mockPush).toBeCalled(); + }); + it('"onCopy" function should be called when the user clicks the "Duplicate instance" button', () => { + renderViewInstance(); + userEvent.click(screen.getByRole('button', { name: 'Actions' })); + userEvent.click(screen.getByRole('button', { name: 'Duplicate instance' })); + expect(defaultProp.onCopy).toBeCalled(); + }); + it('"handleViewSource" should be called when the user clicks the "View source" button', async () => { + renderViewInstance(); + userEvent.click(screen.getByRole('button', { name: 'Actions' })); + const veiwSourceButton = screen.getByRole('button', { name: 'View source' }); + await waitFor(() => { + expect(veiwSourceButton).not.toHaveAttribute('disabled'); + }); + userEvent.click(veiwSourceButton); + expect(goToMock).toBeCalled(); + }); + it('"createHoldingsMarc" should be called when the user clicks the "Add MARC holdings record" button', () => { + renderViewInstance(); + userEvent.click(screen.getByRole('button', { name: 'Actions' })); + userEvent.click(screen.getByRole('button', { name: 'Add MARC holdings record' })); + expect(mockPush).toBeCalled(); + }); + it('"Move items within an instance" button to be clicked', () => { + renderViewInstance(); + userEvent.click(screen.getByRole('button', { name: 'Actions' })); + userEvent.click(screen.getByRole('button', { name: 'Move items within an instance' })); + expect(renderViewInstance()).toBeTruthy(); + }); + it('"Export instance (MARC)" button to be clicked', () => { + renderViewInstance(); + userEvent.click(screen.getByRole('button', { name: 'Actions' })); + userEvent.click(screen.getByRole('button', { name: 'Export instance (MARC)' })); + expect(renderViewInstance()).toBeTruthy(); + }); + it('"InstancePlugin" should render when user clicks "Move holdings/items to another instance" button', () => { + renderViewInstance(); + userEvent.click(screen.getByRole('button', { name: 'Actions' })); + userEvent.click(screen.getByRole('button', { name: 'Move holdings/items to another instance' })); + expect(screen.getByRole('button', { name: '+' })); + }); + it('"ImportRecordModal" component should render when user clicks "Overlay source bibliographic record" button', () => { + renderViewInstance(); + userEvent.click(screen.getByRole('button', { name: 'Actions' })); + userEvent.click(screen.getByRole('button', { name: 'Overlay source bibliographic record' })); + expect(screen.getByText('ImportRecordModal')).toBeInTheDocument(); + }); + it('"handleImportRecordModalSubmit" should be called when the user clicks the "handleSubmit" button', () => { + renderViewInstance(); + userEvent.click(screen.getByRole('button', { name: 'Actions' })); + userEvent.click(screen.getByRole('button', { name: 'Overlay source bibliographic record' })); + userEvent.click(screen.getByRole('button', { name: 'handleSubmit' })); + expect(updateMock).toBeCalled(); + }); + it('"ImportRecordModal" component should be closed when the user clicks "handleClose" button', () => { + renderViewInstance(); + userEvent.click(screen.getByRole('button', { name: 'Actions' })); + userEvent.click(screen.getByRole('button', { name: 'Overlay source bibliographic record' })); + userEvent.click(screen.getByRole('button', { name: 'handleCancel' })); + expect(screen.queryByText('ImportRecordModal')).not.toBeInTheDocument(); + }); + it('NewOrderModal should render when the user clicks the new order button', () => { + renderViewInstance(); + userEvent.click(screen.getByRole('button', { name: 'Actions' })); + userEvent.click(screen.getByRole('button', { name: 'New order' })); + expect(screen.queryByText(/Create order/i)).toBeInTheDocument(); + }); + it('push function should be called when the user clicks the "Edit MARC bibliographic record" button', async () => { + renderViewInstance(); + const expectedValue = { + pathname: `/inventory/quick-marc/edit-bib/${defaultProp.selectedInstance.id}`, + search: 'filters=test1&query=test2&sort=test3&qindex=test' + }; + userEvent.click(screen.getByRole('button', { name: 'Actions' })); + const button = screen.getByRole('button', { name: 'Edit MARC bibliographic record' }); + await waitFor(() => { + expect(button).not.toHaveAttribute('disabled'); + }); + userEvent.click(button); + expect(mockPush).toBeCalledWith(expectedValue); + }); + it('push function should be called when the user clicks the "Derive new MARC bibliographic record" button', async () => { + renderViewInstance(); + const expectedValue = { + pathname: `/inventory/quick-marc/duplicate-bib/${defaultProp.selectedInstance.id}`, + search: 'filters=test1&query=test2&sort=test3&qindex=test' + }; + userEvent.click(screen.getByRole('button', { name: 'Actions' })); + const button = screen.getByRole('button', { name: 'Derive new MARC bibliographic record' }); + await waitFor(() => { + expect(button).not.toHaveAttribute('disabled'); + }); + userEvent.click(button); + expect(mockPush).toBeCalledWith(expectedValue); + }); + it('NewOrderModal should be closed when the user clicks the close button', async () => { + renderViewInstance(); + userEvent.click(screen.getByRole('button', { name: 'Actions' })); + userEvent.click(screen.getByRole('button', { name: 'New order' })); + expect(screen.queryByText(/Create order/i)).toBeInTheDocument(); + userEvent.click(screen.getByRole('button', { name: 'Cancel' })); + await waitFor(() => { + expect(screen.queryByText(/Create order/i)).not.toBeInTheDocument(); + }); + }); + }); + describe('Tests for shortcut of HasCommand', () => { + it('updateLocation function to be triggered on clicking new button', () => { + renderViewInstance(); + userEvent.click(screen.getByRole('button', { name: 'new' })); + expect(defaultProp.updateLocation).toBeCalled(); + }); + it('onClickEditInstance function to be triggered on clicking edit button', () => { + renderViewInstance(); + userEvent.click(screen.getByRole('button', { name: 'edit' })); + expect(mockPush).toBeCalled(); + }); + it('onCopy function to be triggered on clicking duplicateRecord button', () => { + renderViewInstance(); + userEvent.click(screen.getByRole('button', { name: 'duplicateRecord' })); + expect(defaultProp.onCopy).toBeCalled(); + }); + it('collapseAllSections triggered on clicking collapseAllSections button', () => { + renderViewInstance(); + userEvent.click(screen.getByRole('button', { name: 'collapseAllSections' })); + expect(spyOncollapseAllSections).toBeCalled(); + }); + it('expandAllSections triggered on clicking expandAllSections button', () => { + renderViewInstance(); + userEvent.click(screen.getByRole('button', { name: 'expandAllSections' })); + expect(spyOnexpandAllSections).toBeCalled(); }); }); }); diff --git a/src/common/hooks/index.js b/src/common/hooks/index.js index 2a1564652..4b6765dd1 100644 --- a/src/common/hooks/index.js +++ b/src/common/hooks/index.js @@ -4,6 +4,9 @@ export { default as useGoBack } from './useGoBack'; export { default as useFacets } from './useFacets'; export { default as useHolding } from './useHolding'; export { default as useInstanceQuery } from './useInstanceQuery'; +export { default as useDefaultJobProfile } from './useDefaultJobProfile'; +export { default as useAllowedJobProfiles } from './useAllowedJobProfiles'; + export * from './useConfirmationModal'; export * from './useMoveItemsMutation'; export default {}; diff --git a/src/common/hooks/useAllowedJobProfiles.js b/src/common/hooks/useAllowedJobProfiles.js new file mode 100644 index 000000000..b2764a4dc --- /dev/null +++ b/src/common/hooks/useAllowedJobProfiles.js @@ -0,0 +1,36 @@ +import { useQuery } from 'react-query'; +import { isEmpty } from 'lodash'; + +import { + useOkapiKy, + useNamespace, +} from '@folio/stripes/core'; + +import { + DATA_IMPORT_JOB_PROFILES_ROUTE, + LIMIT_MAX, +} from '../../constants'; + +const useAllowedJobProfiles = (allowedJobProfileIds) => { + const ky = useOkapiKy(); + const [namespace] = useNamespace({ key: 'allowedJobProfiles' }); + + const DATA_TYPE_MARC_QUERY = 'dataType==("MARC")'; + + const ids = allowedJobProfileIds.join(' or '); + const allowedIdsQuery = `id==(${ids})`; + const path = `${DATA_IMPORT_JOB_PROFILES_ROUTE}?limit=${LIMIT_MAX}&query=${DATA_TYPE_MARC_QUERY} and ${allowedIdsQuery} sortBy name`; + + const { isLoading, data: allowedJobProfiles = {} } = useQuery({ + queryKey: [namespace, allowedJobProfileIds], + queryFn: () => ky.get(path).json(), + enabled: !isEmpty(allowedJobProfileIds), + }); + + return ({ + isLoading, + allowedJobProfiles: allowedJobProfiles.jobProfiles, + }); +}; + +export default useAllowedJobProfiles; diff --git a/src/common/hooks/useAllowedJobProfiles.test.js b/src/common/hooks/useAllowedJobProfiles.test.js new file mode 100644 index 000000000..95a2aead3 --- /dev/null +++ b/src/common/hooks/useAllowedJobProfiles.test.js @@ -0,0 +1,42 @@ +import React from 'react'; +import { + QueryClient, + QueryClientProvider, +} from 'react-query'; +import { renderHook } from '@folio/jest-config-stripes/testing-library/react-hooks'; + +import '../../../test/jest/__mock__'; +import { useOkapiKy } from '@folio/stripes/core'; + +import useAllowedJobProfiles from './useAllowedJobProfiles'; + +const queryClient = new QueryClient(); + +// eslint-disable-next-line react/prop-types +const wrapper = ({ children }) => ( + + {children} + +); + +const allowedJobProfileIds = ['testId']; + +describe('useAllowedJobProfiles', () => { + it('should fetch default job profile', async () => { + useOkapiKy.mockClear().mockReturnValue({ + get: () => ({ + json: () => ({ + jobProfiles: allowedJobProfileIds, + }), + }), + }); + + const { result, waitFor } = renderHook(() => useAllowedJobProfiles(allowedJobProfileIds), { wrapper }); + + await waitFor(() => { + return !result.current.isLoading; + }); + + expect(result.current.allowedJobProfiles).toBe(allowedJobProfileIds); + }); +}); diff --git a/src/common/hooks/useConfirmationModal.test.js b/src/common/hooks/useConfirmationModal.test.js index 501d37475..6ca14282c 100644 --- a/src/common/hooks/useConfirmationModal.test.js +++ b/src/common/hooks/useConfirmationModal.test.js @@ -1,6 +1,6 @@ import React from 'react'; -import { waitFor } from '@testing-library/react'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { waitFor } from '@folio/jest-config-stripes/testing-library/react'; +import { act, renderHook } from '@folio/jest-config-stripes/testing-library/react-hooks'; import { useConfirmationModal } from './useConfirmationModal'; describe('useConfirmationModal', () => { diff --git a/src/common/hooks/useControlledAccordion/useControlledAccordion.test.js b/src/common/hooks/useControlledAccordion/useControlledAccordion.test.js index f4d990c55..1c9759cf1 100644 --- a/src/common/hooks/useControlledAccordion/useControlledAccordion.test.js +++ b/src/common/hooks/useControlledAccordion/useControlledAccordion.test.js @@ -1,4 +1,4 @@ -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, renderHook } from '@folio/jest-config-stripes/testing-library/react-hooks'; import useControlledAccordion from './useControlledAccordion'; diff --git a/src/common/hooks/useDefaultJobProfile.js b/src/common/hooks/useDefaultJobProfile.js new file mode 100644 index 000000000..e485ee353 --- /dev/null +++ b/src/common/hooks/useDefaultJobProfile.js @@ -0,0 +1,28 @@ +import { useQuery } from 'react-query'; + +import { + useOkapiKy, + useNamespace, +} from '@folio/stripes/core'; + +import { DATA_IMPORT_JOB_PROFILES_ROUTE } from '../../constants'; + +const useDefaultJobProfile = (jobProfileId) => { + const ky = useOkapiKy(); + const [namespace] = useNamespace({ key: 'defaultJobProfileId' }); + + const path = `${DATA_IMPORT_JOB_PROFILES_ROUTE}/${jobProfileId}`; + + const { isLoading, data: defaultJobProfile = {} } = useQuery({ + queryKey: [namespace, jobProfileId], + queryFn: () => ky.get(path).json(), + enabled: !!jobProfileId, + }); + + return ({ + isLoading, + defaultJobProfile, + }); +}; + +export default useDefaultJobProfile; diff --git a/src/common/hooks/useDefaultJobProfile.test.js b/src/common/hooks/useDefaultJobProfile.test.js new file mode 100644 index 000000000..2fd93b991 --- /dev/null +++ b/src/common/hooks/useDefaultJobProfile.test.js @@ -0,0 +1,42 @@ +import React from 'react'; +import { + QueryClient, + QueryClientProvider, +} from 'react-query'; +import { renderHook } from '@folio/jest-config-stripes/testing-library/react-hooks'; + +import '../../../test/jest/__mock__'; +import { useOkapiKy } from '@folio/stripes/core'; + +import useDefaultJobProfile from './useDefaultJobProfile'; + +const queryClient = new QueryClient(); + +// eslint-disable-next-line react/prop-types +const wrapper = ({ children }) => ( + + {children} + +); + +const jobProfileId = 'jobProfileId'; + +describe('useDefaultJobProfile', () => { + it('should fetch default job profile', async () => { + useOkapiKy.mockClear().mockReturnValue({ + get: () => ({ + json: () => ({ + id: jobProfileId, + }), + }), + }); + + const { result, waitFor } = renderHook(() => useDefaultJobProfile(jobProfileId), { wrapper }); + + await waitFor(() => { + return !result.current.isLoading; + }); + + expect(result.current.defaultJobProfile.id).toBe(jobProfileId); + }); +}); diff --git a/src/common/hooks/useFacets.js b/src/common/hooks/useFacets.js index 80dcc35f2..916259359 100644 --- a/src/common/hooks/useFacets.js +++ b/src/common/hooks/useFacets.js @@ -192,7 +192,7 @@ const useFacets = ( const isNoFilterSelected = _.every(accordionsData, value => !value?.isSelected); if (!query && prevQuery.current && isNoFilterSelected) return; - handleFetchFacets({ facetToOpen: facetNameToOpen }); + handleFetchFacets({ focusedFacet: facetNameToOpen }); } }, [accordionsData]); diff --git a/src/common/hooks/useFacets.test.js b/src/common/hooks/useFacets.test.js new file mode 100644 index 000000000..fbfab6ac8 --- /dev/null +++ b/src/common/hooks/useFacets.test.js @@ -0,0 +1,312 @@ +import { renderHook, act } from '@folio/jest-config-stripes/testing-library/react-hooks'; +import { useLocation } from 'react-router-dom'; +import React from 'react'; +import { useFacetSettings } from '../../stores/facetsStore'; +import useFacets from './useFacets'; + +jest.mock('react-router-dom', () => ({ + useLocation: jest.fn(), +})); + +jest.mock('../../stores/facetsStore', () => ({ + useFacetSettings: jest + .fn() + .mockReturnValue([{ foo: { value: 'bar' }, quux: { value: 'changed' } }]), +})); + +describe('useFacets', () => { + const segmentAccordions = { + test: true, + foo: true, + baz: false, + }; + const segmentOptions = { baz: ['qux'], quux: ['corge'] }; + const selectedFacetFilters = { + selectedFilters: { foo: { value: 'bar' } }, + facetName: 'test', + }; + const getNewRecords = jest.fn(() => { + return { quux: ['corge', 'grault'], garply: ['waldo'] }; + }); + + const data = { + query: { query: null, filters: 'filters' }, + onFetchFacets: jest.fn(), + parentResources: { + facets: { records: [[{ name: 'baz' }]], isPending: true }, + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + useLocation.mockReturnValue({ pathname: '/path' }); + useFacetSettings.mockReturnValue([selectedFacetFilters, jest.fn()]); + }); + + it('returns initial state', () => { + const { result } = renderHook(() => useFacets( + segmentAccordions, + segmentOptions, + selectedFacetFilters, + getNewRecords, + data + )); + expect(result.current[0]).toEqual(segmentAccordions); + }); + + it('updates accordions state on new toggle', () => { + const { result } = renderHook(() => useFacets( + segmentAccordions, + segmentOptions, + selectedFacetFilters, + getNewRecords, + data + )); + + act(() => { + result.current[1]({ id: 'foo' }); + }); + expect(result.current[0]).toEqual({ + ...segmentAccordions, + foo: false, + }); + }); + + it('updates accordions state on open toggle', () => { + const { result } = renderHook(() => useFacets( + segmentAccordions, + segmentOptions, + selectedFacetFilters, + getNewRecords, + data + )); + + act(() => { + result.current[2]({ + onMoreClickedFacet: true, + focusedFacet: '', + facetToOpen: true, + dateFacet: true, + }); + }); + expect(result.current[0]).toEqual({ + ...segmentAccordions, + foo: true, + }); + }); + + it('updates accordions state on segment toggle', () => { + const { result } = renderHook(() => useFacets( + segmentAccordions, + segmentOptions, + {}, + getNewRecords, + data + )); + + act(() => { + result.current[2]({ + onMoreClickedFacet: true, + focusedFacet: '', + facetToOpen: true, + dateFacet: true, + }); + }); + expect(result.current[0]).toEqual({ + ...segmentAccordions, + foo: true, + }); + }); + + it('updates accordions state on records toggle', () => { + const { result } = renderHook(() => useFacets( + segmentAccordions, + segmentOptions, + selectedFacetFilters, + getNewRecords, + data + )); + + act(() => { + result.current[2]({ + onMoreClickedFacet: true, + focusedFacet: '', + facetToOpen: false, + dateFacet: true, + }); + }); + expect(result.current[0]).toEqual({ + ...segmentAccordions, + foo: true, + }); + }); + + it('updates accordions state on facet toggle', () => { + const { result } = renderHook(() => useFacets( + segmentAccordions, + segmentOptions, + selectedFacetFilters, + getNewRecords, + data + )); + + act(() => { + result.current[2]({ + onMoreClickedFacet: false, + focusedFacet: true, + facetToOpen: false, + dateFacet: true, + }); + }); + expect(result.current[0]).toEqual({ + ...segmentAccordions, + foo: true, + }); + }); + + it('updates accordions state on date toggle', () => { + const { result } = renderHook(() => useFacets( + segmentAccordions, + segmentOptions, + selectedFacetFilters, + getNewRecords, + data + )); + + act(() => { + result.current[2]({ + onMoreClickedFacet: false, + focusedFacet: true, + facetToOpen: false, + dateFacet: false, + }); + }); + expect(result.current[0]).toEqual({ + ...segmentAccordions, + foo: true, + }); + }); + + it('updates facet settings on data filter search', () => { + const spy = jest.spyOn(React, 'useRef'); + spy.mockImplementation(() => { + return { current: {}, ...segmentAccordions }; + }); + const { result } = renderHook(() => useFacets( + segmentAccordions, + segmentOptions, + selectedFacetFilters, + getNewRecords, + data + )); + + act(() => { + result.current[3]({ name: 'quux', value: 'changed' }); + }); + expect(result.current[0]).toEqual({ + ...segmentAccordions, + foo: true, + }); + }); + + it('updates facet settings on accordions filter search', () => { + const spy = jest.spyOn(React, 'useRef'); + spy.mockImplementation(() => { + return { current: { test: ['ere'] }, ...segmentAccordions }; + }); + const { result } = renderHook(() => useFacets( + segmentAccordions, + segmentOptions, + { selectedFilters: undefined, facetName: 'test' }, + getNewRecords, + data + )); + act(() => { + result.current[3]({ name: 'quux', value: 'changed' }); + }); + expect(result.current[0]).toEqual({ + ...segmentAccordions, + foo: true, + }); + }); + it('updates facet settings on selected filter search', () => { + jest.spyOn(React, 'useRef').mockReturnValueOnce({ + current: {}, + }); + const { result } = renderHook(() => useFacets( + segmentAccordions, + segmentOptions, + { + selectedFilters: undefined, + facetName: 'test', + }, + getNewRecords, + data + )); + + act(() => { + result.current[5]('test'); + }); + expect(result.current[0]).toEqual({ + ...segmentAccordions, + foo: true, + }); + }); + it('updates facet settings on facet filter search', () => { + const spy = jest.spyOn(React, 'useRef'); + spy.mockImplementation(() => { + return { + current: { + test: ['ere'], + prevFacetValue: 'test', + selectedFilters: ['hggh'], + facetName: 'test', + }, + ...segmentAccordions, + }; + }); + const { result } = renderHook(() => useFacets( + segmentAccordions, + segmentOptions, + { + selectedFilters: undefined, + facetName: 'test', + }, + getNewRecords, + data + )); + + act(() => { + result.current[5]('test'); + }); + expect(result.current[0]).toEqual({ + ...segmentAccordions, + foo: true, + }); + }); + it('updates facet settings on ref filter search', () => { + const spy = jest.spyOn(React, 'useRef'); + spy.mockImplementation(() => { + return { + current: { test: ['ere'], prevFacetValue: 'test', facetName: 'test' }, + ...segmentAccordions, + }; + }); + const { result } = renderHook(() => useFacets( + { foo: false }, + segmentOptions, + { + selectedFilters: undefined, + facetName: 'test', + }, + getNewRecords, + data + )); + act(() => { + result.current[5]('test'); + }); + expect(result.current[0]).toEqual({ + foo: false, + }); + }); +}); diff --git a/src/common/hooks/useGoBack.test.js b/src/common/hooks/useGoBack.test.js index 69639eb94..45672584b 100644 --- a/src/common/hooks/useGoBack.test.js +++ b/src/common/hooks/useGoBack.test.js @@ -1,4 +1,4 @@ -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook, act } from '@folio/jest-config-stripes/testing-library/react-hooks'; import { createMemoryHistory } from 'history'; import { Router } from 'react-router-dom'; import useGoBack from './useGoBack'; diff --git a/src/common/hooks/useHolding/useHolding.test.js b/src/common/hooks/useHolding/useHolding.test.js index a543af849..4b627002a 100644 --- a/src/common/hooks/useHolding/useHolding.test.js +++ b/src/common/hooks/useHolding/useHolding.test.js @@ -2,7 +2,7 @@ import '../../../../test/jest/__mock__'; import React from 'react'; import { QueryClient, QueryClientProvider } from 'react-query'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@folio/jest-config-stripes/testing-library/react-hooks'; import { useOkapiKy } from '@folio/stripes/core'; diff --git a/src/common/hooks/useInstance.test.js b/src/common/hooks/useInstance.test.js index ebfc1e2a4..28539398d 100644 --- a/src/common/hooks/useInstance.test.js +++ b/src/common/hooks/useInstance.test.js @@ -1,4 +1,4 @@ -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook, act } from '@folio/jest-config-stripes/testing-library/react-hooks'; import useInstance from './useInstance'; describe('useInstance', () => { diff --git a/src/common/hooks/useInstanceQuery/useInstanceQuery.test.js b/src/common/hooks/useInstanceQuery/useInstanceQuery.test.js index 65ebaf42b..d31162211 100644 --- a/src/common/hooks/useInstanceQuery/useInstanceQuery.test.js +++ b/src/common/hooks/useInstanceQuery/useInstanceQuery.test.js @@ -2,7 +2,7 @@ import '../../../../test/jest/__mock__'; import React from 'react'; import { QueryClient, QueryClientProvider } from 'react-query'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@folio/jest-config-stripes/testing-library/react-hooks'; import { useOkapiKy } from '@folio/stripes/core'; diff --git a/src/components/BrowseInventoryFilters/BrowseInventoryFilters.test.js b/src/components/BrowseInventoryFilters/BrowseInventoryFilters.test.js index 91399d73b..b938aebc4 100644 --- a/src/components/BrowseInventoryFilters/BrowseInventoryFilters.test.js +++ b/src/components/BrowseInventoryFilters/BrowseInventoryFilters.test.js @@ -1,5 +1,5 @@ -import userEvent from '@testing-library/user-event'; -import { act, screen } from '@testing-library/react'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; +import { act, screen } from '@folio/jest-config-stripes/testing-library/react'; import '__mock__'; diff --git a/src/components/BrowseResultsList/BrowseResultsList.js b/src/components/BrowseResultsList/BrowseResultsList.js index 08640c719..9a3f068d5 100644 --- a/src/components/BrowseResultsList/BrowseResultsList.js +++ b/src/components/BrowseResultsList/BrowseResultsList.js @@ -100,7 +100,7 @@ const BrowseResultsList = ({ }; BrowseResultsList.propTypes = { - browseData: PropTypes.arrayOf(PropTypes.object).isRequired, + browseData: PropTypes.arrayOf(PropTypes.object), isEmptyMessage: PropTypes.node.isRequired, isLoading: PropTypes.bool, pagination: PropTypes.shape({ diff --git a/src/components/BrowseResultsList/BrowseResultsList.test.js b/src/components/BrowseResultsList/BrowseResultsList.test.js index 0eac1f1b9..ee578c42e 100644 --- a/src/components/BrowseResultsList/BrowseResultsList.test.js +++ b/src/components/BrowseResultsList/BrowseResultsList.test.js @@ -1,9 +1,9 @@ import '../../../test/jest/__mock__'; -import userEvent from '@testing-library/user-event'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; import flow from 'lodash/flow'; import queryString from 'query-string'; -import { act, cleanup, screen } from '@testing-library/react'; +import { act, cleanup, screen } from '@folio/jest-config-stripes/testing-library/react'; import { createMemoryHistory } from 'history'; import { Router } from 'react-router-dom'; diff --git a/src/components/BrowseResultsList/constants.js b/src/components/BrowseResultsList/constants.js index eb762f1f0..8a1b5517d 100644 --- a/src/components/BrowseResultsList/constants.js +++ b/src/components/BrowseResultsList/constants.js @@ -4,6 +4,12 @@ import { browseModeOptions } from '../../constants'; export const VISIBLE_COLUMNS_MAP = { [browseModeOptions.SUBJECTS]: ['subject', 'numberOfTitles'], [browseModeOptions.CALL_NUMBERS]: ['callNumber', 'title', 'numberOfTitles'], + [browseModeOptions.DEWEY]: ['callNumber', 'title', 'numberOfTitles'], + [browseModeOptions.LIBRARY_OF_CONGRESS]: ['callNumber', 'title', 'numberOfTitles'], + [browseModeOptions.LOCAL]: ['callNumber', 'title', 'numberOfTitles'], + [browseModeOptions.NATIONAL_LIBRARY_OF_MEDICINE]: ['callNumber', 'title', 'numberOfTitles'], + [browseModeOptions.OTHER]: ['callNumber', 'title', 'numberOfTitles'], + [browseModeOptions.SUPERINTENDENT]: ['callNumber', 'title', 'numberOfTitles'], [browseModeOptions.CONTRIBUTORS]: ['contributor', 'contributorType', 'relatorTerm', 'numberOfTitles'], }; diff --git a/src/components/BrowseResultsList/getBrowseResultsFormatter.test.js b/src/components/BrowseResultsList/getBrowseResultsFormatter.test.js index c53283a22..81db14d0f 100644 --- a/src/components/BrowseResultsList/getBrowseResultsFormatter.test.js +++ b/src/components/BrowseResultsList/getBrowseResultsFormatter.test.js @@ -1,7 +1,7 @@ import '../../../test/jest/__mock__'; -import userEvent from '@testing-library/user-event'; -import { act, screen } from '@testing-library/react'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; +import { act, screen } from '@folio/jest-config-stripes/testing-library/react'; import { createMemoryHistory } from 'history'; import { Router } from 'react-router-dom'; diff --git a/src/components/BrowseResultsList/utils.js b/src/components/BrowseResultsList/utils.js index dbb8a5013..ab55c744d 100644 --- a/src/components/BrowseResultsList/utils.js +++ b/src/components/BrowseResultsList/utils.js @@ -20,6 +20,30 @@ export const getSearchParams = (row, qindex) => { qindex: queryIndexes.CALL_NUMBER, query: row.shelfKey, }, + [browseModeOptions.DEWEY]: { + qindex: queryIndexes.CALL_NUMBER, + query: row.shelfKey, + }, + [browseModeOptions.LIBRARY_OF_CONGRESS]: { + qindex: queryIndexes.CALL_NUMBER, + query: row.shelfKey, + }, + [browseModeOptions.LOCAL]: { + qindex: queryIndexes.CALL_NUMBER, + query: row.shelfKey, + }, + [browseModeOptions.NATIONAL_LIBRARY_OF_MEDICINE]: { + qindex: queryIndexes.CALL_NUMBER, + query: row.shelfKey, + }, + [browseModeOptions.OTHER]: { + qindex: queryIndexes.CALL_NUMBER, + query: row.shelfKey, + }, + [browseModeOptions.SUPERINTENDENT]: { + qindex: queryIndexes.CALL_NUMBER, + query: row.shelfKey, + }, [browseModeOptions.CONTRIBUTORS]: { qindex: queryIndexes.CONTRIBUTOR, query: row.name, @@ -28,7 +52,7 @@ export const getSearchParams = (row, qindex) => { [browseModeOptions.SUBJECTS]: { qindex: queryIndexes.SUBJECT, query: row.value, - authorityId: row.authorityId, + ...(row.authorityId && { filters: `${FACETS.AUTHORITY_ID}.${row.authorityId}` }), }, }; diff --git a/src/components/BrowseResultsPane/BrowseResultsPane.js b/src/components/BrowseResultsPane/BrowseResultsPane.js index abdc888a7..3c93d14b3 100644 --- a/src/components/BrowseResultsPane/BrowseResultsPane.js +++ b/src/components/BrowseResultsPane/BrowseResultsPane.js @@ -97,7 +97,7 @@ const BrowseResultsPane = ({ }; BrowseResultsPane.propTypes = { - browseData: PropTypes.arrayOf(PropTypes.object).isRequired, + browseData: PropTypes.arrayOf(PropTypes.object), filters: PropTypes.object.isRequired, isFetching: PropTypes.bool, isFiltersOpened: PropTypes.bool.isRequired, diff --git a/src/components/BrowseResultsPane/BrowseResultsPane.test.js b/src/components/BrowseResultsPane/BrowseResultsPane.test.js index 07a7fb9a6..55b67d992 100644 --- a/src/components/BrowseResultsPane/BrowseResultsPane.test.js +++ b/src/components/BrowseResultsPane/BrowseResultsPane.test.js @@ -1,6 +1,6 @@ import '../../../test/jest/__mock__'; -import { screen } from '@testing-library/react'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; import { renderWithIntl, diff --git a/src/components/CheckboxFacet/CheckboxFacet.js b/src/components/CheckboxFacet/CheckboxFacet.js index 4bfacd354..818a55f9a 100644 --- a/src/components/CheckboxFacet/CheckboxFacet.js +++ b/src/components/CheckboxFacet/CheckboxFacet.js @@ -39,13 +39,20 @@ export default class CheckboxFacet extends React.Component { }; componentDidUpdate(prevProps) { + const prevDataLength = prevProps.dataOptions.length; + const currentDataLength = this.props.dataOptions.length; + if ( this.state.isMoreClicked && - prevProps.dataOptions.length === DEFAULT_FILTERS_NUMBER && - this.props.dataOptions.length > DEFAULT_FILTERS_NUMBER + prevDataLength === DEFAULT_FILTERS_NUMBER && + currentDataLength > DEFAULT_FILTERS_NUMBER ) { this.updateMore(); } + + if (prevDataLength > currentDataLength && currentDataLength === DEFAULT_FILTERS_NUMBER) { + this.setDefaultMore(); + } } onMoreClick = (totalOptions) => { @@ -98,6 +105,10 @@ export default class CheckboxFacet extends React.Component { }); } + setDefaultMore = () => { + this.setState(({ more: SHOW_OPTIONS_COUNT })); + } + render() { const { dataOptions, diff --git a/src/components/CheckboxFacet/CheckboxFacet.test.js b/src/components/CheckboxFacet/CheckboxFacet.test.js index 56eb4cf45..5a1c03aae 100644 --- a/src/components/CheckboxFacet/CheckboxFacet.test.js +++ b/src/components/CheckboxFacet/CheckboxFacet.test.js @@ -1,6 +1,6 @@ import React from 'react'; -import { screen, act, render } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; +import { screen, act, render } from '@folio/jest-config-stripes/testing-library/react'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; import '../../../test/jest/__mock__'; import { renderWithIntl, translationsProperties } from '../../../test/jest/helpers'; import CheckboxFacet from './CheckboxFacet'; diff --git a/src/components/CheckboxFacet/CheckboxFacetList.test.js b/src/components/CheckboxFacet/CheckboxFacetList.test.js index 7a355e523..7592abcef 100644 --- a/src/components/CheckboxFacet/CheckboxFacetList.test.js +++ b/src/components/CheckboxFacet/CheckboxFacetList.test.js @@ -1,6 +1,6 @@ import React from 'react'; -import { render, waitFor, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; +import { render, waitFor, screen } from '@folio/jest-config-stripes/testing-library/react'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; import { useSearchValue } from '../../stores/facetsStore'; import '../../../test/jest/__mock__'; import CheckboxFacetList from './CheckboxFacetList'; diff --git a/src/components/FacetOptionFormatter/FacetOptionFormatter.test.js b/src/components/FacetOptionFormatter/FacetOptionFormatter.test.js index 3b79ae5bd..d276a875e 100644 --- a/src/components/FacetOptionFormatter/FacetOptionFormatter.test.js +++ b/src/components/FacetOptionFormatter/FacetOptionFormatter.test.js @@ -1,6 +1,6 @@ import '../../../test/jest/__mock__'; -import { render } from '@testing-library/react'; +import { render } from '@folio/jest-config-stripes/testing-library/react'; import FacetOptionFormatter from './FacetOptionFormatter'; diff --git a/src/components/FilterNavigation/FilterNavigation.js b/src/components/FilterNavigation/FilterNavigation.js index fae03052e..5f16087d3 100644 --- a/src/components/FilterNavigation/FilterNavigation.js +++ b/src/components/FilterNavigation/FilterNavigation.js @@ -1,33 +1,49 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; +import queryString from 'query-string'; + import { ButtonGroup, Button, } from '@folio/stripes/components'; import { segments } from '../../constants'; +import { useLastSearchTerms } from '../../hooks'; + +const FilterNavigation = ({ segment, onChange }) => { + const { getLastSearch } = useLastSearchTerms(); + + return ( + + { + Object.keys(segments).map((name) => { + const searchParams = queryString.parse(getLastSearch(name)); -const FilterNavigation = ({ segment, onChange }) => ( - - { - Object.keys(segments).map(name => ( - - )) - } - -); + searchParams.segment = name; + + return ( + + ); + }) + } + + ); +}; FilterNavigation.propTypes = { segment: PropTypes.string, diff --git a/src/components/HoldingsRecordFilters/HoldingsRecordFilters.test.js b/src/components/HoldingsRecordFilters/HoldingsRecordFilters.test.js index a8fb96f13..eb9358918 100644 --- a/src/components/HoldingsRecordFilters/HoldingsRecordFilters.test.js +++ b/src/components/HoldingsRecordFilters/HoldingsRecordFilters.test.js @@ -1,70 +1,172 @@ import React from 'react'; -import { BrowserRouter as Router } from 'react-router-dom'; -import { noop } from 'lodash'; - import '../../../test/jest/__mock__'; +import { BrowserRouter as Router } from 'react-router-dom'; +import { screen, waitFor } from '@testing-library/react'; +import { ModuleHierarchyProvider } from '@folio/stripes-core/src/components/ModuleHierarchy'; -import { ModuleHierarchyProvider } from '@folio/stripes/core'; +import userEvent from '@testing-library/user-event'; import renderWithIntl from '../../../test/jest/helpers/renderWithIntl'; - +import { + FACETS +} from '../../constants'; import HoldingsRecordFilters from './HoldingsRecordFilters'; +import translationsProperties from '../../../test/jest/helpers/translationsProperties'; + +jest.mock('../CheckboxFacet/CheckboxFacet', () => jest.fn().mockReturnValue('CheckboxFacet')); + +jest.mock('../../facetUtils', () => ({ + ...jest.requireActual('../../facetUtils'), + getSourceOptions: jest.fn(), + getSuppressedOptions: jest.fn(), +})); +const activeFilters = { + [FACETS.EFFECTIVE_LOCATION]: ['loc1'], + [FACETS.HOLDINGS_PERMANENT_LOCATION]: ['loc2'], + [FACETS.HOLDINGS_TYPE]: ['loc3'], + [FACETS.HOLDINGS_DISCOVERY_SUPPRESS]: ['loc4'], + [FACETS.HOLDINGS_STATISTICAL_CODE_IDS]: ['loc5'], + [FACETS.HOLDINGS_CREATED_DATE]: ['2022-01-01'], + [FACETS.HOLDINGS_UPDATED_DATE]: ['2022-01-01'], + [FACETS.HOLDINGS_SOURCE]: ['loc8'] +}; const resources = { facets: { hasLoaded: true, resource: 'facets', - records: [], + records: [{ + 'items.effectiveLocationId': 'effectiveLocationId1', + 'holdings.permanentLocationId': 'permanentLocationId1', + 'holdings.statisticalCodeIds': 'statisticalCodeIds1', + 'holdings.discoverySuppress': 'discoverySuppress1', + 'holdings.sourceId': 'sourceId1', + 'holdingsTags': 'holdingsTags1', + 'holdings.holdingsTypeId': 'holdingsTypeId1', + }], other: { totalRecords: 0 } }, }; const data = { locations: [], + statisticalCodes: [], + holdingsSources: [], + holdingsTypes: [], resourceTypes: [], instanceFormats: [], modesOfIssuance: [], - statisticalCodes: [], tagsRecords: [], natureOfContentTerms: [], query: [], - onFetchFacets: noop, + onFetchFacets: jest.fn(), parentResources: resources, }; +const onChange = jest.fn(); +const onClear = jest.fn(); + const renderHoldingsRecordFilters = () => { return renderWithIntl( - + , + translationsProperties ); }; describe('HoldingsRecordFilters', () => { - beforeEach(() => { + it('Should Render effectiveLocation, Clear selectedfilters buttons', async () => { + renderHoldingsRecordFilters(); + const effectiveLocation = document.querySelector('[id="accordion-toggle-button-effectiveLocation"]'); + userEvent.click(effectiveLocation); + const Clearselectedfilters = screen.getAllByRole('button'); + userEvent.click(Clearselectedfilters[1]); + await waitFor(() => { + expect(onClear).toBeCalled(); + }); + }); + + it('Should Render holdingsPermanentLocation, Clear selectedfilters buttons', async () => { renderHoldingsRecordFilters(); + const holdingsPermanentLocation = document.querySelector('[id="accordion-toggle-button-holdingsPermanentLocation"]'); + userEvent.click(holdingsPermanentLocation); + const Clearselectedfilters = screen.getAllByRole('button'); + userEvent.click(Clearselectedfilters[3]); + await waitFor(() => { + expect(onClear).toBeCalled(); + }); }); - it('Contains a filter for creation date', () => { - expect(document.querySelector('#holdingsCreatedDate')).toBeInTheDocument(); + it('Should Render holdingsType, Clear selectedfilters buttons', async () => { + renderHoldingsRecordFilters(); + const holdingsType = document.querySelector('[id="accordion-toggle-button-holdingsType"]'); + userEvent.click(holdingsType); + const Clearselectedfilters = screen.getAllByRole('button'); + userEvent.click(Clearselectedfilters[5]); + await waitFor(() => { + expect(onClear).toBeCalled(); + }); }); - it('Contains a filter for update date', () => { - expect(document.querySelector('#holdingsUpdatedDate')).toBeInTheDocument(); + it('Should Render holdingsDiscoverySuppress, Clear selectedfilters buttons', async () => { + renderHoldingsRecordFilters(); + const holdingsDiscoverySuppress = document.querySelector('[id="accordion-toggle-button-holdingsDiscoverySuppress"]'); + userEvent.click(holdingsDiscoverySuppress); + const Clearselectedfilters = screen.getAllByRole('button'); + userEvent.click(Clearselectedfilters[7]); + await waitFor(() => { + expect(onClear).toBeCalled(); + }); }); - it('Contains a filter for statistical code', () => { - expect(document.querySelector('#holdingsStatisticalCodeIds')).toBeInTheDocument(); + it('Should Render holdingsStatisticalCodeIds, Clear selectedfilters buttons', async () => { + renderHoldingsRecordFilters(); + const holdingsStatisticalCodeIds = document.querySelector('[id="accordion-toggle-button-holdingsStatisticalCodeIds"]'); + userEvent.click(holdingsStatisticalCodeIds); + const Clearselectedfilters = screen.getAllByRole('button'); + userEvent.click(Clearselectedfilters[9]); + await waitFor(() => { + expect(onClear).toBeCalled(); + }); }); - it('Contains a filter for source', () => { - expect(document.querySelector('#holdingsSource')).toBeInTheDocument(); + it('Should Render holdingsCreatedDate, Clear selectedfilters buttons', async () => { + renderHoldingsRecordFilters(); + const holdingsCreatedDate = document.querySelector('[id="accordion-toggle-button-holdingsCreatedDate"]'); + userEvent.click(holdingsCreatedDate); + const Clearselectedfilters = screen.getAllByRole('button'); + userEvent.click(Clearselectedfilters[11]); + await waitFor(() => { + expect(onClear).toBeCalled(); + }); + }); + + it('Should Render holdingsUpdatedDate, Clear selectedfilters buttons', async () => { + renderHoldingsRecordFilters(); + const holdingsUpdatedDate = document.querySelector('[id="accordion-toggle-button-holdingsUpdatedDate"]'); + userEvent.click(holdingsUpdatedDate); + const Clearselectedfilters = screen.getAllByRole('button'); + userEvent.click(Clearselectedfilters[18]); + await waitFor(() => { + expect(onClear).toBeCalled(); + }); + }); + it('Should Render holdingsSource, Clear selectedfilters buttons', async () => { + renderHoldingsRecordFilters(); + const holdingsSource = document.querySelector('[id="accordion-toggle-button-holdingsSource"]'); + userEvent.click(holdingsSource); + const Clearselectedfilters = screen.getAllByRole('button'); + userEvent.click(Clearselectedfilters[25]); + await waitFor(() => { + expect(onClear).toBeCalled(); + }); }); }); diff --git a/src/components/HoldingsRecordFilters/holdingsRecordFilterRenderer.test.js b/src/components/HoldingsRecordFilters/holdingsRecordFilterRenderer.test.js new file mode 100644 index 000000000..d5b53a042 --- /dev/null +++ b/src/components/HoldingsRecordFilters/holdingsRecordFilterRenderer.test.js @@ -0,0 +1,118 @@ +import React from 'react'; +import { BrowserRouter as Router } from 'react-router-dom'; + +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; +import { screen } from '@folio/jest-config-stripes/testing-library/react'; +import '../../../test/jest/__mock__/currencyData.mock'; +import '../../../test/jest/__mock__/stripesConfig.mock'; +import '../../../test/jest/__mock__/stripesCore.mock'; +import '../../../test/jest/__mock__/stripesIcon.mock'; +import renderWithIntl from '../../../test/jest/helpers/renderWithIntl'; +import translationsProperties from '../../../test/jest/helpers/translationsProperties'; + +import holdingsRecordFilterRenderer from './holdingsRecordFilterRenderer'; + + +import { + getSourceOptions, + getSuppressedOptions, + processFacetOptions, + processStatisticalCodes, +} from '../../facetUtils'; + +jest.mock('../../facetUtils', () => ({ + ...jest.requireActual('../../facetUtils'), + getSourceOptions: jest.fn(), + getSuppressedOptions: jest.fn(), + processFacetOptions: jest.fn(), + processStatisticalCodes: jest.fn(), +})); + +jest.mock('@folio/stripes/components', () => ({ + ...jest.requireActual('@folio/stripes/components'), + Accordion: jest.fn(({ onClearFilter, children }) => ( +
+ {children} + +
+ )), +}), { virtual: true }); + +jest.mock('../CheckboxFacet/CheckboxFacet', () => ({ onChange }) => ( +
+
CheckboxFacet
+ +
+)); + +const onChangeMock = jest.fn(); +const resources = { + facets: { + hasLoaded: true, + resource: 'facets', + records: [{ + 'items.effectiveLocationId': 'effectiveLocationId1', + 'holdings.permanentLocationId': 'permanentLocationId1', + 'holdings.statisticalCodeIds': 'statisticalCodeIds1', + 'holdings.discoverySuppress': 'discoverySuppress1', + 'holdings.sourceId': 'sourceId1', + 'holdingsTags': 'holdingsTags1', + 'holdings.holdingsTypeId': 'holdingsTypeId1', + }], + }, +}; +const DATA = { + + locations: [], + statisticalCodes: [], + holdingsSources: [], + holdingsTypes: [], + tags: [], + query: { filters: '' }, + onFetchFacets: jest.fn(), + parentResources: resources +}; + +const renderFilters = (data = DATA, onChange = onChangeMock) => renderWithIntl( + {holdingsRecordFilterRenderer(data)(onChange)}, + translationsProperties +); + + +describe('holdingsRecordFilterRenderer', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('processFacetOptions should be called when holdingsRecordFilterRenderer renders', () => { + renderFilters(); + expect(processFacetOptions).toHaveBeenCalledTimes(4); + }); + + it('getSuppressedOptions should be called when holdingsRecordFilterRenderer renders', () => { + renderFilters(); + expect(getSuppressedOptions).toHaveBeenCalledTimes(1); + }); + it('getSourceOptions should be called when holdingsRecordFilterRenderer renders', () => { + renderFilters(); + expect(getSourceOptions).toBeCalled(); + }); + it('processStatisticalCodes should be called when holdingsRecordFilterRenderer renders', () => { + renderFilters(); + expect(processStatisticalCodes).toHaveBeenCalledTimes(1); + }); + + it('onChange function to be called when clearfilter button is clicked', () => { + renderFilters(); + userEvent.click(screen.getAllByRole('button', { name: 'onClearFilter' })[0]); + expect(onChangeMock).toBeCalled(); + }); + it('onChange function to be called when onChange button is clicked', () => { + renderFilters(); + userEvent.click(screen.getAllByRole('button', { name: 'onChange' })[0]); + expect(onChangeMock).toBeCalled(); + }); +}); + + + diff --git a/src/components/ImportRecordModal/ImportRecordModal.test.js b/src/components/ImportRecordModal/ImportRecordModal.test.js index 320b64a76..2bbe2c950 100644 --- a/src/components/ImportRecordModal/ImportRecordModal.test.js +++ b/src/components/ImportRecordModal/ImportRecordModal.test.js @@ -7,7 +7,7 @@ import '../../../test/jest/__mock__'; import { fireEvent, screen, -} from '@testing-library/dom'; +} from '@folio/jest-config-stripes/testing-library/dom'; import { renderWithIntl, translationsProperties, diff --git a/src/components/InstanceFilters/InstanceFilters.test.js b/src/components/InstanceFilters/InstanceFilters.test.js index c1919e66d..7aa8fa236 100644 --- a/src/components/InstanceFilters/InstanceFilters.test.js +++ b/src/components/InstanceFilters/InstanceFilters.test.js @@ -1,20 +1,59 @@ import React from 'react'; +import '../../../test/jest/__mock__'; import { BrowserRouter as Router } from 'react-router-dom'; -import { noop } from 'lodash'; +import { screen, waitFor } from '@folio/jest-config-stripes/testing-library/react'; +import { ModuleHierarchyProvider } from '@folio/stripes-core/src/components/ModuleHierarchy'; -import '../../../test/jest/__mock__'; +import userEvent from '@folio/jest-config-stripes/testing-library/user-event'; +import renderWithIntl from '../../../test/jest/helpers/renderWithIntl'; +import { + FACETS +} from '../../constants'; +import InstanceFilters from './InstanceFilters'; +import translationsProperties from '../../../test/jest/helpers/translationsProperties'; -import { ModuleHierarchyProvider } from '@folio/stripes/core'; +jest.mock('../CheckboxFacet/CheckboxFacet', () => jest.fn().mockReturnValue('CheckboxFacet')); -import renderWithIntl from '../../../test/jest/helpers/renderWithIntl'; +jest.mock('../../facetUtils', () => ({ + ...jest.requireActual('../../facetUtils'), + getSourceOptions: jest.fn(), + getSuppressedOptions: jest.fn(), +})); -import InstanceFilters from './InstanceFilters'; +const activeFilters = { + [FACETS.EFFECTIVE_LOCATION]: ['loc1'], + [FACETS.ITEM_STATUS]: ['ITEM_STATUS1'], + [FACETS.RESOURCE]: ['RESOURCE1'], + [FACETS.FORMAT]: ['Format1'], + [FACETS.MODE]: ['Mode1'], + [FACETS.NATURE_OF_CONTENT]: ['NATUREOFCONTENT1'], + [FACETS.STAFF_SUPPRESS]: ['STAFFSUPPRESS1'], + [FACETS.INSTANCES_DISCOVERY_SUPPRESS]: ['DISCOVERYSUPPRESS1'], + [FACETS.STATISTICAL_CODE_IDS]: ['STATISTICALCODEIDS1'], + [FACETS.CREATED_DATE]: ['2022-01-01'], + [FACETS.UPDATED_DATE]: ['2022-01-01'], + [FACETS.STATUS]: ['STATUS1'], + [FACETS.SOURCE]: ['SOURCE1'] +}; const resources = { facets: { hasLoaded: true, resource: 'facets', - records: [], + records: [{ + 'items.effectiveLocationId': 'effectiveLocationId1', + 'languages': 'languages', + 'statisticalCodeIds': 'statisticalCodeIds1', + 'discoverySuppress': 'discoverySuppress1', + 'source': 'source1', + 'instanceTags': 'instanceTags1', + 'statusId': 'statusId1', + 'staffSuppress': 'staffSuppress1', + 'natureOfContentTermIds': 'natureOfContentTermIds1', + 'modeOfIssuanceId': 'modeOfIssuanceId1', + 'instanceFormatIds': 'instanceFormatIds1', + 'instanceTypeId': 'instanceTypeId1', + }], other: { totalRecords: 0 } }, }; @@ -22,46 +61,148 @@ const resources = { const data = { locations: [], resourceTypes: [], - statisticalCodes: [], instanceFormats: [], modesOfIssuance: [], + statisticalCodes: [], tagsRecords: [], natureOfContentTerms: [], query: [], - onFetchFacets: noop, + onFetchFacets: jest.fn(), parentResources: resources, }; - +const onChange = jest.fn(); +const onClear = jest.fn(); const renderInstanceFilters = () => { return renderWithIntl( - + , + translationsProperties ); }; describe('InstanceFilters', () => { - beforeEach(() => { + it('Should Clear selected filters for effective Location', async () => { renderInstanceFilters(); + const Clearselectedfilters = screen.getAllByRole('button'); + userEvent.click(Clearselectedfilters[1]); + await waitFor(() => { + expect(onClear).toBeCalled(); + }); + }); + it('Should Clear selected filters for language', async () => { + renderInstanceFilters(); + const Clearselectedfilters = screen.getAllByRole('button'); + userEvent.click(Clearselectedfilters[3]); + await waitFor(() => { + expect(onClear).toBeCalled(); + }); + }); + + it('Should Clear selected filters for resource', async () => { + renderInstanceFilters(); + const Clearselectedfilters = screen.getAllByRole('button'); + userEvent.click(Clearselectedfilters[5]); + await waitFor(() => { + expect(onClear).toBeCalled(); + }); }); - it('Contains a filter for creation date ', () => { - expect(document.querySelector('#createdDate')).toBeInTheDocument(); + it('Should Clear selected filters for format', async () => { + renderInstanceFilters(); + const Clearselectedfilters = screen.getAllByRole('button'); + userEvent.click(Clearselectedfilters[7]); + await waitFor(() => { + expect(onClear).toBeCalled(); + }); }); - it('Contains a filter for update date ', () => { - expect(document.querySelector('#updatedDate')).toBeInTheDocument(); + it('Should Clear selected filters for mode', async () => { + renderInstanceFilters(); + const Clearselectedfilters = screen.getAllByRole('button'); + userEvent.click(Clearselectedfilters[9]); + await waitFor(() => { + expect(onClear).toBeCalled(); + }); + }); + + it('Should Clear selected filters for nature Of Content', async () => { + renderInstanceFilters(); + const Clearselectedfilters = screen.getAllByRole('button'); + userEvent.click(Clearselectedfilters[11]); + await waitFor(() => { + expect(onClear).toBeCalled(); + }); }); - it('Contains a filter for statistical code', () => { - expect(document.querySelector('#statisticalCodeIds')).toBeInTheDocument(); + it('Should Clear selected filters for staffSuppress', async () => { + renderInstanceFilters(); + const Clearselectedfilters = screen.getAllByRole('button'); + userEvent.click(Clearselectedfilters[13]); + await waitFor(() => { + expect(onClear).toBeCalled(); + }); + }); + + it('Should Clear selected filters for Suppress from discovery', async () => { + renderInstanceFilters(); + const Clearselectedfilters = screen.getAllByRole('button'); + userEvent.click(Clearselectedfilters[15]); + await waitFor(() => { + expect(onClear).toBeCalled(); + }); + }); + + it('Should Clear selected filters for Statistical code filter list', async () => { + renderInstanceFilters(); + const Clearselectedfilters = screen.getAllByRole('button'); + userEvent.click(Clearselectedfilters[17]); + await waitFor(() => { + expect(onClear).toBeCalled(); + }); + }); + + it('Should Clear selected filters for Date created filter list', async () => { + renderInstanceFilters(); + const Clearselectedfilters = screen.getAllByRole('button'); + userEvent.click(Clearselectedfilters[19]); + await waitFor(() => { + expect(onClear).toBeCalled(); + }); + }); + + it('Should Clear selected filters for Date updated filter list', async () => { + renderInstanceFilters(); + const Clearselectedfilters = screen.getAllByRole('button'); + userEvent.click(Clearselectedfilters[26]); + await waitFor(() => { + expect(onClear).toBeCalled(); + }); + }); + + it('Should Clear selected filters for Instance status filter list', async () => { + renderInstanceFilters(); + const Clearselectedfilters = screen.getAllByRole('button'); + userEvent.click(Clearselectedfilters[33]); + await waitFor(() => { + expect(onClear).toBeCalled(); + }); + }); + + it('Should Clear selected filters for Source filter list', async () => { + renderInstanceFilters(); + const Clearselectedfilters = screen.getAllByRole('button'); + userEvent.click(Clearselectedfilters[35]); + await waitFor(() => { + expect(onClear).toBeCalled(); + }); }); }); diff --git a/src/components/InstanceFilters/InstanceFiltersBrowse/instanceFiltersBrowse.test.js b/src/components/InstanceFilters/InstanceFiltersBrowse/instanceFiltersBrowse.test.js index cbc74eee2..ecae4dff6 100644 --- a/src/components/InstanceFilters/InstanceFiltersBrowse/instanceFiltersBrowse.test.js +++ b/src/components/InstanceFilters/InstanceFiltersBrowse/instanceFiltersBrowse.test.js @@ -4,7 +4,7 @@ import { noop } from 'lodash'; import { screen, fireEvent, -} from '@testing-library/react'; +} from '@folio/jest-config-stripes/testing-library/react'; import '../../../../test/jest/__mock__'; diff --git a/src/components/InstanceFilters/instanceFilterRenderer.test.js b/src/components/InstanceFilters/instanceFilterRenderer.test.js new file mode 100644 index 000000000..fe2d18b37 --- /dev/null +++ b/src/components/InstanceFilters/instanceFilterRenderer.test.js @@ -0,0 +1,131 @@ +import React from 'react'; +import { BrowserRouter as Router } from 'react-router-dom'; +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import '../../../test/jest/__mock__/currencyData.mock'; +import '../../../test/jest/__mock__/stripesConfig.mock'; +import '../../../test/jest/__mock__/stripesCore.mock'; +import '../../../test/jest/__mock__/stripesIcon.mock'; +import renderWithIntl from '../../../test/jest/helpers/renderWithIntl'; +import translationsProperties from '../../../test/jest/helpers/translationsProperties'; + +import instanceFilterRenderer from './instanceFilterRenderer'; + + +import { + getSourceOptions, + getSuppressedOptions, + processFacetOptions, + processStatisticalCodes, +} from '../../facetUtils'; +import { languageOptionsES } from './languages'; + +jest.mock('./languages', () => ({ + ...jest.requireActual('./languages'), + languageOptionsES: jest.fn(), +})); +jest.mock('@folio/stripes/components', () => ({ + ...jest.requireActual('@folio/stripes/components'), + Accordion: jest.fn(({ onClearFilter, children }) => ( +
+ {children} + +
+ )), +}), { virtual: true }); + +jest.mock('../../facetUtils', () => ({ + ...jest.requireActual('../../facetUtils'), + getSourceOptions: jest.fn(), + getSuppressedOptions: jest.fn(), + processFacetOptions: jest.fn(), + processStatisticalCodes: jest.fn(), +})); +jest.mock('../CheckboxFacet/CheckboxFacet', () => ({ onChange }) => ( +
+
CheckboxFacet
+ +
+)); + +const onChangeMock = jest.fn(); +const resources = { + facets: { + hasLoaded: true, + resource: 'facets', + records: [{ + 'items.effectiveLocationId': 'effectiveLocationId1', + 'languages': 'languages', + 'statisticalCodeIds': 'statisticalCodeIds1', + 'discoverySuppress': 'discoverySuppress1', + 'source': 'source1', + 'instanceTags': 'instanceTags1', + 'statusId': 'statusId1', + 'staffSuppress': 'staffSuppress1', + 'natureOfContentTermIds': 'natureOfContentTermIds1', + 'modeOfIssuanceId': 'modeOfIssuanceId1', + 'instanceFormatIds': 'instanceFormatIds1', + 'instanceTypeId': 'instanceTypeId1', + }], + }, +}; +const DATA = { + locations: ['Location 1', 'Location 2'], + instanceTypes: ['Book', 'CD'], + instanceFormats: ['Physical', 'Electronic'], + instanceStatuses: ['Available', 'Checked Out'], + modesOfIssuance: ['Monograph', 'Serial'], + natureOfContentTerms: ['Fiction', 'Non-fiction'], + statisticalCodes: [], + query: { + filters: 'locations.id,instanceTypes.id', + }, + onFetchFacets: jest.fn(), + parentResources: resources, +}; + +const renderFilters = (data = DATA, onChange = onChangeMock) => renderWithIntl( + {instanceFilterRenderer(data)(onChange)}, + translationsProperties +); + +describe('instanceFilterRenderer', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + it('processFacetOptions should be called when instanceFilterRenderer renders', () => { + renderFilters(); + expect(processFacetOptions).toBeCalledWith(undefined, DATA.locations, undefined, expect.anything(), expect.anything()); + expect(processFacetOptions).toBeCalledWith(undefined, DATA.instanceTypes, undefined, expect.anything(), expect.anything()); + expect(processFacetOptions).toBeCalledWith(undefined, DATA.instanceFormats, undefined, expect.anything(), expect.anything()); + expect(processFacetOptions).toBeCalledWith(undefined, DATA.modesOfIssuance, undefined, expect.anything(), expect.anything()); + expect(processFacetOptions).toBeCalledWith(undefined, DATA.natureOfContentTerms, undefined, expect.anything(), expect.anything()); + expect(processFacetOptions).toBeCalledWith(undefined, DATA.instanceStatuses, undefined, expect.anything(), expect.anything()); + }); + it('languageOptionsES should be called when instanceFilterRenderer renders', () => { + renderFilters(); + expect(languageOptionsES).toBeCalled(); + }); + it('getSuppressedOptions should be called when instanceFilterRenderer renders', () => { + renderFilters(); + expect(getSuppressedOptions).toHaveBeenCalledTimes(2); + }); + it('getSourceOptions should be called when instanceFilterRenderer renders', () => { + renderFilters(); + expect(getSourceOptions).toBeCalled(); + }); + it('processStatisticalCodes should be called when instanceFilterRenderer renders', () => { + renderFilters(); + expect(processStatisticalCodes).toBeCalledWith(undefined, DATA.statisticalCodes, undefined, expect.anything(), expect.anything()); + }); + it('onChange function to be called when clearfilter button is clicked', () => { + renderFilters(); + userEvent.click(screen.getAllByRole('button', { name: 'onClearFilter' })[0]); + expect(onChangeMock).toBeCalled(); + }); + it('onChange function to be called when onChange button is clicked', () => { + renderFilters(); + userEvent.click(screen.getAllByRole('button', { name: 'onChange' })[0]); + expect(onChangeMock).toBeCalled(); + }); +}); diff --git a/src/components/InstancesList/InstancesList.js b/src/components/InstancesList/InstancesList.js index 04b012aaf..26bd224a7 100644 --- a/src/components/InstancesList/InstancesList.js +++ b/src/components/InstancesList/InstancesList.js @@ -31,6 +31,7 @@ import { Icon, Checkbox, MenuSection, + Select, checkScope, HasCommand, MCLPagingTypes, @@ -53,10 +54,12 @@ import { omitFromArray, isTestEnv, handleKeyCommand, - buildSingleItemQuery + buildSingleItemQuery, } from '../../utils'; import { INSTANCES_ID_REPORT_TIMEOUT, + SORTABLE_SEARCH_RESULT_LIST_COLUMNS, + queryIndexes, segments, } from '../../constants'; import { @@ -67,13 +70,14 @@ import ErrorModal from '../ErrorModal'; import CheckboxColumn from './CheckboxColumn'; import SelectedRecordsModal from '../SelectedRecordsModal'; import ImportRecordModal from '../ImportRecordModal'; - import { buildQuery } from '../../routes/buildManifestObject'; import { getItem, setItem, } from '../../storage'; import facetsStore from '../../stores/facetsStore'; +import registerLogoutListener from '../../hooks/useLogout/utils'; +import { advancedSearchIndexes } from '../../filterConfig'; import css from './instances.css'; @@ -121,6 +125,7 @@ class InstancesList extends React.Component { location: PropTypes.shape({ search: PropTypes.string, state: PropTypes.object, + pathname: PropTypes.string, }), stripes: PropTypes.object.isRequired, history: PropTypes.shape({ @@ -131,6 +136,7 @@ class InstancesList extends React.Component { getLastSearchOffset: PropTypes.func.isRequired, storeLastSearch: PropTypes.func.isRequired, storeLastSearchOffset: PropTypes.func.isRequired, + storeLastSegment: PropTypes.func.isRequired, }; static contextType = CalloutContext; @@ -155,6 +161,7 @@ class InstancesList extends React.Component { isImportRecordModalOpened: false, optionSelected: '', searchAndSortKey: 0, + segmentsSortBy: this.getInitialSegmentsSortBy(), isSingleResult: this.props.showSingleResult, searchInProgress: false, }; @@ -164,6 +171,7 @@ class InstancesList extends React.Component { const { history, getParams, + namespace, } = this.props; const params = getParams(); @@ -185,10 +193,13 @@ class InstancesList extends React.Component { openedFromBrowse: params.selectedBrowseResult === 'true', optionSelected: '', }); + + registerLogoutListener(this.clearStorage, namespace, 'instances-list-logout', history); } componentDidUpdate(prevProps) { const qindex = this.getQIndexFromParams(); + const sortBy = this.getSortFromParams(); this.storeLastSearchTerms(prevProps); @@ -200,6 +211,16 @@ class InstancesList extends React.Component { // eslint-disable-next-line react/no-did-update-set-state this.setState({ optionSelected: qindex }); } + + if (this.state.segmentsSortBy.find(x => x.name === this.props.segment && x.sort !== sortBy)) { + this.setSegmentSortBy(sortBy); + } + + const id = this.props.location.pathname.split('/')[3]; + + if (id) { + setItem(`${this.props.namespace}.${this.props.segment}.lastOpenRecord`, id); + } } componentWillUnmount() { @@ -213,6 +234,16 @@ class InstancesList extends React.Component { authorityId: '', }; + clearStorage = () => { + const { + namespace, + } = this.props; + + Object.values(segments).forEach((segment) => { + setItem(`${namespace}.${segment}.lastOpenRecord`, null); + }); + } + processLastSearchTerms = () => { const { getParams, @@ -220,12 +251,13 @@ class InstancesList extends React.Component { parentMutator, getLastSearchOffset, storeLastSearch, + segment, } = this.props; const params = getParams(); - const lastSearchOffset = getLastSearchOffset(); + const lastSearchOffset = getLastSearchOffset(segment); const offset = params.selectedBrowseResult === 'true' ? 0 : lastSearchOffset; - storeLastSearch(location.search); + storeLastSearch(location.search, segment); parentMutator.resultOffset.replace(offset); } @@ -235,14 +267,15 @@ class InstancesList extends React.Component { parentResources, storeLastSearch, storeLastSearchOffset, + segment, } = this.props; if (prevProps.location.search !== location.search) { - storeLastSearch(location.search); + storeLastSearch(location.search, segment); } if (prevProps.parentResources.resultOffset !== parentResources.resultOffset) { - storeLastSearchOffset(parentResources.resultOffset); + storeLastSearchOffset(parentResources.resultOffset, segment); } } @@ -251,10 +284,21 @@ class InstancesList extends React.Component { return params.get('qindex'); } + getSortFromParams = () => { + const params = new URLSearchParams(this.props.location.search); + return params.get('sort'); + } + getInitialToggableColumns = () => { return getItem(VISIBLE_COLUMNS_STORAGE_KEY) || TOGGLEABLE_COLUMNS; } + getInitialSegmentsSortBy = () => { + return Object.keys(segments).map(name => ( + { name, sort: SORTABLE_SEARCH_RESULT_LIST_COLUMNS.TITLE } + )); + } + getVisibleColumns = () => { const columns = this.state.visibleColumns; const visibleColumns = new Set([...columns, ...NON_TOGGLEABLE_COLUMNS]); @@ -352,6 +396,8 @@ class InstancesList extends React.Component { } refocusOnInputSearch = (segment) => { + const { storeLastSegment } = this.props; + // when navigation button is clicked to change the search segment // the focus stays on the button so refocus back on the input search. // https://issues.folio.org/browse/UIIN-1358 @@ -360,16 +406,35 @@ class InstancesList extends React.Component { optionSelected: '' }); } + storeLastSegment(segment); facetsStore.getState().resetFacetSettings(); document.getElementById('input-inventory-search').focus(); } + onSearchModeSwitch = () => { + const { + namespace, + location: { pathname }, + segment, + } = this.props; + + const id = pathname.split('/')[3]; + + if (id) { + setItem(`${namespace}.${segment}.lastOpenRecord`, id); + } + } + renderNavigation = () => ( <> + - ); @@ -578,6 +643,22 @@ class InstancesList extends React.Component { setItem(`${namespace}.position`, null); } + setSegmentSortBy = (sortBy) => { + const { segment } = this.props; + + const segmentsSortBy = this.state.segmentsSortBy.map((key) => { + if (key.name === segment) { + key.sort = sortBy; + return key; + } + return key; + }); + + this.setState({ + segmentsSortBy + }); + } + getActionMenu = ({ onToggle }) => { const { parentResources, intl, segment } = this.props; const { inTransitItemsExportInProgress } = this.state; @@ -594,6 +675,43 @@ class InstancesList extends React.Component { }; }; + const setSortedColumn = (event) => { + const { + match: { path }, + goTo, + getParams, + } = this.props; + + onToggle(); + + const params = getParams(); + params.sort = event.target.value; + + this.setSegmentSortBy(params.sort); + + const { sort, ...rest } = params; + const queryParams = params.sort === '' ? rest : { sort, ...rest }; + + goTo(path, { ...queryParams }); + }; + + const sortOptions = Object.values(SORTABLE_SEARCH_RESULT_LIST_COLUMNS).map(option => ({ + value: option, + label: intl.formatMessage({ id: `ui-inventory.actions.menuSection.sortBy.${option}` }), + })); + + const sortByOptions = [ + { + value: '', + label: intl.formatMessage({ id: 'ui-inventory.actions.menuSection.sortBy.relevance' }), + }, + ...sortOptions, + ]; + + const getSortByValue = () => { + return this.state.segmentsSortBy.find(x => x.name === segment).sort?.replace('-', '') || ''; + }; + return ( <> @@ -700,6 +818,17 @@ class InstancesList extends React.Component { isDisabled: !selectedRowsCount, })} + + ` field) are rendered for each item: - -``` - -``` - -## Props - -Name | type | description | default | required ---- | --- | --- | --- | --- -name | string | Name of particular array of data that the rendered array of fields will refer to. | | yes -label | string | The visible label of the array of fields | | yes -addLabel | string | text for the 'Add item' button that's rendered with the fields. | | -addButtonId | string | HTML `id` attribute to assign to the add button. If not provided, one will be generated. | | -template | array of objects | Each object within the array represents props that will be passed to the rendered final-form `` inputs. | | yes -newItemTemplate | object | Object representing the default field values applied when a new item is added to the array. | | yes -addDefaultItem | bool | Sets whether or not the list will add a default, empty item. | `false` | - -## Single field example -Renders a single `` for each item... -``` - -``` - -## Custom Field components - -The `component` property of the `template` array can be used to pass in an existing component. If deeper control of props is necessary, such as option lists for `