From 7b1e9b4cbdd960297587e28fe1450878567fcfb2 Mon Sep 17 00:00:00 2001 From: "Matteo V." Date: Mon, 19 Aug 2024 16:04:23 +0200 Subject: [PATCH 01/39] Fix #10506 Adding possibility to fetch legends images using Bearer token (#10507) --- web/client/components/misc/SecureImage.jsx | 69 +++++++++++++++++++ web/client/plugins/TOC/components/Legend.jsx | 50 +++++++++----- .../TOC/components/__tests__/Legend-test.jsx | 35 +++++----- 3 files changed, 119 insertions(+), 35 deletions(-) create mode 100644 web/client/components/misc/SecureImage.jsx diff --git a/web/client/components/misc/SecureImage.jsx b/web/client/components/misc/SecureImage.jsx new file mode 100644 index 0000000000..dc6bc3810d --- /dev/null +++ b/web/client/components/misc/SecureImage.jsx @@ -0,0 +1,69 @@ +/** + * Copyright 2024, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, { useEffect, useState } from 'react'; +import axios from 'axios'; + +import { getAuthenticationMethod } from '../../utils/SecurityUtils'; + + +const SecureImage = ({ + alt, + src, + ...props +}) => { + const [imageSrc, setImageSrc] = useState(''); + + // Function to validate the image once it loads + const validateImg = (imgElement) => { + // Implement your validation logic here + // For example, check image dimensions, aspect ratio, etc. + if (imgElement.naturalWidth === 0 || imgElement.naturalHeight === 0) { + console.error('Image validation failed: Image is not valid.'); + } + }; + + useEffect(() => { + const authMethod = getAuthenticationMethod(src); + + if (authMethod === "bearer") { + axios.get(src, { + responseType: 'blob' + }) + .then((response) => { + const imageUrl = URL.createObjectURL(response.data); + setImageSrc(imageUrl); + }) + .catch((error) => { + console.error('Error fetching image:', error); + }); + } else { + setImageSrc(src); + } + + // Clean up the URL object when the component unmounts + return () => { + if (imageSrc) { + URL.revokeObjectURL(imageSrc); + } + }; + }, [src]); + + return ( + {alt} validateImg(e.target)} + src={imageSrc} + style={props.style} + {...props} + /> + ); +}; + +export default SecureImage; diff --git a/web/client/plugins/TOC/components/Legend.jsx b/web/client/plugins/TOC/components/Legend.jsx index 4e55fb53c9..4274a356b8 100644 --- a/web/client/plugins/TOC/components/Legend.jsx +++ b/web/client/plugins/TOC/components/Legend.jsx @@ -14,11 +14,12 @@ import PropTypes from 'prop-types'; import React from 'react'; import { - addAuthenticationParameter, addAuthenticationToSLD, clearNilValuesForParams } from '../../../utils/SecurityUtils'; import Message from '../../../components/I18N/Message'; +import SecureImage from '../../../components/misc/SecureImage'; + import { randomInt } from '../../../utils/RandomUtils'; /** @@ -85,23 +86,26 @@ class Legend extends React.Component { const cleanParams = clearNilValuesForParams(layer.params); const scale = this.getScale(props); - let query = assign({}, { - service: "WMS", - request: "GetLegendGraphic", - format: "image/png", - height: props.legendHeight, - width: props.legendWidth, - layer: layer.name, - style: layer.style || null, - version: layer.version || "1.3.0", - SLD_VERSION: "1.1.0", - LEGEND_OPTIONS: props.legendOptions - }, layer.legendParams || {}, - props.language && layer.localizedLayerStyles ? {LANGUAGE: props.language} : {}, - addAuthenticationToSLD(cleanParams || {}, props.layer), - cleanParams && cleanParams.SLD_BODY ? {SLD_BODY: cleanParams.SLD_BODY} : {}, - scale !== null ? { SCALE: scale } : {}); - addAuthenticationParameter(url, query); + let query = assign( + {}, + { + service: "WMS", + request: "GetLegendGraphic", + format: "image/png", + height: props.legendHeight, + width: props.legendWidth, + layer: layer.name, + style: layer.style || null, + version: layer.version || "1.3.0", + SLD_VERSION: "1.1.0", + LEGEND_OPTIONS: props.legendOptions + }, + layer.legendParams || {}, + props.language && layer.localizedLayerStyles ? {LANGUAGE: props.language} : {}, + addAuthenticationToSLD(cleanParams || {}, props.layer), + cleanParams && cleanParams.SLD_BODY ? {SLD_BODY: cleanParams.SLD_BODY} : {}, + scale !== null ? { SCALE: scale } : {} + ); return urlUtil.format({ host: urlObj.host, @@ -114,7 +118,15 @@ class Legend extends React.Component { } render() { if (!this.state.error && this.props.layer && this.props.layer.type === "wms" && this.props.layer.url) { - return this.validateImg(e.target)} src={this.getUrl(this.props)} style={this.props.style}/>; + const url = this.getUrl(this.props); + return ( + this.validateImg(e.target)} + src={url} + style={this.props.style} + /> + ); } return ; } diff --git a/web/client/plugins/TOC/components/__tests__/Legend-test.jsx b/web/client/plugins/TOC/components/__tests__/Legend-test.jsx index 999a0db999..48080a7411 100644 --- a/web/client/plugins/TOC/components/__tests__/Legend-test.jsx +++ b/web/client/plugins/TOC/components/__tests__/Legend-test.jsx @@ -11,11 +11,10 @@ import React from 'react'; import ReactDOM from 'react-dom'; import Rx from 'rxjs'; import url from 'url'; +import * as TestUtils from 'react-dom/test-utils'; import Legend from '../Legend'; -import * as TestUtils from 'react-dom/test-utils'; - describe("test the Layer legend", () => { beforeEach((done) => { document.body.innerHTML = '
'; @@ -176,13 +175,15 @@ describe("test the Layer legend", () => { "name": "layer3", "format": "image/png" }; - ReactDOM.render( - , - document.getElementById("container")); + TestUtils.act(() => { + ReactDOM.render( + , + document.getElementById("container")); + }); const legendImage = document.querySelector("img"); expect(legendImage).toBeTruthy(); const { query } = url.parse(legendImage.getAttribute('src'), true); @@ -197,13 +198,15 @@ describe("test the Layer legend", () => { "name": "layer3", "format": "image/png" }; - ReactDOM.render( - , - document.getElementById("container")); + TestUtils.act(() => { + ReactDOM.render( + , + document.getElementById("container")); + }); const legendImage = document.querySelector("img"); expect(legendImage).toBeTruthy(); const { query } = url.parse(legendImage.getAttribute('src'), true); From 04e8784d0f636c15a33f43c6a14d8a1682d6df90 Mon Sep 17 00:00:00 2001 From: RowHeat <40065760+rowheat02@users.noreply.github.com> Date: Tue, 27 Aug 2024 13:20:41 +0545 Subject: [PATCH 02/39] Fix #10513 Print preview state is not aligned with TOC (#10517) --- web/client/plugins/Print.jsx | 11 +-- web/client/plugins/__tests__/Print-test.jsx | 88 +++++++++++++++++++++ 2 files changed, 94 insertions(+), 5 deletions(-) diff --git a/web/client/plugins/Print.jsx b/web/client/plugins/Print.jsx index c7a6967006..045f83f430 100644 --- a/web/client/plugins/Print.jsx +++ b/web/client/plugins/Print.jsx @@ -25,14 +25,14 @@ import Dialog from '../components/misc/Dialog'; import printReducers from '../reducers/print'; import printEpics from '../epics/print'; import { printSpecificationSelector } from "../selectors/print"; -import { layersSelector } from '../selectors/layers'; +import { layersSelector, rawGroupsSelector } from '../selectors/layers'; import { currentLocaleSelector } from '../selectors/locale'; import { mapSelector, scalesSelector } from '../selectors/map'; import { mapTypeSelector } from '../selectors/maptype'; import { normalizeSRS, convertDegreesToRadian } from '../utils/CoordinatesUtils'; import { getMessageById } from '../utils/LocaleUtils'; import { defaultGetZoomForExtent, getResolutions, mapUpdated, dpi2dpu, DEFAULT_SCREEN_DPI, getScales, reprojectZoom } from '../utils/MapUtils'; -import { isInsideResolutionsLimits } from '../utils/LayersUtils'; +import { getDerivedLayersVisibility, isInsideResolutionsLimits } from '../utils/LayersUtils'; import { has, includes } from 'lodash'; import {additionalLayersSelector} from "../selectors/additionallayers"; import { MapLibraries } from '../utils/MapTypeUtils'; @@ -629,8 +629,9 @@ export default { (state) => state.browser && (!state.browser.ie || state.browser.ie11), currentLocaleSelector, mapTypeSelector, - (state) => state.print.map - ], (open, capabilities, printSpec, pdfUrl, error, map, layers, additionalLayers, scales, usePreview, currentLocale, mapType, printMap) => ({ + (state) => state.print.map, + rawGroupsSelector + ], (open, capabilities, printSpec, pdfUrl, error, map, layers, additionalLayers, scales, usePreview, currentLocale, mapType, printMap, groups) => ({ open, capabilities, printSpec, @@ -638,7 +639,7 @@ export default { error, map, layers: [ - ...layers.filter(filterLayer), + ...getDerivedLayersVisibility(layers, groups).filter(filterLayer), ...(printSpec?.additionalLayers ? additionalLayers.map(l => l.options).filter( l => { const isVector = l.type === 'vector'; diff --git a/web/client/plugins/__tests__/Print-test.jsx b/web/client/plugins/__tests__/Print-test.jsx index 6e13211d02..e584399970 100644 --- a/web/client/plugins/__tests__/Print-test.jsx +++ b/web/client/plugins/__tests__/Print-test.jsx @@ -569,4 +569,92 @@ describe('Print Plugin', () => { } }); }); + it("test removing visible layers with invisible group", (done) => { + const actions = { + onPrint: () => {} + }; + let spy = expect.spyOn(actions, "onPrint"); + getPrintPlugin({ + layers: + { + flat: [ + { + id: 'test:Linea_costa__38262060-608e-11ef-b6d2-f1ba404475c4', + format: 'image/png', + group: 'Default.34a0a320-608e-11ef-b6d2-f1ba404475c4', + search: { + url: '/geoserver/wfs', + type: 'wfs' + }, + name: 'test:Linea_costa', + description: '', + title: 'Linea_costa', + type: 'wms', + url: '/geoserver/wms', + visibility: true + }, + { + type: 'wms', + format: 'image/png', + featureInfo: null, + url: '/geoserver/wms', + visibility: true, + dimensions: [], + name: 'test:areeverdiPolygon', + title: 'areeverdiPolygon', + id: 'test:areeverdiPolygon__722d7920-608e-11ef-8123-43293ce7e0e8' + } + ], + groups: [ + { + id: 'Default', + title: 'Default', + name: 'Default', + nodes: [ + 'test:areeverdiPolygon__722d7920-608e-11ef-8123-43293ce7e0e8', + { + id: 'Default.34a0a320-608e-11ef-b6d2-f1ba404475c4', + title: 'g1', + name: '34a0a320-608e-11ef-b6d2-f1ba404475c4', + nodes: [ + 'test:Linea_costa__38262060-608e-11ef-b6d2-f1ba404475c4' + ], + visibility: false + } + ], + visibility: true + } + ] + }, + projection: "EPSG:4326", + state: { + ...initialState, + map: { + ...initialState.map, + zoom: 5.1 + } + } + }).then(({ Plugin }) => { + try { + ReactDOM.render(, document.getElementById("container")); + const submit = document.getElementsByClassName("print-submit").item(0); + expect(submit).toExist(); + ReactTestUtils.Simulate.click(submit); + + setTimeout(() => { + expect(spy.calls.length).toBe(1); + expect(spy.calls[0].arguments[1].layers.length).toBe(1); + expect(spy.calls[0].arguments[1].layers[0].layers).toEqual(['test:areeverdiPolygon']); + done(); + }, 0); + } catch (ex) { + done(ex); + } + }); + }); }); From 7049fa2f5319b38cf403f5f00772666c46bd481b Mon Sep 17 00:00:00 2001 From: fkellner Date: Thu, 29 Aug 2024 15:07:57 +0200 Subject: [PATCH 03/39] Fix #10175 : fix 2 services search test nondeterminism (#10176) Apparently, which search service observable emits results first does not depend on the order in which they are passed into the search. The test however assumed there to be an order, causing it to break on MacOS. This is now fixed. On Behalf of DB Systel Co-authored-by: Florian Kellner --- web/client/epics/__tests__/search-test.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/web/client/epics/__tests__/search-test.js b/web/client/epics/__tests__/search-test.js index a5c8d3d504..e5ed6500fe 100644 --- a/web/client/epics/__tests__/search-test.js +++ b/web/client/epics/__tests__/search-test.js @@ -563,7 +563,8 @@ describe('search Epics', () => { typeName: 'topp:states', queriableAttributes: [STATE_NAME], returnFullData: false - } + }, + priority: 2 }, { type: 'wfs', @@ -572,7 +573,8 @@ describe('search Epics', () => { typeName: 'topp:states', queriableAttributes: [STATE_NAME], returnFullData: false - } + }, + priority: 1 }], maxResults }; @@ -587,12 +589,12 @@ describe('search Epics', () => { expect(actions[1].type).toBe(TEXT_SEARCH_LOADING); expect(actions[2].type).toBe(TEXT_SEARCH_RESULTS_LOADED); expect(actions[2].results.length).toBe(maxResults); - expect(head(actions[2].results).id).toBe("states.1"); - expect(last(actions[2].results).id).toBe("states.5"); + expect(head(actions[2].results).id).toMatch(/^(states|states-ari).1$/); + expect(last(actions[2].results).id).toMatch(/^(states|states-ari).5$/); expect(actions[3].type).toBe(TEXT_SEARCH_RESULTS_LOADED); expect(actions[3].results.length).toBe(maxResults); - expect(head(actions[3].results).id).toBe("states.1"); - expect(last(actions[3].results).id).toBe("states.5"); + expect(head(actions[3].results).id).toBe("states-ari.1"); + expect(last(actions[3].results).id).toBe("states-ari.5"); expect(actions[4].type).toBe(TEXT_SEARCH_LOADING); done(); }, {}); From 49855517283d2fffd7ab47f644344bd1ac026573 Mon Sep 17 00:00:00 2001 From: fkellner Date: Thu, 29 Aug 2024 18:07:22 +0200 Subject: [PATCH 04/39] Fix #10127: update tests for react-redux (#10142) Updates tests relying on 'render' returning a reference to enable react-redux 7.x upwards (where some components become stateless and render no longer returns a reference, even though it was successful) On Behalf of DB Systel Co-authored-by: Florian Kellner --- .../__tests__/StandardAppComponent-test.jsx | 25 ++-- .../app/__tests__/StandardContainer-test.jsx | 16 +-- .../app/__tests__/StandardRouter-test.jsx | 90 ++++++++------- .../buttons/__tests__/ZoomButton-test.jsx | 109 +++++++++--------- .../__tests__/ZoomToMaxExtentButton-test.jsx | 93 ++++++++------- .../viewers/__tests__/viewers-test.jsx | 71 +++++++----- .../components/home/__tests__/Home-test.jsx | 12 +- .../__tests__/PluginsContainer-test.jsx | 43 +++---- .../__tests__/PaginationToolbar-test.jsx | 8 +- .../__tests__/PaginationToolbar-test.jsx | 8 +- .../viewer/__tests__/MapViewer-test.jsx | 65 +++++++---- .../utils/__tests__/PluginsUtils-test.js | 5 +- 12 files changed, 306 insertions(+), 239 deletions(-) diff --git a/web/client/components/app/__tests__/StandardAppComponent-test.jsx b/web/client/components/app/__tests__/StandardAppComponent-test.jsx index 50f72cd513..2a5cae426b 100644 --- a/web/client/components/app/__tests__/StandardAppComponent-test.jsx +++ b/web/client/components/app/__tests__/StandardAppComponent-test.jsx @@ -34,17 +34,21 @@ describe('StandardAppComponent', () => { setTimeout(done); }); - it('creates a default app', () => { + it('creates a default app', (done) => { const store = { dispatch: () => {}, subscribe: () => {}, getState: () => ({}) }; - const app = ReactDOM.render(, document.getElementById("container")); - expect(app).toExist(); + const container = document.getElementById("container"); + expect(container.innerHTML).toNotExist(); + ReactDOM.render(, container, () => { + expect(container.innerHTML).toExist(); + done(); + }); }); - it('creates a default app with plugins', () => { + it('creates a default app with plugins', (done) => { const plugins = { MyPlugin }; @@ -54,11 +58,12 @@ describe('StandardAppComponent', () => { subscribe: () => {}, getState: () => ({}) }; - const app = ReactDOM.render(, document.getElementById("container")); - expect(app).toExist(); - - const dom = ReactDOM.findDOMNode(app); - - expect(dom.getElementsByClassName('MyPlugin').length).toBe(1); + const container = document.getElementById("container"); + expect(container.innerHTML).toNotExist(); + ReactDOM.render(, container, () => { + expect(container.innerHTML).toExist(); + expect(container.getElementsByClassName('MyPlugin').length).toBe(1); + done(); + }); }); }); diff --git a/web/client/components/app/__tests__/StandardContainer-test.jsx b/web/client/components/app/__tests__/StandardContainer-test.jsx index d28697a425..0609714724 100644 --- a/web/client/components/app/__tests__/StandardContainer-test.jsx +++ b/web/client/components/app/__tests__/StandardContainer-test.jsx @@ -49,7 +49,7 @@ describe('StandardContainer', () => { }); - it('creates a default app with component and plugins', () => { + it('creates a default app with component and plugins', (done) => { const plugins = { MyPlugin: {} }; @@ -63,11 +63,13 @@ describe('StandardContainer', () => { const componentConfig = { component: mycomponent }; - - const app = ReactDOM.render(, document.getElementById("container")); - expect(app).toExist(); - const dom = ReactDOM.findDOMNode(app); - expect(dom.getElementsByClassName('mycomponent').length).toBe(1); - expect(dom.getElementsByClassName('MyPlugin').length).toBe(1); + const container = document.getElementById("container"); + expect(container.innerHTML).toNotExist(); + ReactDOM.render(, container, () => { + expect(container.innerHTML).toExist(); + expect(container.getElementsByClassName('mycomponent').length).toBe(1); + expect(container.getElementsByClassName('MyPlugin').length).toBe(1); + done(); + }); }); }); diff --git a/web/client/components/app/__tests__/StandardRouter-test.jsx b/web/client/components/app/__tests__/StandardRouter-test.jsx index e067ee9a01..a7c08378e0 100644 --- a/web/client/components/app/__tests__/StandardRouter-test.jsx +++ b/web/client/components/app/__tests__/StandardRouter-test.jsx @@ -49,7 +49,7 @@ describe('StandardRouter', () => { setTimeout(done); }); - it('creates a default router app', () => { + it('creates a default router app', (done) => { const store = { dispatch: () => {}, subscribe: () => { @@ -58,11 +58,15 @@ describe('StandardRouter', () => { unsubscribe: () => {}, getState: () => ({}) }; - const app = ReactDOM.render(, document.getElementById("container")); - expect(app).toExist(); + const container = document.getElementById("container"); + expect(container.innerHTML).toNotExist(); + ReactDOM.render(, container, () => { + expect(container.innerHTML).toExist(); + done(); + }); }); - it('creates a default router app with pages', () => { + it('creates a default router app with pages', (done) => { const store = { dispatch: () => {}, subscribe: () => { @@ -75,14 +79,16 @@ describe('StandardRouter', () => { path: '/', component: mycomponent }]; - const app = ReactDOM.render(, document.getElementById("container")); - expect(app).toExist(); - const dom = ReactDOM.findDOMNode(app); - - expect(dom.getElementsByClassName('mycomponent').length).toBe(1); + const container = document.getElementById("container"); + expect(container.innerHTML).toNotExist(); + ReactDOM.render(, container, () => { + expect(container.innerHTML).toExist(); + expect(container.getElementsByClassName('mycomponent').length).toBe(1); + done(); + }); }); - it('creates a default router app with pages and plugins', () => { + it('creates a default router app with pages and plugins', (done) => { const plugins = { MyPlugin: {} }; @@ -99,15 +105,16 @@ describe('StandardRouter', () => { path: '/', component: mycomponent }]; - const app = ReactDOM.render(, document.getElementById("container")); - expect(app).toExist(); - - const dom = ReactDOM.findDOMNode(app); - - expect(dom.getElementsByClassName('MyPlugin').length).toBe(1); + const container = document.getElementById("container"); + expect(container.innerHTML).toNotExist(); + ReactDOM.render(, container, () => { + expect(container.innerHTML).toExist(); + expect(container.getElementsByClassName('MyPlugin').length).toBe(1); + done(); + }); }); - it('if we dont wait for theme no spinner is shown', () => { + it('if we dont wait for theme no spinner is shown', (done) => { const plugins = { MyPlugin: {} }; @@ -124,14 +131,15 @@ describe('StandardRouter', () => { path: '/', component: mycomponent }]; - const app = ReactDOM.render(, document.getElementById("container")); - expect(app).toExist(); - - const dom = ReactDOM.findDOMNode(app); - - expect(dom.getElementsByClassName('_ms2_init_spinner').length).toBe(0); + const container = document.getElementById("container"); + expect(container.innerHTML).toNotExist(); + ReactDOM.render(, container, () => { + expect(container.innerHTML).toExist(); + expect(container.getElementsByClassName('_ms2_init_spinner').length).toBe(0); + done(); + }); }); - it('if we wait for theme no spinner is shown if the theme is already loaded', () => { + it('if we wait for theme no spinner is shown if the theme is already loaded', (done) => { const plugins = { MyPlugin: {} }; @@ -148,14 +156,15 @@ describe('StandardRouter', () => { path: '/', component: mycomponent }]; - const app = ReactDOM.render(, document.getElementById("container")); - expect(app).toExist(); - - const dom = ReactDOM.findDOMNode(app); - - expect(dom.getElementsByClassName('_ms2_init_spinner').length).toBe(0); + const container = document.getElementById("container"); + expect(container.innerHTML).toNotExist(); + ReactDOM.render(, container, () => { + expect(container.innerHTML).toExist(); + expect(container.getElementsByClassName('_ms2_init_spinner').length).toBe(0); + done(); + }); }); - it('if we wait for theme spinner is shown if the theme is not already loaded', () => { + it('if we wait for theme spinner is shown if the theme is not already loaded', (done) => { const plugins = { MyPlugin: {} }; @@ -172,12 +181,13 @@ describe('StandardRouter', () => { path: '/', component: mycomponent }]; - const app = ReactDOM.render(, document.getElementById("container")); - expect(app).toExist(); - - const dom = ReactDOM.findDOMNode(app); - - expect(dom.getElementsByClassName('_ms2_init_spinner').length).toBe(1); + const container = document.getElementById("container"); + expect(container.innerHTML).toNotExist(); + ReactDOM.render(, container, () => { + expect(container.innerHTML).toExist(); + expect(container.getElementsByClassName('_ms2_init_spinner').length).toBe(1); + done(); + }); }); it('if we wait for theme onThemeLoaded is called when theme is loaded', (done) => { const plugins = { @@ -196,11 +206,10 @@ describe('StandardRouter', () => { path: '/', component: mycomponent }]; - const app = ReactDOM.render(, document.getElementById("container")); - expect(app).toExist(); }); it('if we wait for theme onThemeLoaded is called when theme custom is loaded', (done) => { @@ -220,7 +229,7 @@ describe('StandardRouter', () => { path: '/', component: mycomponent }]; - const app = ReactDOM.render( + ReactDOM.render( { done(); }} /> , document.getElementById("container")); - expect(app).toExist(); }); }); diff --git a/web/client/components/buttons/__tests__/ZoomButton-test.jsx b/web/client/components/buttons/__tests__/ZoomButton-test.jsx index e9dd8c0b90..409e67fa13 100644 --- a/web/client/components/buttons/__tests__/ZoomButton-test.jsx +++ b/web/client/components/buttons/__tests__/ZoomButton-test.jsx @@ -43,57 +43,59 @@ describe('This test for ZoomButton', () => { }); // test DEFAULTS - it('test default properties', () => { - const zmeBtn = ReactDOM.render( + it('test default properties', (done) => { + const container = document.getElementById("container"); + expect(container.innerHTML).toNotExist(); + ReactDOM.render( , - document.getElementById("container")); - expect(zmeBtn).toExist(); - - const zmeBtnNode = ReactDOM.findDOMNode(zmeBtn); - expect(zmeBtnNode).toExist(); - expect(zmeBtnNode.id).toBe("mapstore-zoom"); - - expect(zmeBtnNode).toExist(); - expect(zmeBtnNode.className.indexOf('square-button') >= 0).toBe(true); - expect(zmeBtnNode.innerHTML).toExist(); + container, () => { + expect(container.innerHTML).toExist(); + const zmeBtnNode = document.getElementById("mapstore-zoom"); + expect(zmeBtnNode).toExist(); + expect(zmeBtnNode.className.indexOf('square-button') >= 0).toBe(true); + expect(zmeBtnNode.innerHTML).toExist(); + done(); + }); }); - it('test glyphicon property', () => { - const zmeBtn = ReactDOM.render( + it('test glyphicon property', (done) => { + const container = document.getElementById("container"); + expect(container.innerHTML).toNotExist(); + ReactDOM.render( , - document.getElementById("container")); - expect(zmeBtn).toExist(); - - const zmeBtnNode = ReactDOM.findDOMNode(zmeBtn); - expect(zmeBtnNode).toExist(); - expect(zmeBtnNode).toExist(); - const icons = zmeBtnNode.getElementsByTagName('span'); - expect(icons.length).toBe(1); + container, () => { + expect(container.innerHTML).toExist(); + const zmeBtnNode = document.getElementById("mapstore-zoom"); + expect(zmeBtnNode).toExist(); + const icons = zmeBtnNode.getElementsByTagName('span'); + expect(icons.length).toBe(1); + done(); + }); }); - it('test glyphicon property with text', () => { - const zmeBtn = ReactDOM.render( + it('test glyphicon property with text', (done) => { + ReactDOM.render( , - document.getElementById("container")); - expect(zmeBtn).toExist(); - - const zmeBtnNode = ReactDOM.findDOMNode(zmeBtn); - expect(zmeBtnNode).toExist(); - expect(zmeBtnNode).toExist(); + document.getElementById("container"), + () => { + const zmeBtnNode = document.getElementById("mapstore-zoom"); + expect(zmeBtnNode).toExist(); - const btnItems = zmeBtnNode.getElementsByTagName('span'); - expect(btnItems.length).toBe(1); + const btnItems = zmeBtnNode.getElementsByTagName('span'); + expect(btnItems.length).toBe(1); - expect(zmeBtnNode.innerText.indexOf("button") !== -1).toBe(true); + expect(zmeBtnNode.innerText.indexOf("button") !== -1).toBe(true); + done(); + }); }); - it('test if click on button launches the proper action', () => { + it('test if click on button launches the proper action', (done) => { let genericTest = function() { let actions = { @@ -102,7 +104,7 @@ describe('This test for ZoomButton', () => { } }; let spy = expect.spyOn(actions, "onZoom"); - var cmp = ReactDOM.render( + ReactDOM.render( { } }} /> - , document.getElementById("container")); - expect(cmp).toExist(); + , document.getElementById("container") + , () => { + const cmpDom = document.getElementById("mapstore-zoom"); + expect(cmpDom).toExist(); - const cmpDom = document.getElementById("mapstore-zoom"); - expect(cmpDom).toExist(); - - cmpDom.click(); + cmpDom.click(); - expect(spy.calls.length).toBe(1); - expect(spy.calls[0].arguments.length).toBe(1); + expect(spy.calls.length).toBe(1); + expect(spy.calls[0].arguments.length).toBe(1); + done(); + }); }; genericTest(); }); - it('create glyphicon with custom css class', () => { - const zmeBtn = ReactDOM.render( + it('create glyphicon with custom css class', (done) => { + const container = document.getElementById("container"); + ReactDOM.render( , - document.getElementById("container")); - expect(zmeBtn).toExist(); - - const zmeBtnNode = ReactDOM.findDOMNode(zmeBtn); - expect(zmeBtnNode).toExist(); - - expect(zmeBtnNode.className.indexOf('custom') !== -1).toBe(true); + container, () => { + expect(container.innerHTML).toExist(); + const zmeBtnNode = document.getElementById("mapstore-zoom"); + expect(zmeBtnNode).toExist(); + + expect(zmeBtnNode.className.indexOf('custom') !== -1).toBe(true); + done(); + }); }); it('test zoom in', () => { diff --git a/web/client/components/buttons/__tests__/ZoomToMaxExtentButton-test.jsx b/web/client/components/buttons/__tests__/ZoomToMaxExtentButton-test.jsx index f5c448c09f..363c5896b6 100644 --- a/web/client/components/buttons/__tests__/ZoomToMaxExtentButton-test.jsx +++ b/web/client/components/buttons/__tests__/ZoomToMaxExtentButton-test.jsx @@ -43,54 +43,57 @@ describe('This test for ZoomToMaxExtentButton', () => { }); // test DEFAULTS - it('test default properties', () => { - const zmeBtn = ReactDOM.render( + it('test default properties', (done) => { + const container = document.getElementById("container"); + ReactDOM.render( , - document.getElementById("container")); - expect(zmeBtn).toExist(); - - const zmeBtnNode = ReactDOM.findDOMNode(zmeBtn); - expect(zmeBtnNode).toExist(); - expect(zmeBtnNode.id).toBe("mapstore-zoomtomaxextent"); - - expect(zmeBtnNode).toExist(); - expect(zmeBtnNode.className.indexOf('default') >= 0).toBe(true); - expect(zmeBtnNode.innerHTML).toExist(); + container, + () => { + expect(container.innerHTML).toExist(); + const zmeBtnNode = document.getElementById("mapstore-zoomtomaxextent"); + expect(zmeBtnNode).toExist(); + expect(zmeBtnNode.className.indexOf('default') >= 0).toBe(true); + expect(zmeBtnNode.innerHTML).toExist(); + done(); + }); }); - it('test glyphicon property', () => { - const zmeBtn = ReactDOM.render( + it('test glyphicon property', (done) => { + const container = document.getElementById("container"); + ReactDOM.render( , - document.getElementById("container")); - expect(zmeBtn).toExist(); - - const zmeBtnNode = ReactDOM.findDOMNode(zmeBtn); - expect(zmeBtnNode).toExist(); - expect(zmeBtnNode).toExist(); - const icons = zmeBtnNode.getElementsByTagName('span'); - expect(icons.length).toBe(1); + container, + () => { + expect(container.innerHTML).toExist(); + const zmeBtnNode = document.getElementById("mapstore-zoomtomaxextent"); + expect(zmeBtnNode).toExist(); + const icons = zmeBtnNode.getElementsByTagName('span'); + expect(icons.length).toBe(1); + done(); + }); }); - it('test glyphicon property with text', () => { - const zmeBtn = ReactDOM.render( + it('test glyphicon property with text', (done) => { + const container = document.getElementById("container"); + ReactDOM.render( , - document.getElementById("container")); - expect(zmeBtn).toExist(); - - const zmeBtnNode = ReactDOM.findDOMNode(zmeBtn); - expect(zmeBtnNode).toExist(); - expect(zmeBtnNode).toExist(); - - const btnItems = zmeBtnNode.getElementsByTagName('span'); - expect(btnItems.length).toBe(1); - - expect(zmeBtnNode.innerText.indexOf("button") !== -1).toBe(true); + container, + () => { + expect(container.innerHTML).toExist(); + const zmeBtnNode = document.getElementById("mapstore-zoomtomaxextent"); + expect(zmeBtnNode).toExist(); + const btnItems = zmeBtnNode.getElementsByTagName('span'); + expect(btnItems.length).toBe(1); + + expect(zmeBtnNode.innerText.indexOf("button") !== -1).toBe(true); + done(); + }); }); it('test if click on button launches the proper action', () => { @@ -145,18 +148,20 @@ describe('This test for ZoomToMaxExtentButton', () => { genericTest("image"); }); - it('create glyphicon with custom css class', () => { - const zmeBtn = ReactDOM.render( + it('create glyphicon with custom css class', (done) => { + const container = document.getElementById("container"); + ReactDOM.render( , - document.getElementById("container")); - expect(zmeBtn).toExist(); - - const zmeBtnNode = ReactDOM.findDOMNode(zmeBtn); - expect(zmeBtnNode).toExist(); - - expect(zmeBtnNode.className.indexOf('custom') !== -1).toBe(true); + container, + () => { + expect(container.innerHTML).toExist(); + const zmeBtnNode = document.getElementById("mapstore-zoomtomaxextent"); + expect(zmeBtnNode).toExist(); + expect(zmeBtnNode.className.indexOf('custom') !== -1).toBe(true); + done(); + }); }); it('test zoom to initial extent', () => { diff --git a/web/client/components/data/identify/viewers/__tests__/viewers-test.jsx b/web/client/components/data/identify/viewers/__tests__/viewers-test.jsx index b3f6ba51df..cfdefbdcd3 100644 --- a/web/client/components/data/identify/viewers/__tests__/viewers-test.jsx +++ b/web/client/components/data/identify/viewers/__tests__/viewers-test.jsx @@ -14,6 +14,12 @@ import HTMLViewer from '../HTMLViewer'; import JSONViewer from '../JSONViewer'; import TextViewer from '../TextViewer'; +import configureStore from 'redux-mock-store'; +import thunkMiddleware from 'redux-thunk'; +import {Provider} from "react-redux"; +const mockStore = configureStore([thunkMiddleware]); +const store = mockStore({}); + const SimpleRowViewer = (props) => { return
{['name', 'description'].map((key) => {key}:{props[key]})}
; }; @@ -51,7 +57,8 @@ describe('Identity Viewers', () => { }); it('test JSONViewer', () => { - const cmp = ReactDOM.render( { description: 'mydescription' } }] - }} rowViewer={SimpleRowViewer}/>, document.getElementById("container")); - expect(cmp).toExist(); + }} rowViewer={SimpleRowViewer}/>, container); + expect(container.innerHTML).toExist(); - const cmpDom = ReactDOM.findDOMNode(cmp); + const cmpDom = container.firstElementChild; expect(cmpDom).toExist(); expect(cmpDom.innerHTML.indexOf('myname') !== -1).toBe(true); @@ -73,7 +80,8 @@ describe('Identity Viewers', () => { const MyRowViewer = (props) => { return This is my viewer: {props.feature.id}; }; - const cmp = ReactDOM.render( { description: 'mydescription' } }] - }} />, document.getElementById("container")); - expect(cmp).toExist(); + }} />, container); + expect(container.innerHTML).toExist(); - const cmpDom = ReactDOM.findDOMNode(cmp); + const cmpDom = container.firstElementChild; expect(cmpDom).toExist(); expect(cmpDom.innerText.indexOf('This is my viewer: 1') !== -1).toBe(true); }); it('test JSONViewer with TEMPLATE', () => { - const cmp = ReactDOM.render( + const container = document.getElementById("container"); + ReactDOM.render( { description: 'mydescription' } }] - }} />, document.getElementById("container")); - expect(cmp).toExist(); + }} />, container); + expect(container.innerHTML).toExist(); - const cmpDom = ReactDOM.findDOMNode(cmp); + const cmpDom = container.firstElementChild; expect(cmpDom).toExist(); const templateDOM = document.getElementById('my-template'); @@ -118,7 +127,8 @@ describe('Identity Viewers', () => { }); it('test JSONViewer with TEMPLATE and missing properties', () => { - const cmp = ReactDOM.render( + const container = document.getElementById("container"); + ReactDOM.render( { description: 'mydescription' } }] - }} />, document.getElementById("container")); - expect(cmp).toExist(); + }} />, container); + expect(container.innerHTML).toExist(); - const cmpDom = ReactDOM.findDOMNode(cmp); + const cmpDom = container.firstElementChild; expect(cmpDom).toExist(); const templateDOM = document.getElementById('my-template'); @@ -146,7 +156,7 @@ describe('Identity Viewers', () => { }); it('test JSONViewer with TEMPLATE with tag inside variable', () => { - ReactDOM.render( + ReactDOM.render( { description: 'mydescription' } }] - }} />, document.getElementById("container")); + }} />, document.getElementById("container")); let templateDOM = document.getElementById('my-template'); expect(templateDOM.innerHTML).toBe('the property name is myname'); - ReactDOM.render( + ReactDOM.render( { description: 'mydescription' } }] - }} />, document.getElementById("container")); + }} />, document.getElementById("container")); templateDOM = document.getElementById('my-template'); expect(templateDOM.innerHTML).toBe('the property description is mydescription'); }); it('test JSONViewer with TEMPLATE multiple features', () => { - const cmp = ReactDOM.render( + const container = document.getElementById("container"); + ReactDOM.render( { description: 'newDescription' } }] - }} />, document.getElementById("container")); - expect(cmp).toExist(); + }} />, container); + expect(container.innerHTML).toExist(); - const cmpDom = ReactDOM.findDOMNode(cmp); + const cmpDom = container.firstElementChild; expect(cmpDom).toExist(); const templateDOM = document.getElementsByClassName('my-template'); @@ -225,7 +236,7 @@ describe('Identity Viewers', () => { it('test JSONViewer with TEMPLATE but missing/empty template', () => { // when template is missing, undefined or equal to


response is displayed in PROPERTIES format - ReactDOM.render( + ReactDOM.render( { description: 'mydescription' } }] - }} />, document.getElementById("container")); + }} />, document.getElementById("container")); let propertiesViewer = document.getElementsByClassName('mapstore-json-viewer'); expect(propertiesViewer.length).toBe(1); - ReactDOM.render( + ReactDOM.render( { description: 'mydescription' } }] - }} />, document.getElementById("container")); + }} />, document.getElementById("container")); propertiesViewer = document.getElementsByClassName('mapstore-json-viewer'); expect(propertiesViewer.length).toBe(1); //


