From 4f8ff655f453a7e6cda183573ed7eb564b38c0c1 Mon Sep 17 00:00:00 2001 From: Ahmad Ayubi <55214462+ahmadayubi@users.noreply.github.com> Date: Wed, 10 Feb 2021 13:51:30 -0500 Subject: [PATCH] Adding tabbing navigation and accessibility to map (#270) * Allow features to be focused * Adds crosshair, shows on keyboard movement * Shows crosshair on tab * Only run on moveend if there are queryable layers * Revert listener change * Adds tests for keyboard interaction * Remove close button from feature popup * Add bypass navigation * Query popup fix * Add feature count, move controls to bottom * Test update to consider skip buttons * Test update * Adds next and previous focus buttons * Add tests for keyboard interaction * Rename variables and add comments * Remove handlers on close Co-authored-by: Peter Rushforth --- .github/workflows/sync.yml | 2 +- src/mapml-viewer.js | 1 + src/mapml.css | 38 ++++ src/mapml/handlers/QueryHandler.js | 13 +- src/mapml/index.js | 4 + src/mapml/layers/Crosshair.js | 95 +++++++++ src/mapml/layers/FeatureLayer.js | 142 +++++++++++++ src/mapml/layers/MapLayer.js | 14 +- src/mapml/layers/TemplatedFeaturesLayer.js | 22 +- src/web-map.js | 1 + test/e2e/core/keyboardInteraction.html | 86 ++++++++ test/e2e/core/keyboardInteraction.test.js | 228 +++++++++++++++++++++ test/e2e/core/layerContextMenu.test.js | 16 -- test/e2e/layers/mapMLFeatures.html | 1 + 14 files changed, 637 insertions(+), 26 deletions(-) create mode 100644 src/mapml/layers/Crosshair.js create mode 100644 test/e2e/core/keyboardInteraction.html create mode 100644 test/e2e/core/keyboardInteraction.test.js diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index 15410d286..eb2001a78 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -29,5 +29,5 @@ jobs: destination_folder: 'dist' user_email: aayub041@uottawa.ca user_name: 'ahmadayubi' - commit_msg: 'Sync MapML Build' + commit_msg: '[AUTO] Sync MapML Build' destination_branch: main diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index 397413ea8..dc22fb5b5 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -211,6 +211,7 @@ export class MapViewer extends HTMLElement { this._attributionControl = this._map.attributionControl.setPrefix('Maps4HTML | Leaflet'); this.setControls(false,false,true); + this._crosshair = M.crosshair().addTo(this._map); // Make the Leaflet container element programmatically identifiable // (https://github.com/Leaflet/Leaflet/issues/7193). diff --git a/src/mapml.css b/src/mapml.css index af8e90376..37fada4c1 100644 --- a/src/mapml.css +++ b/src/mapml.css @@ -389,3 +389,41 @@ summary { .leaflet-container .leaflet-control-container { visibility: unset!important; } + +.mapml-crosshair { + margin: -18px 0 0 -18px; + width: 36px; + height: 36px; + left: 50%; + top: 50%; + content: ''; + display: block; + position: absolute; + z-index: 10000; +} + +.mapml-popup-button { + padding: 0 4px 0 4px; + border: none; + text-align: center; + width: 18px; + height: 14px; + font: 16px/14px Tahoma, Verdana, sans-serif; + color: #c3c3c3; + text-decoration: none; + font-weight: bold; + background: transparent; + white-space: nowrap; +} + +.mapml-focus-buttons { + white-space: nowrap; + display: inline; +} + +.mapml-feature-count { + display:inline; + white-space: nowrap; + text-align: center; + padding: 2px; +} \ No newline at end of file diff --git a/src/mapml/handlers/QueryHandler.js b/src/mapml/handlers/QueryHandler.js index bbcd15653..356a86750 100644 --- a/src/mapml/handlers/QueryHandler.js +++ b/src/mapml/handlers/QueryHandler.js @@ -151,14 +151,15 @@ export var QueryHandler = L.Handler.extend({ }); f.addTo(map); - var c = document.createElement('iframe'); + let div = L.DomUtil.create("div", "mapml-popup-content"), + c = L.DomUtil.create("iframe"); c.csp = "script-src 'none'"; c.style = "border: none"; c.srcdoc = mapmldoc.querySelector('feature properties').innerHTML; - + div.appendChild(c); // passing a latlng to the popup is necessary for when there is no // geometry / null geometry - layer.bindPopup(c, popupOptions).openPopup(loc); + layer.bindPopup(div, popupOptions).openPopup(loc); layer.on('popupclose', function() { map.removeLayer(f); }); @@ -166,11 +167,13 @@ export var QueryHandler = L.Handler.extend({ } function handleOtherResponse(response, layer, loc) { return response.text().then(text => { - var c = document.createElement('iframe'); + let div = L.DomUtil.create("div", "mapml-popup-content"), + c = L.DomUtil.create("iframe"); c.csp = "script-src 'none'"; c.style = "border: none"; c.srcdoc = text; - layer.bindPopup(c, popupOptions).openPopup(loc); + div.appendChild(c); + layer.bindPopup(div, popupOptions).openPopup(loc); }); } } diff --git a/src/mapml/index.js b/src/mapml/index.js index 1853e3f5b..e703becac 100644 --- a/src/mapml/index.js +++ b/src/mapml/index.js @@ -52,6 +52,7 @@ import { QueryHandler } from './handlers/QueryHandler'; import { ContextMenu } from './handlers/ContextMenu'; import { Util } from './utils/Util'; import { ReloadButton, reloadButton } from './control/ReloadButton'; +import { Crosshair, crosshair } from "./layers/Crosshair"; /* global L, Node */ (function (window, document, undefined) { @@ -627,4 +628,7 @@ M.mapMLStaticTileLayer = mapMLStaticTileLayer; M.DebugOverlay = DebugOverlay; M.debugOverlay = debugOverlay; +M.Crosshair = Crosshair; +M.crosshair = crosshair; + }(window, document)); diff --git a/src/mapml/layers/Crosshair.js b/src/mapml/layers/Crosshair.js new file mode 100644 index 000000000..ad622adb1 --- /dev/null +++ b/src/mapml/layers/Crosshair.js @@ -0,0 +1,95 @@ +export var Crosshair = L.Layer.extend({ + onAdd: function (map) { + + //SVG crosshair design from https://github.com/xguaita/Leaflet.MapCenterCoord/blob/master/src/icons/MapCenterCoordIcon1.svg?short_path=81a5c76 + let svgInnerHTML = ` + + `; + + this._container = L.DomUtil.create("div", "mapml-crosshair", map._container); + this._container.innerHTML = svgInnerHTML; + this._mapFocused = false; + this._isQueryable = false; + + map.on("layerchange layeradd layerremove overlayremove", this._toggleEvents, this); + L.DomEvent.on(map._container, "keydown keyup mousedown", this._onKey, this); + + + this._addOrRemoveCrosshair(); + }, + + _toggleEvents: function () { + if (this._hasQueryableLayer()) { + this._map.on("viewreset move moveend", this._addOrRemoveCrosshair, this); + } else { + this._map.off("viewreset move moveend", this._addOrRemoveCrosshair, this); + } + this._addOrRemoveCrosshair(); + }, + + _addOrRemoveCrosshair: function (e) { + if (this._hasQueryableLayer()) { + this._container.style.visibility = null; + } else { + this._container.style.visibility = "hidden"; + } + }, + + _hasQueryableLayer: function () { + let layers = this._map.options.mapEl.layers; + if (this._mapFocused) { + for (let layer of layers) { + if (layer.checked && layer._layer.queryable) { + return true; + } + } + } + return false; + }, + + _onKey: function (e) { + //set mapFocused = true if arrow buttons are used + if (["keydown", "keyup"].includes(e.type) && e.target.classList.contains("leaflet-container") && [32, 37, 38, 39, 40, 187, 189].includes(+e.keyCode)) { + this._mapFocused = true; + //set mapFocused = true if map is focued using tab + } else if (e.type === "keyup" && e.target.classList.contains("leaflet-container") && +e.keyCode === 9) { + this._mapFocused = true; + // set mapFocused = false and close all popups if tab or escape is used + } else if((e.type === "keyup" && e.target.classList.contains("leaflet-interactive") && +e.keyCode === 9) || +e.keyCode === 27){ + this._mapFocused = false; + this._map.closePopup(); + // set mapFocused = false if any other key is pressed + } else { + this._mapFocused = false; + } + this._addOrRemoveCrosshair(); + }, + +}); + + +export var crosshair = function (options) { + return new Crosshair(options); +}; \ No newline at end of file diff --git a/src/mapml/layers/FeatureLayer.js b/src/mapml/layers/FeatureLayer.js index 0083cc18e..1ddf8dfc0 100644 --- a/src/mapml/layers/FeatureLayer.js +++ b/src/mapml/layers/FeatureLayer.js @@ -36,6 +36,12 @@ export var MapMLFeatures = L.FeatureGroup.extend({ } }, + onAdd: function(map){ + L.FeatureGroup.prototype.onAdd.call(this, map); + map.on("popupopen", this._attachSkipButtons, this); + this._updateTabIndex(); + }, + getEvents: function(){ if(this._staticFeature){ return { @@ -47,6 +53,141 @@ export var MapMLFeatures = L.FeatureGroup.extend({ }; }, + _updateTabIndex: function(){ + for(let feature in this._features){ + for(let path of this._features[feature]){ + if(path._path){ + if(path._path.getAttribute("d") !== "M0 0"){ + path._path.setAttribute("tabindex", 0); + } else { + path._path.removeAttribute("tabindex"); + } + if(path._path.childElementCount === 0) { + let title = document.createElement("title"); + title.innerText = "Feature"; + path._path.appendChild(title); + } + } + } + } + }, + + _attachSkipButtons: function(e){ + if(!e.popup._source._path) return; + if(!e.popup._container.querySelector('div[class="mapml-focus-buttons"]')){ + //add when popopen event happens instead + let div = L.DomUtil.create("div", "mapml-focus-buttons"); + + // creates |< button, focuses map + let mapFocusButton = L.DomUtil.create('a',"mapml-popup-button", div); + mapFocusButton.href = '#'; + mapFocusButton.role = "button"; + mapFocusButton.title = "Focus Map"; + mapFocusButton.innerHTML = '|❮'; + L.DomEvent.disableClickPropagation(mapFocusButton); + L.DomEvent.on(mapFocusButton, 'click', L.DomEvent.stop); + L.DomEvent.on(mapFocusButton, 'click', this._skipBackward, this); + + // creates < button, focuses previous feature, if none exists focuses the current feature + let previousButton = L.DomUtil.create('a', "mapml-popup-button", div); + previousButton.href = '#'; + previousButton.role = "button"; + previousButton.title = "Previous Feature"; + previousButton.innerHTML = "❮"; + L.DomEvent.disableClickPropagation(previousButton); + L.DomEvent.on(previousButton, 'click', L.DomEvent.stop); + L.DomEvent.on(previousButton, 'click', this._previousFeature, e.popup); + + // static feature counter that 1/1 + let featureCount = L.DomUtil.create("p", "mapml-feature-count", div), currentFeature = 1; + featureCount.innerText = currentFeature+"/1"; + //for(let feature of e.popup._source._path.parentNode.children){ + // if(feature === e.popup._source._path)break; + // currentFeature++; + //} + //featureCount.innerText = currentFeature+"/"+e.popup._source._path.parentNode.childElementCount; + + // creates > button, focuses next feature, if none exists focuses the current feature + let nextButton = L.DomUtil.create('a', "mapml-popup-button", div); + nextButton.href = '#'; + nextButton.role = "button"; + nextButton.title = "Next Feature"; + nextButton.innerHTML = "❯"; + L.DomEvent.disableClickPropagation(nextButton); + L.DomEvent.on(nextButton, 'click', L.DomEvent.stop); + L.DomEvent.on(nextButton, 'click', this._nextFeature, e.popup); + + // creates >| button, focuses map controls + let controlFocusButton = L.DomUtil.create('a',"mapml-popup-button", div); + controlFocusButton.href = '#'; + controlFocusButton.role = "button"; + controlFocusButton.title = "Focus Controls"; + controlFocusButton.innerHTML = '❯|'; + L.DomEvent.disableClickPropagation(controlFocusButton); + L.DomEvent.on(controlFocusButton, 'click', L.DomEvent.stop); + L.DomEvent.on(controlFocusButton, 'click', this._skipForward, this); + + let divider = L.DomUtil.create("hr"); + divider.style.borderTop = "1px solid #bbb"; + + e.popup._content.appendChild(divider); + e.popup._content.appendChild(div); + } + + // When popup is open, what gets focused with tab needs to be done using JS as the DOM order is not in an accessibility friendly manner + function focusFeature(focusEvent){ + if(focusEvent.originalEvent.path[0].title==="Focus Controls" && +focusEvent.originalEvent.keyCode === 9){ + L.DomEvent.stop(focusEvent); + e.popup._source._path.focus(); + } else if(focusEvent.originalEvent.shiftKey && +focusEvent.originalEvent.keyCode === 9){ + e.target.closePopup(e.popup); + L.DomEvent.stop(focusEvent); + e.popup._source._path.focus(); + } + } + + function removeHandlers(removeEvent){ + if (removeEvent.popup === e.popup){ + e.target.off("keydown", focusFeature); + e.target.off("popupclose", removeHandlers); + } + } + // e.target = this._map + // Looks for keydown, more specifically tab and shift tab + e.target.on("keydown", focusFeature); + + // if popup closes then the focusFeature handler can be removed + e.target.on("popupclose", removeHandlers); + }, + + _skipBackward: function(e){ + this._map.closePopup(); + this._map._container.focus(); + }, + + _previousFeature: function(e){ + this._map.closePopup(); + if(this._source._path.previousSibling){ + this._source._path.previousSibling.focus(); + } else { + this._source._path.focus(); + } + }, + + _nextFeature: function(e){ + this._map.closePopup(); + if(this._source._path.nextSibling){ + this._source._path.nextSibling.focus(); + } else { + this._source._path.focus(); + } + }, + + _skipForward: function(e){ + this._map.closePopup(); + this._map._controlContainer.focus(); + }, + _handleMoveEnd : function(){ let mapZoom = this._map.getZoom(); if(mapZoom > this.zoomBounds.maxZoom || mapZoom < this.zoomBounds.minZoom){ @@ -62,6 +203,7 @@ export var MapMLFeatures = L.FeatureGroup.extend({ this._map.getPixelBounds(), mapZoom,this._map.options.projection)); this._removeCSS(); + this._updateTabIndex(); }, //sets default if any are missing, better to only replace ones that are missing diff --git a/src/mapml/layers/MapLayer.js b/src/mapml/layers/MapLayer.js index 162a1a218..d78987052 100644 --- a/src/mapml/layers/MapLayer.js +++ b/src/mapml/layers/MapLayer.js @@ -93,8 +93,9 @@ export var MapMLLayer = L.Layer.extend({ // need to parse as HTML to preserve semantics and styles if (properties) { var c = document.createElement('div'); + c.classList.add("mapml-popup-content"); c.insertAdjacentHTML('afterbegin', properties.innerHTML); - geometry.bindPopup(c, {autoPan:false}); + geometry.bindPopup(c, {autoPan:false, closeButton: false, minWidth: 108}); } } }); @@ -122,8 +123,9 @@ export var MapMLLayer = L.Layer.extend({ // need to parse as HTML to preserve semantics and styles if (properties) { var c = document.createElement('div'); + c.classList.add("mapml-popup-content"); c.insertAdjacentHTML('afterbegin', properties.innerHTML); - geometry.bindPopup(c, {autoPan:false}); + geometry.bindPopup(c, {autoPan:false, closeButton: false, minWidth: 108}); } } }).addTo(map); @@ -189,6 +191,7 @@ export var MapMLLayer = L.Layer.extend({ setTimeout(() => { map.fire('checkdisabled'); }, 0); + map.on("popupopen", this._focusPopup, this); }, _validProjection : function(map){ @@ -1152,7 +1155,12 @@ export var MapMLLayer = L.Layer.extend({ if (this._templatedLayer && this._templatedLayer._queries) { return this._templatedLayer._queries; } - } + }, + _focusPopup: function(e){ + let content = e.popup._container.getElementsByClassName("mapml-popup-content")[0]; + content.setAttribute("tabindex", "-1"); + content.focus(); + }, }); export var mapMLLayer = function (url, node, options) { if (!url && !node) return null; diff --git a/src/mapml/layers/TemplatedFeaturesLayer.js b/src/mapml/layers/TemplatedFeaturesLayer.js index 5fee6ed43..21a0c5794 100644 --- a/src/mapml/layers/TemplatedFeaturesLayer.js +++ b/src/mapml/layers/TemplatedFeaturesLayer.js @@ -39,7 +39,7 @@ export var TemplatedFeaturesLayer = L.Layer.extend({ // need to parse as HTML to preserve semantics and styles var c = document.createElement('div'); c.insertAdjacentHTML('afterbegin', properties.innerHTML); - geometry.bindPopup(c, {autoPan:false}); + geometry.bindPopup(c, {autoPan:false, closeButton: false}); } }); } @@ -87,6 +87,7 @@ export var TemplatedFeaturesLayer = L.Layer.extend({ parser = new DOMParser(), features = this._features, map = this._map, + context = this, MAX_PAGES = 10, _pullFeatureFeed = function (url, limit) { return (fetch (url,{redirect: 'follow',headers: headers}) @@ -111,6 +112,7 @@ export var TemplatedFeaturesLayer = L.Layer.extend({ _pullFeatureFeed(this._getfeaturesUrl(), MAX_PAGES) .then(function() { map.addLayer(features); + M.TemplatedFeaturesLayer.prototype._updateTabIndex(context); }) .catch(function (error) { console.log(error);}); }, @@ -119,6 +121,24 @@ export var TemplatedFeaturesLayer = L.Layer.extend({ this._updateZIndex(); return this; }, + _updateTabIndex: function(context){ + let c = context || this; + for(let layerNum in c._features._layers){ + let layer = c._features._layers[layerNum]; + if(layer._path){ + if(layer._path.getAttribute("d") !== "M0 0"){ + layer._path.setAttribute("tabindex", 0); + } else { + layer._path.removeAttribute("tabindex"); + } + if(layer._path.childElementCount === 0) { + let title = document.createElement("title"); + title.innerText = "Feature"; + layer._path.appendChild(title); + } + } + } + }, _updateZIndex: function () { if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) { this._container.style.zIndex = this.options.zIndex; diff --git a/src/web-map.js b/src/web-map.js index b48a09796..0e9c414b3 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -224,6 +224,7 @@ export class WebMap extends HTMLMapElement { this._attributionControl = this._map.attributionControl.setPrefix('Maps4HTML | Leaflet'); this.setControls(false,false,true); + this._crosshair = M.crosshair().addTo(this._map); if (this.hasAttribute('name')) { var name = this.getAttribute('name'); if (name) { diff --git a/test/e2e/core/keyboardInteraction.html b/test/e2e/core/keyboardInteraction.html new file mode 100644 index 000000000..fbe8add98 --- /dev/null +++ b/test/e2e/core/keyboardInteraction.html @@ -0,0 +1,86 @@ + + + + Keyboard Interaction Test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Test

+ test +
+ + + 11 11 12 11 12 12 11 12 + + +
+ + + +

Test

+
+ + + 257421 -3567196 -271745 1221771 -3896544 242811 -3183549 -2613313 + + +
+ + + +

Test

+
+ + + 2771 3106 2946 3113 2954 3210 2815 3192 + + +
+
+ +
+ + + \ No newline at end of file diff --git a/test/e2e/core/keyboardInteraction.test.js b/test/e2e/core/keyboardInteraction.test.js new file mode 100644 index 000000000..9866c958f --- /dev/null +++ b/test/e2e/core/keyboardInteraction.test.js @@ -0,0 +1,228 @@ +const playwright = require("playwright"); +jest.setTimeout(50000); +(async () => { + for (const browserType of BROWSER) { + describe( + "Playwright Keyboard Navigation + Query Layer Tests in " + browserType, + () => { + beforeAll(async () => { + browser = await playwright[browserType].launch({ + headless: ISHEADLESS, + slowMo: 50, + }); + context = await browser.newContext(); + page = await context.newPage(); + if (browserType === "firefox") { + await page.waitForNavigation(); + } + await page.goto(PATH + "keyboardInteraction.html"); + }); + + afterAll(async function () { + await browser.close(); + }); + describe("Crosshair Tests in " + browserType, () => { + test("[" + browserType + "]" + " Crosshair hidden onload, shows on focus", async () => { + const beforeTabHidden = await page.$eval("div > div.mapml-crosshair", (div) => div.style.visibility); + await page.keyboard.press("Tab"); + const afterTab = await page.$eval("div > div.mapml-crosshair", (div) => div.style.visibility); + expect(beforeTabHidden).toEqual("hidden"); + expect(afterTab).toEqual(""); + }); + + test("[" + browserType + "]" + " Crosshair remains on map move with arrow keys + space", async () => { + await page.keyboard.press("ArrowUp"); + await page.waitForTimeout(500); + await page.keyboard.press("ArrowDown"); + await page.waitForTimeout(500); + await page.keyboard.press("ArrowLeft"); + await page.waitForTimeout(500); + await page.keyboard.press("ArrowRight"); + await page.waitForTimeout(500); + await page.keyboard.press(" ") + await page.waitForTimeout(500); + const afterMove = await page.$eval("div > div.mapml-crosshair", (div) => div.style.visibility); + expect(afterMove).toEqual(""); + }); + + test("[" + browserType + "]" + " Crosshair hidden on esc + tab out", async () => { + await page.keyboard.press("Escape"); + const afterEsc = await page.$eval("div > div.mapml-crosshair", (div) => div.style.visibility); + await page.click("body"); + await page.keyboard.press("Tab"); + await page.keyboard.press("ArrowUp"); + + await page.keyboard.press("Tab"); + const afterTab = await page.$eval("div > div.mapml-crosshair", (div) => div.style.visibility); + + expect(afterEsc).toEqual("hidden"); + expect(afterTab).toEqual("hidden"); + }); + + test("[" + browserType + "]" + " Crosshair hidden when queryable layer is unselected, shows on reselect", async () => { + await page.click("body"); + await page.keyboard.press("Tab"); + await page.keyboard.press("ArrowUp"); + await page.evaluateHandle(() => document.querySelector("layer-").removeAttribute("checked")); + const afterUncheck = await page.$eval("div > div.mapml-crosshair", (div) => div.style.visibility); + + await page.evaluateHandle(() => document.querySelector("layer-").setAttribute("checked", "")); + const afterCheck = await page.$eval("div > div.mapml-crosshair", (div) => div.style.visibility); + + expect(afterUncheck).toEqual("hidden"); + expect(afterCheck).toEqual(""); + }); + }); + describe("Tab Navigable Tests in " + browserType, () => { + test("[" + browserType + "]" + " Tab focuses inline features", async () => { + await page.click("body"); + await page.keyboard.press("Tab"); + + await page.keyboard.press("Tab"); + const aHandle = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nextHandle = await page.evaluateHandle(doc => doc.shadowRoot, aHandle); + const resultHandle = await page.evaluateHandle(root => root.activeElement, nextHandle); + const focused = await (await page.evaluateHandle(elem => elem.getAttribute("d"), resultHandle)).jsonValue(); + + await page.keyboard.press("Tab"); + const aHandleNext = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nextHandleNext = await page.evaluateHandle(doc => doc.shadowRoot, aHandleNext); + const resultHandleNext = await page.evaluateHandle(root => root.activeElement, nextHandleNext); + const focusedNext = await (await page.evaluateHandle(elem => elem.getAttribute("d"), resultHandleNext)).jsonValue(); + + expect(focused).toEqual("M330 83L553 83L553 339L330 339z"); + expect(focusedNext).toEqual("M-53 393L140 393L113 146L-53 191z"); + }); + + test("[" + browserType + "]" + " Tab focuses fetched features", async () => { + await page.evaluateHandle(() => document.getElementById("vector").setAttribute("checked", "")); + await page.click("body"); + await page.keyboard.press("Tab"); + + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + const aHandle = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nextHandle = await page.evaluateHandle(doc => doc.shadowRoot, aHandle); + const resultHandle = await page.evaluateHandle(root => root.activeElement, nextHandle); + const focused = await (await page.evaluateHandle(elem => elem.getAttribute("d"), resultHandle)).jsonValue(); + + await page.keyboard.press("Tab"); + const aHandleNext = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nextHandleNext = await page.evaluateHandle(doc => doc.shadowRoot, aHandleNext); + const resultHandleNext = await page.evaluateHandle(root => root.activeElement, nextHandleNext); + const focusedNext = await (await page.evaluateHandle(elem => elem.getAttribute("d"), resultHandleNext)).jsonValue(); + + expect(focused).toEqual("M190 357L203 355L209 374L213 379L211 380L213 391L186 393L184 357z"); + expect(focusedNext).toEqual("M-30 139L-29 138L-31 140zM-30 136L-29 138L-31 138zM-29 126L-28 127L-30 127zM-32 125L-30 131L-32 137L-31 138L-34 141L-34 138L-36 136L-36 139L-37 136L-34 132L-35 131L-33 126zM-36 130L-35 131L-37 132zM-31 121L-30 120L-28 122L-28 127L-31 125L-31 121zM-33 123L-34 122L-32 120L-31 124L-32 123L-35 126L-35 124zM-36 123L-37 124L-37 118L-35 113L-32 114L-34 120zM-27 110L-26 115L-27 114L-28 118L-32 119L-33 118L-30 115L-27 108zM-36 113L-36 114zM-33 106L-29 110L-31 112L-31 115L-33 111L-34 113L-35 112L-35 108L-33 105zM3 6L7 13L11 15L13 23L-37 80L-35 84L-31 85L-33 86L-33 99L-29 99L-27 97L-22 98L-22 117L-24 128L-20 136L-23 142L-28 144L-29 143L-26 140L-29 139L-30 136L-28 135L-30 136L-30 134L-28 132L-30 132L-26 127L-28 120L-26 117L-27 107L-25 102L-29 109L-29 103L-31 100L-31 106L-35 103L-36 92L-38 89L-34 86L-38 86L-40 84L-43 77L-48 74L-48 69L-50 69L-50 66L-53 64L-50 65L-50 60L-53 58L-53 -20L-49 -17L-53 -14L-47 -7L-48 -10L-45 -14L-44 -13L-47 -9L-43 -12L-45 -18L-42 -20L-40 -31L-36 -31L-33 -26L-30 -25L-19 -26L-20 -25L-18 -23L-13 -23L-12 -20L-5 -18L-4 -15L-7 -14L-5 -14L-5 -11L-1 -7L-3 -6L-2 -2L2 1L3 5z"); + }); + }); + + describe("Feature Popup Tab Navigation Tests in " + browserType, () => { + test("[" + browserType + "]" + " Inline features popup focus order", async () => { + await page.click("div > div.leaflet-control-container > div.leaflet-top.leaflet-left > div.mapml-reload-button.leaflet-bar.leaflet-control > a"); + await page.evaluateHandle(() => document.getElementById("vector").removeAttribute("checked")); + await page.evaluateHandle(() => document.getElementById("query").removeAttribute("checked")); + await page.click("body"); + await page.keyboard.press("Tab"); + + await page.keyboard.press("Tab"); + await page.keyboard.press("Enter"); + const h = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nh = await page.evaluateHandle(doc => doc.shadowRoot, h); + const rh = await page.evaluateHandle(root => root.activeElement, nh); + const f = await (await page.evaluateHandle(elem => elem.className, rh)).jsonValue(); + + await page.keyboard.press("Tab"); + const h2 = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nh2 = await page.evaluateHandle(doc => doc.shadowRoot, h2); + const rh2 = await page.evaluateHandle(root => root.activeElement, nh2); + const f2 = await (await page.evaluateHandle(elem => elem.tagName, rh2)).jsonValue(); + + await page.keyboard.press("Tab"); + const h3 = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nh3 = await page.evaluateHandle(doc => doc.shadowRoot, h3); + const rh3 = await page.evaluateHandle(root => root.activeElement, nh3); + const f3 = await (await page.evaluateHandle(elem => elem.title, rh3)).jsonValue(); + + await page.keyboard.press("Tab"); + const h4 = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nh4 = await page.evaluateHandle(doc => doc.shadowRoot, h4); + const rh4 = await page.evaluateHandle(root => root.activeElement, nh4); + const f4 = await (await page.evaluateHandle(elem => elem.title, rh4)).jsonValue(); + + await page.keyboard.press("Tab"); + const h5 = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nh5 = await page.evaluateHandle(doc => doc.shadowRoot, h5); + const rh5 = await page.evaluateHandle(root => root.activeElement, nh5); + const f5 = await (await page.evaluateHandle(elem => elem.title, rh5)).jsonValue(); + + await page.keyboard.press("Tab"); + const h6 = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nh6 = await page.evaluateHandle(doc => doc.shadowRoot, h6); + const rh6 = await page.evaluateHandle(root => root.activeElement, nh6); + const f6 = await (await page.evaluateHandle(elem => elem.title, rh6)).jsonValue(); + + expect(f).toEqual("mapml-popup-content"); + expect(f2).toEqual("A"); + expect(f3).toEqual("Focus Map"); + expect(f4).toEqual("Previous Feature"); + expect(f5).toEqual("Next Feature"); + expect(f6).toEqual("Focus Controls"); + }); + + test("[" + browserType + "]" + " Tab to next feature after tabbing out of popup", async () => { + await page.keyboard.press("Tab"); + const h = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nh = await page.evaluateHandle(doc => doc.shadowRoot, h); + const rh = await page.evaluateHandle(root => root.activeElement, nh); + const f = await (await page.evaluateHandle(elem => elem.getAttribute("d"), rh)).jsonValue(); + + expect(f).toEqual("M-53 451L153 508L113 146L-53 191z"); + }); + + test("[" + browserType + "]" + " Shift + Tab to previous feature while popup open", async () => { + await page.keyboard.press("Enter"); + await page.keyboard.press("Shift+Tab"); + const h = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nh = await page.evaluateHandle(doc => doc.shadowRoot, h); + const rh = await page.evaluateHandle(root => root.activeElement, nh); + const f = await (await page.evaluateHandle(elem => elem.getAttribute("d"), rh)).jsonValue(); + + expect(f).toEqual("M330 83L553 83L553 339L330 339z"); + }); + + test("[" + browserType + "]" + " Previous feature button focuses previous feature", async () => { + await page.keyboard.press("Tab"); + await page.keyboard.press("Enter"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Enter"); + const h = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nh = await page.evaluateHandle(doc => doc.shadowRoot, h); + const rh = await page.evaluateHandle(root => root.activeElement, nh); + const f = await (await page.evaluateHandle(elem => elem.getAttribute("d"), rh)).jsonValue(); + + expect(f).toEqual("M330 83L553 83L553 339L330 339z"); + }); + + test("[" + browserType + "]" + " Next feature button focuses next feature", async () => { + await page.keyboard.press("Tab"); + await page.keyboard.press("Enter"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Enter"); + const h = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nh = await page.evaluateHandle(doc => doc.shadowRoot, h); + const rh = await page.evaluateHandle(root => root.activeElement, nh); + const f = await (await page.evaluateHandle(elem => elem.getAttribute("d"), rh)).jsonValue(); + + expect(f).toEqual("M285 373L460 380L468 477L329 459z"); + }); + }); + } + ); + } +})(); \ No newline at end of file diff --git a/test/e2e/core/layerContextMenu.test.js b/test/e2e/core/layerContextMenu.test.js index 43810b7d7..3b8ba493c 100644 --- a/test/e2e/core/layerContextMenu.test.js +++ b/test/e2e/core/layerContextMenu.test.js @@ -1,22 +1,6 @@ const playwright = require("playwright"); jest.setTimeout(50000); (async () => { - - //expected topLeft values in the different cs, at the different - //positions the map goes in - let expectedPCRS = [ - { horizontal: -5537023.0124460235, vertical: 2671749.64016594 }, - { horizontal: -2810486.309372615, vertical: 5328171.619676568 }]; - let expectedGCRS = [ - { horizontal: -134.50882532096858, vertical: 34.758856143866666 }, - { horizontal: -146.23778791492126, vertical: 54.997129539321016 }]; - let expectedFirstTileMatrix = [ - { horizontal: 2.96484375, vertical: 3.7304687500000004 }, - { horizontal: 3.242456896551724, vertical: 3.4599946120689657 }]; - let expectedFirstTCRS = [ - { horizontal: 759, vertical: 955.0000000000001 }, - { horizontal: 830.0689655172414, vertical: 885.7586206896552 }]; - for (const browserType of BROWSER) { describe( "Playwright Layer Context Menu Tests in " + browserType, diff --git a/test/e2e/layers/mapMLFeatures.html b/test/e2e/layers/mapMLFeatures.html index e704a2df0..7b1cea116 100644 --- a/test/e2e/layers/mapMLFeatures.html +++ b/test/e2e/layers/mapMLFeatures.html @@ -43,6 +43,7 @@

Test

+ test