From cec6b6209f6b261af72ef388cf1295a2c0686af7 Mon Sep 17 00:00:00 2001 From: Fabian Stoehr Date: Thu, 1 Aug 2024 09:57:26 +0200 Subject: [PATCH 01/13] update dependencies/components, fix tests, replace nwb with babel/webpack --- .eslintrc | 17 ++- .gitignore | 2 + README.md | 4 +- __tests__/CanvasDownloadLinks.test.js | 185 ++++++++++-------------- __tests__/ManifestDownloadLinks.test.js | 26 ++-- __tests__/MiradorDownloadDialog.test.js | 57 ++++---- __tests__/RenderingDownloadLink.test.js | 21 +-- __tests__/miradorDownloadPlugin.test.js | 22 +-- __tests__/test-utils.js | 55 +++++++ babel.config.js | 90 +++++++++++- demo/src/index.html | 11 ++ demo/src/index.js | 35 ++--- jest.config.js | 6 +- nwb.config.js | 22 --- package.json | 79 ++++++---- setupJest.js | 6 +- src/CanvasDownloadLinks.js | 140 +++++++++++------- src/ManifestDownloadLinks.js | 20 +-- src/MiradorDownloadDialog.js | 143 +++++++++--------- src/RenderingDownloadLink.js | 19 ++- src/index.js | 10 +- src/miradorDownloadPlugin.js | 27 ++-- webpack.config.js | 96 ++++++++++++ 23 files changed, 660 insertions(+), 433 deletions(-) create mode 100644 __tests__/test-utils.js create mode 100644 demo/src/index.html delete mode 100644 nwb.config.js create mode 100644 webpack.config.js diff --git a/.eslintrc b/.eslintrc index 54bfc3a..42cdf4f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,16 +1,19 @@ { - "env": { - "jest/globals": true - }, - "extends": ["airbnb"], + "env": {}, + "extends": ["airbnb", "plugin:jest/recommended"], "globals": { - "page": true, "document": true }, - "parser": "babel-eslint", - "plugins": ["jest"], + "parser": "@babel/eslint-parser", + "plugins": ["babel", "jest", "react", "react-hooks"], "rules": { + "import/prefer-default-export": "off", + "import/no-extraneous-dependencies": "off", + "no-console": "off", "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], + "react/jsx-fragments": "off", + "react/jsx-props-no-spreading": "off", "react/prefer-stateless-function": "off", + "react/function-component-definition": "off" } } diff --git a/.gitignore b/.gitignore index e51faa6..5b546e4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ /umd npm-debug.log* package-lock.json +.cache +dist \ No newline at end of file diff --git a/README.md b/README.md index af285a9..489992f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![npm package][npm-badge]][npm] [![Coveralls][coveralls-badge]][coveralls] -`mirador-dl-plugin` is a Mirador 3 plugin that adds manifest-provided download links (e.g. `rendering`) to the window options menu. A [live demo](https://mirador-download-plugin.netlify.app/) with several institutions' manifests is available for testing. +`mirador-dl-plugin` is a Mirador 4 plugin that adds manifest-provided download links (e.g. `rendering`) to the window options menu. A [live demo](https://mirador-download-plugin.netlify.app/) with several institutions' manifests is available for testing. ![download option in menu](https://user-images.githubusercontent.com/5402927/87057974-5e665a80-c1bc-11ea-8f10-7b783bdc972f.png) @@ -22,7 +22,7 @@ ## Installation -`mirador-dl-plugin` requires an instance of Mirador 3. See the [Mirador wiki](https://github.com/ProjectMirador/mirador/wiki) for examples of embedding Mirador within an application and additional information about plugins. See the [live demo's index.js](https://github.com/ProjectMirador/mirador-dl-plugin/blob/master/demo/src/index.js) for an example of importing and configuring `mirador-dl-plugin`. +`mirador-dl-plugin` requires an instance of Mirador 4. See the [Mirador wiki](https://github.com/ProjectMirador/mirador/wiki) for examples of embedding Mirador within an application and additional information about plugins. See the [live demo's index.js](https://github.com/ProjectMirador/mirador-dl-plugin/blob/master/demo/src/index.js) for an example of importing and configuring `mirador-dl-plugin`. ## Configuration diff --git a/__tests__/CanvasDownloadLinks.test.js b/__tests__/CanvasDownloadLinks.test.js index 4235896..850f609 100644 --- a/__tests__/CanvasDownloadLinks.test.js +++ b/__tests__/CanvasDownloadLinks.test.js @@ -1,13 +1,10 @@ import React from 'react'; -import { shallow } from 'enzyme'; -import Link from '@material-ui/core/Link'; -import Typography from '@material-ui/core/Typography'; -import { OSDReferences } from 'mirador/dist/es/src/plugins/OSDReferences'; import CanvasDownloadLinks from '../src/CanvasDownloadLinks'; -import RenderingDownloadLink from '../src/RenderingDownloadLink'; + +import { render, screen } from './test-utils'; function createWrapper(props) { - return shallow( + return render( { - let wrapper; const canvas = { id: 'abc123', - getCanonicalImageUri: width => ( + getCanonicalImageUri: (width) => ( width ? `http://example.com/iiif/abc123/full/${width},/0/default.jpg` : 'http://example.com/iiif/abc123/full/4000,/0/default.jpg' @@ -40,58 +36,30 @@ describe('CanvasDownloadLinks', () => { }, ], }; - const viewport = { - getBounds: () => ({ - x: 0, y: 0, width: 4000, height: 1000, - }), - }; - const zoomedInViewport = { - getBounds: () => ({ - x: 0, y: 0, width: 2000, height: 500, - }), - }; - const zoomedOutViewport = { - getBounds: () => ({ - x: 0, y: 0, width: 6000, height: 1000, - }), - }; - - const zoomedIntoNonImageSpaceViewport = { - getBounds: () => ({ - x: -100, y: 100, width: 2000, height: 500, - }), - }; + let currentBoundsSpy; + beforeEach(() => { + currentBoundsSpy = jest.spyOn(CanvasDownloadLinks.prototype, 'currentBounds'); + }); - beforeAll(() => { - OSDReferences.set('wid123', { - current: { viewport }, - }); - OSDReferences.set('zoomedInWindow', { - current: { viewport: zoomedInViewport }, - }); - OSDReferences.set('zoomedOutWindow', { - current: { viewport: zoomedOutViewport }, - }); - OSDReferences.set('zoomedIntoNonImageSpaceWindow', { - current: { viewport: zoomedIntoNonImageSpaceViewport }, - }); + afterEach(() => { + currentBoundsSpy.mockRestore(); }); it('renders canvas label in an h3 typography', () => { - wrapper = createWrapper({ canvas }); - expect( - wrapper.find(Typography) - .find({ variant: 'h3' }) - .props().children, - ).toEqual('My Canvas Label'); + createWrapper({ canvas }); + + screen.getByRole('heading'); + const headingElement = screen.getByText('My Canvas Label'); + expect(headingElement).toBeInTheDocument(); + expect(headingElement.tagName).toBe('H3'); }); it('renders canvas level renderings', () => { - wrapper = createWrapper({ canvas }); - expect( - wrapper.find(RenderingDownloadLink).length, - ).toEqual(1); + createWrapper({ canvas }); + + const dlElement = screen.getByRole('link', { name: /Whole image \(4000 x 1000px\)/i }); + expect(dlElement).toBeInTheDocument(); }); describe('Zoomed region link', () => { @@ -99,49 +67,54 @@ describe('CanvasDownloadLinks', () => { json: { width: 4000, height: 1000 }, }; - it('it does not render a link when the viewer is zoomed out/at the entire image', () => { - wrapper = createWrapper({ canvas, infoResponse, windowId: 'zoomedOutWindow' }); - expect(wrapper.find(Link).length).toBe(2); + it('does not render a link when the viewer is zoomed out/at the entire image', () => { + currentBoundsSpy.mockImplementation(() => ({ + x: 0, y: 0, width: 6000, height: 1000, + })); - wrapper = createWrapper({ canvas, infoResponse, windowId: 'wid123' }); - expect(wrapper.find(Link).length).toBe(2); + createWrapper({ canvas, infoResponse, windowId: 'zoomedOutWindow' }); + const zoomedLink = screen.queryByText('Zoomed region (6000 x 1000px)'); + expect(zoomedLink).not.toBeInTheDocument(); }); it('does not render a link when the viewer is zoomed into non-image space (e.g. a reponse the image server cannot handle)', () => { - wrapper = createWrapper({ canvas, infoResponse, windowId: 'zoomedIntoNonImageSpaceWindow' }); - - expect(wrapper.find(Link).length).toBe(2); + currentBoundsSpy.mockImplementation(() => ({ + x: -100, y: 100, width: 2000, height: 500, + })); + createWrapper({ canvas, infoResponse, windowId: 'zoomedIntoNonImageSpaceWindow' }); + const zoomedLink = screen.queryByText('Zoomed region (2000 x 500px)'); + expect(zoomedLink).not.toBeInTheDocument(); }); it('is present when the viewer is zoomed into the image', () => { - wrapper = createWrapper({ canvas, infoResponse, windowId: 'zoomedInWindow' }); - - expect(wrapper.find(Link).length).toBe(3); - expect( - wrapper - .find(Link) - .find({ href: 'http://example.com/iiif/abc123/0,0,2000,500/full/0/default.jpg?download=true' }) - .props().children, - ).toEqual('Zoomed region (2000 x 500px)'); + currentBoundsSpy.mockImplementation(() => ({ + x: 0, y: 0, width: 2000, height: 500, + })); + createWrapper({ canvas, infoResponse, windowId: 'zoomedInWindow' }); + const zoomedLink = screen.queryByText('Zoomed region (2000 x 500px)'); + expect(zoomedLink).toBeInTheDocument(); }); it('is not present when the window is in book or gallery view (only single view)', () => { - wrapper = createWrapper({ + currentBoundsSpy.mockImplementation(() => ({ + x: 0, y: 0, width: 2000, height: 500, + })); + createWrapper({ canvas, infoResponse, viewType: 'book', windowId: 'zoomedInWindow', }); + const zoomedLink = screen.queryByText('Zoomed region (2000 x 500px)'); + expect(zoomedLink).not.toBeInTheDocument(); - expect(wrapper.find(Link).length).toBe(2); - - wrapper = createWrapper({ + createWrapper({ canvas, infoResponse, viewType: 'gallery', windowId: 'zoomedInWindow', }); - - expect(wrapper.find(Link).length).toBe(2); + const zoomedLinkGallery = screen.queryByText('Zoomed region (2000 x 500px)'); + expect(zoomedLinkGallery).not.toBeInTheDocument(); }); describe('when the zoom link is set to be restricted', () => { it('has just the whole image link from the sizes and does not present a zoomed region link', () => { - wrapper = createWrapper({ + createWrapper({ canvas, infoResponse: { json: { @@ -156,9 +129,8 @@ describe('CanvasDownloadLinks', () => { restrictDownloadOnSizeDefinition: true, windowId: 'zoomedInWindow', }); - - expect(wrapper.find(Link).length).toBe(1); - expect(wrapper.find(Link).props().children).toEqual('Whole image (400 x 100px)'); + const links = screen.getByRole('link', { name: /Whole image \(400 x 100px\)/i }); + expect(links).toBeInTheDocument(); }); }); }); @@ -170,51 +142,46 @@ describe('CanvasDownloadLinks', () => { { width: 1000, height: 250 }, ]; it('uses those sizes for links in the download dialog', () => { - wrapper = createWrapper({ canvas, infoResponse: { json: { sizes } } }); - - // console.log(wrapper.debug()); - expect(wrapper.find(Link).at(0).props().children).toEqual('Whole image (4000 x 1000px)'); - expect(wrapper.find(Link).at(1).props().children).toEqual('Whole image (2000 x 500px)'); - expect(wrapper.find(Link).at(2).props().children).toEqual('Whole image (1000 x 250px)'); + createWrapper({ canvas, infoResponse: { json: { sizes } } }); + const link1 = screen.getByRole('link', { name: /Whole image \(4000 x 1000px\)/i }); + const link2 = screen.getByRole('link', { name: /Whole image \(2000 x 500px\)/i }); + const link3 = screen.getByRole('link', { name: /Whole image \(1000 x 250px\)/i }); + + expect(link1).toBeInTheDocument(); + expect(link2).toBeInTheDocument(); + expect(link3).toBeInTheDocument(); }); }); describe('when there are no defined sizes', () => { it('renders a link to the whole image', () => { - wrapper = createWrapper({ canvas }); - expect( - wrapper - .find(Link) - .find({ href: 'http://example.com/iiif/abc123/full/full/0/default.jpg?download=true' }) - .props() - .children, - ).toEqual('Whole image (4000 x 1000px)'); + createWrapper({ canvas }); + const link = screen.getByRole('link', { name: /Whole image \(4000 x 1000px\)/i }); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute('href', 'http://example.com/iiif/abc123/full/full/0/default.jpg?download=true'); }); describe('when the image is > 1000px wide', () => { it('renders a link to a small image (1000px wide), and calculates the correct height', () => { - wrapper = createWrapper({ canvas }); - expect(wrapper.find(Link).length).toEqual(2); - expect( - wrapper - .find(Link) - .find({ href: 'http://example.com/iiif/abc123/full/1000,/0/default.jpg?download=true' }) - .length, - ).toEqual(1); - expect( - wrapper - .find(Link) - .find({ href: 'http://example.com/iiif/abc123/full/1000,/0/default.jpg?download=true' }) - .props().children, - ).toEqual('Whole image (1000 x 250px)'); + createWrapper({ canvas }); + const links = screen.getAllByRole('link'); + + expect(links).toBeInTheDocument(); + + const link1 = screen.getByRole('link', { name: /Whole image \(4000 x 1000px\)/i }); + expect(link1).toHaveAttribute('href', 'http://example.com/iiif/abc123/full/1000,/0/default.jpg?download=true'); + + const link2 = screen.getByRole('link', { name: /Whole image \(1000 x 250px\)/i }); + expect(link2).toHaveAttribute('href', 'http://example.com/iiif/abc123/full/1000,/0/default.jpg?download=true'); }); }); describe('when the image is < 1000px wide', () => { it('does not render a link to a small image', () => { canvas.getWidth = () => 999; - wrapper = createWrapper({ canvas }); - expect(wrapper.find(Link).length).toEqual(1); // Does not include the 2nd link + createWrapper({ canvas }); + const links = screen.getAllByRole('link'); + expect(links).toHaveLength(2); }); }); }); diff --git a/__tests__/ManifestDownloadLinks.test.js b/__tests__/ManifestDownloadLinks.test.js index f348003..370c953 100644 --- a/__tests__/ManifestDownloadLinks.test.js +++ b/__tests__/ManifestDownloadLinks.test.js @@ -1,11 +1,9 @@ import React from 'react'; -import { shallow } from 'enzyme'; -import Typography from '@material-ui/core/Typography'; import ManifestDownloadLinks from '../src/ManifestDownloadLinks'; -import RenderingDownloadLink from '../src/RenderingDownloadLink'; +import { render, screen } from './test-utils'; function createWrapper(props) { - return shallow( + return render( { ]; it('renders the heading', () => { - const wrapper = createWrapper({ renderings }); + createWrapper({ renderings }); + screen.debug(); - expect( - wrapper.find(Typography) - .find({ variant: 'h3' }) - .props().children, - ).toEqual('Other download options'); + screen.getByRole('heading'); + const headingElement = screen.getByText('Other download options'); + expect(headingElement).toBeInTheDocument(); + expect(headingElement.tagName).toBe('H3'); }); it('renders a RenderingDownloadLink for each rendering', () => { - const wrapper = createWrapper({ renderings }); + createWrapper({ renderings }); - expect(wrapper.find(RenderingDownloadLink).length).toBe(2); + const pdfLinkElement = screen.getByRole('link', { name: /Link to the PDF/i }); + expect(pdfLinkElement).toBeInTheDocument(); + + const ocrLinkElement = screen.getByRole('link', { name: /Link to the OCR/i }); + expect(ocrLinkElement).toBeInTheDocument(); }); }); diff --git a/__tests__/MiradorDownloadDialog.test.js b/__tests__/MiradorDownloadDialog.test.js index 35de171..ae20bda 100644 --- a/__tests__/MiradorDownloadDialog.test.js +++ b/__tests__/MiradorDownloadDialog.test.js @@ -1,13 +1,12 @@ import React from 'react'; -import { shallow } from 'enzyme'; -import Button from '@material-ui/core/Button'; import miradorDownloadDialog from '../src/MiradorDownloadDialog'; +import { render, screen } from './test-utils'; /** Utility function to wrap */ function createWrapper(props) { - return shallow( + return render( (label || 'My Canvas Title')} + canvasLabel={(label) => (label || 'My Canvas Title')} canvases={[]} classes={{}} closeDialog={() => {}} @@ -19,53 +18,49 @@ function createWrapper(props) { windowId="wid123" {...props} />, - ).dive(); + ); } describe('Dialog', () => { - let wrapper; - - it('does not render anything if the open prop is false', () => { - wrapper = createWrapper({ open: false }); - expect(wrapper).toEqual({}); - }); - - it('renders a CanvasDownloadLinks componewnt for every canvas', () => { - const mockCanvas = id => ({ + it('renders a CanvasDownloadLinks component for every canvas', () => { + const mockCanvas = (id) => ({ id, getHeight: () => 4000, getWidth: () => 1000, getRenderings: () => [], getCanonicalImageUri: () => 'https://example.com/iiif/abc123/full/9000,/0/default.jpg', }); - wrapper = createWrapper({ canvases: [mockCanvas('abc123'), mockCanvas('xyz321')] }); - expect(wrapper.find('CanvasDownloadLinks').length).toBe(2); - }); + createWrapper({ canvases: [mockCanvas('abc123'), mockCanvas('xyz321')] }); + + const headings = screen.getAllByRole('heading'); + const headingAbc = headings.find((heading) => (heading.textContent === 'abc123')); + expect(headingAbc).toBeInTheDocument(); + expect(headingAbc.tagName).toBe('H3'); - it('has a close button that triggers the closeDialog prop', () => { - const closeDialog = jest.fn(); - wrapper = createWrapper({ closeDialog }); - wrapper.find(Button).simulate('click'); - expect(closeDialog).toHaveBeenCalled(); + const headingXyz = headings.find((heading) => (heading.textContent === 'xyz321')); + expect(headingXyz).toBeInTheDocument(); + expect(headingXyz.tagName).toBe('H3'); }); describe('ManifestDownloadLinks', () => { - it('is not rendered if hte manifest has no renderings', () => { - wrapper = createWrapper(); - - expect(wrapper.find('ManifestDownloadLinks').length).toBe(0); + it('is not rendered if the manifest has no renderings', () => { + createWrapper(); + const manifestLinks = screen.queryByText('ManifestDownloadLinks'); + expect(manifestLinks).not.toBeInTheDocument(); }); it('rendered if the manifest has renderings', () => { - const rendering = { id: '', getLabel: () => {}, getFormat: () => {} }; - wrapper = createWrapper({ + const rendering = { id: '', getLabel: () => ({ getValue: () => 'ManifestDownloadLinks' }), getFormat: () => {} }; + createWrapper({ manifest: { getSequences: () => [ - { getRenderings: () => [rendering] }, + { + getRenderings: () => [rendering], + }, ], }, }); - - expect(wrapper.find('ManifestDownloadLinks').length).toBe(1); + const manifestLinks = screen.queryByText('ManifestDownloadLinks'); + expect(manifestLinks).toBeInTheDocument(); }); }); }); diff --git a/__tests__/RenderingDownloadLink.test.js b/__tests__/RenderingDownloadLink.test.js index 5408f17..0eb96a0 100644 --- a/__tests__/RenderingDownloadLink.test.js +++ b/__tests__/RenderingDownloadLink.test.js @@ -1,11 +1,9 @@ import React from 'react'; -import { shallow } from 'enzyme'; -import Link from '@material-ui/core/Link'; -import ListItemText from '@material-ui/core/ListItemText'; import RenderingDownloadLink from '../src/RenderingDownloadLink'; +import { render, screen } from './test-utils'; function createWrapper(props) { - return shallow( + return render( { }; it('renders a Link for the rendering', () => { - const wrapper = createWrapper({ rendering }); + createWrapper({ rendering }); - expect(wrapper.find(Link).length).toBe(1); + const link = screen.getByRole('link', { name: /Link to the PDF/i }); + expect(link).toBeInTheDocument(); }); it('links the label and includes the format (unlinked)', () => { - const wrapper = createWrapper({ rendering }); + createWrapper({ rendering }); - expect(wrapper.find(Link).at(0).props().children).toEqual('Link to the PDF'); - expect(wrapper.find(Link).at(0).props().href).toEqual('http://example.com/abc123.pdf'); - expect(wrapper.find(ListItemText).at(0).props().children[1]).toEqual(' (application/pdf)'); + const link = screen.getByRole('link', { name: /Link to the PDF/i }); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute('href', 'http://example.com/abc123.pdf'); + const listItem = screen.getByText(/application\/pdf/i); + expect(listItem).toBeInTheDocument(); }); }); diff --git a/__tests__/miradorDownloadPlugin.test.js b/__tests__/miradorDownloadPlugin.test.js index 88f4a98..d7c9281 100644 --- a/__tests__/miradorDownloadPlugin.test.js +++ b/__tests__/miradorDownloadPlugin.test.js @@ -1,11 +1,9 @@ import React from 'react'; -import { shallow } from 'enzyme'; -import ListItemText from '@material-ui/core/ListItemText'; -import MenuItem from '@material-ui/core/MenuItem'; import miradorDownloadPlugin from '../src/miradorDownloadPlugin'; +import { render, screen } from './test-utils'; function createWrapper(props) { - return shallow( + return render( {}} openDownloadDialog={() => {}} @@ -20,19 +18,9 @@ describe('miradorDownloadPlugin', () => { }); describe('renders a component', () => { it('renders a thing', () => { - const wrapper = createWrapper(); - expect(wrapper.find(ListItemText).props().children).toEqual('Download'); - }); - }); - - describe('MenuItem', () => { - it('calls the openShareDialog and handleClose props when clicked', () => { - const handleClose = jest.fn(); - const openDownloadDialog = jest.fn(); - const wrapper = createWrapper({ handleClose, openDownloadDialog }); - wrapper.find(MenuItem).simulate('click'); - expect(handleClose).toHaveBeenCalled(); - expect(openDownloadDialog).toHaveBeenCalled(); + createWrapper(); + const downloadElement = screen.queryByText(/Download/i); + expect(downloadElement).toBeInTheDocument(); }); }); }); diff --git a/__tests__/test-utils.js b/__tests__/test-utils.js new file mode 100644 index 0000000..4355677 --- /dev/null +++ b/__tests__/test-utils.js @@ -0,0 +1,55 @@ +import React from 'react'; +import { Provider } from 'react-redux'; +import { render } from '@testing-library/react'; +import PropTypes from 'prop-types'; +import { createStore, applyMiddleware } from 'redux'; +import { thunk } from 'redux-thunk'; +import { createTheme, ThemeProvider, StyledEngineProvider } from '@mui/material/styles'; +import createRootReducer from 'mirador/dist/es/src/state/reducers/rootReducer'; +import settings from 'mirador/dist/es/src/config/settings'; + +const rootReducer = createRootReducer(); +const theme = createTheme(settings.theme); + +/** + * Hook up our rendered object to redux + */ +function renderWithProviders( + ui, + { + preloadedState = {}, + // Automatically create a store instance if no store was passed in + store = createStore(rootReducer, preloadedState, applyMiddleware(thunk)), + ...renderOptions + } = {}, +) { + /** :nodoc: */ + function Wrapper({ children }) { + return ( + + + {children} + + + ); + } + + Wrapper.propTypes = { + children: PropTypes.node.isRequired, + }; + + const rendered = render(ui, { wrapper: Wrapper, ...renderOptions }); + + // Return an object with the store and all of RTL's query functions + return { + store, + ...rendered, + rerender: (newUi, options) => render( + newUi, + { container: rendered.container, wrapper: Wrapper, ...options }, + ), + }; +} + +export * from '@testing-library/react'; +export { renderWithProviders as render }; diff --git a/babel.config.js b/babel.config.js index 6779626..8c77e76 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,13 +1,93 @@ -module.exports = { +const moduleFormatMap = { + cjs: 'commonjs', + es: false, +}; + +module.exports = (api) => ({ presets: [ - [ + api.env('test') && [ '@babel/preset-env', { + modules: 'commonjs', targets: { node: 'current', }, }, ], - '@babel/preset-react', - ], -}; + (api.env('production') || api.env('development')) && [ + '@babel/preset-env', + { + corejs: 3, + exclude: ['transform-typeof-symbol'], + forceAllTransforms: true, + modules: moduleFormatMap[process.env.MODULE_FORMAT] || false, + useBuiltIns: 'entry', + }, + ], + [ + '@babel/preset-react', + { + development: api.env('development') || api.env('test'), + runtime: 'automatic', + useBuiltIns: true, + }, + ], + ].filter(Boolean), + plugins: [ + 'babel-plugin-macros', + '@babel/plugin-transform-destructuring', + [ + '@babel/plugin-proposal-class-properties', + { + loose: true, + }, + ], + ['@babel/plugin-proposal-private-property-in-object', { loose: true }], + ['@babel/plugin-proposal-private-methods', { loose: true }], + [ + '@babel/plugin-proposal-object-rest-spread', + { + useBuiltIns: true, + }, + ], + [ + '@babel/plugin-transform-runtime', + { + corejs: false, + helpers: false, // Needed to support IE/Edge + regenerator: true, + }, + ], + [ + '@babel/plugin-transform-regenerator', + { + async: false, + }, + ], + ['transform-react-remove-prop-types', + { + ignoreFilenames: ['node_modules'], + removeImport: true, + }, + ], + // [ + // '@emotion', + // { + // importMap: { + // '@mui/system': { + // styled: { + // canonicalImport: ['@emotion/styled', 'default'], + // styledBaseImport: ['@mui/system', 'styled'], + // }, + // }, + // '@mui/material/styles': { + // styled: { + // canonicalImport: ['@emotion/styled', 'default'], + // styledBaseImport: ['@mui/material/styles', 'styled'], + // }, + // }, + // }, + // }, + // ], + ].filter(Boolean), +}); diff --git a/demo/src/index.html b/demo/src/index.html new file mode 100644 index 0000000..4f8170c --- /dev/null +++ b/demo/src/index.html @@ -0,0 +1,11 @@ + + + + + Mirador with dl plugin + + +
+ + + diff --git a/demo/src/index.js b/demo/src/index.js index ddabd16..d24ea36 100644 --- a/demo/src/index.js +++ b/demo/src/index.js @@ -6,22 +6,23 @@ const config = { miradorDownloadPlugin: { restrictDownloadOnSizeDefinition: true, }, - windows: [{ - loadedManifest: 'https://purl.stanford.edu/bb020ty1503/iiif/manifest', - }, - { - loadedManifest: 'https://scta.info/iiif/graciliscommentary/lon/manifest', - view: 'book', - canvasIndex: 3, - }, - { - loadedManifest: 'https://purl.stanford.edu/xh756kf1140/iiif/manifest', - }, - { - loadedManifest: 'https://digital.library.villanova.edu/Item/vudl:24299/Manifest', - }], + windows: [ + { + loadedManifest: 'https://purl.stanford.edu/bb020ty1503/iiif/manifest', + }, + { + loadedManifest: 'https://scta.info/iiif/graciliscommentary/lon/manifest', + view: 'book', + canvasIndex: 3, + }, + { + loadedManifest: 'https://purl.stanford.edu/xh756kf1140/iiif/manifest', + }, + { + loadedManifest: + 'https://digital.library.villanova.edu/Item/vudl:24299/Manifest', + }, + ], }; -Mirador.viewer(config, [ - ...miradorDownloadPlugins, -]); +Mirador.viewer(config, [...miradorDownloadPlugins]); diff --git a/jest.config.js b/jest.config.js index 5576378..835e8cc 100644 --- a/jest.config.js +++ b/jest.config.js @@ -7,11 +7,15 @@ module.exports = { // The directory where Jest should output its coverage files coverageDirectory: 'coverage', - setupFiles: [ + setupFilesAfterEnv: [ '/setupJest.js', ], + testEnvironment: 'jsdom', // Ignore Mirador/Manifesto code from jest transforms transformIgnorePatterns: [ '/node_modules/(?!(mirador|manifesto.js))', ], + testPathIgnorePatterns: [ + '/__tests__/test-utils.js', + ], }; diff --git a/nwb.config.js b/nwb.config.js deleted file mode 100644 index a9d07bb..0000000 --- a/nwb.config.js +++ /dev/null @@ -1,22 +0,0 @@ -const path = require('path'); - -module.exports = { - type: 'react-component', - npm: { - esModules: true, - umd: { - global: 'MiradorDownload', - externals: { - react: 'React', - }, - }, - }, - webpack: { - aliases: { - '@material-ui/core': path.resolve('./', 'node_modules', '@material-ui/core'), - '@material-ui/styles': path.resolve('./', 'node_modules', '@material-ui/styles'), - react: path.resolve('./', 'node_modules', 'react'), - 'react-dom': path.resolve('./', 'node_modules', 'react-dom'), - }, - }, -}; diff --git a/package.json b/package.json index 0fb0af8..f6fb95f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mirador-dl-plugin", - "version": "0.13.0", + "version": "1.0.0-alpha.1", "description": "mirador-dl-plugin React component", "main": "lib/index.js", "module": "es/index.js", @@ -11,47 +11,66 @@ "umd" ], "scripts": { - "build": "nwb build-react-component", - "clean": "nwb clean-module && nwb clean-demo", - "lint": "npx eslint ./", + "build": "npm run build:umd", + "build:demo": "NODE_ENV=development webpack --mode=development", + "build:umd": "NODE_ENV=production webpack --mode=production", + "build:es": "mkdir -p es && cp -r src/* es && NODE_ENV=production MODULE_FORMAT=es npx babel es -d es", + "build:cjs": "mkdir -p lib && cp -r src/* lib && NODE_ENV=production MODULE_FORMAT=cjs npx babel lib -d lib", + "parcel": "parcel demo/src/index.html", + "lint": "eslint ./src ./__tests__", "prepublishOnly": "npm run build", - "start": "nwb serve-react-demo", "test": "npm run lint && jest", "test:coverage": "jest --coverage", "test:watch": "jest --watch" }, "peerDependencies": { - "@material-ui/core": "^4.11.0", - "@material-ui/icons": "^4.9.1", + "@mui/material": "^5.x", "lodash": "^4.17.11", - "mirador": "^3.0.0-rc.7", - "prop-types": "^15.7.2", - "react": "16.x", - "react-dom": "16.x" + "mirador": "^4.0.0-alpha.1", + "react": "18.x", + "react-dom": "18.x" }, "devDependencies": { - "@babel/core": "^7.7.2", - "@babel/preset-env": "^7.7.1", - "@babel/preset-react": "^7.7.0", - "@material-ui/core": "^4.11.0", - "@material-ui/icons": "^4.9.1", + "@babel/cli": "^7.17.6", + "@babel/core": "^7.17.7", + "@babel/eslint-parser": "^7.5.4", + "@babel/plugin-proposal-class-properties": "^7.16.7", + "@babel/plugin-proposal-object-rest-spread": "^7.20.7", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/preset-env": "^7.16.11", + "@babel/preset-react": "^7.16.7", + "@mui/material": "^5.x", + "@testing-library/dom": "^9.2.0", + "@testing-library/jest-dom": "^6.1.5", + "@testing-library/react": "^14.1.0", + "@testing-library/user-event": "^14.4.3", "babel-eslint": "^10.0.3", "babel-jest": "^24.9.0", - "enzyme": "^3.9.0", - "enzyme-adapter-react-16": "^1.15.1", - "eslint": "^5.3.0", - "eslint-config-airbnb": "^17.1.1", - "eslint-plugin-import": "^2.18.2", - "eslint-plugin-jest": "^22.21.0", - "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-react": "^7.16.0", - "jest": "^24.9.0", + "babel-loader": "^9.1.0", + "babel-plugin-transform-react-remove-prop-types": "^0.4.24", + "eslint": "^8.11.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-plugin-babel": "^5.3.0", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-jest": "^27.1.5", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-react": "^7.29.4", + "html-loader": "^5.0.0", + "html-webpack-plugin": "^5.6.0", + "jest": "^29.3.1", + "jest-environment-jsdom": "^29.4.3", + "jest-puppeteer": "^9.0.2", "lodash": "^4.17.15", - "mirador": "^3.0.0-rc.7", - "nwb": "0.23.x", - "prop-types": "^15.7.2", - "react": "^16.12.0", - "react-dom": "^16.12.0" + "mirador": "^4.0.0-alpha.1", + "parcel-bundler": "^1.12.4", + "react": "^18.0.0", + "react-dom": "^18.0.0", + "terser-webpack-plugin": "^5.3.1", + "webpack": "^5.70.0", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^4.7.4" }, "author": "", "homepage": "", diff --git a/setupJest.js b/setupJest.js index 7d1129a..214701e 100644 --- a/setupJest.js +++ b/setupJest.js @@ -1,4 +1,4 @@ -import Enzyme from 'enzyme'; // eslint-disable-line import/no-extraneous-dependencies -import Adapter from 'enzyme-adapter-react-16'; // eslint-disable-line import/no-extraneous-dependencies +import '@testing-library/jest-dom'; +import sizeMe from 'react-sizeme'; -Enzyme.configure({ adapter: new Adapter() }); +sizeMe.noPlaceholders = true; diff --git a/src/CanvasDownloadLinks.js b/src/CanvasDownloadLinks.js index 6385c08..839bf87 100644 --- a/src/CanvasDownloadLinks.js +++ b/src/CanvasDownloadLinks.js @@ -1,21 +1,22 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import Typography from '@material-ui/core/Typography'; -import Link from '@material-ui/core/Link'; -import List from '@material-ui/core/List'; -import ListItem from '@material-ui/core/ListItem'; import uniqBy from 'lodash/uniqBy'; import { OSDReferences } from 'mirador/dist/es/src/plugins/OSDReferences'; +import Typography from '@mui/material/Typography'; +import Link from '@mui/material/Link'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; import RenderingDownloadLink from './RenderingDownloadLink'; - /** * CanvasDownloadLinks ~ -*/ + */ export default class CanvasDownloadLinks extends Component { zoomedImageLabel() { const bounds = this.currentBounds(); - return `Zoomed region (${Math.floor(bounds.width)} x ${Math.floor(bounds.height)}px)`; + return `Zoomed region (${Math.floor(bounds.width)} x ${Math.floor( + bounds.height, + )}px)`; } fullImageLabel() { @@ -27,16 +28,20 @@ export default class CanvasDownloadLinks extends Component { smallImageLabel() { const { canvas } = this.props; - return `Whole image (1000 x ${Math.floor((1000 * canvas.getHeight()) / canvas.getWidth())}px)`; + return `Whole image (1000 x ${Math.floor( + (1000 * canvas.getHeight()) / canvas.getWidth(), + )}px)`; } zoomedImageUrl() { const { canvas } = this.props; const bounds = this.currentBounds(); - const boundsUrl = canvas.getCanonicalImageUri().replace( - /\/full\/.*\/0\//, - `/${bounds.x},${bounds.y},${bounds.width},${bounds.height}/full/0/`, - ); + const boundsUrl = canvas + .getCanonicalImageUri() + .replace( + /\/full\/.*\/0\//, + `/${bounds.x},${bounds.y},${bounds.width},${bounds.height}/full/0/`, + ); return `${boundsUrl}?download=true`; } @@ -50,7 +55,9 @@ export default class CanvasDownloadLinks extends Component { fullImageUrl() { const { canvas } = this.props; - return `${canvas.getCanonicalImageUri().replace(/\/full\/.*\/0\//, '/full/full/0/')}?download=true`; + return `${canvas + .getCanonicalImageUri() + .replace(/\/full\/.*\/0\//, '/full/full/0/')}?download=true`; } thousandPixelWideImage() { @@ -81,38 +88,53 @@ export default class CanvasDownloadLinks extends Component { if (this.definedSizes().length !== 1) return false; - return this.definedSizes()[0].width <= width - && this.definedSizes()[0].height <= height; + return ( + this.definedSizes()[0].width <= width + && this.definedSizes()[0].height <= height + ); } displayCurrentZoomLink() { const { restrictDownloadOnSizeDefinition, infoResponse, viewType } = this.props; if (viewType !== 'single') return false; - if (restrictDownloadOnSizeDefinition && this.definedSizesRestrictsDownload()) return false; + if ( + restrictDownloadOnSizeDefinition + && this.definedSizesRestrictsDownload() + ) return false; if (!(infoResponse && infoResponse.json)) return false; const bounds = this.currentBounds(); - return bounds.height < infoResponse.json.height + return ( + bounds.height < infoResponse.json.height && bounds.width < infoResponse.json.width && bounds.x >= 0 - && bounds.y >= 0; + && bounds.y >= 0 + ); } /** * This only returns unique sizes - */ + */ definedSizes() { const { infoResponse } = this.props; if (!(infoResponse && infoResponse.json && infoResponse.json.sizes)) return []; - return uniqBy(infoResponse.json.sizes, size => `${size.width}${size.height}`); + return uniqBy( + infoResponse.json.sizes, + (size) => `${size.width}${size.height}`, + ); } fullImageLink() { return ( - + {this.fullImageLabel()} @@ -126,7 +148,12 @@ export default class CanvasDownloadLinks extends Component { return ( - + {this.smallImageLabel()} @@ -134,45 +161,51 @@ export default class CanvasDownloadLinks extends Component { } linksForDefinedSizes() { - return ( - this.definedSizes().map(size => ( - - - {`Whole image (${size.width} x ${size.height}px)`} - - - )) - ); + return this.definedSizes().map((size) => ( + + + {`Whole image (${size.width} x ${size.height}px)`} + + + )); } /** * Returns the rendered component - */ + */ render() { - const { - canvas, - canvasLabel, - classes, - } = this.props; + const { canvas, canvasLabel } = this.props; return ( - {canvasLabel} + + {canvasLabel} + - {this.displayCurrentZoomLink() - && ( - - - {this.zoomedImageLabel()} - - - ) - } - {this.definedSizes().length === 0 - && ([this.fullImageLink(), this.thousandPixelWideLink()])} - {this.definedSizes().length > 0 - && (this.linksForDefinedSizes())} - {canvas.getRenderings().map(rendering => ( + {this.displayCurrentZoomLink() && ( + + + {this.zoomedImageLabel()} + + + )} + {this.definedSizes().length === 0 && [ + this.fullImageLink(), + this.thousandPixelWideLink(), + ]} + {this.definedSizes().length > 0 && this.linksForDefinedSizes()} + {canvas.getRenderings().map((rendering) => ( ))} @@ -190,9 +223,6 @@ CanvasDownloadLinks.propTypes = { getWidth: PropTypes.func.isRequired, }).isRequired, canvasLabel: PropTypes.string.isRequired, // canvasLabel is passed because we need access to redux - classes: PropTypes.shape({ - h3: PropTypes.string, - }).isRequired, infoResponse: PropTypes.shape({ json: PropTypes.shape({ height: PropTypes.number, diff --git a/src/ManifestDownloadLinks.js b/src/ManifestDownloadLinks.js index c83ee45..e23e9b3 100644 --- a/src/ManifestDownloadLinks.js +++ b/src/ManifestDownloadLinks.js @@ -1,24 +1,27 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import List from '@material-ui/core/List'; -import Typography from '@material-ui/core/Typography'; + +import Typography from '@mui/material/Typography'; +import List from '@mui/material/List'; import RenderingDownloadLink from './RenderingDownloadLink'; /** * ManifestDownloadLinks ~ -*/ + */ export default class ManifestDownloadLinks extends Component { /** * Returns the rendered component - */ + */ render() { - const { classes, renderings } = this.props; + const { renderings } = this.props; return ( - Other download options + + Other download options + - {renderings.map(rendering => ( + {renderings.map((rendering) => ( ))} @@ -28,8 +31,5 @@ export default class ManifestDownloadLinks extends Component { } ManifestDownloadLinks.propTypes = { - classes: PropTypes.shape({ - h3: PropTypes.string, - }).isRequired, renderings: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types }; diff --git a/src/MiradorDownloadDialog.js b/src/MiradorDownloadDialog.js index 19fea94..75b5fda 100644 --- a/src/MiradorDownloadDialog.js +++ b/src/MiradorDownloadDialog.js @@ -1,18 +1,21 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { withStyles } from '@material-ui/core/styles'; -import Button from '@material-ui/core/Button'; -import Dialog from '@material-ui/core/Dialog'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogTitle from '@material-ui/core/DialogTitle'; -import Typography from '@material-ui/core/Typography'; -import { getCanvasLabel, getVisibleCanvases, selectInfoResponse } from 'mirador/dist/es/src/state/selectors/canvases'; +import { + getCanvasLabel, + getVisibleCanvases, + selectInfoResponse, +} from 'mirador/dist/es/src/state/selectors/canvases'; import { getWindowViewType } from 'mirador/dist/es/src/state/selectors/windows'; import { getManifestoInstance } from 'mirador/dist/es/src/state/selectors/manifests'; import { getContainerId } from 'mirador/dist/es/src/state/selectors/config'; import ScrollIndicatedDialogContent from 'mirador/dist/es/src/containers/ScrollIndicatedDialogContent'; -import CanvasDownloadLinks from './CanvasDownloadLinks'; +import Typography from '@mui/material/Typography'; +import Dialog from '@mui/material/Dialog'; +import DialogTitle from '@mui/material/DialogTitle'; +import DialogActions from '@mui/material/DialogActions'; +import Button from '@mui/material/Button'; import ManifestDownloadLinks from './ManifestDownloadLinks'; +import CanvasDownloadLinks from './CanvasDownloadLinks'; const mapDispatchToProps = (dispatch, { windowId }) => ({ closeDialog: () => dispatch({ type: 'CLOSE_WINDOW_DIALOG', windowId }), @@ -20,38 +23,40 @@ const mapDispatchToProps = (dispatch, { windowId }) => ({ const mapStateToProps = (state, { windowId }) => ({ canvases: getVisibleCanvases(state, { windowId }), - canvasLabel: canvasId => (getCanvasLabel(state, { canvasId, windowId })), + canvasLabel: (canvasId) => getCanvasLabel(state, { canvasId, windowId }), containerId: getContainerId(state), - infoResponse: canvasId => (selectInfoResponse(state, { windowId, canvasId }) || {}), + infoResponse: (canvasId) => selectInfoResponse(state, { windowId, canvasId }) || {}, manifest: getManifestoInstance(state, { windowId }), - restrictDownloadOnSizeDefinition: state.config.miradorDownloadPlugin - && state.config - .miradorDownloadPlugin - .restrictDownloadOnSizeDefinition, - open: (state.windowDialogs[windowId] && state.windowDialogs[windowId].openDialog === 'download'), + restrictDownloadOnSizeDefinition: + state.config.miradorDownloadPlugin + && state.config.miradorDownloadPlugin.restrictDownloadOnSizeDefinition, + open: + state.windowDialogs[windowId] + && state.windowDialogs[windowId].openDialog === 'download', viewType: getWindowViewType(state, { windowId }), }); - /** * MiradorDownloadDialog ~ -*/ + */ export class MiradorDownloadDialog extends Component { renderings() { const { manifest } = this.props; - if (!( - manifest - && manifest.getSequences() - && manifest.getSequences()[0] - && manifest.getSequences()[0].getRenderings() - )) return []; + if ( + !( + manifest + && manifest.getSequences() + && manifest.getSequences()[0] + && manifest.getSequences()[0].getRenderings() + ) + ) return []; return manifest.getSequences()[0].getRenderings(); } /** * Returns the rendered component - */ + */ render() { const { canvases, @@ -66,46 +71,48 @@ export class MiradorDownloadDialog extends Component { windowId, } = this.props; - if (!open) return (''); + if (!open) return ''; return ( - - - - Download - - - {canvases.map(canvas => ( - - ))} - {this.renderings().length > 0 - && - } - - - - - - + + + Download + + + {canvases.map((canvas) => ( + + ))} + {this.renderings().length > 0 && ( + + )} + + + + + ); } } @@ -117,7 +124,6 @@ MiradorDownloadDialog.propTypes = { ), classes: PropTypes.shape({ h2: PropTypes.string, - h3: PropTypes.string, }).isRequired, closeDialog: PropTypes.func.isRequired, containerId: PropTypes.string.isRequired, @@ -137,20 +143,11 @@ MiradorDownloadDialog.defaultProps = { restrictDownloadOnSizeDefinition: false, }; -const styles = () => ({ - h2: { - paddingBottom: 0, - }, - h3: { - marginTop: '20px', - }, -}); - export default { target: 'Window', mode: 'add', name: 'MiradorDownloadDialog', - component: withStyles(styles)(MiradorDownloadDialog), + component: MiradorDownloadDialog, mapDispatchToProps, mapStateToProps, }; diff --git a/src/RenderingDownloadLink.js b/src/RenderingDownloadLink.js index 9a4833d..72d5a83 100644 --- a/src/RenderingDownloadLink.js +++ b/src/RenderingDownloadLink.js @@ -1,25 +1,30 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import Link from '@material-ui/core/Link'; -import ListItem from '@material-ui/core/ListItem'; -import ListItemText from '@material-ui/core/ListItemText'; +import Link from '@mui/material/Link'; +import ListItem from '@mui/material/ListItem'; +import ListItemText from '@mui/material/ListItemText'; /** * RenderingDownloadLink ~ -*/ + */ export default class RenderingDownloadLink extends Component { render() { const { rendering } = this.props; return ( - + {rendering.getLabel().getValue()} {rendering.getFormat() && rendering.getFormat().value - && ` (${rendering.getFormat().value})` - } + && ` (${rendering.getFormat().value})`} ); diff --git a/src/index.js b/src/index.js index d2acaae..9b6653e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,12 +1,6 @@ import miradorDownloadPlugin from './miradorDownloadPlugin'; import MiradorDownloadDialogPlugin from './MiradorDownloadDialog'; -export { - miradorDownloadPlugin, - MiradorDownloadDialogPlugin, -}; +export { miradorDownloadPlugin, MiradorDownloadDialogPlugin }; -export default [ - miradorDownloadPlugin, - MiradorDownloadDialogPlugin, -]; +export default [miradorDownloadPlugin, MiradorDownloadDialogPlugin]; diff --git a/src/miradorDownloadPlugin.js b/src/miradorDownloadPlugin.js index 473685f..81cdbf4 100644 --- a/src/miradorDownloadPlugin.js +++ b/src/miradorDownloadPlugin.js @@ -1,10 +1,11 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import ListItemIcon from '@material-ui/core/ListItemIcon'; -import ListItemText from '@material-ui/core/ListItemText'; -import MenuItem from '@material-ui/core/MenuItem'; -import DownloadIcon from '@material-ui/icons/VerticalAlignBottomSharp'; +import MenuItem from '@mui/material/MenuItem'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import VerticalAlignBottomIcon from '@mui/icons-material/VerticalAlignBottom'; +// eslint-disable-next-line default-param-last const downloadDialogReducer = (state = {}, action) => { if (action.type === 'OPEN_WINDOW_DIALOG') { return { @@ -40,16 +41,14 @@ class MiradorDownload extends Component { render() { return ( - - this.openDialogAndCloseMenu()}> - - - - - Download - - - + this.openDialogAndCloseMenu()}> + + + + + Download + + ); } } diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..e77083c --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,96 @@ +const path = require('path'); +const fs = require('fs'); +const webpack = require('webpack'); +const TerserPlugin = require('terser-webpack-plugin'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +/** */ +const baseConfig = (mode) => ({ + entry: ['./src/index.js'], + module: { + rules: [ + { + include: path.resolve(fs.realpathSync(process.cwd()), '.'), // CRL + loader: require.resolve('babel-loader'), + options: { + // Save disk space when time isn't as important + cacheCompression: true, + cacheDirectory: true, + compact: true, + envName: mode, + }, + test: /\.(js|mjs|jsx)$/, + }, + { + test: /\.html$/, + loader: 'html-loader', + }, + ], + }, + optimization: { + minimizer: [ + new TerserPlugin({ + extractComments: true, + }), + ], + }, + output: { + filename: 'mirador-dl-plugin.js', + hashFunction: 'md5', + library: 'MiradorDlPlugin', + libraryExport: 'default', + libraryTarget: 'umd', + path: path.join(__dirname, 'umd'), + }, + plugins: [ + new webpack.IgnorePlugin({ + resourceRegExp: /@blueprintjs\/(core|icons)/, // ignore optional UI framework dependencies + }), + ], + resolve: { + fallback: { url: false }, + extensions: ['.js'], + }, +}); + +module.exports = (env, options) => { + const isProduction = options.mode === 'production'; + const config = baseConfig(options.mode); + + if (isProduction) { + return { + ...config, + devtool: 'source-map', + mode: 'production', + plugins: [ + ...(config.plugins || []), + new webpack.optimize.LimitChunkCountPlugin({ + maxChunks: 1, + }), + ], + }; + } + + return { + ...config, + output: { + filename: 'demo.js', + path: path.join(__dirname, 'demo/dist'), + publicPath: '/', + }, + devServer: { + hot: true, + port: 4444, + static: [ + './demo/dist/', + ], + }, + devtool: 'eval-source-map', + mode: 'development', + entry: ['./demo/src/index.js'], + plugins: [ + ...(config.plugins || []), + new HtmlWebpackPlugin({ template: path.join(__dirname, 'demo/src/index.html') }), + ], + }; +}; From d033b755ddb9f3e6f9bce84af7be4f73844d6a68 Mon Sep 17 00:00:00 2001 From: Fabian Stoehr Date: Thu, 1 Aug 2024 10:05:54 +0200 Subject: [PATCH 02/13] add webpack refresh plugin --- package.json | 1 + webpack.config.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/package.json b/package.json index f6fb95f..a1f7071 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@babel/preset-env": "^7.16.11", "@babel/preset-react": "^7.16.7", "@mui/material": "^5.x", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", "@testing-library/dom": "^9.2.0", "@testing-library/jest-dom": "^6.1.5", "@testing-library/react": "^14.1.0", diff --git a/webpack.config.js b/webpack.config.js index e77083c..0a7b911 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -3,6 +3,7 @@ const fs = require('fs'); const webpack = require('webpack'); const TerserPlugin = require('terser-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); +const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); /** */ const baseConfig = (mode) => ({ @@ -91,6 +92,7 @@ module.exports = (env, options) => { plugins: [ ...(config.plugins || []), new HtmlWebpackPlugin({ template: path.join(__dirname, 'demo/src/index.html') }), + new ReactRefreshWebpackPlugin(), ], }; }; From 890b6f190e7d5f6bc872cb9deed58a97cd2fa532 Mon Sep 17 00:00:00 2001 From: Fabian Stoehr Date: Thu, 1 Aug 2024 10:18:02 +0200 Subject: [PATCH 03/13] add emotion --- babel.config.js | 38 +++++++++++++++++++------------------- package.json | 6 ++++++ 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/babel.config.js b/babel.config.js index 8c77e76..d2c9247 100644 --- a/babel.config.js +++ b/babel.config.js @@ -70,24 +70,24 @@ module.exports = (api) => ({ removeImport: true, }, ], - // [ - // '@emotion', - // { - // importMap: { - // '@mui/system': { - // styled: { - // canonicalImport: ['@emotion/styled', 'default'], - // styledBaseImport: ['@mui/system', 'styled'], - // }, - // }, - // '@mui/material/styles': { - // styled: { - // canonicalImport: ['@emotion/styled', 'default'], - // styledBaseImport: ['@mui/material/styles', 'styled'], - // }, - // }, - // }, - // }, - // ], + [ + '@emotion', + { + importMap: { + '@mui/system': { + styled: { + canonicalImport: ['@emotion/styled', 'default'], + styledBaseImport: ['@mui/system', 'styled'], + }, + }, + '@mui/material/styles': { + styled: { + canonicalImport: ['@emotion/styled', 'default'], + styledBaseImport: ['@mui/material/styles', 'styled'], + }, + }, + }, + }, + ], ].filter(Boolean), }); diff --git a/package.json b/package.json index a1f7071..716b229 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,12 @@ "test:coverage": "jest --coverage", "test:watch": "jest --watch" }, + "dependencies": { + "@emotion/babel-plugin": "^11.11.0", + "@emotion/cache": "^11.11.0", + "@emotion/react": "^11.10.6", + "@emotion/styled": "^11.10.6" + }, "peerDependencies": { "@mui/material": "^5.x", "lodash": "^4.17.11", From e5d1dcdd4db43b254ebd3079e2bfcda6f1d96c04 Mon Sep 17 00:00:00 2001 From: Fabian Stoehr Date: Thu, 1 Aug 2024 12:44:04 +0200 Subject: [PATCH 04/13] add missing babel pluguins --- package.json | 1 + webpack.config.js | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 716b229..519be66 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.16.7", "@babel/preset-env": "^7.16.11", "@babel/preset-react": "^7.16.7", "@mui/material": "^5.x", diff --git a/webpack.config.js b/webpack.config.js index 0a7b911..ff36994 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,8 +2,8 @@ const path = require('path'); const fs = require('fs'); const webpack = require('webpack'); const TerserPlugin = require('terser-webpack-plugin'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); /** */ const baseConfig = (mode) => ({ @@ -36,9 +36,9 @@ const baseConfig = (mode) => ({ ], }, output: { - filename: 'mirador-dl-plugin.js', + filename: 'mirador-image-tools.js', hashFunction: 'md5', - library: 'MiradorDlPlugin', + library: 'MiradorImageTools', libraryExport: 'default', libraryTarget: 'umd', path: path.join(__dirname, 'umd'), From 752e5543ea61ae1f0a1239cec31f45c48a4e212f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerd=20M=C3=BCller?= Date: Thu, 24 Oct 2024 12:43:41 +0200 Subject: [PATCH 05/13] fix: add eslint-plugin-react-hooks dependency --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 519be66..fc627a8 100644 --- a/package.json +++ b/package.json @@ -44,8 +44,8 @@ "@babel/plugin-proposal-object-rest-spread": "^7.20.7", "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", - "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.16.7", + "@babel/plugin-transform-runtime": "^7.24.7", "@babel/preset-env": "^7.16.11", "@babel/preset-react": "^7.16.7", "@mui/material": "^5.x", @@ -65,6 +65,7 @@ "eslint-plugin-jest": "^27.1.5", "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-react": "^7.29.4", + "eslint-plugin-react-hooks": "^5.0.0", "html-loader": "^5.0.0", "html-webpack-plugin": "^5.6.0", "jest": "^29.3.1", From bf3e4a4d9767d6904823a3d28bc683922cf66315 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerd=20M=C3=BCller?= Date: Thu, 24 Oct 2024 14:27:34 +0200 Subject: [PATCH 06/13] test: adjust canvas download link tests --- __tests__/CanvasDownloadLinks.test.js | 73 ++++++++++++++----------- __tests__/ManifestDownloadLinks.test.js | 1 - package.json | 4 +- src/MiradorDownloadDialog.js | 4 +- 4 files changed, 45 insertions(+), 37 deletions(-) diff --git a/__tests__/CanvasDownloadLinks.test.js b/__tests__/CanvasDownloadLinks.test.js index 850f609..f714a1b 100644 --- a/__tests__/CanvasDownloadLinks.test.js +++ b/__tests__/CanvasDownloadLinks.test.js @@ -1,8 +1,10 @@ import React from 'react'; import CanvasDownloadLinks from '../src/CanvasDownloadLinks'; - import { render, screen } from './test-utils'; +/** + * Helper function to render the CanvasDownloadLinks component with custom props. + */ function createWrapper(props) { return render( { }; let currentBoundsSpy; + beforeEach(() => { currentBoundsSpy = jest.spyOn(CanvasDownloadLinks.prototype, 'currentBounds'); }); @@ -46,59 +49,64 @@ describe('CanvasDownloadLinks', () => { currentBoundsSpy.mockRestore(); }); - it('renders canvas label in an h3 typography', () => { + it('renders the canvas label as an h3 heading', () => { createWrapper({ canvas }); - screen.getByRole('heading'); const headingElement = screen.getByText('My Canvas Label'); expect(headingElement).toBeInTheDocument(); expect(headingElement.tagName).toBe('H3'); }); - it('renders canvas level renderings', () => { + it('renders canvas-level renderings', () => { createWrapper({ canvas }); - const dlElement = screen.getByRole('link', { name: /Whole image \(4000 x 1000px\)/i }); - expect(dlElement).toBeInTheDocument(); + const downloadLink = screen.getByRole('link', { name: /Whole image \(4000 x 1000px\)/i }); + expect(downloadLink).toBeInTheDocument(); }); - describe('Zoomed region link', () => { + describe('Zoomed region link behavior', () => { const infoResponse = { json: { width: 4000, height: 1000 }, }; - it('does not render a link when the viewer is zoomed out/at the entire image', () => { + it('does not render a zoom link when the viewer is zoomed out to the entire image', () => { currentBoundsSpy.mockImplementation(() => ({ x: 0, y: 0, width: 6000, height: 1000, })); createWrapper({ canvas, infoResponse, windowId: 'zoomedOutWindow' }); + const zoomedLink = screen.queryByText('Zoomed region (6000 x 1000px)'); expect(zoomedLink).not.toBeInTheDocument(); }); - it('does not render a link when the viewer is zoomed into non-image space (e.g. a reponse the image server cannot handle)', () => { + it('does not render a link when zoomed into non-image space', () => { currentBoundsSpy.mockImplementation(() => ({ x: -100, y: 100, width: 2000, height: 500, })); + createWrapper({ canvas, infoResponse, windowId: 'zoomedIntoNonImageSpaceWindow' }); + const zoomedLink = screen.queryByText('Zoomed region (2000 x 500px)'); expect(zoomedLink).not.toBeInTheDocument(); }); - it('is present when the viewer is zoomed into the image', () => { + it('renders a zoomed region link when zoomed into the image', () => { currentBoundsSpy.mockImplementation(() => ({ x: 0, y: 0, width: 2000, height: 500, })); + createWrapper({ canvas, infoResponse, windowId: 'zoomedInWindow' }); + const zoomedLink = screen.queryByText('Zoomed region (2000 x 500px)'); expect(zoomedLink).toBeInTheDocument(); }); - it('is not present when the window is in book or gallery view (only single view)', () => { + it('does not render a zoomed region link in non-single view types (e.g., book or gallery view)', () => { currentBoundsSpy.mockImplementation(() => ({ x: 0, y: 0, width: 2000, height: 500, })); + createWrapper({ canvas, infoResponse, viewType: 'book', windowId: 'zoomedInWindow', }); @@ -112,37 +120,39 @@ describe('CanvasDownloadLinks', () => { expect(zoomedLinkGallery).not.toBeInTheDocument(); }); - describe('when the zoom link is set to be restricted', () => { - it('has just the whole image link from the sizes and does not present a zoomed region link', () => { + describe('when zoom link is restricted', () => { + it('only renders a whole image link based on the available sizes', () => { createWrapper({ canvas, infoResponse: { json: { width: 4000, height: 1000, - sizes: [{ - width: 400, - height: 100, - }], + sizes: [{ width: 400, height: 100 }], }, }, restrictDownloadOnSizeDefinition: true, windowId: 'zoomedInWindow', }); - const links = screen.getByRole('link', { name: /Whole image \(400 x 100px\)/i }); - expect(links).toBeInTheDocument(); + + const downloadLink = screen.getByRole('link', { name: /Whole image \(400 x 100px\)/i }); + expect(downloadLink).toBeInTheDocument(); }); }); }); - describe('when there is are sizes defined in the infoResponse', () => { + describe('when sizes are defined in the infoResponse', () => { const sizes = [ { width: 4000, height: 1000 }, { width: 2000, height: 500 }, { width: 1000, height: 250 }, ]; - it('uses those sizes for links in the download dialog', () => { + + // TODO: Fix the tests that fails because of OSDReferences.get(windowId).current.viewport + // eslint-disable-next-line jest/no-disabled-tests + it.skip('renders download links for all sizes in the dialog', () => { createWrapper({ canvas, infoResponse: { json: { sizes } } }); + const link1 = screen.getByRole('link', { name: /Whole image \(4000 x 1000px\)/i }); const link2 = screen.getByRole('link', { name: /Whole image \(2000 x 500px\)/i }); const link3 = screen.getByRole('link', { name: /Whole image \(1000 x 250px\)/i }); @@ -153,35 +163,34 @@ describe('CanvasDownloadLinks', () => { }); }); - describe('when there are no defined sizes', () => { - it('renders a link to the whole image', () => { + describe('when no sizes are defined', () => { + it('renders a link to the full-size image', () => { createWrapper({ canvas }); + const link = screen.getByRole('link', { name: /Whole image \(4000 x 1000px\)/i }); expect(link).toBeInTheDocument(); expect(link).toHaveAttribute('href', 'http://example.com/iiif/abc123/full/full/0/default.jpg?download=true'); }); - describe('when the image is > 1000px wide', () => { - it('renders a link to a small image (1000px wide), and calculates the correct height', () => { + describe('when the image width exceeds 1000px', () => { + it('renders links to both the full-size and smaller (1000px wide) versions', () => { createWrapper({ canvas }); - const links = screen.getAllByRole('link'); - - expect(links).toBeInTheDocument(); const link1 = screen.getByRole('link', { name: /Whole image \(4000 x 1000px\)/i }); - expect(link1).toHaveAttribute('href', 'http://example.com/iiif/abc123/full/1000,/0/default.jpg?download=true'); + expect(link1).toHaveAttribute('href', 'http://example.com/iiif/abc123/full/full/0/default.jpg?download=true'); const link2 = screen.getByRole('link', { name: /Whole image \(1000 x 250px\)/i }); expect(link2).toHaveAttribute('href', 'http://example.com/iiif/abc123/full/1000,/0/default.jpg?download=true'); }); }); - describe('when the image is < 1000px wide', () => { - it('does not render a link to a small image', () => { + describe('when the image width is less than 1000px', () => { + it('does not render a link to a smaller version', () => { canvas.getWidth = () => 999; createWrapper({ canvas }); + const links = screen.getAllByRole('link'); - expect(links).toHaveLength(2); + expect(links).toHaveLength(2); // Should only show full-size versions. }); }); }); diff --git a/__tests__/ManifestDownloadLinks.test.js b/__tests__/ManifestDownloadLinks.test.js index 370c953..4b9ffc4 100644 --- a/__tests__/ManifestDownloadLinks.test.js +++ b/__tests__/ManifestDownloadLinks.test.js @@ -28,7 +28,6 @@ describe('ManifestDownloadLinks', () => { it('renders the heading', () => { createWrapper({ renderings }); - screen.debug(); screen.getByRole('heading'); const headingElement = screen.getByText('Other download options'); diff --git a/package.json b/package.json index fc627a8..0dac909 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "peerDependencies": { "@mui/material": "^5.x", "lodash": "^4.17.11", - "mirador": "^4.0.0-alpha.1", + "mirador": "^4.0.0-alpha.2", "react": "18.x", "react-dom": "18.x" }, @@ -65,7 +65,7 @@ "eslint-plugin-jest": "^27.1.5", "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-react": "^7.29.4", - "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-hooks": "^4.3.0", "html-loader": "^5.0.0", "html-webpack-plugin": "^5.6.0", "jest": "^29.3.1", diff --git a/src/MiradorDownloadDialog.js b/src/MiradorDownloadDialog.js index 75b5fda..cb566c1 100644 --- a/src/MiradorDownloadDialog.js +++ b/src/MiradorDownloadDialog.js @@ -83,8 +83,8 @@ export class MiradorDownloadDialog extends Component { fullWidth maxWidth="xs" > - - Download + + Download {canvases.map((canvas) => ( From 30853a0957671a186c32e7682d42bcba7a9a6f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerd=20M=C3=BCller?= Date: Thu, 24 Oct 2024 14:38:57 +0200 Subject: [PATCH 07/13] chore: extend npm build script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0dac909..62b3460 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "umd" ], "scripts": { - "build": "npm run build:umd", + "build": "npm run build:umd && npm run build:demo", "build:demo": "NODE_ENV=development webpack --mode=development", "build:umd": "NODE_ENV=production webpack --mode=production", "build:es": "mkdir -p es && cp -r src/* es && NODE_ENV=production MODULE_FORMAT=es npx babel es -d es", From c9a6be72d360f4ede1772d93d644f1fd6e4a1ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerd=20M=C3=BCller?= Date: Thu, 7 Nov 2024 11:06:02 +0100 Subject: [PATCH 08/13] test: add currentBoundsSpy --- __tests__/CanvasDownloadLinks.test.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/__tests__/CanvasDownloadLinks.test.js b/__tests__/CanvasDownloadLinks.test.js index f714a1b..74225e0 100644 --- a/__tests__/CanvasDownloadLinks.test.js +++ b/__tests__/CanvasDownloadLinks.test.js @@ -1,4 +1,5 @@ import React from 'react'; +import { OSDReferences } from 'mirador/dist/es/src/plugins/OSDReferences'; import CanvasDownloadLinks from '../src/CanvasDownloadLinks'; import { render, screen } from './test-utils'; @@ -148,9 +149,15 @@ describe('CanvasDownloadLinks', () => { { width: 1000, height: 250 }, ]; - // TODO: Fix the tests that fails because of OSDReferences.get(windowId).current.viewport - // eslint-disable-next-line jest/no-disabled-tests - it.skip('renders download links for all sizes in the dialog', () => { + const viewport = { + getBounds: () => ({ + x: 0, y: 0, width: 4000, height: 1000, + }), + }; + OSDReferences.set('wid123', { + current: { viewport }, + }); + it('renders download links for all sizes in the dialog', () => { createWrapper({ canvas, infoResponse: { json: { sizes } } }); const link1 = screen.getByRole('link', { name: /Whole image \(4000 x 1000px\)/i }); From 060c8f0db223e525ec316870fabab8c76ddbacd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerd=20M=C3=BCller?= Date: Thu, 7 Nov 2024 11:13:24 +0100 Subject: [PATCH 09/13] chore: harmonizing npm scripts --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 62b3460..392541f 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,10 @@ "build:umd": "NODE_ENV=production webpack --mode=production", "build:es": "mkdir -p es && cp -r src/* es && NODE_ENV=production MODULE_FORMAT=es npx babel es -d es", "build:cjs": "mkdir -p lib && cp -r src/* lib && NODE_ENV=production MODULE_FORMAT=cjs npx babel lib -d lib", - "parcel": "parcel demo/src/index.html", + "clean": "rm -rf ./umd && rm -rf ./es && rm -rf ./lib && rm -rf ./demo/dist", "lint": "eslint ./src ./__tests__", - "prepublishOnly": "npm run build", + "prepublishOnly": "npm run clean && npm run build:es && npm run build:cjs && npm run build", + "start": "NODE_ENV=development webpack serve --open", "test": "npm run lint && jest", "test:coverage": "jest --coverage", "test:watch": "jest --watch" @@ -32,7 +33,7 @@ "peerDependencies": { "@mui/material": "^5.x", "lodash": "^4.17.11", - "mirador": "^4.0.0-alpha.2", + "mirador": "^4.0.0-alpha.1", "react": "18.x", "react-dom": "18.x" }, @@ -73,7 +74,6 @@ "jest-puppeteer": "^9.0.2", "lodash": "^4.17.15", "mirador": "^4.0.0-alpha.1", - "parcel-bundler": "^1.12.4", "react": "^18.0.0", "react-dom": "^18.0.0", "terser-webpack-plugin": "^5.3.1", From 32d5949719b628f1bdefc5378172409b6fac67fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerd=20M=C3=BCller?= Date: Thu, 7 Nov 2024 12:03:22 +0100 Subject: [PATCH 10/13] test: add missing tests --- __tests__/CanvasDownloadLinks.test.js | 3 ++- __tests__/MiradorDownloadDialog.test.js | 15 ++++++++++++++- __tests__/miradorDownloadPlugin.test.js | 14 +++++++++++++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/__tests__/CanvasDownloadLinks.test.js b/__tests__/CanvasDownloadLinks.test.js index 74225e0..c7428ec 100644 --- a/__tests__/CanvasDownloadLinks.test.js +++ b/__tests__/CanvasDownloadLinks.test.js @@ -137,6 +137,7 @@ describe('CanvasDownloadLinks', () => { }); const downloadLink = screen.getByRole('link', { name: /Whole image \(400 x 100px\)/i }); + expect(screen.getAllByRole('link')).toHaveLength(2); // Should only show small-size version and link to PDF. expect(downloadLink).toBeInTheDocument(); }); }); @@ -197,7 +198,7 @@ describe('CanvasDownloadLinks', () => { createWrapper({ canvas }); const links = screen.getAllByRole('link'); - expect(links).toHaveLength(2); // Should only show full-size versions. + expect(links).toHaveLength(2); // Should only show full-size version and link to PDF. }); }); }); diff --git a/__tests__/MiradorDownloadDialog.test.js b/__tests__/MiradorDownloadDialog.test.js index ae20bda..66fc45f 100644 --- a/__tests__/MiradorDownloadDialog.test.js +++ b/__tests__/MiradorDownloadDialog.test.js @@ -1,6 +1,6 @@ import React from 'react'; import miradorDownloadDialog from '../src/MiradorDownloadDialog'; -import { render, screen } from './test-utils'; +import { fireEvent, render, screen } from './test-utils'; /** Utility function to wrap */ function createWrapper(props) { @@ -22,6 +22,11 @@ function createWrapper(props) { } describe('Dialog', () => { + it('does not render anything if the open prop is false', () => { + createWrapper({ open: false }); + expect(screen.queryByTestId('dialog-content')).toBeNull(); + }); + it('renders a CanvasDownloadLinks component for every canvas', () => { const mockCanvas = (id) => ({ id, @@ -42,6 +47,14 @@ describe('Dialog', () => { expect(headingXyz.tagName).toBe('H3'); }); + it('has a close button that triggers the closeDialog prop', async () => { + const closeDialog = jest.fn(); + createWrapper({ closeDialog }); + const closeButton = await screen.findByText(/Close/); + fireEvent.click(closeButton); + expect(closeDialog).toHaveBeenCalled(); + }); + describe('ManifestDownloadLinks', () => { it('is not rendered if the manifest has no renderings', () => { createWrapper(); diff --git a/__tests__/miradorDownloadPlugin.test.js b/__tests__/miradorDownloadPlugin.test.js index d7c9281..fe362e4 100644 --- a/__tests__/miradorDownloadPlugin.test.js +++ b/__tests__/miradorDownloadPlugin.test.js @@ -1,6 +1,6 @@ import React from 'react'; import miradorDownloadPlugin from '../src/miradorDownloadPlugin'; -import { render, screen } from './test-utils'; +import { fireEvent, render, screen } from './test-utils'; function createWrapper(props) { return render( @@ -24,3 +24,15 @@ describe('miradorDownloadPlugin', () => { }); }); }); + +describe('MenuItem', () => { + it('calls the openShareDialog and handleClose props when clicked', async () => { + const handleClose = jest.fn(); + const openDownloadDialog = jest.fn(); + createWrapper({ handleClose, openDownloadDialog }); + const openDownloadDialogButton = await screen.findByText(/Download/); + fireEvent.click(openDownloadDialogButton); + expect(handleClose).toHaveBeenCalled(); + expect(openDownloadDialog).toHaveBeenCalled(); + }); +}); From 305341374a6b5b05ae6816e510a6e97129809516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerd=20M=C3=BCller?= Date: Thu, 7 Nov 2024 12:03:50 +0100 Subject: [PATCH 11/13] refactor: remove classes prop --- .eslintrc | 2 +- src/MiradorDownloadDialog.js | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.eslintrc b/.eslintrc index 42cdf4f..e3c328d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -9,7 +9,7 @@ "rules": { "import/prefer-default-export": "off", "import/no-extraneous-dependencies": "off", - "no-console": "off", + "no-console": "warn", "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], "react/jsx-fragments": "off", "react/jsx-props-no-spreading": "off", diff --git a/src/MiradorDownloadDialog.js b/src/MiradorDownloadDialog.js index cb566c1..c102790 100644 --- a/src/MiradorDownloadDialog.js +++ b/src/MiradorDownloadDialog.js @@ -61,7 +61,6 @@ export class MiradorDownloadDialog extends Component { const { canvases, canvasLabel, - classes, closeDialog, containerId, infoResponse, @@ -75,6 +74,7 @@ export class MiradorDownloadDialog extends Component { return ( 0 && ( )} @@ -122,9 +121,6 @@ MiradorDownloadDialog.propTypes = { canvases: PropTypes.arrayOf( PropTypes.shape({ id: PropTypes.string, index: PropTypes.number }), ), - classes: PropTypes.shape({ - h2: PropTypes.string, - }).isRequired, closeDialog: PropTypes.func.isRequired, containerId: PropTypes.string.isRequired, infoResponse: PropTypes.func.isRequired, From 11ed210b03c4ff10fa653195edf2d294f6ac0d5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerd=20M=C3=BCller?= Date: Thu, 7 Nov 2024 12:12:06 +0100 Subject: [PATCH 12/13] test: consolidated naming style for test cases --- __tests__/CanvasDownloadLinks.test.js | 40 +++++++++++++------------ __tests__/ManifestDownloadLinks.test.js | 4 +-- __tests__/MiradorDownloadDialog.test.js | 13 ++++---- __tests__/RenderingDownloadLink.test.js | 4 +-- __tests__/miradorDownloadPlugin.test.js | 8 ++--- 5 files changed, 36 insertions(+), 33 deletions(-) diff --git a/__tests__/CanvasDownloadLinks.test.js b/__tests__/CanvasDownloadLinks.test.js index c7428ec..8a14e5d 100644 --- a/__tests__/CanvasDownloadLinks.test.js +++ b/__tests__/CanvasDownloadLinks.test.js @@ -58,19 +58,21 @@ describe('CanvasDownloadLinks', () => { expect(headingElement.tagName).toBe('H3'); }); - it('renders canvas-level renderings', () => { - createWrapper({ canvas }); + describe('Canvas Renderings', () => { + it('includes a canvas-level rendering as a download link', () => { + createWrapper({ canvas }); - const downloadLink = screen.getByRole('link', { name: /Whole image \(4000 x 1000px\)/i }); - expect(downloadLink).toBeInTheDocument(); + const downloadLink = screen.getByRole('link', { name: /Whole image \(4000 x 1000px\)/i }); + expect(downloadLink).toBeInTheDocument(); + }); }); - describe('Zoomed region link behavior', () => { + describe('Zoomed Region Links', () => { const infoResponse = { json: { width: 4000, height: 1000 }, }; - it('does not render a zoom link when the viewer is zoomed out to the entire image', () => { + it('does not render a zoom link when viewer is zoomed out to full image', () => { currentBoundsSpy.mockImplementation(() => ({ x: 0, y: 0, width: 6000, height: 1000, })); @@ -81,7 +83,7 @@ describe('CanvasDownloadLinks', () => { expect(zoomedLink).not.toBeInTheDocument(); }); - it('does not render a link when zoomed into non-image space', () => { + it('does not render a zoom link when zoomed into an area outside of the image bounds', () => { currentBoundsSpy.mockImplementation(() => ({ x: -100, y: 100, width: 2000, height: 500, })); @@ -92,7 +94,7 @@ describe('CanvasDownloadLinks', () => { expect(zoomedLink).not.toBeInTheDocument(); }); - it('renders a zoomed region link when zoomed into the image', () => { + it('renders a zoomed region link when zoomed into a valid area of the image', () => { currentBoundsSpy.mockImplementation(() => ({ x: 0, y: 0, width: 2000, height: 500, })); @@ -103,7 +105,7 @@ describe('CanvasDownloadLinks', () => { expect(zoomedLink).toBeInTheDocument(); }); - it('does not render a zoomed region link in non-single view types (e.g., book or gallery view)', () => { + it('does not render a zoomed region link in non-single view types (e.g., book, gallery views)', () => { currentBoundsSpy.mockImplementation(() => ({ x: 0, y: 0, width: 2000, height: 500, })); @@ -121,8 +123,8 @@ describe('CanvasDownloadLinks', () => { expect(zoomedLinkGallery).not.toBeInTheDocument(); }); - describe('when zoom link is restricted', () => { - it('only renders a whole image link based on the available sizes', () => { + describe('Download Link Size Restrictions', () => { + it('renders only a single download link based on the restricted sizes', () => { createWrapper({ canvas, infoResponse: { @@ -143,7 +145,7 @@ describe('CanvasDownloadLinks', () => { }); }); - describe('when sizes are defined in the infoResponse', () => { + describe('When Defined Sizes Are Present in infoResponse', () => { const sizes = [ { width: 4000, height: 1000 }, { width: 2000, height: 500 }, @@ -158,7 +160,7 @@ describe('CanvasDownloadLinks', () => { OSDReferences.set('wid123', { current: { viewport }, }); - it('renders download links for all sizes in the dialog', () => { + it('renders download links for all specified sizes in the dialog', () => { createWrapper({ canvas, infoResponse: { json: { sizes } } }); const link1 = screen.getByRole('link', { name: /Whole image \(4000 x 1000px\)/i }); @@ -171,8 +173,8 @@ describe('CanvasDownloadLinks', () => { }); }); - describe('when no sizes are defined', () => { - it('renders a link to the full-size image', () => { + describe('When No Sizes Are Defined in infoResponse', () => { + it('renders a single link to the full-size image', () => { createWrapper({ canvas }); const link = screen.getByRole('link', { name: /Whole image \(4000 x 1000px\)/i }); @@ -180,8 +182,8 @@ describe('CanvasDownloadLinks', () => { expect(link).toHaveAttribute('href', 'http://example.com/iiif/abc123/full/full/0/default.jpg?download=true'); }); - describe('when the image width exceeds 1000px', () => { - it('renders links to both the full-size and smaller (1000px wide) versions', () => { + describe('For Images Wider Than 1000px', () => { + it('renders links for both full-size and 1000px wide versions', () => { createWrapper({ canvas }); const link1 = screen.getByRole('link', { name: /Whole image \(4000 x 1000px\)/i }); @@ -192,8 +194,8 @@ describe('CanvasDownloadLinks', () => { }); }); - describe('when the image width is less than 1000px', () => { - it('does not render a link to a smaller version', () => { + describe('For Images Less Than 1000px Wide', () => { + it('does not render a smaller version link if image is under 1000px wide', () => { canvas.getWidth = () => 999; createWrapper({ canvas }); diff --git a/__tests__/ManifestDownloadLinks.test.js b/__tests__/ManifestDownloadLinks.test.js index 4b9ffc4..4897ffd 100644 --- a/__tests__/ManifestDownloadLinks.test.js +++ b/__tests__/ManifestDownloadLinks.test.js @@ -26,7 +26,7 @@ describe('ManifestDownloadLinks', () => { }, ]; - it('renders the heading', () => { + it('displays the heading "Other download options" as an H3 element', () => { createWrapper({ renderings }); screen.getByRole('heading'); @@ -35,7 +35,7 @@ describe('ManifestDownloadLinks', () => { expect(headingElement.tagName).toBe('H3'); }); - it('renders a RenderingDownloadLink for each rendering', () => { + it('renders a download link for each item in the renderings list', () => { createWrapper({ renderings }); const pdfLinkElement = screen.getByRole('link', { name: /Link to the PDF/i }); diff --git a/__tests__/MiradorDownloadDialog.test.js b/__tests__/MiradorDownloadDialog.test.js index 66fc45f..ccb7a30 100644 --- a/__tests__/MiradorDownloadDialog.test.js +++ b/__tests__/MiradorDownloadDialog.test.js @@ -22,12 +22,12 @@ function createWrapper(props) { } describe('Dialog', () => { - it('does not render anything if the open prop is false', () => { + it('does not render content when the open prop is false', () => { createWrapper({ open: false }); expect(screen.queryByTestId('dialog-content')).toBeNull(); }); - it('renders a CanvasDownloadLinks component for every canvas', () => { + it('renders a CanvasDownloadLinks component with headings for each canvas', () => { const mockCanvas = (id) => ({ id, getHeight: () => 4000, @@ -47,7 +47,7 @@ describe('Dialog', () => { expect(headingXyz.tagName).toBe('H3'); }); - it('has a close button that triggers the closeDialog prop', async () => { + it('calls the closeDialog function when the close button is clicked', async () => { const closeDialog = jest.fn(); createWrapper({ closeDialog }); const closeButton = await screen.findByText(/Close/); @@ -56,12 +56,13 @@ describe('Dialog', () => { }); describe('ManifestDownloadLinks', () => { - it('is not rendered if the manifest has no renderings', () => { + it('does not render when there are no manifest renderings', () => { createWrapper(); const manifestLinks = screen.queryByText('ManifestDownloadLinks'); expect(manifestLinks).not.toBeInTheDocument(); }); - it('rendered if the manifest has renderings', () => { + + it('renders when the manifest contains renderings', () => { const rendering = { id: '', getLabel: () => ({ getValue: () => 'ManifestDownloadLinks' }), getFormat: () => {} }; createWrapper({ manifest: { @@ -151,7 +152,7 @@ describe('mapStateToProps', () => { const mapStateToProps = miradorDownloadDialog.mapStateToProps(state, props); describe('infoResponse', () => { - it('gets the correct info response from state', () => { + it('fetches the correct info response for the given canvas ID', () => { expect(mapStateToProps.infoResponse('http://example.com/abc123/canvas/0').json.sizes.length).toBe(6); }); }); diff --git a/__tests__/RenderingDownloadLink.test.js b/__tests__/RenderingDownloadLink.test.js index 0eb96a0..d46ac9e 100644 --- a/__tests__/RenderingDownloadLink.test.js +++ b/__tests__/RenderingDownloadLink.test.js @@ -18,14 +18,14 @@ describe('RenderingDownloadLink', () => { getFormat: () => ({ value: 'application/pdf' }), }; - it('renders a Link for the rendering', () => { + it('displays a download link with the label text from the rendering', () => { createWrapper({ rendering }); const link = screen.getByRole('link', { name: /Link to the PDF/i }); expect(link).toBeInTheDocument(); }); - it('links the label and includes the format (unlinked)', () => { + it('renders the download link with the correct URL and format information', () => { createWrapper({ rendering }); const link = screen.getByRole('link', { name: /Link to the PDF/i }); diff --git a/__tests__/miradorDownloadPlugin.test.js b/__tests__/miradorDownloadPlugin.test.js index fe362e4..6c6516b 100644 --- a/__tests__/miradorDownloadPlugin.test.js +++ b/__tests__/miradorDownloadPlugin.test.js @@ -13,11 +13,11 @@ function createWrapper(props) { } describe('miradorDownloadPlugin', () => { - it('has the correct target', () => { + it('sets the correct target to "WindowTopBarPluginMenu"', () => { expect(miradorDownloadPlugin.target).toBe('WindowTopBarPluginMenu'); }); - describe('renders a component', () => { - it('renders a thing', () => { + describe('Component Rendering', () => { + it('displays a "Download" element when rendered', () => { createWrapper(); const downloadElement = screen.queryByText(/Download/i); expect(downloadElement).toBeInTheDocument(); @@ -26,7 +26,7 @@ describe('miradorDownloadPlugin', () => { }); describe('MenuItem', () => { - it('calls the openShareDialog and handleClose props when clicked', async () => { + it('triggers both openDownloadDialog and handleClose when "Download" is clicked', async () => { const handleClose = jest.fn(); const openDownloadDialog = jest.fn(); createWrapper({ handleClose, openDownloadDialog }); From c89a97159c160d5010a22ad14fe38be618f7a857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerd=20M=C3=BCller?= Date: Thu, 7 Nov 2024 12:21:19 +0100 Subject: [PATCH 13/13] fix: adjust download menu item --- src/miradorDownloadPlugin.js | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/miradorDownloadPlugin.js b/src/miradorDownloadPlugin.js index 7797cbe..81cdbf4 100644 --- a/src/miradorDownloadPlugin.js +++ b/src/miradorDownloadPlugin.js @@ -40,19 +40,15 @@ class MiradorDownload extends Component { } render() { - const { handleClose, openDownloadDialog, ...menuProps } = this.props; - return ( - - this.openDialogAndCloseMenu()}> - - - - - Download - - - + this.openDialogAndCloseMenu()}> + + + + + Download + + ); } }