diff --git a/package-lock.json b/package-lock.json index 1f3486729..5ff4cf506 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.11.0", "license": "W3C", "devDependencies": { - "@playwright/test": "^1.24.2", + "@playwright/test": "^1.27.0", "diff": "^5.1.0", "express": "^4.17.1", "grunt": "^1.4.0", @@ -30,7 +30,7 @@ "leaflet": "^1.9.4", "leaflet.locatecontrol": "^0.79.0", "path": "^0.12.7", - "playwright": "^1.24.2", + "playwright": "^1.27.0", "proj4": "^2.6.2", "proj4leaflet": "^1.0.2", "rollup": "^2.23.1" @@ -1513,19 +1513,22 @@ } }, "node_modules/@playwright/test": { - "version": "1.24.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.24.2.tgz", - "integrity": "sha512-Q4X224pRHw4Dtkk5PoNJplZCokLNvVbXD9wDQEMrHcEuvWpJWEQDeJ9gEwkZ3iCWSFSWBshIX177B231XW4wOQ==", + "version": "1.35.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.35.1.tgz", + "integrity": "sha512-b5YoFe6J9exsMYg0pQAobNDR85T1nLumUYgUTtKm4d21iX2L7WqKq9dW8NGJ+2vX0etZd+Y7UeuqsxDXm9+5ZA==", "dev": true, "dependencies": { "@types/node": "*", - "playwright-core": "1.24.2" + "playwright-core": "1.35.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=14" + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" } }, "node_modules/@sideway/address": { @@ -4192,6 +4195,20 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -9723,31 +9740,31 @@ } }, "node_modules/playwright": { - "version": "1.24.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.24.2.tgz", - "integrity": "sha512-iMWDLgaFRT+7dXsNeYwgl8nhLHsUrzFyaRVC+ftr++P1dVs70mPrFKBZrGp1fOKigHV9d1syC03IpPbqLKlPsg==", + "version": "1.35.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.35.1.tgz", + "integrity": "sha512-NbwBeGJLu5m7VGM0+xtlmLAH9VUfWwYOhUi/lSEDyGg46r1CA9RWlvoc5yywxR9AzQb0mOCm7bWtOXV7/w43ZA==", "dev": true, "hasInstallScript": true, "dependencies": { - "playwright-core": "1.24.2" + "playwright-core": "1.35.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=14" + "node": ">=16" } }, "node_modules/playwright-core": { - "version": "1.24.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.24.2.tgz", - "integrity": "sha512-zfAoDoPY/0sDLsgSgLZwWmSCevIg1ym7CppBwllguVBNiHeixZkc1AdMuYUPZC6AdEYc4CxWEyLMBTw2YcmRrA==", + "version": "1.35.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.35.1.tgz", + "integrity": "sha512-pNXb6CQ7OqmGDRspEjlxE49w+4YtR6a3X6mT1hZXeJHWmsEz7SunmvZeiG/+y1yyMZdHnnn73WKYdtV1er0Xyg==", "dev": true, "bin": { - "playwright": "cli.js" + "playwright-core": "cli.js" }, "engines": { - "node": ">=14" + "node": ">=16" } }, "node_modules/posix-character-classes": { @@ -10225,6 +10242,21 @@ "fsevents": "~2.1.2" } }, + "node_modules/rollup/node_modules/fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "deprecated": "\"Please update to latest v2.3 or v2.2\"", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/rsvp": { "version": "4.8.5", "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", @@ -13391,13 +13423,14 @@ } }, "@playwright/test": { - "version": "1.24.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.24.2.tgz", - "integrity": "sha512-Q4X224pRHw4Dtkk5PoNJplZCokLNvVbXD9wDQEMrHcEuvWpJWEQDeJ9gEwkZ3iCWSFSWBshIX177B231XW4wOQ==", + "version": "1.35.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.35.1.tgz", + "integrity": "sha512-b5YoFe6J9exsMYg0pQAobNDR85T1nLumUYgUTtKm4d21iX2L7WqKq9dW8NGJ+2vX0etZd+Y7UeuqsxDXm9+5ZA==", "dev": true, "requires": { "@types/node": "*", - "playwright-core": "1.24.2" + "fsevents": "2.3.2", + "playwright-core": "1.35.1" } }, "@sideway/address": { @@ -15513,6 +15546,13 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -19747,18 +19787,18 @@ } }, "playwright": { - "version": "1.24.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.24.2.tgz", - "integrity": "sha512-iMWDLgaFRT+7dXsNeYwgl8nhLHsUrzFyaRVC+ftr++P1dVs70mPrFKBZrGp1fOKigHV9d1syC03IpPbqLKlPsg==", + "version": "1.35.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.35.1.tgz", + "integrity": "sha512-NbwBeGJLu5m7VGM0+xtlmLAH9VUfWwYOhUi/lSEDyGg46r1CA9RWlvoc5yywxR9AzQb0mOCm7bWtOXV7/w43ZA==", "dev": true, "requires": { - "playwright-core": "1.24.2" + "playwright-core": "1.35.1" } }, "playwright-core": { - "version": "1.24.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.24.2.tgz", - "integrity": "sha512-zfAoDoPY/0sDLsgSgLZwWmSCevIg1ym7CppBwllguVBNiHeixZkc1AdMuYUPZC6AdEYc4CxWEyLMBTw2YcmRrA==", + "version": "1.35.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.35.1.tgz", + "integrity": "sha512-pNXb6CQ7OqmGDRspEjlxE49w+4YtR6a3X6mT1hZXeJHWmsEz7SunmvZeiG/+y1yyMZdHnnn73WKYdtV1er0Xyg==", "dev": true }, "posix-character-classes": { @@ -20116,6 +20156,15 @@ "dev": true, "requires": { "fsevents": "~2.1.2" + }, + "dependencies": { + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + } } }, "rsvp": { diff --git a/package.json b/package.json index d9aca5692..e48a7057a 100644 --- a/package.json +++ b/package.json @@ -56,8 +56,8 @@ "leaflet": "^1.9.4", "leaflet.locatecontrol": "^0.79.0", "path": "^0.12.7", - "@playwright/test": "^1.24.2", - "playwright": "^1.24.2", + "@playwright/test": "^1.27.0", + "playwright": "^1.27.0", "proj4": "^2.6.2", "proj4leaflet": "^1.0.2", "rollup": "^2.23.1" diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index 1210f4b72..4132f1eb8 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -1087,6 +1087,7 @@ export class MapViewer extends HTMLElement { this._traversalCall = 1; this._map.panBy([initialLocation.x - curr.x, initialLocation.y - curr.y]); } + this._map.getContainer().focus(); } _toggleFullScreen() { diff --git a/src/mapml.css b/src/mapml.css index e6eb37a4c..eaa5990e3 100644 --- a/src/mapml.css +++ b/src/mapml.css @@ -51,7 +51,7 @@ left: 45px; } } - + /* Generic class for seamless buttons */ .mapml-button { background-color: transparent; @@ -909,7 +909,16 @@ label.mapml-layer-item-toggle { width: 450px; font-size: 16px; } - +@container leafletmap (max-width: 650px ) { + .mapml-feature-index { + width: 70cqw; + } +} +@container leafletmap (max-width: 390px ) { + .mapml-feature-index { + bottom: 100px; + } +} .mapml-feature-index-content > span{ width: 140px; white-space: nowrap; diff --git a/src/mapml/control/FullscreenButton.js b/src/mapml/control/FullscreenButton.js index 2ade90123..9b0448e93 100644 --- a/src/mapml/control/FullscreenButton.js +++ b/src/mapml/control/FullscreenButton.js @@ -84,6 +84,7 @@ L.Map.include({ this._enablePseudoFullscreen(container); } } + this.getContainer().focus(); }, _enablePseudoFullscreen: function (container) { diff --git a/src/mapml/control/GeolocationButton.js b/src/mapml/control/GeolocationButton.js index 56be01a3c..a69311735 100644 --- a/src/mapml/control/GeolocationButton.js +++ b/src/mapml/control/GeolocationButton.js @@ -4,18 +4,28 @@ export var GeolocationButton = L.Control.extend({ }, onAdd: function (map) { - this.locateControl = L.control - .locate({ - showPopup: false, - strings: { - title: M.options.locale.btnLocTrackOff - }, - position: this.options.position, - locateOptions: { - maxZoom: 16 - } - }) - .addTo(map); + // customize locate control to focus map after start/stop, so that + // featureIndexOverlay is correctly displayed + L.Control.CustomLocate = L.Control.Locate.extend({ + start: function () { + L.Control.Locate.prototype.start.call(this); + map.getContainer().focus(); + }, + stop: function () { + L.Control.Locate.prototype.stop.call(this); + map.getContainer().focus(); + } + }); + this.locateControl = new L.Control.CustomLocate({ + showPopup: false, + strings: { + title: M.options.locale.btnLocTrackOff + }, + position: this.options.position, + locateOptions: { + maxZoom: 16 + } + }).addTo(map); var container = this.locateControl._container; var button = this.locateControl; diff --git a/src/mapml/layers/FeatureIndexOverlay.js b/src/mapml/layers/FeatureIndexOverlay.js index 813f67a5a..677cd39e7 100644 --- a/src/mapml/layers/FeatureIndexOverlay.js +++ b/src/mapml/layers/FeatureIndexOverlay.js @@ -24,11 +24,7 @@ export var FeatureIndexOverlay = L.Layer.extend({ ); this._body.index = 0; this._output.initialFocus = false; - map.on( - 'layerchange layeradd layerremove overlayremove', - this._toggleEvents, - this - ); + map.on('focus blur popupclose', this._addOrRemoveFeatureIndex, this); map.on('moveend focus templatedfeatureslayeradd', this._checkOverlap, this); map.on('keydown', this._onKeyDown, this); this._addOrRemoveFeatureIndex(); @@ -77,6 +73,8 @@ export var FeatureIndexOverlay = L.Layer.extend({ let index = 1; let keys = Object.keys(features); let body = this._body; + let noFeaturesMessage = document.createElement('span'); + noFeaturesMessage.innerHTML = M.options.locale.fIndexNoFeatures; body.innerHTML = ''; body.index = 0; @@ -123,6 +121,9 @@ export var FeatureIndexOverlay = L.Layer.extend({ } }); this._addToggleKeys(); + if (index === 1) { + body.appendChild(noFeaturesMessage); + } }, _updateOutput: function (label, index, key) { @@ -189,16 +190,7 @@ export var FeatureIndexOverlay = L.Layer.extend({ } }, - _toggleEvents: function () { - this._map.on( - 'viewreset move moveend focus blur popupclose', - this._addOrRemoveFeatureIndex, - this - ); - }, - _addOrRemoveFeatureIndex: function (e) { - let features = this._body.allFeatures ? this._body.allFeatures.length : 0; //Toggle aria-hidden attribute so screen reader rereads the feature index on focus if (!this._output.initialFocus) { this._output.setAttribute('aria-hidden', 'true'); @@ -214,11 +206,15 @@ export var FeatureIndexOverlay = L.Layer.extend({ this._output.popupClosed = true; } else if (e && e.type === 'focus') { this._container.removeAttribute('hidden'); - if (features !== 0) - this._output.classList.remove('mapml-screen-reader-output'); - } else if (e && e.originalEvent && e.originalEvent.type === 'pointermove') { - this._container.setAttribute('hidden', ''); - this._output.classList.add('mapml-screen-reader-output'); + this._output.classList.remove('mapml-screen-reader-output'); + // this is a very subtle branch. The event that gets handled below is a blur + // event, which happens to have the e.target._popup property + // when there will be a popup. Because blur gets handled here, it doesn't + // get handled in the next else if block, which would hide both the reticle + // and the index menu, and then recursively call this method with no event + // argument, which manipulates the aria-hidden attribute on the output + // in order to have the screenreader read its contents when the focus returns + // to (what exactly???). } else if (e && e.target._popup) { this._container.setAttribute('hidden', ''); } else if (e && e.type === 'blur') { @@ -226,14 +222,8 @@ export var FeatureIndexOverlay = L.Layer.extend({ this._output.classList.add('mapml-screen-reader-output'); this._output.initialFocus = false; this._addOrRemoveFeatureIndex(); - } else if (this._map.isFocused && e) { - this._container.removeAttribute('hidden'); - if (features !== 0) { - this._output.classList.remove('mapml-screen-reader-output'); - } else { - this._output.classList.add('mapml-screen-reader-output'); - } } else { + // this is the default block, called when no event is passed (recursive call) this._container.setAttribute('hidden', ''); this._output.classList.add('mapml-screen-reader-output'); } diff --git a/src/mapml/layers/MapMLLayer.js b/src/mapml/layers/MapMLLayer.js index 2d49c14b8..da8216fb5 100644 --- a/src/mapml/layers/MapMLLayer.js +++ b/src/mapml/layers/MapMLLayer.js @@ -2057,6 +2057,7 @@ export var MapMLLayer = L.Layer.extend({ e.preventDefault(); featureEl.zoomTo(); featureEl._map.closePopup(); + featureEl._map.getContainer().focus(); }; content.insertBefore( zoomLink, diff --git a/src/mapml/options.js b/src/mapml/options.js index 73995b365..008ff476f 100644 --- a/src/mapml/options.js +++ b/src/mapml/options.js @@ -53,6 +53,7 @@ export var Options = { kbdNextFeature: 'Next feature', dfLayer: 'Layer', popupZoom: 'Zoom to here', - dfPastedLayer: 'Pasted layer' + dfPastedLayer: 'Pasted layer', + fIndexNoFeatures: 'No features found' } }; diff --git a/src/mapml/utils/Util.js b/src/mapml/utils/Util.js index e00feb307..f01368ae4 100644 --- a/src/mapml/utils/Util.js +++ b/src/mapml/utils/Util.js @@ -558,6 +558,7 @@ export var Util = { ); if (opacity) layer.opacity = opacity; } + map.getContainer().focus(); }, // _gcrsToTileMatrix returns the [column, row] of the tiles at map center. Used for Announce movement for screen readers diff --git a/src/web-map.js b/src/web-map.js index ff03f80ae..a8268ea73 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -1132,6 +1132,7 @@ export class WebMap extends HTMLMapElement { this._traversalCall = 1; this._map.panBy([initialLocation.x - curr.x, initialLocation.y - curr.y]); } + this._map.getContainer().focus(); } _toggleFullScreen() { diff --git a/test/e2e/api/locateApi.test.js b/test/e2e/api/locateApi.test.js index eb76d56b0..a2af63e66 100644 --- a/test/e2e/api/locateApi.test.js +++ b/test/e2e/api/locateApi.test.js @@ -81,13 +81,7 @@ test.describe('Locate API Test', () => { test('Testing API when the button is used', async () => { await page.reload(); await page.click('body > mapml-viewer'); - 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'); - await page.keyboard.press('Tab'); - await page.keyboard.press('Enter'); + await page.getByTitle('Show my location - location tracking off').click(); await page.mouse.move(600, 300); await page.mouse.down(); diff --git a/test/e2e/core/featureIndexOverlay.html b/test/e2e/core/featureIndexOverlay.html index 417f9ab87..f6226545e 100644 --- a/test/e2e/core/featureIndexOverlay.html +++ b/test/e2e/core/featureIndexOverlay.html @@ -14,11 +14,10 @@ - + - + @@ -36,6 +35,27 @@ + + + + + + + + + + + Test link + + + + -75.705278 45.397778 + + + + + + diff --git a/test/e2e/core/featureIndexOverlayFocus.test.js b/test/e2e/core/featureIndexOverlayFocus.test.js new file mode 100644 index 000000000..6b4d8051a --- /dev/null +++ b/test/e2e/core/featureIndexOverlayFocus.test.js @@ -0,0 +1,162 @@ +import { test, expect, chromium } from '@playwright/test'; + +test.use({ + geolocation: { longitude: -75.705278, latitude: 45.397778 }, + permissions: ['geolocation'] +}); + +test.describe('Feature Index Overlay Focus tests', () => { + let page; + let context; + test.beforeAll(async () => { + context = await chromium.launchPersistentContext('', { + headless: false, + slowMo: 250 + }); + page = + context.pages().find((page) => page.url() === 'about:blank') || + (await context.newPage()); + await page.goto('featureIndexOverlay.html'); + }); + + test.afterAll(async function () { + await context.close(); + }); + + test('Feature index overlay and reticle shows on focus', async () => { + const hiddenOverlay = await page.$eval( + 'div > output.mapml-feature-index', + (output) => output.classList.contains('mapml-screen-reader-output') + ); + const hiddenReticle = await page.$eval( + 'div > div.mapml-feature-index-box', + (div) => div.hasAttribute('hidden') + ); + + await page.keyboard.press('Tab'); + await page.waitForTimeout(500); + const afterTabOverlay = await page.$eval( + 'div > output.mapml-feature-index', + (output) => output.classList.contains('mapml-screen-reader-output') + ); + const afterTabReticle = await page.$eval( + 'div > div.mapml-feature-index-box', + (div) => div.hasAttribute('hidden') + ); + + expect(hiddenOverlay).toEqual(true); + expect(hiddenReticle).toEqual(true); + expect(afterTabOverlay).toEqual(false); + expect(afterTabReticle).toEqual(false); + }); + test('Feature index overlay and reticle show on fullscreen', async () => { + await page.locator('#map1').getByTitle('View Fullscreen').click(); + const afterFullscreenReticle = page.locator( + '#map1 .mapml-feature-index-box' + ); + expect(await afterFullscreenReticle.isHidden()).toBe(false); + + const afterFullscreenOutput = page.locator( + '#map1 output.mapml-feature-index' + ); + expect( + await afterFullscreenOutput.evaluate((o) => + o.classList.contains('mapml-screen-reader-output') + ) + ).toBe(false); + await page.locator('#map1').getByTitle('Exit Fullscreen').click(); + }); + test('Feature index overlay and reticle show on reload', async () => { + await page.keyboard.press('ArrowRight'); + await page.locator('#map1').getByTitle('Reload').click(); + const afterReloadReticle = page.locator('#map1 .mapml-feature-index-box'); + expect(await afterReloadReticle.isHidden()).toBe(false); + + const afterReloadOutput = page.locator('#map1 output.mapml-feature-index'); + expect( + await afterReloadOutput.evaluate((o) => + o.classList.contains('mapml-screen-reader-output') + ) + ).toBe(false); + }); + test('Feature index overlay and reticle show on history-based navigation', async () => { + await page.keyboard.press('ArrowRight'); + await page.keyboard.press('ArrowUp'); + await page.keyboard.press('ArrowRight'); + await page.keyboard.press('ArrowLeft'); + await page.keyboard.press('Shift+F10'); + await page.locator('#map1').getByText('Back').click(); + await page.keyboard.press('Shift+F10'); + await page.locator('#map1').getByText('Back').click(); + const afterHistoryNavReticle = page.locator( + '#map1 .mapml-feature-index-box' + ); + expect(await afterHistoryNavReticle.isHidden()).toBe(false); + + const afterHistoryNavOutput = page.locator( + '#map1 output.mapml-feature-index' + ); + expect( + await afterHistoryNavOutput.evaluate((o) => + o.classList.contains('mapml-screen-reader-output') + ) + ).toBe(false); + await page.locator('#map1').getByTitle('Reload').click(); + }); + test('Feature index overlay and reticle show on geolocation activation, deactivation', async () => { + await page + .locator('#map3') + .getByTitle('Show my location - location tracking off') + .click(); + const afterGeolocationStartReticle = page.locator( + '#map3 .mapml-feature-index-box' + ); + expect(await afterGeolocationStartReticle.isHidden()).toBe(false); + + const afterGeolocationStartOutput = page.locator( + '#map3 output.mapml-feature-index' + ); + expect( + await afterGeolocationStartOutput.evaluate((o) => + o.classList.contains('mapml-screen-reader-output') + ) + ).toBe(false); + + await page + .locator('#map3') + .getByTitle('Show my location - location tracking on') + .click(); + const afterGeolocationStopReticle = page.locator( + '#map3 .mapml-feature-index-box' + ); + expect(await afterGeolocationStopReticle.isHidden()).toBe(false); + + const afterGeolocationStopOutput = page.locator( + '#map3 output.mapml-feature-index' + ); + expect( + await afterGeolocationStopOutput.evaluate((o) => + o.classList.contains('mapml-screen-reader-output') + ) + ).toBe(false); + await page.locator('#map3').getByTitle('Reload').click(); + }); + test('Feature index overlay and reticle show after following a link', async () => { + await page.locator('#map3').scrollIntoViewIfNeeded(); + await page.locator('#map3 .leaflet-interactive.map-a').click(); + const afterFollowingLinkReticle = page.locator( + '#map3 .mapml-feature-index-box' + ); + expect(await afterFollowingLinkReticle.isHidden()).toBe(false); + + const afterFollowingLinkOutput = page.locator( + '#map3 output.mapml-feature-index' + ); + expect( + await afterFollowingLinkOutput.evaluate((o) => + o.classList.contains('mapml-screen-reader-output') + ) + ).toBe(false); + await page.locator('#map3').getByTitle('Reload').click(); + }); +}); diff --git a/test/e2e/core/featureIndexOverlay.test.js b/test/e2e/core/featureIndexOverlayResults.test.js similarity index 77% rename from test/e2e/core/featureIndexOverlay.test.js rename to test/e2e/core/featureIndexOverlayResults.test.js index 8657fd396..2a5911a5b 100644 --- a/test/e2e/core/featureIndexOverlay.test.js +++ b/test/e2e/core/featureIndexOverlayResults.test.js @@ -1,6 +1,6 @@ import { test, expect, chromium } from '@playwright/test'; -test.describe('Feature Index Overlay test', () => { +test.describe('Feature Index Overlay results test', () => { let page; let context; test.beforeAll(async () => { @@ -15,34 +15,8 @@ test.describe('Feature Index Overlay test', () => { await context.close(); }); - test('Feature index overlay and reticle shows on focus', async () => { - const hiddenOverlay = await page.$eval( - 'div > output.mapml-feature-index', - (output) => output.classList.contains('mapml-screen-reader-output') - ); - const hiddenReticle = await page.$eval( - 'div > div.mapml-feature-index-box', - (div) => div.hasAttribute('hidden') - ); - - await page.keyboard.press('Tab'); - await page.waitForTimeout(500); - const afterTabOverlay = await page.$eval( - 'div > output.mapml-feature-index', - (output) => output.classList.contains('mapml-screen-reader-output') - ); - const afterTabReticle = await page.$eval( - 'div > div.mapml-feature-index-box', - (div) => div.hasAttribute('hidden') - ); - - expect(hiddenOverlay).toEqual(true); - expect(hiddenReticle).toEqual(true); - expect(afterTabOverlay).toEqual(false); - expect(afterTabReticle).toEqual(false); - }); - test('Feature index content is correct', async () => { + await page.keyboard.press('Tab'); const spanCount = await page.$eval( 'div > output.mapml-feature-index > span', (span) => span.childElementCount @@ -109,21 +83,26 @@ test.describe('Feature Index Overlay test', () => { expect(firstFeature).toContain('1 Maine'); }); - test('Feature index overlay is hidden when empty, reticle still visible', async () => { + test('Feature index message for "No features found", reticle still visible', async () => { await page.keyboard.press('ArrowUp'); await page.waitForTimeout(1000); - const overlay = await page.$eval( + const overlayVisible = await page.$eval( 'div > output.mapml-feature-index', - (output) => output.classList.contains('mapml-screen-reader-output') + (output) => !output.classList.contains('mapml-screen-reader-output') ); - const reticle = await page.$eval( + const reticleVisible = await page.$eval( 'div > div.mapml-feature-index-box', - (div) => div.hasAttribute('hidden') + (div) => !div.hasAttribute('hidden') + ); + const message = await page.$eval( + '.mapml-feature-index-content > span', + (message) => message.textContent ); - expect(overlay).toEqual(true); - expect(reticle).toEqual(false); + expect(overlayVisible).toEqual(true); + expect(reticleVisible).toEqual(true); + expect(message).toEqual('No features found'); }); test('Popup test with templated features', async () => { diff --git a/test/e2e/layers/multipleQueryExtents.test.js b/test/e2e/layers/multipleQueryExtents.test.js index 5a57ae6f7..c5c0b58f5 100644 --- a/test/e2e/layers/multipleQueryExtents.test.js +++ b/test/e2e/layers/multipleQueryExtents.test.js @@ -52,20 +52,12 @@ test.describe('Multiple Extent Query Tests', () => { test('Querying overlapping extents, user is able to navigate into second set of query results using popup controls', async () => { let feature; - let nextFeatureButton = - 'div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-popup-pane > div > div.leaflet-popup-content-wrapper > div > div > nav > button:nth-child(4)'; - await page.click(nextFeatureButton); - // await page.waitForTimeout(500); - await page.click(nextFeatureButton); - // await page.waitForTimeout(500); - await page.click(nextFeatureButton); - // await page.waitForTimeout(500); - await page.click(nextFeatureButton); - // await page.waitForTimeout(500); - await page.click(nextFeatureButton); - // await page.waitForTimeout(500); - await page.click(nextFeatureButton); - // await page.waitForTimeout(500); + await page.getByTitle('Next Feature', { exact: true }).click(); + await page.getByTitle('Next Feature', { exact: true }).click(); + await page.getByTitle('Next Feature', { exact: true }).click(); + await page.getByTitle('Next Feature', { exact: true }).click(); + await page.getByTitle('Next Feature', { exact: true }).click(); + await page.getByTitle('Next Feature', { exact: true }).click(); const name = await page .frameLocator('iframe') @@ -87,10 +79,7 @@ test.describe('Multiple Extent Query Tests', () => { }); test("Navigate back from second query result set to end of first query result set by clicking '< / Previous'", async () => { - // click the '<' (previous) button in the popup. - await page.click( - 'div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-popup-pane > div > div.leaflet-popup-content-wrapper > div > div > nav > button:nth-child(2)' - ); + await page.getByTitle('Previous Feature', { exact: true }).click(); const feature = await page.$eval( 'div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-overlay-pane > div > div.mapml-vector-container > svg > g', (g) => (g.firstElementChild ? g.firstElementChild : false) @@ -114,10 +103,12 @@ test.describe('Multiple Extent Query Tests', () => { await page.evaluateHandle(() => document.querySelector('mapml-viewer').zoomTo(10, 5, 0) ); - await page.click('div'); - await page.waitForSelector( - 'div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-popup-pane > div' - ); + await page.locator('mapml-viewer').click({ position: { x: 250, y: 250 } }); + await page + .locator( + 'div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-popup-pane > div' + ) + .waitFor(); const popupNum = await page.$eval( 'div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-popup-pane', (div) => div.childElementCount @@ -126,32 +117,23 @@ test.describe('Multiple Extent Query Tests', () => { }); test('Only features from one extent are returned for queries inside its (non overlapping) bounds', async () => { - var numFeatures = await page.$eval( - 'div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-popup-pane > div > div.leaflet-popup-content-wrapper > div > div > nav > p', - (p) => p.innerText - ); - expect(numFeatures).toEqual('1/6'); - for (let i = 0; i < 6; i++) { - await page.click( - 'div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-popup-pane > div > div.leaflet-popup-content-wrapper > div > div > nav > button:nth-child(4)' - ); - await page.waitForSelector( - 'div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-popup-pane > div > div.leaflet-popup-content-wrapper > div > div > iframe' - ); - } - let feature = await page.$eval( - 'div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-overlay-pane > div > div.mapml-vector-container > svg > g', - (g) => (g.firstElementChild ? g.firstElementChild : false) - ); - expect(feature).toBeFalsy(); - - const popup = await page.$eval( - 'div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-popup-pane > div > div.leaflet-popup-content-wrapper > div > div > iframe', + await page.getByRole('button', { name: 'Close popup' }).click(); + await page.locator('mapml-viewer').click({ position: { x: 450, y: 150 } }); + await page.getByTitle('Next Feature').click(); + await page.getByTitle('Next Feature').click(); + await page.getByTitle('Next Feature').click(); + await page.getByTitle('Next Feature').click(); + await page.getByTitle('Next Feature').click(); + let feature = page.locator('.mapml-vector-container > svg > g'); + await expect(feature).toBeEmpty(); + + const frame = page.locator('iframe'); + const popup = await frame.evaluate( (iframe) => iframe.contentWindow.document.querySelector('h1').innerText ); expect(popup).toEqual('No Geometry'); - numFeatures = await page.$eval( + let numFeatures = await page.$eval( 'div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-popup-pane > div > div.leaflet-popup-content-wrapper > div > div > nav > p', (p) => p.innerText ); @@ -159,14 +141,11 @@ test.describe('Multiple Extent Query Tests', () => { }); test('No features returned when queried outside of bounds of all extents', async () => { + await page.keyboard.press('Escape'); await page.evaluateHandle(() => document.querySelector('mapml-viewer').zoomTo(-18, 5, 0) ); - await page.click('div'); - await page.waitForSelector( - 'div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-popup-pane > div', - { state: 'hidden' } - ); + await page.locator('mapml-viewer').click({ position: { x: 400, y: 250 } }); const popupNumRight = await page.$eval( 'div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-popup-pane', (div) => div.childElementCount @@ -175,7 +154,7 @@ test.describe('Multiple Extent Query Tests', () => { await page.evaluateHandle(() => document.querySelector('mapml-viewer').zoomTo(-16, -40, 0) ); - await page.click('div'); + await page.locator('mapml-viewer').click({ position: { x: 250, y: 400 } }); const popupNumBottom = await page.$eval( 'div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-popup-pane', (div) => div.childElementCount @@ -184,7 +163,7 @@ test.describe('Multiple Extent Query Tests', () => { await page.evaluateHandle(() => document.querySelector('mapml-viewer').zoomTo(33, -170, 0) ); - await page.click('div'); + await page.locator('mapml-viewer').click({ position: { x: 50, y: 250 } }); const popupNumLeft = await page.$eval( 'div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-popup-pane', (div) => div.childElementCount @@ -193,7 +172,7 @@ test.describe('Multiple Extent Query Tests', () => { await page.evaluateHandle(() => document.querySelector('mapml-viewer').zoomTo(30, 98, 0) ); - await page.click('div'); + await page.locator('mapml-viewer').click({ position: { x: 250, y: 50 } }); const popupNumTop = await page.$eval( 'div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-popup-pane', (div) => div.childElementCount diff --git a/test/e2e/mapml-viewer/locateButton.test.js b/test/e2e/mapml-viewer/locateButton.test.js index 6d7a8c0e6..f541f3225 100644 --- a/test/e2e/mapml-viewer/locateButton.test.js +++ b/test/e2e/mapml-viewer/locateButton.test.js @@ -22,14 +22,7 @@ test.describe('Geolocation control tests', () => { test('Using geolocation control to control map', async () => { await page.click('body > mapml-viewer'); - 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'); - await page.keyboard.press('Tab'); - await page.keyboard.press('Tab'); - await page.keyboard.press('Enter'); + await page.getByTitle('Show my location - location tracking off').click(); let locateButton_lat = await page.$eval( 'body > mapml-viewer', @@ -54,14 +47,7 @@ test.describe('Geolocation control tests', () => { test('Geolocation control state changes when pressed', async () => { await page.click('body > mapml-viewer'); - 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'); - await page.keyboard.press('Tab'); - await page.keyboard.press('Tab'); - await page.keyboard.press('Enter'); + await page.getByTitle('Show my location - location tracking on').click(); let locationOnText = await page.evaluate( () => M.options.locale.btnLocTrackOn @@ -79,7 +65,7 @@ test.describe('Geolocation control tests', () => { ); expect(locateButton_title1).toEqual(locationOffText); - await page.keyboard.press('Enter'); + await page.getByTitle('Show my location - location tracking off').click(); let locateButton_title2 = await page.$eval( 'div > div.leaflet-control-container > div.leaflet-bottom.leaflet-right > div > a',