From b256f91ec29376ae7619219833241cdd5e557f83 Mon Sep 17 00:00:00 2001 From: Benedikt Seidl Date: Mon, 17 Jun 2024 09:14:05 +0200 Subject: [PATCH 1/3] Setup testing framework --- .eslintrc.js | 13 +++++++++++-- .gitignore | 2 ++ .babelrc.json => babel.config.json | 7 +------ jest.config.json | 20 ++++++++++++++++++++ package.json | 16 ++++++++++++++-- tests/jest.setup.js | 1 + tests/libs/openlayers.test.js | 5 +++++ tests/mocks/AssetMock.js | 1 + 8 files changed, 55 insertions(+), 10 deletions(-) rename .babelrc.json => babel.config.json (61%) create mode 100644 jest.config.json create mode 100644 tests/jest.setup.js create mode 100644 tests/libs/openlayers.test.js create mode 100644 tests/mocks/AssetMock.js diff --git a/.eslintrc.js b/.eslintrc.js index 2ede77591..6825127d3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,7 +2,7 @@ module.exports = { parser: "@babel/eslint-parser", // https://github.com/babel/babel/tree/main/eslint/babel-eslint-parser parserOptions: { babelOptions: { - configFile: "./.babelrc.json" + configFile: "./babel.config.json" }, ecmaFeatures: { arrowFunctions: true, @@ -25,7 +25,10 @@ module.exports = { }, plugins: [ "perfectionist", // https://github.com/azat-io/eslint-plugin-perfectionist - "react" // https://github.com/yannickcr/eslint-plugin-react + "react", // https://github.com/yannickcr/eslint-plugin-react + "jest", // https://github.com/jest-community/eslint-plugin-jest + "jest-dom", // https://github.com/testing-library/eslint-plugin-jest-dom + "testing-library" // https://github.com/testing-library/eslint-plugin-testing-library ], extends: [ "eslint:recommended", @@ -36,6 +39,12 @@ module.exports = { browser: true, // browser global variables node: true // Node.js global variables and Node.js-specific rules }, + overrides: [ + { + files: ["tests/**/*"], + env: { jest: true } + } + ], settings: { react: { version: "detect" diff --git a/.gitignore b/.gitignore index aa1c83173..399c86878 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ node_modules/ +coverage/ +.idea/ npm-debug.log # On a fresh install with yarn 3.3.0 these extra files were generated. diff --git a/.babelrc.json b/babel.config.json similarity index 61% rename from .babelrc.json rename to babel.config.json index fe63b8a84..89c452411 100644 --- a/.babelrc.json +++ b/babel.config.json @@ -4,12 +4,7 @@ "@babel/plugin-transform-object-rest-spread" ], "presets": [ - [ - "@babel/preset-env", - { - "modules": false - } - ], + "@babel/preset-env", "@babel/preset-react" ] } diff --git a/jest.config.json b/jest.config.json new file mode 100644 index 000000000..070c3c04c --- /dev/null +++ b/jest.config.json @@ -0,0 +1,20 @@ +{ + "collectCoverageFrom": [ + "./**/*.{js,jsx}" + ], + "coverageDirectory": "coverage", + "moduleNameMapper": { + "^openlayers$": "/libs/openlayers.js", + "\\.(css|less|svg|png)$": "/tests/mocks/AssetMock.js" + }, + "setupFilesAfterEnv": [ + "/tests/jest.setup.js" + ], + "testEnvironment": "jsdom", + "testMatch": [ + "/tests/**/*.test.(js|jsx|ts|tsx)" + ], + "transformIgnorePatterns": [ + "node_modules/(?!(color-name|color-parse|color-rgba|color-space|flat|ol|ol-ext)/)" + ] +} diff --git a/package.json b/package.json index 630893c4b..1a7f6c760 100644 --- a/package.json +++ b/package.json @@ -73,16 +73,28 @@ "@babel/preset-env": "^7.24.5", "@babel/preset-react": "^7.24.1", "@furkot/webfonts-generator": "^2.0.2", + "@testing-library/dom": "^10.1.0", + "@testing-library/jest-dom": "^6.4.6", + "@testing-library/react": "^16.0.0", "@types/react": "^18.3.1", "eslint": "^8.56.0", + "eslint-plugin-jest": "^28.6.0", + "eslint-plugin-jest-dom": "^5.4.0", "eslint-plugin-perfectionist": "^2.10.0", - "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react": "^7.34.1", + "eslint-plugin-testing-library": "^6.2.2", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", "mkdirp": "^3.0.1", "object-path": "^0.11.8", "react-docgen": "^5.4.3", + "redux-mock-store": "^1.5.4", "typescript": "^5.4.5" }, "scripts": { - "plugindoc": "node scripts/gen-plugin-docs.js" + "plugindoc": "node scripts/gen-plugin-docs.js", + "lint": "eslint . --config .eslintrc.js --ext .js,.jsx,.ts,.tsx", + "test": "jest", + "coverage": "jest --coverage" } } diff --git a/tests/jest.setup.js b/tests/jest.setup.js new file mode 100644 index 000000000..7b0828bfa --- /dev/null +++ b/tests/jest.setup.js @@ -0,0 +1 @@ +import '@testing-library/jest-dom'; diff --git a/tests/libs/openlayers.test.js b/tests/libs/openlayers.test.js new file mode 100644 index 000000000..fe31e0658 --- /dev/null +++ b/tests/libs/openlayers.test.js @@ -0,0 +1,5 @@ +import ol from 'openlayers'; + +test('import openlayers', () => { + expect(ol).not.toBe(undefined); +}); diff --git a/tests/mocks/AssetMock.js b/tests/mocks/AssetMock.js new file mode 100644 index 000000000..f053ebf79 --- /dev/null +++ b/tests/mocks/AssetMock.js @@ -0,0 +1 @@ +module.exports = {}; From e27152349a72b34a8a7ec83610747a050d4b4002 Mon Sep 17 00:00:00 2001 From: Benedikt Seidl Date: Wed, 19 Jun 2024 11:13:08 +0200 Subject: [PATCH 2/3] Add basic tests for some components --- components/StandardApp.jsx | 7 +- tests/components/CoordinateDisplayer.test.jsx | 24 ++++++ tests/components/SearchBox.test.jsx | 76 +++++++++++++++++++ tests/components/StandardApp.test.jsx | 28 +++++++ tests/jest.setup.js | 11 +++ tests/mocks/MockMap.js | 5 ++ 6 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 tests/components/CoordinateDisplayer.test.jsx create mode 100644 tests/components/SearchBox.test.jsx create mode 100644 tests/components/StandardApp.test.jsx create mode 100644 tests/mocks/MockMap.js diff --git a/components/StandardApp.jsx b/components/StandardApp.jsx index 5f773882a..a8ae3f561 100644 --- a/components/StandardApp.jsx +++ b/components/StandardApp.jsx @@ -130,7 +130,8 @@ class AppInitComponent extends React.Component { initialView = { center: coords, zoom: zoom, - crs: params.crs || theme.mapCrs}; + crs: params.crs || theme.mapCrs + }; } } else if (params.e) { const bounds = params.e.split(/[;,]/g).map(x => parseFloat(x)); @@ -223,6 +224,10 @@ export default class StandardApp extends React.Component { ); } setupTouchEvents = (el) => { + if (el === null) { + // Do nothing when unmounting + return; + } el.addEventListener('touchstart', ev => { this.touchY = ev.targetTouches[0].clientY; }, { passive: false }); diff --git a/tests/components/CoordinateDisplayer.test.jsx b/tests/components/CoordinateDisplayer.test.jsx new file mode 100644 index 000000000..63d7746e7 --- /dev/null +++ b/tests/components/CoordinateDisplayer.test.jsx @@ -0,0 +1,24 @@ +import React from 'react'; +import {Provider} from "react-redux"; + +import {render, screen} from '@testing-library/react'; +import configureStore from 'redux-mock-store'; + +import CoordinateDisplayer from '../../components/CoordinateDisplayer'; + +const mockStore = configureStore(); + +test('current coordinates are shown', () => { + const store = mockStore({ + map: { projection: 'EPSG:4326' }, + mousePosition: { position: { coordinate: [123, 456] } } + }); + + render( + + + + ); + + expect(screen.getByRole('textbox')).toHaveValue('123.0000 456.0000'); +}); diff --git a/tests/components/SearchBox.test.jsx b/tests/components/SearchBox.test.jsx new file mode 100644 index 000000000..f67665b3e --- /dev/null +++ b/tests/components/SearchBox.test.jsx @@ -0,0 +1,76 @@ +import React from 'react'; +import {Provider} from "react-redux"; + +import {act, fireEvent, render, screen} from '@testing-library/react'; +import configureStore from 'redux-mock-store'; + +import SearchBox from '../../components/SearchBox'; + +const mockStore = configureStore(); + +const searchProviders = { + testing: { + onSearch: (text, searchParams, callback) => callback({ + results: [ + { + id: 'layer1', + title: 'Layer 1', + items: [ + { + id: 'item1', + text: 'Item 1' + }, + { + id: 'item2', + text: 'Item 2' + } + ] + }, + { + id: 'layer2', + title: 'Layer 2', + items: [ + { + id: 'item3', + text: 'Item 3' + } + ] + } + ] + }) + } +}; + +// eslint-disable-next-line +const Search = SearchBox(searchProviders); + +test('search results are visible', () => { + const store = mockStore({ + map: { projection: 'EPSG:4326' }, + layers: { flat: [] }, + theme: { current: { searchProviders: ['testing'] } } + }); + + const searchOptions = { + allowSearchFilters: false + }; + + render( + + + + ); + + const input = screen.getByRole('input'); + expect(input).toHaveValue(''); + + fireEvent.change(input, { target: { value: 'Test' } }); + act(() => input.focus()); + + expect(input).toHaveValue('Test'); + expect(screen.getByText('Layer 1')).toBeInTheDocument(); + expect(screen.getByText('Layer 2')).toBeInTheDocument(); + expect(screen.getByText('Item 1')).toBeInTheDocument(); + expect(screen.getByText('Item 2')).toBeInTheDocument(); + expect(screen.getByText('Item 3')).toBeInTheDocument(); +}); diff --git a/tests/components/StandardApp.test.jsx b/tests/components/StandardApp.test.jsx new file mode 100644 index 000000000..7227d9257 --- /dev/null +++ b/tests/components/StandardApp.test.jsx @@ -0,0 +1,28 @@ +import React from 'react'; +import {Provider} from "react-redux"; + +import {render} from '@testing-library/react'; +import configureStore from 'redux-mock-store'; + +import StandardApp from "../../components/StandardApp"; + +const mockStore = configureStore(); + +test('app is running w/o plugins', () => { + const store = mockStore({}); + + const appConfig = { + initialState: { + defaultState: {} + }, + pluginsDef: { + plugins: {} + } + }; + + render( + + + + ); +}); diff --git a/tests/jest.setup.js b/tests/jest.setup.js index 7b0828bfa..a9cf5db4f 100644 --- a/tests/jest.setup.js +++ b/tests/jest.setup.js @@ -1 +1,12 @@ +import LocaleUtils from "../utils/LocaleUtils"; +import MapUtils from "../utils/MapUtils"; +import MockMap from "./mocks/MockMap"; + import '@testing-library/jest-dom'; + +// Mock translation function, just return the message key +LocaleUtils.tr = (key) => key; +LocaleUtils.lang = () => 'en'; + +// Mock the Map object globally +MapUtils.registerHook(MapUtils.GET_MAP, new MockMap()); diff --git a/tests/mocks/MockMap.js b/tests/mocks/MockMap.js new file mode 100644 index 000000000..a66cb3b30 --- /dev/null +++ b/tests/mocks/MockMap.js @@ -0,0 +1,5 @@ +export default class MockMap { + addLayer = () => null; + removeLayer = () => null; + getViewport = () => document.createElement('div'); +} From 4ad2e232866b451d341129e69b877d563228d2a6 Mon Sep 17 00:00:00 2001 From: Benedikt Seidl Date: Wed, 19 Jun 2024 12:39:09 +0200 Subject: [PATCH 3/3] Include jest tests in GitHub action The parameter SARIF_ESLINT_IGNORE_SUPPRESSED in eslint.yml prevents that errors that are disabled inline still show up on GitHub. --- .github/workflows/eslint.yml | 11 ++++------- .github/workflows/jest.yml | 24 ++++++++++++++++++++++++ package.json | 1 + 3 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/jest.yml diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index 1a9ebde1f..bf0928dff 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -31,15 +31,12 @@ jobs: uses: actions/checkout@v3 - name: Install ESLint - run: | - npm install eslint@8.56.0 - npm install @microsoft/eslint-formatter-sarif@3.0.0 - npm install eslint-plugin-react@7.33.2 + run: yarn install - name: Run ESLint - run: npx eslint . - --config .eslintrc.js - --ext .js,.jsx,.ts,.tsx + env: + SARIF_ESLINT_IGNORE_SUPPRESSED: true + run: yarn lint --format @microsoft/eslint-formatter-sarif --output-file eslint-results.sarif continue-on-error: true diff --git a/.github/workflows/jest.yml b/.github/workflows/jest.yml new file mode 100644 index 000000000..c15f1369e --- /dev/null +++ b/.github/workflows/jest.yml @@ -0,0 +1,24 @@ +name: Jest + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '29 3 * * 5' + +jobs: + eslint: + name: Run jest tests + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Install Jest + run: yarn install + + - name: Run Jest + run: yarn test diff --git a/package.json b/package.json index 1a7f6c760..20d5bb6c0 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "@babel/preset-env": "^7.24.5", "@babel/preset-react": "^7.24.1", "@furkot/webfonts-generator": "^2.0.2", + "@microsoft/eslint-formatter-sarif": "^3.1.0", "@testing-library/dom": "^10.1.0", "@testing-library/jest-dom": "^6.4.6", "@testing-library/react": "^16.0.0",