diff --git a/package.json b/package.json
index 567e12b8..7ef7e175 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "react-spatial",
"license": "MIT",
"description": "Components to build React map apps.",
- "version": "1.11.0",
+ "version": "1.11.1-beta.2",
"dependencies": {
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
diff --git a/src/components/BaseLayerSwitcher/BaseLayerSwitcher.js b/src/components/BaseLayerSwitcher/BaseLayerSwitcher.js
index 40b12b27..983340e4 100644
--- a/src/components/BaseLayerSwitcher/BaseLayerSwitcher.js
+++ b/src/components/BaseLayerSwitcher/BaseLayerSwitcher.js
@@ -46,21 +46,24 @@ const propTypes = {
* @param {function} Translation function returning the translated string.
*/
t: PropTypes.func,
-};
-const defaultProps = {
- className: "rs-base-layer-switcher",
- altText: "Source not found",
- titles: {
- button: "Base layers",
- openSwitcher: "Open Baselayer-Switcher",
- closeSwitcher: "Close Baselayer-Switcher",
- },
- closeButtonImage: ,
- layerImages: undefined,
- t: (s) => {
- return s;
- },
+ /**
+ * Callback function on close button click.
+ * @param {function} Callback function triggered when a switcher button is clicked. Takes the event as argument.
+ */
+ onCloseButtonClick: PropTypes.func,
+
+ /**
+ * Callback function on layer button click.
+ * @param {function} Callback function triggered when a switcher button is clicked. Takes the event and the layer as arguments.
+ */
+ onLayerButtonClick: PropTypes.func,
+
+ /**
+ * Callback function on main switcher button click.
+ * @param {function} Callback function triggered when a switcher button is clicked. Takes the event as argument.
+ */
+ onSwitcherButtonClick: PropTypes.func,
};
const getVisibleLayer = (layers) => {
@@ -90,6 +93,31 @@ const getImageStyle = (url) => {
: null;
};
+function CloseButton({ onClick, tabIndex, title, children }) {
+ return (
+
{
+ return e.which === 13 && onClick();
+ }}
+ tabIndex={tabIndex}
+ aria-label={title}
+ title={title}
+ >
+ {children}
+
+ );
+}
+
+CloseButton.propTypes = {
+ onClick: PropTypes.func.isRequired,
+ tabIndex: PropTypes.string.isRequired,
+ title: PropTypes.string.isRequired,
+ children: PropTypes.node.isRequired,
+};
+
/**
* The BaseLayerSwitcher component renders a button interface for switching the visible
* [mobility-toolbox-js layer](https://mobility-toolbox-js.geops.io/api/identifiers%20html#ol-layers)
@@ -98,12 +126,19 @@ const getImageStyle = (url) => {
function BaseLayerSwitcher({
layers,
- layerImages,
- className,
- altText,
- titles,
- closeButtonImage,
- t,
+ layerImages = undefined,
+ className = "rs-base-layer-switcher",
+ altText = "Source not found",
+ titles = {
+ button: "Base layers",
+ openSwitcher: "Open Baselayer-Switcher",
+ closeSwitcher: "Close Baselayer-Switcher",
+ },
+ closeButtonImage = ,
+ onCloseButtonClick = null,
+ onLayerButtonClick = null,
+ onSwitcherButtonClick = null,
+ t = (s) => s,
}) {
const [switcherOpen, setSwitcherOpen] = useState(false);
const [isClosed, setIsClosed] = useState(true);
@@ -111,20 +146,6 @@ function BaseLayerSwitcher({
getVisibleLayer(layers) || layers[0],
);
- useEffect(() => {
- // Update the layer selected when a visibility changes.
- const olKeys = (layers || []).map((layer) => {
- return layer.on("change:visible", (evt) => {
- if (evt.target.visible && currentLayer !== evt.target) {
- setCurrentLayer(evt.target);
- }
- });
- });
- return () => {
- unByKey(olKeys);
- };
- }, [currentLayer, layers]);
-
/* Images are loaded from props if provided, fallback from layer */
const images = layerImages
? Object.keys(layerImages).map((layerImage) => {
@@ -137,12 +158,17 @@ function BaseLayerSwitcher({
const openClass = switcherOpen ? " rs-open" : "";
const hiddenStyle = switcherOpen && !isClosed ? "visible" : "hidden";
- const handleSwitcherClick = () => {
+ const handleSwitcherClick = (evt) => {
+ const nextLayer = layers.find((layer) => {
+ return !layer.visible;
+ });
+ const onButtonClick =
+ layers.length === 2 ? onLayerButtonClick : onSwitcherButtonClick;
+ if (onButtonClick) {
+ onButtonClick(evt, nextLayer);
+ }
if (layers.length === 2) {
/* On only two layer options the opener becomes a layer toggle button */
- const nextLayer = layers.find((layer) => {
- return !layer.visible;
- });
if (currentLayer.setVisible) {
currentLayer.setVisible(false);
} else {
@@ -160,7 +186,10 @@ function BaseLayerSwitcher({
return setSwitcherOpen(true) && setIsClosed(false);
};
- const onLayerSelect = (layer) => {
+ const onLayerSelect = (layer, evt) => {
+ if (onLayerButtonClick) {
+ onLayerButtonClick(evt, layer);
+ }
if (!switcherOpen) {
setSwitcherOpen(true);
return;
@@ -214,30 +243,24 @@ function BaseLayerSwitcher({
};
}, [switcherOpen]);
+ useEffect(() => {
+ // Update the layer selected when a visibility changes.
+ const olKeys = (layers || []).map((layer) => {
+ return layer.on("change:visible", (evt) => {
+ if (evt.target.visible && currentLayer !== evt.target) {
+ setCurrentLayer(evt.target);
+ }
+ });
+ });
+ return () => {
+ unByKey(olKeys);
+ };
+ }, [currentLayer, layers]);
+
if (!layers || layers.length < 2 || !currentLayer) {
return null;
}
- const toggleBtn = (
-
-
{
- return setSwitcherOpen(false);
- }}
- onKeyPress={(e) => {
- return e.which === 13 && setSwitcherOpen(false);
- }}
- tabIndex={switcherOpen ? "0" : "-1"}
- aria-label={titles.closeSwitcher}
- title={titles.closeSwitcher}
- >
- {closeButtonImage}
-
-
- );
-
return (
{
- return onLayerSelect(layer);
+ onClick={(evt) => {
+ return onLayerSelect(layer, evt);
}}
- onKeyPress={(e) => {
- if (e.which === 13) {
- onLayerSelect(layer);
+ onKeyPress={(evt) => {
+ if (evt.which === 13) {
+ onLayerSelect(layer, evt);
}
}}
style={imageStyle}
@@ -311,12 +334,22 @@ function BaseLayerSwitcher({
);
})}
- {toggleBtn}
+
{
+ if (onCloseButtonClick) {
+ onCloseButtonClick(evt);
+ }
+ setSwitcherOpen(false);
+ }}
+ tabIndex={switcherOpen ? "0" : "-1"}
+ title={titles.closeSwitcher}
+ >
+ {closeButtonImage}
+
);
}
BaseLayerSwitcher.propTypes = propTypes;
-BaseLayerSwitcher.defaultProps = defaultProps;
export default BaseLayerSwitcher;
diff --git a/src/components/BaseLayerSwitcher/BaseLayerSwitcher.scss b/src/components/BaseLayerSwitcher/BaseLayerSwitcher.scss
index 224b2b62..ad1a34aa 100644
--- a/src/components/BaseLayerSwitcher/BaseLayerSwitcher.scss
+++ b/src/components/BaseLayerSwitcher/BaseLayerSwitcher.scss
@@ -4,6 +4,7 @@
transition: 800ms width;
overflow: hidden;
display: flex;
+ align-items: center;
padding: 2px;
pointer-events: none;
diff --git a/src/components/BaseLayerSwitcher/__snapshots__/BaseLayerSwitcher.test.js.snap b/src/components/BaseLayerSwitcher/__snapshots__/BaseLayerSwitcher.test.js.snap
index 7a08b625..4997d301 100644
--- a/src/components/BaseLayerSwitcher/__snapshots__/BaseLayerSwitcher.test.js.snap
+++ b/src/components/BaseLayerSwitcher/__snapshots__/BaseLayerSwitcher.test.js.snap
@@ -64,25 +64,23 @@ exports[`BaseLayerSwitcher matches snapshots using default properties. 1`] = `
-
`;
diff --git a/src/components/Zoom/Zoom.js b/src/components/Zoom/Zoom.js
index a3e393bb..322e5c98 100644
--- a/src/components/Zoom/Zoom.js
+++ b/src/components/Zoom/Zoom.js
@@ -45,17 +45,18 @@ const propTypes = {
* Display a slider to zoom.
*/
zoomSlider: PropTypes.bool,
-};
-const defaultProps = {
- titles: {
- zoomIn: "Zoom in",
- zoomOut: "Zoom out",
- },
- zoomInChildren: ,
- zoomOutChildren: ,
- zoomSlider: false,
- delta: 1,
+ /**
+ * Callback function on zoom-in button click.
+ * @param {function} Callback function triggered when zoom-in button is clicked. Takes the event as argument.
+ */
+ onZoomInButtonClick: PropTypes.func,
+
+ /**
+ * Callback function on zoom-out button click.
+ * @param {function} Callback function triggered when the zoom-out button is clicked. Takes the event as argument.
+ */
+ onZoomOutButtonClick: PropTypes.func,
};
const updateZoom = (map, delta) => {
@@ -79,11 +80,16 @@ const updateZoom = (map, delta) => {
*/
function Zoom({
map,
- titles,
- zoomInChildren,
- zoomOutChildren,
- zoomSlider,
- delta,
+ titles = {
+ zoomIn: "Zoom in",
+ zoomOut: "Zoom out",
+ },
+ zoomInChildren = ,
+ zoomOutChildren = ,
+ zoomSlider = false,
+ onZoomInButtonClick = null,
+ onZoomOutButtonClick = null,
+ delta = 1,
...other
}) {
const ref = useRef();
@@ -91,20 +97,26 @@ function Zoom({
const zoomIn = useCallback(
(evt) => {
+ if (onZoomInButtonClick) {
+ onZoomInButtonClick(evt);
+ }
if (!evt.which || evt.which === 13) {
updateZoom(map, delta);
}
},
- [delta, map],
+ [delta, map, onZoomInButtonClick],
);
const zoomOut = useCallback(
(evt) => {
+ if (onZoomOutButtonClick) {
+ onZoomOutButtonClick(evt);
+ }
if (!evt.which || evt.which === 13) {
updateZoom(map, -delta);
}
},
- [delta, map],
+ [delta, map, onZoomOutButtonClick],
);
const zoomInDisabled = useMemo(() => {
@@ -175,6 +187,5 @@ function Zoom({
}
Zoom.propTypes = propTypes;
-Zoom.defaultProps = defaultProps;
export default React.memo(Zoom);
diff --git a/src/components/Zoom/Zoom.test.js b/src/components/Zoom/Zoom.test.js
index c02a4404..228215dc 100644
--- a/src/components/Zoom/Zoom.test.js
+++ b/src/components/Zoom/Zoom.test.js
@@ -1,84 +1,61 @@
import React from "react";
-import renderer from "react-test-renderer";
-import { configure, shallow, mount } from "enzyme";
-import Adapter from "@cfaester/enzyme-adapter-react-18";
+import { fireEvent, render } from "@testing-library/react";
import { act } from "react-dom/test-utils";
import MapEvent from "ol/MapEvent";
import OLView from "ol/View";
import OLMap from "ol/Map";
import Zoom from "./Zoom";
-configure({ adapter: new Adapter() });
-
describe("Zoom", () => {
test("should match snapshot.", () => {
const map = new OLMap({});
- const component = renderer.create();
- const tree = component.toJSON();
- expect(tree).toMatchSnapshot();
+ const { container } = render();
+ expect(container.innerHTML).toMatchSnapshot();
});
test("should match snapshot with custom attributes", () => {
const map = new OLMap({});
- const component = renderer.create(
+ const { container } = render(
,
);
- const tree = component.toJSON();
- expect(tree).toMatchSnapshot();
+ expect(container.innerHTML).toMatchSnapshot();
});
test("should match snapshot with zoom slider", () => {
const map = new OLMap({});
- const component = renderer.create();
- const tree = component.toJSON();
- expect(tree).toMatchSnapshot();
+ const { container } = render();
+ expect(container.innerHTML).toMatchSnapshot();
});
[
["click", {}],
["keypress", { which: 13 }],
].forEach((evt) => {
- test(`should zoom in on ${evt[0]}.`, () => {
+ test(`should zoom in on ${evt[0]}.`, async () => {
const map = new OLMap({ view: new OLView({ zoom: 5 }) });
- const zooms = shallow();
- zooms
- .find(".rs-zoom-in")
- .first()
- .simulate(...evt);
-
+ const { container } = render();
+ await fireEvent.click(container.querySelector(".rs-zoom-in"));
expect(map.getView().getZoom()).toBe(6);
});
- test(`should zoom in on ${evt[0]} (delta: 0.3).`, () => {
+ test(`should zoom in on ${evt[0]} (delta: 0.3).`, async () => {
const map = new OLMap({ view: new OLView({ zoom: 5 }) });
- const zooms = shallow();
- zooms
- .find(".rs-zoom-in")
- .first()
- .simulate(...evt);
-
+ const { container } = render();
+ await fireEvent.click(container.querySelector(".rs-zoom-in"));
expect(map.getView().getZoom()).toBe(5.3);
});
- test(`should zoom out on ${evt[0]}.`, () => {
+ test(`should zoom out on ${evt[0]}.`, async () => {
const map = new OLMap({ view: new OLView({ zoom: 5 }) });
- const zooms = shallow();
- zooms
- .find(".rs-zoom-out")
- .first()
- .simulate(...evt);
-
+ const { container } = render();
+ await fireEvent.click(container.querySelector(".rs-zoom-out"));
expect(map.getView().getZoom()).toBe(4);
});
- test(`should zoom out on ${evt[0]} (delta: 0.3).`, () => {
+ test(`should zoom out on ${evt[0]} (delta: 0.3).`, async () => {
const map = new OLMap({ view: new OLView({ zoom: 5 }) });
- const zooms = shallow();
- zooms
- .find(".rs-zoom-out")
- .first()
- .simulate(...evt);
-
+ const { container } = render();
+ await fireEvent.click(container.querySelector(".rs-zoom-out"));
expect(map.getView().getZoom()).toBe(4.7);
});
});
@@ -87,55 +64,75 @@ describe("Zoom", () => {
const map = new OLMap({});
const spy = jest.spyOn(map, "removeControl");
const spy2 = jest.spyOn(map, "addControl");
- const wrapper = mount();
+ const { unmount } = render();
expect(spy).toHaveBeenCalledTimes(0);
- wrapper.unmount();
+ unmount();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy.mock.calls[0][0]).toBe(spy2.mock.calls[0][0]);
});
- test("should disable zoom-in button on mount with max zoom..", () => {
+ test("should disable zoom-in button on mount with max zoom..", async () => {
const map = new OLMap({
view: new OLView({ maxZoom: 20, zoom: 20 }),
});
const spy = jest.spyOn(map.getView(), "setZoom");
- const wrapper = mount();
+ const { rerender, container } = render();
act(() => {
map.dispatchEvent(new MapEvent("moveend", map));
});
- wrapper.update();
- expect(wrapper.find(".rs-zoom-in").prop("disabled")).toEqual(true);
- wrapper.find(".rs-zoom-in").first().simulate("click");
+ rerender();
+ expect(container.querySelector(".rs-zoom-in").disabled).toEqual(true);
+ await fireEvent.click(container.querySelector(".rs-zoom-in"));
expect(spy).toHaveBeenCalledTimes(0);
});
- test("should disable zoom-out button on mount with min zoom.", () => {
+ test("should disable zoom-out button on mount with min zoom.", async () => {
const map = new OLMap({
view: new OLView({ minZoom: 2, zoom: 2 }),
});
const spy = jest.spyOn(map.getView(), "setZoom");
- const wrapper = mount();
+ const { rerender, container } = render();
act(() => {
map.dispatchEvent(new MapEvent("moveend", map));
});
- wrapper.update();
- expect(wrapper.find(".rs-zoom-out").prop("disabled")).toEqual(true);
- wrapper.find(".rs-zoom-out").first().simulate("click");
+ rerender();
+ expect(container.querySelector(".rs-zoom-out").disabled).toEqual(true);
+ await fireEvent.click(container.querySelector(".rs-zoom-out"));
expect(spy).toHaveBeenCalledTimes(0);
});
});
-test("should disable zoom-out button when reaching min zoom.", () => {
+test("should disable zoom-out button when reaching min zoom.", async () => {
const map = new OLMap({
view: new OLView({ minZoom: 2, zoom: 3 }),
});
const spy = jest.spyOn(map.getView(), "setZoom");
- const wrapper = mount();
- wrapper.find(".rs-zoom-out").first().simulate("click");
+ const { rerender, container } = render();
+ await fireEvent.click(container.querySelector(".rs-zoom-out"));
expect(spy).toHaveBeenCalledTimes(1);
act(() => {
map.dispatchEvent(new MapEvent("moveend", map));
});
- wrapper.update();
- expect(wrapper.find(".rs-zoom-out").prop("disabled")).toEqual(true);
+ rerender();
+ expect(container.querySelector(".rs-zoom-out").disabled).toEqual(true);
+});
+
+test("should trigger callback functions.", async () => {
+ const map = new OLMap({
+ view: new OLView({ minZoom: 2, zoom: 3 }),
+ });
+ const zoomIn = jest.fn();
+ const zoomOut = jest.fn();
+ const { container } = render(
+ ,
+ );
+ await fireEvent.click(container.querySelector(".rs-zoom-out"));
+ expect(zoomOut).toHaveBeenCalledTimes(1);
+ await fireEvent.click(container.querySelector(".rs-zoom-in"));
+ await fireEvent.click(container.querySelector(".rs-zoom-in"));
+ expect(zoomIn).toHaveBeenCalledTimes(2);
});
diff --git a/src/components/Zoom/__snapshots__/Zoom.test.js.snap b/src/components/Zoom/__snapshots__/Zoom.test.js.snap
index 4278c387..f01bc42b 100644
--- a/src/components/Zoom/__snapshots__/Zoom.test.js.snap
+++ b/src/components/Zoom/__snapshots__/Zoom.test.js.snap
@@ -1,200 +1,137 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Zoom should match snapshot with custom attributes 1`] = `
-
`;
exports[`Zoom should match snapshot with zoom slider 1`] = `
-
-
+
-
`;
exports[`Zoom should match snapshot. 1`] = `
-