is the value of react-quill when empty - ReactDOM.render( + ReactDOM.render( { description: 'mydescription' } }] - }} />, document.getElementById("container")); + }} />, document.getElementById("container")); propertiesViewer = document.getElementsByClassName('mapstore-json-viewer'); expect(propertiesViewer.length).toBe(1); diff --git a/web/client/components/home/__tests__/Home-test.jsx b/web/client/components/home/__tests__/Home-test.jsx index 44b3a624a2..2bece8f872 100644 --- a/web/client/components/home/__tests__/Home-test.jsx +++ b/web/client/components/home/__tests__/Home-test.jsx @@ -22,18 +22,20 @@ describe("Test Home component", () => { }); it('creates component with defaults', () => { - const cmp = ReactDOM.render(, document.getElementById("container")); - expect(cmp).toBeTruthy(); + const container = document.getElementById("container"); + ReactDOM.render(, container); + expect(container.innerHTML).toExist(); const icons = document.querySelectorAll(".glyphicon-home"); expect(icons.length).toEqual(1); expect(icons[0]).toBeTruthy(); }); it('creates component with custom icon text', () => { - const cmp = ReactDOM.render( + const container = document.getElementById("container"); + ReactDOM.render( , document.getElementById("container")); - expect(cmp).toBeTruthy(); + />, container); + expect(container.innerHTML).toExist(); const buttons = document.querySelectorAll("button"); expect(buttons.length).toEqual(1); expect(buttons[0]).toBeTruthy(); diff --git a/web/client/components/plugins/__tests__/PluginsContainer-test.jsx b/web/client/components/plugins/__tests__/PluginsContainer-test.jsx index 3e0338b70a..3f727e0732 100644 --- a/web/client/components/plugins/__tests__/PluginsContainer-test.jsx +++ b/web/client/components/plugins/__tests__/PluginsContainer-test.jsx @@ -116,14 +116,15 @@ describe('PluginsContainer', () => { }); it('checks filterDisabledPlugins one disabled', () => { - const cmp = ReactDOM.render( + const container = document.getElementById("container"); + ReactDOM.render( - , document.getElementById("container")); - expect(cmp).toExist(); + , container); + expect(container.innerHTML).toExist(); - const cmpDom = ReactDOM.findDOMNode(cmp); + const cmpDom = container.firstElementChild; expect(cmpDom).toExist(); const rendered = cmpDom.getElementsByTagName("div"); @@ -131,14 +132,15 @@ describe('PluginsContainer', () => { }); it('checks filterDisabledPlugins no disabled', () => { - const cmp = ReactDOM.render( + const container = document.getElementById("container"); + ReactDOM.render( - , document.getElementById("container")); - expect(cmp).toExist(); + , container); + expect(container.innerHTML).toExist(); - const cmpDom = ReactDOM.findDOMNode(cmp); + const cmpDom = container.firstElementChild; expect(cmpDom).toExist(); const rendered = cmpDom.getElementsByTagName("div"); @@ -146,41 +148,42 @@ describe('PluginsContainer', () => { }); it('test noRoot option disable root rendering of plugins', () => { // Not rendered without container - let cmp = ReactDOM.render( + const container = document.getElementById("container"); + ReactDOM.render( - , document.getElementById("container")); - expect(cmp).toExist(); + , container); + expect(container.innerHTML).toExist(); - let cmpDom = ReactDOM.findDOMNode(cmp); + let cmpDom = container.firstElementChild; expect(cmpDom).toExist(); let rendered = cmpDom.getElementsByTagName("div"); // rendered in container expect(rendered.length).toBe(1); - cmp = ReactDOM.render( + ReactDOM.render( - , document.getElementById("container")); - expect(cmp).toExist(); + , container); + expect(container.innerHTML).toExist(); - cmpDom = ReactDOM.findDOMNode(cmp); + cmpDom = container.firstElementChild; expect(cmpDom).toExist(); - rendered = cmpDom.getElementsByTagName("div"); expect(document.getElementById('no-impl-item-no-root-plugin')).toNotExist(); expect(document.getElementById('no-root')).toExist(); }); it('checks plugin with forwardRef = true connect option', () => { - const app = ReactDOM.render( + const container = document.getElementById("container"); + ReactDOM.render( - , document.getElementById("container")); + , container); - expect(app).toExist(); + expect(container.innerHTML).toExist(); expect(window.WithGlobalRefPlugin.myFunc).toExist(); }); }); diff --git a/web/client/plugins/contextmanager/__tests__/PaginationToolbar-test.jsx b/web/client/plugins/contextmanager/__tests__/PaginationToolbar-test.jsx index 7a532be5f9..4415a3a2e7 100644 --- a/web/client/plugins/contextmanager/__tests__/PaginationToolbar-test.jsx +++ b/web/client/plugins/contextmanager/__tests__/PaginationToolbar-test.jsx @@ -60,13 +60,13 @@ describe("contextmanager PaginationToolbar component", () => { } }); - const comp = ReactDOM.render( + const container = document.getElementById('container'); + ReactDOM.render( - , document.getElementById("container")); - expect(comp).toExist(); + , container); + expect(container.innerHTML).toExist(); - const container = document.getElementById('container'); const pagination = container.getElementsByClassName('pagination'); expect(pagination).toExist(); expect(pagination.length).toBe(1); diff --git a/web/client/plugins/contexts/__tests__/PaginationToolbar-test.jsx b/web/client/plugins/contexts/__tests__/PaginationToolbar-test.jsx index fb748fbaf0..b878e2d910 100644 --- a/web/client/plugins/contexts/__tests__/PaginationToolbar-test.jsx +++ b/web/client/plugins/contexts/__tests__/PaginationToolbar-test.jsx @@ -60,13 +60,13 @@ describe("contexts PaginationToolbar component", () => { } }); - const comp = ReactDOM.render( + const container = document.getElementById('container'); + ReactDOM.render( - , document.getElementById("container")); - expect(comp).toExist(); + , container); + expect(container.innerHTML).toExist(); - const container = document.getElementById('container'); const pagination = container.getElementsByClassName('pagination'); expect(pagination).toExist(); expect(pagination.length).toBe(1); diff --git a/web/client/product/components/viewer/__tests__/MapViewer-test.jsx b/web/client/product/components/viewer/__tests__/MapViewer-test.jsx index 6de99b8f66..ade43915ee 100644 --- a/web/client/product/components/viewer/__tests__/MapViewer-test.jsx +++ b/web/client/product/components/viewer/__tests__/MapViewer-test.jsx @@ -20,10 +20,11 @@ const store = mockStore({}); const location = document.location; const renderMapViewerComp = (mapViewerPros) => { - return ReactDOM.render( + const container = document.getElementById("container"); + ReactDOM.render( - , document.getElementById("container")); + , container); }; describe("Test the MapViewerCmp component", () => { @@ -37,58 +38,75 @@ describe("Test the MapViewerCmp component", () => { setTimeout(done); }); - it('testing creation with defaults', () => { - const mapViewerPros = { wrappedContainer: MapViewerContainer }; - const cmpMapViewerCmp = renderMapViewerComp(mapViewerPros); - expect(cmpMapViewerCmp).toExist(); + it('testing creation with defaults', (done) => { + const mapViewerPros = { + wrappedContainer: MapViewerContainer, + onLoaded: (res) => { + expect(res).toBe(true); + done(); + } + }; + renderMapViewerComp(mapViewerPros); }); - it('testing creation with mapId = new', () => { + it('testing creation with mapId = new', (done) => { const match = {params: {mapId: "new"}}; const mapViewerPros = { match, location, onInit: () => {}, wrappedContainer: MapViewerContainer, loadMapConfig: (cfgUrl, mapId) => { expect(cfgUrl).toBe("new.json"); expect(mapId).toBe(null); + }, + onLoaded: (res) => { + expect(res).toBe(true); + done(); }}; - const cmpMapViewerCmp = renderMapViewerComp(mapViewerPros); - expect(cmpMapViewerCmp).toExist(); + renderMapViewerComp(mapViewerPros); }); - it('testing creation with mapId = anyString', () => { + it('testing creation with mapId = anyString', (done) => { const match = {params: {mapId: "anyString"}}; const mapViewerPros = { match, location, onInit: () => {}, wrappedContainer: MapViewerContainer, loadMapConfig: (cfgUrl, mapId) => { expect(cfgUrl).toBe("anyString.json"); expect(mapId).toBe(null); + }, + onLoaded: (res) => { + expect(res).toBe(true); + done(); }}; - const cmpMapViewerCmp = renderMapViewerComp(mapViewerPros); - expect(cmpMapViewerCmp).toExist(); + renderMapViewerComp(mapViewerPros); }); - it('testing creation with mapId = 0', () => { + it('testing creation with mapId = 0', (done) => { const match = {params: {mapId: 0}}; const mapViewerPros = { match, location, onInit: () => {}, wrappedContainer: MapViewerContainer, loadMapConfig: (cfgUrl, mapId) => { expect(cfgUrl).toBe("config.json"); expect(mapId).toBe(null); + }, + onLoaded: (res) => { + expect(res).toBe(true); + done(); }}; - const component = renderMapViewerComp(mapViewerPros); - expect(component).toExist(); + renderMapViewerComp(mapViewerPros); }); - it('testing creation with mapId = 1', () => { + it('testing creation with mapId = 1', (done) => { const match = {params: {mapId: 1}}; const mapViewerPros = { match, location, onInit: () => {}, wrappedContainer: MapViewerContainer, loadMapConfig: (cfgUrl, mapId) => { expect(cfgUrl).toBe("/rest/geostore/data/1"); expect(mapId).toBe(1); + }, + onLoaded: (res) => { + expect(res).toBe(true); + done(); }}; - const component = renderMapViewerComp(mapViewerPros); - expect(component).toExist(); + renderMapViewerComp(mapViewerPros); }); it('testing update of map on mapId change', (done) => { let count = 1; @@ -112,8 +130,15 @@ describe("Test the MapViewerCmp component", () => { renderMapViewerComp({ ...mapViewerPros, location: { ...location }}); // render second time setTimeout(() => { - const component = renderMapViewerComp({ ...mapViewerPros, match: match2, location: {...location}}); - expect(component).toExist(); + renderMapViewerComp({ + ...mapViewerPros, + match: match2, + location: {...location}, + onLoaded: (res) => { + expect(res).toBe(true); + done(); + } + }); }, 300); }); diff --git a/web/client/utils/__tests__/PluginsUtils-test.js b/web/client/utils/__tests__/PluginsUtils-test.js index 79eaae6b02..13204d66a7 100644 --- a/web/client/utils/__tests__/PluginsUtils-test.js +++ b/web/client/utils/__tests__/PluginsUtils-test.js @@ -85,8 +85,9 @@ describe('PluginsUtils', () => { test: "statetest" }) }; - const app = ReactDOM.render(, document.getElementById("container")); - const domElement = ReactDOM.findDOMNode(app); + const container = document.getElementById("container"); + ReactDOM.render(, container); + const domElement = container.firstElementChild; expect(domElement.innerText).toBe("plugintest"); }); it('handleExpression', () => { From 842b59b307dbf77c549b5ae2e51d7f0318350e44 Mon Sep 17 00:00:00 2001 From: Suren Date: Fri, 30 Aug 2024 04:25:40 -0700 Subject: [PATCH 05/39] #10521 - Update center comparison in OL map component (#10525) --- web/client/components/map/openlayers/Map.jsx | 16 +++++++-- .../map/openlayers/__tests__/Map-test.jsx | 36 +++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/web/client/components/map/openlayers/Map.jsx b/web/client/components/map/openlayers/Map.jsx index c1a2fcb233..b8208e028b 100644 --- a/web/client/components/map/openlayers/Map.jsx +++ b/web/client/components/map/openlayers/Map.jsx @@ -534,11 +534,23 @@ class OpenlayersMap extends React.Component { return new View(viewOptions); }; + isNearlyEqual = (a, b) => { + /** + * this implementation will update the map only if the movement + * between 8 decimals (coordinate precision in mm) in the reference system + * to avoid rounded value changes due to float mathematic operations or transformed value + */ + if (a === undefined || b === undefined) { + return false; + } + return a.toFixed(8) - b.toFixed(8) <= 0.00000001; + }; + _updateMapPositionFromNewProps = (newProps) => { var view = this.map.getView(); const currentCenter = this.props.center; - const centerIsUpdated = newProps.center.y === currentCenter.y && - newProps.center.x === currentCenter.x; + const centerIsUpdated = this.isNearlyEqual(newProps.center.y, currentCenter.y) && + this.isNearlyEqual(newProps.center.x, currentCenter.x); if (!centerIsUpdated) { // let center = ol.proj.transform([newProps.center.x, newProps.center.y], 'EPSG:4326', newProps.projection); diff --git a/web/client/components/map/openlayers/__tests__/Map-test.jsx b/web/client/components/map/openlayers/__tests__/Map-test.jsx index b354395bea..f070e7cd2d 100644 --- a/web/client/components/map/openlayers/__tests__/Map-test.jsx +++ b/web/client/components/map/openlayers/__tests__/Map-test.jsx @@ -1489,4 +1489,40 @@ describe('OpenlayersMap', () => { expect(customHooRegister.getHook(MapUtils.ZOOM_TO_EXTENT_HOOK)).toBeTruthy(); }); }); + it('update map position center based on nearly equal match when receiving new props', () => { + let map = ReactDOM.render( + + , document.getElementById("map")); + map = ReactDOM.render( + + , document.getElementById("map") + ); + expect(map).toBeTruthy(); + // center is not modified + expect(map.map.getView().getCenter()).toEqual([10.3346776780, 43.9323233378]); + + map = ReactDOM.render( + + , document.getElementById("map") + ); + // center is modified + expect(map.map.getView().getCenter()).toEqual([10.3346773790, 43.9323234388]); + }); }); From 5c5279965fd041907989415bad9e21b08a5f5a69 Mon Sep 17 00:00:00 2001 From: Lorenzo Natali Date: Thu, 5 Sep 2024 09:40:36 +0200 Subject: [PATCH 06/39] Fixed test build failures caused by ChromeHeadless height and a test dependent on window height (#10532) * Fixed test due to chrome headless issue in size * Fixed test due to chrome headless issue in size --- .../__tests__/withMediaVisibilityContainer-test.jsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/web/client/components/geostory/common/enhancers/__tests__/withMediaVisibilityContainer-test.jsx b/web/client/components/geostory/common/enhancers/__tests__/withMediaVisibilityContainer-test.jsx index 3b4ee7361f..404c4545f6 100644 --- a/web/client/components/geostory/common/enhancers/__tests__/withMediaVisibilityContainer-test.jsx +++ b/web/client/components/geostory/common/enhancers/__tests__/withMediaVisibilityContainer-test.jsx @@ -48,8 +48,8 @@ describe('withMediaVisibilityContainer HOC', () => { ReactDOM.render(
-
+ style={{ width: 10, height: 10, overflow: 'scroll' }}> +
@@ -64,11 +64,12 @@ describe('withMediaVisibilityContainer HOC', () => { it('scroll in view should render the actual component (lazy loading)', (done) => { const DEBOUNCE_TIME = 1; + // note: this test fails if the window is too small in height ReactDOM.render(
-
+ style={{ width: 10, height: 10, overflow: 'scroll' }}> +
)} debounceTime={DEBOUNCE_TIME}/>
, document.getElementById("container")); @@ -101,8 +102,8 @@ describe('withMediaVisibilityContainer HOC', () => { ReactDOM.render(
-
+ style={{ width: 10, height: 10, overflow: 'scroll' }}> +
From d9f53902e79bc919f21d7d2b42de808eefc5e8fc Mon Sep 17 00:00:00 2001 From: mahmoud adel <58145645+mahmoudadel54@users.noreply.github.com> Date: Thu, 5 Sep 2024 18:58:35 +0300 Subject: [PATCH 07/39] #10489: Fix 10438: Problems with GeoStory map configurations merge process (#10516) --- web/client/components/geostory/common/MapEditor.jsx | 11 ++++++++++- .../components/geostory/common/enhancers/map.jsx | 4 ++-- web/client/utils/__tests__/GeoStoryUtils-test.js | 1 + 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/web/client/components/geostory/common/MapEditor.jsx b/web/client/components/geostory/common/MapEditor.jsx index 7313d692db..d618232cdb 100644 --- a/web/client/components/geostory/common/MapEditor.jsx +++ b/web/client/components/geostory/common/MapEditor.jsx @@ -31,6 +31,7 @@ import Message from '../../I18N/Message'; import MapConfiguratorTabs from './map/MapConfiguratorTabs'; import withMapConfiguratorTabs from './enhancers/withMapConfiguratorTabs'; +import set from 'lodash/fp/set'; const StepHeader = ({title, description}) => ( @@ -102,7 +103,15 @@ const MapEditor = ({ closeNodeEditor={closeNodeEditor} editNode={editNode} map={map} - onChange={onChange}/> + onChange={(path, value) => { + const config = set(path, value, { map }); + const { layers, groups } = config?.map || {}; + if (path.includes('map.layers[')) { + onChangeMap('layers', layers, "replace"); + } else { + onChangeMap('groups', groups, "replace"); + } + }}/> ] || [ - (path, value) => { - update(`${focusedContent.path}.map.${path}`, value, "merge"); + (path, value, mode = "merge") => { + update(`${focusedContent.path}.map.${path}`, value, mode); }, onChange: ({update, focusedContent = {}}) => (path, value, mode = 'merge') => { diff --git a/web/client/utils/__tests__/GeoStoryUtils-test.js b/web/client/utils/__tests__/GeoStoryUtils-test.js index c1c3fcf569..1a774edeb8 100644 --- a/web/client/utils/__tests__/GeoStoryUtils-test.js +++ b/web/client/utils/__tests__/GeoStoryUtils-test.js @@ -823,3 +823,4 @@ describe("GeoStory Utils", () => { }); }); }); + From 8956d9c2753666a0c0166f90aee84192f8c5ac1b Mon Sep 17 00:00:00 2001 From: "Matteo V." Date: Fri, 6 Sep 2024 10:09:57 +0200 Subject: [PATCH 08/39] Fix #10530 fix catalog editor for array scenario (#10531) --- .../components/catalog/editor/MainForm.jsx | 14 ++++---- .../catalog/editor/MainFormUtils.js | 19 +++++++---- .../editor/__tests__/MainFormUtils-test.js | 34 ++++++++++--------- web/client/translations/data.de-DE.json | 5 +-- web/client/translations/data.en-US.json | 1 + web/client/translations/data.es-ES.json | 1 + web/client/translations/data.fr-FR.json | 1 + web/client/translations/data.it-IT.json | 1 + 8 files changed, 44 insertions(+), 32 deletions(-) diff --git a/web/client/components/catalog/editor/MainForm.jsx b/web/client/components/catalog/editor/MainForm.jsx index 140063a504..3ec7ff0e83 100644 --- a/web/client/components/catalog/editor/MainForm.jsx +++ b/web/client/components/catalog/editor/MainForm.jsx @@ -16,7 +16,7 @@ import InfoPopover from '../../widgets/widget/InfoPopover'; import { FormControl as FC, Form, Col, FormGroup, ControlLabel, Alert } from "react-bootstrap"; import localizedProps from '../../misc/enhancers/localizedProps'; -import {defaultPlaceholder, isValidURL} from "./MainFormUtils"; +import {defaultPlaceholder, checkUrl} from "./MainFormUtils"; const FormControl = localizedProps('placeholder')(FC); @@ -153,13 +153,13 @@ export default ({ onChangeType, setValid = () => {} }) => { - const [invalidProtocol, setInvalidProtocol] = useState(false); + const [error, setError] = useState(null); function handleProtocolValidity(url) { onChangeUrl(url); if (url) { - const isInvalidProtocol = !isValidURL(url, null, service?.allowUnsecureLayers); - setInvalidProtocol(isInvalidProtocol); - setValid(!isInvalidProtocol); + const {valid, errorMsgId} = checkUrl(url, null, service?.allowUnsecureLayers); + setError(valid ? null : errorMsgId); + setValid(valid); } } useEffect(() => { @@ -192,8 +192,8 @@ export default ({ - {invalidProtocol ? - + {error ? + : null} ); diff --git a/web/client/components/catalog/editor/MainFormUtils.js b/web/client/components/catalog/editor/MainFormUtils.js index b1cd334289..a2d16a13d5 100644 --- a/web/client/components/catalog/editor/MainFormUtils.js +++ b/web/client/components/catalog/editor/MainFormUtils.js @@ -25,13 +25,18 @@ export const defaultPlaceholder = (service) => { * @param {string} catalogUrl The URL of the catalog * @param {string} currentLocation The current location, by default `window.location.href` * @param {boolean} allowUnsecureLayers flag to allow unsecure url - * @returns {boolean} true if the URL is valid + * @returns {object} {valid: boolean, errorMsgId: string} */ -export const isValidURL = (catalogUrl = '', currentLocation, allowUnsecureLayers) => { - const { protocol: mapStoreProtocol } = url.parse(currentLocation ?? window.location.href); - const { protocol: catalogProtocol } = url.parse(catalogUrl); - if (mapStoreProtocol === 'https:' && !!catalogProtocol) { - return (mapStoreProtocol === catalogProtocol || allowUnsecureLayers); +export const checkUrl = (catalogUrl = '', currentLocation, allowUnsecureLayers) => { + try { + const { protocol: mapStoreProtocol } = url.parse(currentLocation ?? window.location.href); + const { protocol: catalogProtocol } = url.parse(catalogUrl); + if (mapStoreProtocol === 'https:' && !!catalogProtocol) { + const isProtocolValid = (mapStoreProtocol === catalogProtocol || allowUnsecureLayers); + return isProtocolValid ? {valid: true} : {valid: false, errorMsgId: "catalog.invalidUrlHttpProtocol"}; + } + return {valid: true}; + } catch (e) { + return {valid: false, errorMsgId: "catalog.invalidArrayUsageForUrl"}; } - return true; }; diff --git a/web/client/components/catalog/editor/__tests__/MainFormUtils-test.js b/web/client/components/catalog/editor/__tests__/MainFormUtils-test.js index 2c82408cb3..dd67581e79 100644 --- a/web/client/components/catalog/editor/__tests__/MainFormUtils-test.js +++ b/web/client/components/catalog/editor/__tests__/MainFormUtils-test.js @@ -1,12 +1,12 @@ import expect from "expect"; -import { isValidURL } from '../MainFormUtils'; +import { checkUrl } from '../MainFormUtils'; describe('Catalog Main Form Editor Utils', () => { - it('isValidURL', () => { + it('checkUrl', () => { const URLS = [ // http - ['http://myDomain.com/geoserver/wms', 'https://myMapStore.com/geoserver/wms', false], + ['http://myDomain.com/geoserver/wms', 'https://myMapStore.com/geoserver/wms', false, "catalog.invalidUrlHttpProtocol"], ['http://myDomain.com/geoserver/wms', 'http://myMapStore.com/geoserver/wms', true], // https ['https://myDomain.com/geoserver/wms', 'http://myMapStore.com/geoserver/wms', true], @@ -19,16 +19,17 @@ describe('Catalog Main Form Editor Utils', () => { ['/geoserver/wms', 'https://myMapStore.com/geoserver/wms', true], // relative path ["geoserver/wms", "http://myMapStore.com/geoserver/wms", true], - ["geoserver/wms", "https://myMapStore.com/geoserver/wms", true] - - + ["geoserver/wms", "https://myMapStore.com/geoserver/wms", true], + [["geoserver/wms", "geoserver/wms"], "https://myMapStore.com/geoserver/wms", false, "catalog.invalidArrayUsageForUrl"], // array + ["http://com/geoserver/wms", "https://myMapStore.com/geoserver/wms", false, "catalog.invalidUrlHttpProtocol"] ]; - URLS.forEach(([catalogURL, locationURL, valid]) => { - const result = isValidURL(catalogURL, locationURL); - expect(!!result).toEqual(!!valid, `${catalogURL} - added when location is ${locationURL} should be ${valid}, but it is ${result}`); + URLS.forEach(([catalogURL, locationURL, valid, messageId]) => { + const {valid: isValid, errorMsgId} = checkUrl(catalogURL, locationURL); + expect(!!isValid).toEqual(!!valid, `${catalogURL} - added when location is ${locationURL} should be ${valid}, but it is ${isValid}`); + expect(messageId).toEqual(errorMsgId); }); }); - it('isValidURL with allowUnsecureLayers', () => { + it('checkUrl with allowUnsecureLayers', () => { const URLS = [ // http ['http://myDomain.com/geoserver/wms', 'https://myMapStore.com/geoserver/wms', true, true], @@ -44,13 +45,14 @@ describe('Catalog Main Form Editor Utils', () => { ['/geoserver/wms', 'https://myMapStore.com/geoserver/wms', true, false], // relative path ["geoserver/wms", "http://myMapStore.com/geoserver/wms", true, false], - ["geoserver/wms", "https://myMapStore.com/geoserver/wms", true, true] - - + ["geoserver/wms", "https://myMapStore.com/geoserver/wms", true, true], + [["geoserver/wms", "geoserver/wms"], "https://myMapStore.com/geoserver/wms", false, false, "catalog.invalidArrayUsageForUrl"] // array ]; - URLS.forEach(([catalogURL, locationURL, valid, allowUnsecureLayers]) => { - const result = isValidURL(catalogURL, locationURL, allowUnsecureLayers); - expect(!!result).toEqual(!!valid, `${catalogURL} - added when location is ${locationURL} should be ${valid}, but it is ${result}`); + URLS.forEach(([catalogURL, locationURL, valid, allowUnsecureLayers, messageId]) => { + const {valid: isValid, errorMsgId} = checkUrl(catalogURL, locationURL, allowUnsecureLayers); + expect(!!isValid).toEqual(!!valid, `${catalogURL} - added when location is ${locationURL} should be ${valid}, but it is ${isValid}`); + expect(messageId).toEqual(errorMsgId); + expect(!!isValid).toEqual(!!valid, `${catalogURL} - added when location is ${locationURL} should be ${valid}, but it is ${isValid}`); }); }); }); diff --git a/web/client/translations/data.de-DE.json b/web/client/translations/data.de-DE.json index 82a9ebd075..73ddc2cc2c 100644 --- a/web/client/translations/data.de-DE.json +++ b/web/client/translations/data.de-DE.json @@ -1669,6 +1669,7 @@ "advancedSettings": "Erweiterte Einstellungen", "templateMetadataAvailable": "Metadaten im Dublin Core-Format verfügbar: abstract, boundingBox, contributor, creator, description, format, identifier, references, rights, source, subject, temporal, title, type, uri", "invalidUrlHttpProtocol": "Dieser Katalog kann nicht zu den verfügbaren hinzugefügt werden, da er ein http-Protokoll verwendet. Bitte geben Sie eine Katalog-URL an, die das https-Protokoll verwendet.", + "invalidArrayUsageForUrl": "Die Katalog-URL konnte nicht korrekt gelesen werden", "notification": { "errorTitle": "Error", "errorSearchingRecords": "Einige Datensätze wurden nicht gefunden: {records} . Bitte überprüfen Sie die URL des Abfrageparameters", @@ -2730,13 +2731,13 @@ "msExtrudedHeight": "Extrudierte Höhe", "msExtrusionColor": "Extrusionsfarbe", "msExtrusionType": "Extrusionstyp", - "wall": "Wand", + "wall": "Wand", "enableBanding": "Band styling", "selectChannel": "Wählen Sie eine Band aus", "minLabel": "Min", "maxLabel": "Max", "minSourceValue": "Mindestwert der Quelldaten", - "maxSourceValue": "Maximaler Quelldatenwert", + "maxSourceValue": "Maximaler Quelldatenwert", "customParams": "Benutzerdefinierte Parameter", "wrongFormatMsg": "Die eingegebene Konfiguration ist in falschem Format !!" }, diff --git a/web/client/translations/data.en-US.json b/web/client/translations/data.en-US.json index bd8aca9cd4..353b4a7ac1 100644 --- a/web/client/translations/data.en-US.json +++ b/web/client/translations/data.en-US.json @@ -1630,6 +1630,7 @@ "advancedSettings": "Advanced settings", "templateMetadataAvailable": "Metadata available from Dublin Core format: abstract, boundingBox, contributor, creator, description, format, identifier, references, rights, source, subject, temporal, title, type, uri", "invalidUrlHttpProtocol": "This catalog cannot be added to the available ones because it uses an http protocol. Please provide a catalog url that uses https protocol", + "invalidArrayUsageForUrl": "The catalog URL could not be parsed correctly", "notification": { "errorTitle": "Error", "errorSearchingRecords": "Some records have not been found: {records} Please check the query URL and its parameters.", diff --git a/web/client/translations/data.es-ES.json b/web/client/translations/data.es-ES.json index 6735ca97ea..b2a79257bf 100644 --- a/web/client/translations/data.es-ES.json +++ b/web/client/translations/data.es-ES.json @@ -1631,6 +1631,7 @@ "advancedSettings": "Ajustes avanzados", "templateMetadataAvailable": "Metadatos disponibles en formato Dublin Core: abstract, boundingBox, contributor, creator, description, format, identifier, references, rights, source, subject, temporal, title, type, uri", "invalidUrlHttpProtocol": "Este catálogo no se puede agregar a los disponibles porque usa un protocolo http. Proporcione una URL del catálogo que utilice el protocolo https", + "invalidArrayUsageForUrl": "La URL del catálogo no se pudo leer correctamente.", "notification": { "errorTitle": "Error", "errorSearchingRecords": "No se han encontrado algunos registros: {records} . Por favor revise la consulta param url", diff --git a/web/client/translations/data.fr-FR.json b/web/client/translations/data.fr-FR.json index 24d7bd929b..bc4ae4bd44 100644 --- a/web/client/translations/data.fr-FR.json +++ b/web/client/translations/data.fr-FR.json @@ -1631,6 +1631,7 @@ "advancedSettings": "Réglages avancés", "templateMetadataAvailable": "Métadonnées disponibles pour le format Dublin Core : abstract, boundingBox, contributor, creator, description, format, identifier, references, rights, source, subject, temporal, title, type, uri", "invalidUrlHttpProtocol": "Ce catalogue ne peut pas être ajouté à ceux disponibles car il utilise un protocole http. Veuillez fournir une URL de catalogue qui utilise le protocole https.", + "invalidArrayUsageForUrl": "L'URL du catalogue n'a pas pu être lue correctement", "notification": { "errorTitle": "Erreur", "errorSearchingRecords": "Certains enregistrements n'ont pas été trouvés: {records} . Veuillez vérifier l'URL de la requête et ses paramètres.", diff --git a/web/client/translations/data.it-IT.json b/web/client/translations/data.it-IT.json index ff53cd37fc..38c108ac8a 100644 --- a/web/client/translations/data.it-IT.json +++ b/web/client/translations/data.it-IT.json @@ -1630,6 +1630,7 @@ "advancedSettings": "Impostazioni avanzate", "templateMetadataAvailable": "Metadati disponibili del formato Dublin Core: abstract, boundingBox, contributor, creator, description, format, identifier, references, rights, source, subject, temporal, title, type, uri", "invalidUrlHttpProtocol": "Questo catalogo non può essere aggiunto a quelli disponibili perché utilizza un protocollo http. Fornisci un URL di catalogo che utilizzi il protocollo https", + "invalidArrayUsageForUrl": "Non è stato possibile leggere correttamente l'URL del catalogo", "notification": { "errorTitle": "Errore", "errorSearchingRecords": "Alcuni record non sono stati trovati: {records} . Controlla la lista dei parametri nella barra degli indirizzi", From f9348e45bcf07225e3771540409796a2c7650b3a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Sep 2024 11:56:13 +0200 Subject: [PATCH 09/39] Bump actions/download-artifact from 3 to 4.1.7 in /.github/workflows (#10529) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 3 to 4.1.7. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v3...v4.1.7) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Lorenzo Natali --- .github/workflows/create_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml index e71e7c3c65..a015fd3fa3 100644 --- a/.github/workflows/create_release.yml +++ b/.github/workflows/create_release.yml @@ -83,7 +83,7 @@ jobs: needs: build steps: - name: "Download war" - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4.1.7 with: path: artifacts/ - name: Display structure of downloaded files From afd00111ca6f871aa4baaf758822ec575305107f Mon Sep 17 00:00:00 2001 From: "Matteo V." Date: Wed, 11 Sep 2024 17:43:26 +0200 Subject: [PATCH 10/39] Fix #10506 fixed wrong parameter initialization (#10538) --- web/client/components/misc/SecureImage.jsx | 6 +++++- web/client/plugins/TOC/components/Legend.jsx | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/web/client/components/misc/SecureImage.jsx b/web/client/components/misc/SecureImage.jsx index dc6bc3810d..649031d18c 100644 --- a/web/client/components/misc/SecureImage.jsx +++ b/web/client/components/misc/SecureImage.jsx @@ -57,7 +57,11 @@ const SecureImage = ({ return ( {alt} { + if (imageSrc) { + props.onImgError(e); + } + }} onLoad={(e) => validateImg(e.target)} src={imageSrc} style={props.style} diff --git a/web/client/plugins/TOC/components/Legend.jsx b/web/client/plugins/TOC/components/Legend.jsx index 4274a356b8..9646c8c71a 100644 --- a/web/client/plugins/TOC/components/Legend.jsx +++ b/web/client/plugins/TOC/components/Legend.jsx @@ -121,7 +121,7 @@ class Legend extends React.Component { const url = this.getUrl(this.props); return ( this.validateImg(e.target)} src={url} style={this.props.style} From 94aa5d28db1a4a6160f182cb352ada38c05133d9 Mon Sep 17 00:00:00 2001 From: fkellner Date: Fri, 13 Sep 2024 16:20:16 +0200 Subject: [PATCH 11/39] #4647 small dependency updates (#10268) * geosolutions-it#4647: minor dep upgrades/cleanup A few packages had new minor versions or were unused. On Behalf of DB Systel * geosolutions-it#4647: upgrade json2csv to remove 1 critical vulnerability On Behalf of DB Systel * geosolutions-it#4647: remove some deprecation warnings On Behalf of DB Systel --------- Co-authored-by: Florian Kellner --- package.json | 28 +++++-------------- web/client/actions/__tests__/catalog-test.js | 2 +- web/client/epics/widgets.js | 8 +++--- .../plugins/__tests__/DrawerMenu-test.jsx | 9 ++++-- .../plugins/mapEditor/enhancers/editor.js | 3 +- 5 files changed, 19 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index 84d2d813a8..23839d9332 100644 --- a/package.json +++ b/package.json @@ -126,10 +126,9 @@ "@geosolutions/geostyler-sld-parser": "2.0.1-1", "@geosolutions/proj4": "2.4.9", "@geosolutions/react-joyride": "1.10.2", - "@geosolutions/wkt-parser": "1.2.2", "@googlemaps/js-api-loader": "1.12.9", - "@mapbox/geojsonhint": "2.0.1", - "@mapbox/togeojson": "0.16.0", + "@mapbox/geojsonhint": "3.3.0", + "@mapbox/togeojson": "0.16.2", "@mapstore/patcher": "https://github.com/geosolutions-it/Patcher/tarball/master", "@turf/along": "6.5.0", "@turf/area": "6.5.0", @@ -146,8 +145,6 @@ "@turf/difference": "6.5.0", "@turf/flatten": "6.5.0", "@turf/great-circle": "5.1.5", - "@turf/helpers": "6.5.0", - "@turf/inside": "4.1.0", "@turf/length": "6.5.0", "@turf/line-intersect": "4.1.0", "@turf/point-on-surface": "4.1.0", @@ -167,11 +164,10 @@ "cesium": "1.106.1", "chroma-js": "1.3.7", "classnames": "2.2.5", - "codemirror": "5.18.2", + "codemirror": "5.65.16", "colorbrewer": "1.0.0", "concurrently": "6.4.0", "connected-react-router": "6.3.2", - "create-react-class": "15.6.3", "d3-format": "3.1.0", "draft-js": "0.11.0", "draft-js-inline-toolbar-plugin": "3.0.1", @@ -199,16 +195,12 @@ "immutable": "4.0.0-rc.12", "intersection-observer": "0.7.0", "intl": "1.2.2", - "is-equal": "1.5.5", "ismobilejs": "0.5.0", "istanbul-instrumenter-loader": "3.0.1", - "jiff": "0.7.3", - "json-2-csv": "2.1.2", - "json-loader": "0.5.7", + "json-2-csv": "5.5.1", "jsonlint-mod": "1.7.5", - "jsonpath": "1.0.2", - "jszip": "3.1.5", - "keymirror": "0.1.1", + "jsonpath": "1.1.1", + "jszip": "3.10.1", "leaflet": "1.3.1", "leaflet-draw": "1.0.2", "leaflet-extra-markers": "1.0.6", @@ -229,19 +221,16 @@ "object-fit-images": "3.2.4", "ol": "7.4.0", "pdfmake": "0.2.7", - "pdfviewer": "0.3.2", "plotly.js-cartesian-dist": "2.5.1", "prop-types": "15.7.2", "qrcode.react": "0.9.3", "query-string": "6.9.0", "react": "16.10.1", "react-addons-css-transition-group": "15.6.2", - "react-addons-shallow-compare": "15.6.2", "react-bootstrap": "0.31.0", "react-checkbox-tree": "1.5.1", "react-codemirror2": "4.0.0", "react-color": "2.17.0", - "react-confirm-button": "0.0.2", "react-container-dimensions": "1.4.1", "react-contenteditable": "3.3.2", "react-copy-to-clipboard": "5.0.0", @@ -270,9 +259,7 @@ "react-quill": "1.1.0", "react-redux": "6.0.0", "react-resize-detector": "4.2.1", - "react-responsive": "1.3.0", "react-router": "4.1.1", - "react-router-dom": "4.2.2", "react-scroll-up": "1.3.7", "react-select": "1.3.0", "react-share": "1.15.1", @@ -297,7 +284,6 @@ "shpjs": "3.4.2", "simple-statistics": "7.8.3", "stickybits": "3.6.6", - "stream": "0.0.2", "tinycolor2": "1.4.1", "turf-bbox": "3.0.10", "turf-point": "2.0.1", @@ -310,7 +296,7 @@ "web-ifc": "0.0.50", "webfontloader": "1.6.28", "wellknown": "0.5.0", - "xml2js": "0.4.17", + "xml2js": "0.6.2", "xpath": "0.0.27" }, "scripts": { diff --git a/web/client/actions/__tests__/catalog-test.js b/web/client/actions/__tests__/catalog-test.js index f93c6839fd..029e2aa4bc 100644 --- a/web/client/actions/__tests__/catalog-test.js +++ b/web/client/actions/__tests__/catalog-test.js @@ -153,7 +153,7 @@ describe('Test correctness of the catalog actions', () => { expect(retval.service).toBe(service); }); it('deleteService', () => { - var retval = deleteService(status); + var retval = deleteService(); expect(retval).toExist(); expect(retval.type).toBe(DELETE_SERVICE); diff --git a/web/client/epics/widgets.js b/web/client/epics/widgets.js index e1c5b71cc3..5e3b98f308 100644 --- a/web/client/epics/widgets.js +++ b/web/client/epics/widgets.js @@ -54,7 +54,7 @@ import { LOCATION_CHANGE } from 'connected-react-router'; import { saveAs } from 'file-saver'; import {downloadCanvasDataURL} from '../utils/FileUtils'; import {reprojectBbox} from '../utils/CoordinatesUtils'; -import converter from 'json-2-csv'; +import {json2csv} from 'json-2-csv'; import { defaultGetZoomForExtent } from '../utils/MapUtils'; import { updateDependenciesMapOfMapList, DEFAULT_MAP_SETTINGS } from "../utils/WidgetsUtils"; @@ -133,9 +133,9 @@ const configureDependency = (active, dependency, options) => export const exportWidgetData = action$ => action$.ofType(EXPORT_CSV) .do( ({data = [], title = "data"}) => - converter.json2csv(data, (err, csv) => err ? null : saveAs(new Blob([ - csv - ], {type: "text/csv"}), title + ".csv"))) + saveAs(new Blob([ + json2csv(data) + ], {type: "text/csv"}), title + ".csv")) .filter( () => false); /** * Intercepts changes to widgets to catch widgets that can share some dependencies. diff --git a/web/client/plugins/__tests__/DrawerMenu-test.jsx b/web/client/plugins/__tests__/DrawerMenu-test.jsx index c64b1d09d5..05a59a03e4 100644 --- a/web/client/plugins/__tests__/DrawerMenu-test.jsx +++ b/web/client/plugins/__tests__/DrawerMenu-test.jsx @@ -28,9 +28,12 @@ const SAMPLE_ITEM = { const mouseMove = (x, y, node) => { const doc = node ? node.ownerDocument : document; - const evt = doc.createEvent('MouseEvents'); - evt.initMouseEvent('mousemove', true, true, window, - 0, 0, 0, x, y, false, false, false, false, 0, null); + const evt = new MouseEvent( + 'mousemove', + { + canBubble: true, cancelable: true, view: window, detail: 0, screenX: 0, screenY: 0, clientX: x, clientY: y, + ctrlKey: false, altKey: false, shiftKey: false, metaKey: false, button: 0, relatedTarget: null + }); doc.dispatchEvent(evt); return evt; }; diff --git a/web/client/plugins/mapEditor/enhancers/editor.js b/web/client/plugins/mapEditor/enhancers/editor.js index 4b33a2fd5a..862a9446f9 100644 --- a/web/client/plugins/mapEditor/enhancers/editor.js +++ b/web/client/plugins/mapEditor/enhancers/editor.js @@ -22,7 +22,6 @@ import {backgroundListSelector} from '../../../selectors/backgroundselector'; import {mapOptionsToSaveSelector} from '../../../selectors/mapsave'; import {textSearchConfigSelector, bookmarkSearchConfigSelector} from '../../../selectors/searchconfig'; import MapUtils from '../../../utils/MapUtils'; -import { isNull } from 'util'; const saveSelector = createSelector( @@ -52,7 +51,7 @@ export default compose( const mapData = MapUtils.saveMapConfiguration(map, layers, groups, backgrounds, textSearchConfig, bookmarkSearchConfig, additionalOptions); - return save({...mapData.map, layers: mapData.map.layers.map(l => pickBy(l, (p) => p !== undefined && !isNull(p)))}, owner); + return save({...mapData.map, layers: mapData.map.layers.map(l => pickBy(l, (p) => p !== undefined && !(p === null)))}, owner); } }), WithConfirm, From 6f6b28aa4c5568a9c01fe9fafd77dd3c3129da08 Mon Sep 17 00:00:00 2001 From: fkellner Date: Fri, 13 Sep 2024 16:44:39 +0200 Subject: [PATCH 12/39] #10158 painless accessibility improvements (#10159) * geosolutions-it#10158: set HTML document language On Behalf of DB Systel * geosolutions-it#10158: re-enable browser zoom Too much Zoom does seem to break the layout, but handing that responsibility to the end user is more accessible than disabling it. Map zoom is unaffected, and since using browser zoom enlarges map controls, users who tried zooming into the map using browser zoom should be able to notice the actual controls and recover from their mistake. On Behalf of DB Systel * geosolutions-it#10158: Tiny Accessibility Fixes - Convert heading that, semantically, should not be a heading, to a div - correctly assign label to scalebox - add alt tag to attribution logo On Behalf of DB Systel * geosolutions-it#10158: set HTML document language once per mount/update As requested, the document language is now set when the component is mounted or the language is changed instead of with every rerender. --------- Co-authored-by: Florian Kellner --- web/client/components/I18N/Localized.jsx | 16 ++++++++++++++++ .../components/I18N/__tests__/Localized-test.jsx | 15 +++++++++++++++ .../components/mapcontrols/scale/ScaleBox.jsx | 4 ++-- web/client/components/misc/PaginationToolbar.jsx | 2 +- web/client/index.html | 2 +- web/client/indexTemplate.html | 2 +- web/client/product/plugins/Attribution.jsx | 2 +- 7 files changed, 37 insertions(+), 6 deletions(-) diff --git a/web/client/components/I18N/Localized.jsx b/web/client/components/I18N/Localized.jsx index 0f56f3b819..a810e72155 100644 --- a/web/client/components/I18N/Localized.jsx +++ b/web/client/components/I18N/Localized.jsx @@ -32,6 +32,16 @@ class Localized extends React.Component { }; } + componentDidMount() { + this.updateDocumentLangAttribute(); + } + + componentDidUpdate(prevProps) { + if (this.props.locale !== prevProps.locale) { + this.updateDocumentLangAttribute(); + } + } + render() { let { children } = this.props; @@ -63,6 +73,12 @@ class Localized extends React.Component { }; }, {}); }; + + updateDocumentLangAttribute() { + if (document?.documentElement) { + document.documentElement.setAttribute("lang", this.props.locale); + } + } } export default Localized; diff --git a/web/client/components/I18N/__tests__/Localized-test.jsx b/web/client/components/I18N/__tests__/Localized-test.jsx index 829b0a70eb..91ab7542d4 100644 --- a/web/client/components/I18N/__tests__/Localized-test.jsx +++ b/web/client/components/I18N/__tests__/Localized-test.jsx @@ -40,6 +40,21 @@ describe('Test the localization support HOC', () => { expect(dom.innerHTML).toBe("my message"); }); + it('correctly sets the document language', () => { + ReactDOM.render( + + {() => } + + , document.getElementById("container")); + expect(document.documentElement.lang).toBe("it-IT"); + ReactDOM.render( + + {() => } + + , document.getElementById("container")); + expect(document.documentElement.lang).toBe("de-DE"); + }); + it('localizes wrapped HTML component', () => { var localized = ReactDOM.render( diff --git a/web/client/components/mapcontrols/scale/ScaleBox.jsx b/web/client/components/mapcontrols/scale/ScaleBox.jsx index 39333c2303..eebb3d5bad 100644 --- a/web/client/components/mapcontrols/scale/ScaleBox.jsx +++ b/web/client/components/mapcontrols/scale/ScaleBox.jsx @@ -73,8 +73,8 @@ class ScaleBox extends React.Component { } else { control = (
- {this.props.label} - + {this.props.label} + {this.getOptions()}
) diff --git a/web/client/components/misc/PaginationToolbar.jsx b/web/client/components/misc/PaginationToolbar.jsx index f9e6c99038..5a6d710914 100644 --- a/web/client/components/misc/PaginationToolbar.jsx +++ b/web/client/components/misc/PaginationToolbar.jsx @@ -65,7 +65,7 @@ class PaginationToolbar extends React.Component { onSelect={this.onSelect} /> -
{this.props.loading ? : msg}
+
{this.props.loading ? : msg}
); diff --git a/web/client/index.html b/web/client/index.html index d7f952eaf2..c868c1bf74 100644 --- a/web/client/index.html +++ b/web/client/index.html @@ -3,7 +3,7 @@ - + MapStore HomePage