From 237992502e4674eb93da3fc66ae8147a75698aff Mon Sep 17 00:00:00 2001 From: Peter Rushforth Date: Sun, 31 Mar 2024 20:09:11 -0400 Subject: [PATCH] Update mapml-viewer to be shadow host-compatible (#950) * Update mapml-viewer to be shadow host-compatible * Update ci-testing.yml Focus on getting flaky tests to pass --- .github/workflows/ci-testing.yml | 2 +- src/map-extent.js | 9 +- src/map-feature.js | 36 +----- src/map-input.js | 7 +- src/map-link.js | 8 +- src/mapml-viewer.js | 8 +- src/mapml.css | 13 +- src/mapml/control/FullscreenButton.js | 46 ++++--- src/mapml/index.js | 1 + src/mapml/utils/Util.js | 18 +++ src/web-map.js | 22 +++- test/e2e/data/restaurants/african.mapml | 2 +- test/e2e/data/tiles/cbmt/cbmt.mapml | 4 +- .../map-extent/map-extent-in-shadow-root.html | 44 +++++++ .../map-extent-in-shadow-root.test.js | 32 +++++ .../map-feature/feature-in-shadow-root.html | 44 +++++++ .../feature-in-shadow-root.test.js | 32 +++++ .../map-input/map-input-in-shadow-root.html | 44 +++++++ .../map-input-in-shadow-root.test.js | 32 +++++ .../map-link/map-link-in-shadow-root.html | 44 +++++++ .../map-link/map-link-in-shadow-root.test.js | 33 +++++ test/e2e/elements/map/map-in-shadow-root.html | 47 +++++++ .../elements/map/map-in-shadow-root.test.js | 117 ++++++++++++++++++ .../mapml-viewer-in-shadow-root.html | 60 +++++++++ .../mapml-viewer-in-shadow-root.test.js | 117 ++++++++++++++++++ test/server.js | 3 + 26 files changed, 734 insertions(+), 91 deletions(-) create mode 100644 test/e2e/elements/map-extent/map-extent-in-shadow-root.html create mode 100644 test/e2e/elements/map-extent/map-extent-in-shadow-root.test.js create mode 100644 test/e2e/elements/map-feature/feature-in-shadow-root.html create mode 100644 test/e2e/elements/map-feature/feature-in-shadow-root.test.js create mode 100644 test/e2e/elements/map-input/map-input-in-shadow-root.html create mode 100644 test/e2e/elements/map-input/map-input-in-shadow-root.test.js create mode 100644 test/e2e/elements/map-link/map-link-in-shadow-root.html create mode 100644 test/e2e/elements/map-link/map-link-in-shadow-root.test.js create mode 100644 test/e2e/elements/map/map-in-shadow-root.html create mode 100644 test/e2e/elements/map/map-in-shadow-root.test.js create mode 100644 test/e2e/elements/mapml-viewer/mapml-viewer-in-shadow-root.html create mode 100644 test/e2e/elements/mapml-viewer/mapml-viewer-in-shadow-root.test.js diff --git a/.github/workflows/ci-testing.yml b/.github/workflows/ci-testing.yml index 628a7c1b9..516e1b299 100644 --- a/.github/workflows/ci-testing.yml +++ b/.github/workflows/ci-testing.yml @@ -18,7 +18,7 @@ jobs: - run: npx playwright install --with-deps - run: npm install -g grunt-cli - run: grunt default - - run: xvfb-run --auto-servernum -- npx playwright test --workers=1 --retries=3 + - run: xvfb-run --auto-servernum -- npx playwright test tab menu --workers=1 --retries=3 # - run: xvfb-run --auto-servernum -- npm run jest env: CI: true diff --git a/src/map-extent.js b/src/map-extent.js index 303be55cf..693edcbfc 100644 --- a/src/map-extent.js +++ b/src/map-extent.js @@ -125,15 +125,12 @@ export class MapExtent extends HTMLElement { animate: false }); } + getMapEl() { - return this.getRootNode() instanceof ShadowRoot - ? this.getRootNode().host.closest('mapml-viewer,map[is=web-map]') - : this.closest('mapml-viewer,map[is=web-map]'); + return M.getClosest(this, 'mapml-viewer,map[is=web-map]'); } getLayerEl() { - return this.getRootNode() instanceof ShadowRoot - ? this.getRootNode().host - : this.closest('layer-'); + return M.getClosest(this, 'layer-'); } attributeChangedCallback(name, oldValue, newValue) { if (this.#hasConnected /* jshint ignore:line */) { diff --git a/src/map-feature.js b/src/map-feature.js index 18b0633d4..4dee49ade 100644 --- a/src/map-feature.js +++ b/src/map-feature.js @@ -128,39 +128,11 @@ export class MapFeature extends HTMLElement { return this._getFeatureExtent(); } } - getLayerEl() { - let layerEl; - if (this.getRootNode() instanceof ShadowRoot) { - if (this.getRootNode().host.getRootNode() instanceof ShadowRoot) { - // layer- src - // > sd - // map-extent - // map-link - // > sd - // map-feature (1) - layerEl = this.getRootNode().host.getRootNode().host; - } else if (this.getRootNode().host.nodeName === 'MAP-LINK') { - // layer- - // map-extent - // map-link - // > sd - // map-feature (4) - layerEl = this.getRootNode().host.closest('layer-'); - } else { - // layer- src - // > sd - // map-feature (2) - layerEl = this.getRootNode().host; - } - } else { - // layer- - // map-feature (3) - layerEl = this.closest('layer-'); - } - return layerEl; - } getMapEl() { - return this.getLayerEl().closest('mapml-viewer,map[is=web-map]'); + return M.getClosest(this, 'mapml-viewer,map[is=web-map]'); + } + getLayerEl() { + return M.getClosest(this, 'layer-'); } attributeChangedCallback(name, oldValue, newValue) { diff --git a/src/map-input.js b/src/map-input.js index c2e473e5f..3a36e869e 100644 --- a/src/map-input.js +++ b/src/map-input.js @@ -172,10 +172,11 @@ export class MapInput extends HTMLElement { this.setAttribute('step', val); } } + getMapEl() { + return M.getClosest(this, 'mapml-viewer,map[is=web-map]'); + } getLayerEl() { - return this.getRootNode() instanceof ShadowRoot - ? this.getRootNode().host - : this.closest('layer-'); + return M.getClosest(this, 'layer-'); } attributeChangedCallback(name, oldValue, newValue) { this.whenReady() diff --git a/src/map-link.js b/src/map-link.js index 850302da9..e0c8d488a 100644 --- a/src/map-link.js +++ b/src/map-link.js @@ -152,14 +152,10 @@ export class MapLink extends HTMLElement { }); } getMapEl() { - return this.getRootNode() instanceof ShadowRoot - ? this.getRootNode().host.closest('mapml-viewer,map[is=web-map]') - : this.closest('mapml-viewer,map[is=web-map]'); + return M.getClosest(this, 'mapml-viewer,map[is=web-map]'); } getLayerEl() { - return this.getRootNode() instanceof ShadowRoot - ? this.getRootNode().host - : this.closest('layer-'); + return M.getClosest(this, 'layer-'); } attributeChangedCallback(name, oldValue, newValue) { diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index 77c03901c..79fbe626d 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -636,7 +636,11 @@ export class MapViewer extends HTMLElement { false ); - this.parentElement.addEventListener('keyup', function (e) { + let host = + this.getRootNode() instanceof ShadowRoot + ? this.getRootNode().host + : this.parentElement; + host.addEventListener('keyup', function (e) { if ( e.keyCode === 9 && document.activeElement.nodeName === 'MAPML-VIEWER' @@ -661,7 +665,7 @@ export class MapViewer extends HTMLElement { this._map.fire('keypress', { originalEvent: e }); } }); - this.parentElement.addEventListener('mousedown', function (e) { + host.addEventListener('mousedown', function (e) { if (document.activeElement.nodeName === 'MAPML-VIEWER') { document.activeElement.dispatchEvent( new CustomEvent('mapfocused', { detail: { target: this } }) diff --git a/src/mapml.css b/src/mapml.css index 511bbb103..f673e2698 100644 --- a/src/mapml.css +++ b/src/mapml.css @@ -228,7 +228,8 @@ button.mapml-contextmenu-item:disabled { } /* - * Fullscreen control. + * Fullscreen control. Image contains the on and off images, toggled via + * the background-position property */ .leaflet-control-fullscreen a { @@ -238,7 +239,10 @@ button.mapml-contextmenu-item:disabled { background-position: 3px 3px; } -:host(.mapml-fullscreen-on) .leaflet-control-fullscreen a { +/* +* See: https://developer.mozilla.org/en-US/docs/Web/CSS/:host_function +and: https://developer.mozilla.org/en-US/docs/Web/CSS/:fullscreen */ +:host(:fullscreen) .leaflet-control-fullscreen a { background-position: 3px -35px; } @@ -247,11 +251,6 @@ button.mapml-contextmenu-item:disabled { height: 100%!important; } -:host(.mapml-fullscreen-on) { - width: 100%!important; - height: 100%!important; -} - :host(.leaflet-pseudo-fullscreen) { position: fixed!important; width: 100%!important; diff --git a/src/mapml/control/FullscreenButton.js b/src/mapml/control/FullscreenButton.js index 9b0448e93..d78639573 100644 --- a/src/mapml/control/FullscreenButton.js +++ b/src/mapml/control/FullscreenButton.js @@ -53,11 +53,10 @@ L.Map.include({ toggleFullscreen: function (options) { // the element can't contain a shadow root, so we used a child
// can contain a shadow root, so return it directly - var mapEl = this.getContainer().getRootNode().host, - container = mapEl.nodeName === 'DIV' ? mapEl.parentElement : mapEl; + var mapEl = M.getClosest(this.getContainer(), 'mapml-viewer,[is=web-map]'); if (this.isFullscreen()) { if (options && options.pseudoFullscreen) { - this._disablePseudoFullscreen(container); + this._disablePseudoFullscreen(mapEl); } else if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.mozCancelFullScreen) { @@ -67,21 +66,21 @@ L.Map.include({ } else if (document.msExitFullscreen) { document.msExitFullscreen(); } else { - this._disablePseudoFullscreen(container); + this._disablePseudoFullscreen(mapEl); } } else { if (options && options.pseudoFullscreen) { - this._enablePseudoFullscreen(container); - } else if (container.requestFullscreen) { - container.requestFullscreen(); - } else if (container.mozRequestFullScreen) { - container.mozRequestFullScreen(); - } else if (container.webkitRequestFullscreen) { - container.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); - } else if (container.msRequestFullscreen) { - container.msRequestFullscreen(); + this._enablePseudoFullscreen(mapEl); + } else if (mapEl.requestFullscreen) { + mapEl.requestFullscreen(); + } else if (mapEl.mozRequestFullScreen) { + mapEl.mozRequestFullScreen(); + } else if (mapEl.webkitRequestFullscreen) { + mapEl.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); + } else if (mapEl.msRequestFullscreen) { + mapEl.msRequestFullscreen(); } else { - this._enablePseudoFullscreen(container); + this._enablePseudoFullscreen(mapEl); } } this.getContainer().focus(); @@ -101,7 +100,10 @@ L.Map.include({ _setFullscreen: function (fullscreen) { this._isFullscreen = fullscreen; - var container = this.getContainer().getRootNode().host; + var container = M.getClosest( + this.getContainer(), + 'mapml-viewer,[is=web-map]' + ); if (fullscreen) { L.DomUtil.addClass(container, 'mapml-fullscreen-on'); } else { @@ -111,18 +113,12 @@ L.Map.include({ }, _onFullscreenChange: function (e) { - var fullscreenElement = - document.fullscreenElement || - document.mozFullScreenElement || - document.webkitFullscreenElement || - document.msFullscreenElement, - mapEl = this.getContainer().getRootNode().host, - container = mapEl.nodeName === 'DIV' ? mapEl.parentElement : mapEl; - - if (fullscreenElement === container && !this._isFullscreen) { + var fullscreenElement = M.getClosest(this.getContainer(), ':fullscreen'), + mapEl = M.getClosest(this.getContainer(), 'mapml-viewer,[is=web-map]'); + if (fullscreenElement === mapEl && !this._isFullscreen) { this._setFullscreen(true); this.fire('fullscreenchange'); - } else if (fullscreenElement !== container && this._isFullscreen) { + } else if (fullscreenElement !== mapEl && this._isFullscreen) { this._setFullscreen(false); this.fire('fullscreenchange'); } diff --git a/src/mapml/index.js b/src/mapml/index.js index 1b3bcde21..07fb6e138 100644 --- a/src/mapml/index.js +++ b/src/mapml/index.js @@ -840,6 +840,7 @@ import { DOMTokenList } from './utils/DOMTokenList'; M.getZoomBoundsFromMeta = Util.getZoomBoundsFromMeta; M.getZoomBounds = Util.getZoomBounds; M.getNativeVariables = Util.getNativeVariables; + M.getClosest = Util.getClosest; M.QueryHandler = QueryHandler; M.ContextMenu = ContextMenu; diff --git a/src/mapml/utils/Util.js b/src/mapml/utils/Util.js index 01ca6ee16..e7e9f35c9 100644 --- a/src/mapml/utils/Util.js +++ b/src/mapml/utils/Util.js @@ -1519,5 +1519,23 @@ export var Util = { ) newZoom--; return newZoom; + }, + getClosest(node, selector) { + if (!node) { + return null; + } + if (node instanceof ShadowRoot) { + return M.getClosest(node.host, selector); + } + + if (node instanceof HTMLElement) { + if (node.matches(selector)) { + return node; + } else { + return M.getClosest(node.parentNode, selector); + } + } + + return M.getClosest(node.parentNode, selector); } }; diff --git a/src/web-map.js b/src/web-map.js index b7fa2a914..311a071de 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -241,6 +241,7 @@ export class WebMap extends HTMLMapElement { // Set default styles for the map element. let mapDefaultCSS = document.createElement('style'); + mapDefaultCSS.id = 'web-map-default-style'; mapDefaultCSS.innerHTML = `[is="web-map"] {` + `all: initial;` + // Reset properties inheritable from html/body, as some inherited styles may cause unexpected issues with the map element's components (https://github.com/Maps4HTML/Web-Map-Custom-Element/issues/140). @@ -281,7 +282,13 @@ export class WebMap extends HTMLMapElement { shadowRoot.appendChild(tmpl.content.cloneNode(true)); shadowRoot.appendChild(this._container); this.appendChild(rootDiv); - document.head.insertAdjacentElement('afterbegin', mapDefaultCSS); + if (this.getRootNode() instanceof ShadowRoot) { + if (!this.getRootNode().getElementById(mapDefaultCSS.id)) + this.getRootNode().prepend(mapDefaultCSS); + } else { + if (!document.getElementById(mapDefaultCSS.id)) + document.head.insertAdjacentElement('afterbegin', mapDefaultCSS); + } } _createMap() { if (!this._map) { @@ -684,14 +691,17 @@ export class WebMap extends HTMLMapElement { false ); - let mapEl = this; - this.parentElement.addEventListener('keyup', function (e) { + let host = + this.getRootNode() instanceof ShadowRoot + ? this.getRootNode().host + : this.parentElement; + host.addEventListener('keyup', function (e) { if ( e.keyCode === 9 && document.activeElement.className === 'mapml-web-map' ) { // document.activeElement is div.mapml-web-map, not - mapEl.dispatchEvent( + document.activeElement.dispatchEvent( new CustomEvent('mapfocused', { detail: { target: this } }) ); } @@ -711,9 +721,9 @@ export class WebMap extends HTMLMapElement { this._map.fire('keypress', { originalEvent: e }); } }); - this.parentElement.addEventListener('mousedown', function (e) { + host.addEventListener('mousedown', function (e) { if (document.activeElement.className === 'mapml-web-map') { - mapEl.dispatchEvent( + document.activeElement.dispatchEvent( new CustomEvent('mapfocused', { detail: { target: this } }) ); } diff --git a/test/e2e/data/restaurants/african.mapml b/test/e2e/data/restaurants/african.mapml index c0b9d6b94..e9288a18c 100644 --- a/test/e2e/data/restaurants/african.mapml +++ b/test/e2e/data/restaurants/african.mapml @@ -10,7 +10,7 @@ - + Hareg Cafe & Variety diff --git a/test/e2e/data/tiles/cbmt/cbmt.mapml b/test/e2e/data/tiles/cbmt/cbmt.mapml index 03ceae6cf..dbe93e688 100644 --- a/test/e2e/data/tiles/cbmt/cbmt.mapml +++ b/test/e2e/data/tiles/cbmt/cbmt.mapml @@ -13,9 +13,9 @@ diff --git a/test/e2e/elements/map-extent/map-extent-in-shadow-root.html b/test/e2e/elements/map-extent/map-extent-in-shadow-root.html new file mode 100644 index 000000000..251382392 --- /dev/null +++ b/test/e2e/elements/map-extent/map-extent-in-shadow-root.html @@ -0,0 +1,44 @@ + + + + mapml-viewer in shadow root test + + + + + + + + + + \ No newline at end of file diff --git a/test/e2e/elements/map-extent/map-extent-in-shadow-root.test.js b/test/e2e/elements/map-extent/map-extent-in-shadow-root.test.js new file mode 100644 index 000000000..8989ba2f3 --- /dev/null +++ b/test/e2e/elements/map-extent/map-extent-in-shadow-root.test.js @@ -0,0 +1,32 @@ +import { test, expect, chromium } from '@playwright/test'; + +test.describe('map-extent can be inside a shadow root or other custom element', () => { + let page; + let context; + test.beforeAll(async function () { + context = await chromium.launchPersistentContext(''); + page = + context.pages().find((page) => page.url() === 'about:blank') || + (await context.newPage()); + await page.goto('map-extent-in-shadow-root.html'); + }); + test('map-extent getMapEl() works in shadow root', async () => { + const viewer = page.getByTestId('viewer'); + await expect(viewer).toBeTruthy(); + const layer = viewer.getByTestId('test-layer'); + const extent = layer.locator('map-extent'); + const extentGetMapElResult = await extent.evaluate((e) => + e.getMapEl().getAttribute('data-testid') + ); + expect(extentGetMapElResult).toEqual('viewer'); + }); + test('map-extent getLayerEl() works in shadow root', async () => { + const viewer = page.getByTestId('viewer'); + const layer = viewer.getByTestId('test-layer'); + const extent = layer.locator('map-extent'); + const extentGetLayerElResult = await extent.evaluate((e) => + e.getLayerEl().getAttribute('data-testid') + ); + expect(extentGetLayerElResult).toEqual('test-layer'); + }); +}); diff --git a/test/e2e/elements/map-feature/feature-in-shadow-root.html b/test/e2e/elements/map-feature/feature-in-shadow-root.html new file mode 100644 index 000000000..135c3cc78 --- /dev/null +++ b/test/e2e/elements/map-feature/feature-in-shadow-root.html @@ -0,0 +1,44 @@ + + + + mapml-viewer in shadow root test + + + + + + + + + + \ No newline at end of file diff --git a/test/e2e/elements/map-feature/feature-in-shadow-root.test.js b/test/e2e/elements/map-feature/feature-in-shadow-root.test.js new file mode 100644 index 000000000..24348ef19 --- /dev/null +++ b/test/e2e/elements/map-feature/feature-in-shadow-root.test.js @@ -0,0 +1,32 @@ +import { test, expect, chromium } from '@playwright/test'; + +test.describe('map-feature can be inside a shadow root or other custom element', () => { + let page; + let context; + test.beforeAll(async function () { + context = await chromium.launchPersistentContext(''); + page = + context.pages().find((page) => page.url() === 'about:blank') || + (await context.newPage()); + await page.goto('feature-in-shadow-root.html'); + }); + test('map-feature getMapEl() works in shadow root', async () => { + const viewer = page.getByTestId('viewer'); + await expect(viewer).toBeTruthy(); + const layer = viewer.getByTestId('test-layer'); + const feature = layer.getByTestId('hareg'); + const featureGetMapElResult = await feature.evaluate((f) => + f.getMapEl().getAttribute('data-testid') + ); + expect(featureGetMapElResult).toEqual('viewer'); + }); + test('map-feature getLayerEl() works in shadow root', async () => { + const viewer = page.getByTestId('viewer'); + const layer = viewer.getByTestId('test-layer'); + const feature = layer.getByTestId('hareg'); + const featureGetLayerElResult = await feature.evaluate((f) => + f.getLayerEl().getAttribute('data-testid') + ); + expect(featureGetLayerElResult).toEqual('test-layer'); + }); +}); diff --git a/test/e2e/elements/map-input/map-input-in-shadow-root.html b/test/e2e/elements/map-input/map-input-in-shadow-root.html new file mode 100644 index 000000000..251382392 --- /dev/null +++ b/test/e2e/elements/map-input/map-input-in-shadow-root.html @@ -0,0 +1,44 @@ + + + + mapml-viewer in shadow root test + + + + + + + + + + \ No newline at end of file diff --git a/test/e2e/elements/map-input/map-input-in-shadow-root.test.js b/test/e2e/elements/map-input/map-input-in-shadow-root.test.js new file mode 100644 index 000000000..8bde8c937 --- /dev/null +++ b/test/e2e/elements/map-input/map-input-in-shadow-root.test.js @@ -0,0 +1,32 @@ +import { test, expect, chromium } from '@playwright/test'; + +test.describe('map-input can be inside a shadow root or other custom element', () => { + let page; + let context; + test.beforeAll(async function () { + context = await chromium.launchPersistentContext(''); + page = + context.pages().find((page) => page.url() === 'about:blank') || + (await context.newPage()); + await page.goto('map-input-in-shadow-root.html'); + }); + test('map-input getMapEl() works in shadow root', async () => { + const viewer = page.getByTestId('viewer'); + await expect(viewer).toBeTruthy(); + const layer = viewer.getByTestId('test-layer'); + const input = layer.getByTestId('test-input'); + const inputGetMapElResult = await input.evaluate((i) => + i.getMapEl().getAttribute('data-testid') + ); + expect(inputGetMapElResult).toEqual('viewer'); + }); + test('map-input getLayerEl() works in shadow root', async () => { + const viewer = page.getByTestId('viewer'); + const layer = viewer.getByTestId('test-layer'); + const input = layer.getByTestId('test-input'); + const inputGetLayerElResult = await input.evaluate((i) => + i.getLayerEl().getAttribute('data-testid') + ); + expect(inputGetLayerElResult).toEqual('test-layer'); + }); +}); diff --git a/test/e2e/elements/map-link/map-link-in-shadow-root.html b/test/e2e/elements/map-link/map-link-in-shadow-root.html new file mode 100644 index 000000000..251382392 --- /dev/null +++ b/test/e2e/elements/map-link/map-link-in-shadow-root.html @@ -0,0 +1,44 @@ + + + + mapml-viewer in shadow root test + + + + + + + + + + \ No newline at end of file diff --git a/test/e2e/elements/map-link/map-link-in-shadow-root.test.js b/test/e2e/elements/map-link/map-link-in-shadow-root.test.js new file mode 100644 index 000000000..551d6f156 --- /dev/null +++ b/test/e2e/elements/map-link/map-link-in-shadow-root.test.js @@ -0,0 +1,33 @@ +import { test, expect, chromium } from '@playwright/test'; + +test.describe('map-link can be inside a shadow root or other custom element', () => { + let page; + let context; + test.beforeAll(async function () { + context = await chromium.launchPersistentContext(''); + page = + context.pages().find((page) => page.url() === 'about:blank') || + (await context.newPage()); + await page.goto('map-link-in-shadow-root.html'); + }); + + test('map-link getMapEl() works in shadow root', async () => { + const viewer = page.getByTestId('viewer'); + await expect(viewer).toBeTruthy(); + const layer = viewer.getByTestId('test-layer'); + const link = layer.getByTestId('test-link'); + const linkGetMapElResult = await link.evaluate((l) => + l.getMapEl().getAttribute('data-testid') + ); + expect(linkGetMapElResult).toEqual('viewer'); + }); + test('map-link getLayerEl() works in shadow root', async () => { + const viewer = page.getByTestId('viewer'); + const layer = viewer.getByTestId('test-layer'); + const link = layer.getByTestId('test-link'); + const linkGetLayerElResult = await link.evaluate((l) => + l.getLayerEl().getAttribute('data-testid') + ); + expect(linkGetLayerElResult).toEqual('test-layer'); + }); +}); diff --git a/test/e2e/elements/map/map-in-shadow-root.html b/test/e2e/elements/map/map-in-shadow-root.html new file mode 100644 index 000000000..83c7d116e --- /dev/null +++ b/test/e2e/elements/map/map-in-shadow-root.html @@ -0,0 +1,47 @@ + + + + map[is=web-map] in shadow root test + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/e2e/elements/map/map-in-shadow-root.test.js b/test/e2e/elements/map/map-in-shadow-root.test.js new file mode 100644 index 000000000..0c0b113fe --- /dev/null +++ b/test/e2e/elements/map/map-in-shadow-root.test.js @@ -0,0 +1,117 @@ +import { test, expect, chromium } from '@playwright/test'; + +const playwright = require('playwright'); + +test.describe('Playwright map[is=web-map] fullscreen tests', () => { + let context, page; + test.beforeAll(async () => { + context = await chromium.launchPersistentContext('', { + headless: false, + slowMo: 250 + }); + page = await context.newPage(); + }); + test.afterAll(async () => { + await context.close(); + }); + test.beforeEach(async () => { + await page.goto('map-in-shadow-root.html'); + }); + test('Fullscreen button makes shadow DOM map[is=web-map] element the fullscreen element', async () => { + const map1 = page.getByTestId('map1'); + const fullscreenButton = map1.getByTitle(/(View)|(Exit) Fullscreen/i); + await fullscreenButton.click(); + + let fullscreenElement = await map1.evaluate( + (m) => M.getClosest(m._map.getContainer(), ':fullscreen').id + ); + // the first mapml-viewer should be returned by document.fullscreen + expect(fullscreenElement).toEqual('map1'); + await fullscreenButton.click(); + fullscreenElement = await page.evaluate(`document.fullscreenElement`); + expect(fullscreenElement).toBeFalsy(); + + const map2 = page.getByTestId('map2'); + const fullscreenButton2 = map2.getByTitle(/(View)|(Exit) Fullscreen/i); + // do the same with second map / element + await fullscreenButton2.click(); + fullscreenElement = await map2.evaluate( + (m) => M.getClosest(m._map.getContainer(), ':fullscreen').id + ); + expect(fullscreenElement).toEqual('map2'); + try { + // try to click the fullscreen button of the other map that is not in fullscreen + await fullscreenButton.click({ timeout: 500 }); + } catch (e) { + if (e instanceof playwright.errors.TimeoutError) { + // all is well + } + } + // fullscreen element should not have changed + fullscreenElement = await map2.evaluate( + (m) => M.getClosest(m._map.getContainer(), ':fullscreen').id + ); + expect(fullscreenElement).toEqual('map2'); + await fullscreenButton2.click(); // exit fullscreen + }); + + test('Context Menu Fullscreen Button makes shadow DOM map[is=web-map] element fullscreen', async () => { + const map2 = page.getByTestId('map2'); + await map2.click(); + await page.keyboard.press('Shift+F10'); + await page.keyboard.press('F'); + let fullscreenElement = await map2.evaluate( + (m) => M.getClosest(m._map.getContainer(), ':fullscreen').id + ); + expect(fullscreenElement).toEqual('map2'); + await map2.click(); + await page.keyboard.press('Shift+F10'); + await page.keyboard.press('F'); // exit fullscreen + fullscreenElement = await page.evaluate(`document.fullscreenElement`); + expect(fullscreenElement).toBeFalsy(); + }); + test('Fullscreen button makes light DOM map[is=web-map] element the fullscreen element', async () => { + const map3 = page.getByTestId('map3'); + const fullscreenButton = map3.getByTitle(/(View)|(Exit) Fullscreen/i); + await fullscreenButton.click(); + + let fullscreenElement = await page.evaluate( + `document.fullscreenElement.id` + ); + // the first mapml-viewer should be returned by document.fullscreen + expect(fullscreenElement).toEqual('map3'); + + const map2 = page.getByTestId('map2'); + const fullscreenButton2 = map2.getByTitle(/(View)|(Exit) Fullscreen/i); + + try { + // try to click the fullscreen button of the other map that is not in fullscreen + await fullscreenButton2.click({ timeout: 500 }); + } catch (e) { + if (e instanceof playwright.errors.TimeoutError) { + // all is well + } + } + // fullscreen element should not have changed + fullscreenElement = await page.evaluate(`document.fullscreenElement.id`); + expect(fullscreenElement).toEqual('map3'); + await fullscreenButton.click(); // exit fullscreen + fullscreenElement = await page.evaluate(`document.fullscreenElement`); + expect(fullscreenElement).toBeFalsy(); + }); + test('Context Menu Fullscreen Button light DOM map[is=web-map] element fullscreen', async () => { + const map3 = page.getByTestId('map3'); + await map3.click(); + await page.keyboard.press('Shift+F10'); + await page.keyboard.press('F'); + let fullscreenElement = await map3.evaluate( + (m) => M.getClosest(m._map.getContainer(), ':fullscreen').id + ); + expect(fullscreenElement).toEqual('map3'); + await map3.click(); + await page.keyboard.press('Shift+F10'); + await page.keyboard.press('F'); // exit fullscreen + fullscreenElement = await page.evaluate(`document.fullscreenElement`); + expect(fullscreenElement).toBeFalsy(); + }); +}); diff --git a/test/e2e/elements/mapml-viewer/mapml-viewer-in-shadow-root.html b/test/e2e/elements/mapml-viewer/mapml-viewer-in-shadow-root.html new file mode 100644 index 000000000..9c480e9f0 --- /dev/null +++ b/test/e2e/elements/mapml-viewer/mapml-viewer-in-shadow-root.html @@ -0,0 +1,60 @@ + + + + mapml-viewer in shadow root test + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/e2e/elements/mapml-viewer/mapml-viewer-in-shadow-root.test.js b/test/e2e/elements/mapml-viewer/mapml-viewer-in-shadow-root.test.js new file mode 100644 index 000000000..8474dcf2c --- /dev/null +++ b/test/e2e/elements/mapml-viewer/mapml-viewer-in-shadow-root.test.js @@ -0,0 +1,117 @@ +import { test, expect, chromium } from '@playwright/test'; + +const playwright = require('playwright'); + +test.describe('Playwright mapml-viewer fullscreen tests', () => { + let context, page; + test.beforeAll(async () => { + context = await chromium.launchPersistentContext('', { + headless: false, + slowMo: 250 + }); + page = await context.newPage(); + }); + test.afterAll(async () => { + await context.close(); + }); + test.beforeEach(async () => { + await page.goto('mapml-viewer-in-shadow-root.html'); + }); + test('Fullscreen button makes shadow DOM mapml-viewer element the fullscreen element', async () => { + const map1 = page.getByTestId('map1'); + const fullscreenButton = map1.getByTitle(/(View)|(Exit) Fullscreen/i); + await fullscreenButton.click(); + + let fullscreenElement = await map1.evaluate( + (m) => M.getClosest(m._map.getContainer(), ':fullscreen').id + ); + // the first mapml-viewer should be returned by document.fullscreen + expect(fullscreenElement).toEqual('map1'); + await fullscreenButton.click(); + fullscreenElement = await page.evaluate(`document.fullscreenElement`); + expect(fullscreenElement).toBeFalsy(); + + const map2 = page.getByTestId('map2'); + const fullscreenButton2 = map2.getByTitle(/(View)|(Exit) Fullscreen/i); + // do the same with second map / element + await fullscreenButton2.click(); + fullscreenElement = await map2.evaluate( + (m) => M.getClosest(m._map.getContainer(), ':fullscreen').id + ); + expect(fullscreenElement).toEqual('map2'); + try { + // try to click the fullscreen button of the other map that is not in fullscreen + await fullscreenButton.click({ timeout: 500 }); + } catch (e) { + if (e instanceof playwright.errors.TimeoutError) { + // all is well + } + } + // fullscreen element should not have changed + fullscreenElement = await map2.evaluate( + (m) => M.getClosest(m._map.getContainer(), ':fullscreen').id + ); + expect(fullscreenElement).toEqual('map2'); + await fullscreenButton2.click(); // exit fullscreen + }); + + test('Context Menu Fullscreen Button makes shadow DOM mapml-viewer element fullscreen', async () => { + const map2 = page.getByTestId('map2'); + await map2.click(); + await page.keyboard.press('Shift+F10'); + await page.keyboard.press('F'); + let fullscreenElement = await map2.evaluate( + (m) => M.getClosest(m._map.getContainer(), ':fullscreen').id + ); + expect(fullscreenElement).toEqual('map2'); + await map2.click(); + await page.keyboard.press('Shift+F10'); + await page.keyboard.press('F'); // exit fullscreen + fullscreenElement = await page.evaluate(`document.fullscreenElement`); + expect(fullscreenElement).toBeFalsy(); + }); + test('Fullscreen button makes light DOM mapml-viewer element the fullscreen element', async () => { + const map3 = page.getByTestId('map3'); + const fullscreenButton = map3.getByTitle(/(View)|(Exit) Fullscreen/i); + await fullscreenButton.click(); + + let fullscreenElement = await page.evaluate( + `document.fullscreenElement.id` + ); + // the first mapml-viewer should be returned by document.fullscreen + expect(fullscreenElement).toEqual('map3'); + + const map2 = page.getByTestId('map2'); + const fullscreenButton2 = map2.getByTitle(/(View)|(Exit) Fullscreen/i); + + try { + // try to click the fullscreen button of the other map that is not in fullscreen + await fullscreenButton2.click({ timeout: 500 }); + } catch (e) { + if (e instanceof playwright.errors.TimeoutError) { + // all is well + } + } + // fullscreen element should not have changed + fullscreenElement = await page.evaluate(`document.fullscreenElement.id`); + expect(fullscreenElement).toEqual('map3'); + await fullscreenButton.click(); // exit fullscreen + fullscreenElement = await page.evaluate(`document.fullscreenElement`); + expect(fullscreenElement).toBeFalsy(); + }); + test('Context Menu Fullscreen Button light DOM mapml-viewer element fullscreen', async () => { + const map3 = page.getByTestId('map3'); + await map3.click(); + await page.keyboard.press('Shift+F10'); + await page.keyboard.press('F'); + let fullscreenElement = await map3.evaluate( + (m) => M.getClosest(m._map.getContainer(), ':fullscreen').id + ); + expect(fullscreenElement).toEqual('map3'); + await map3.click(); + await page.keyboard.press('Shift+F10'); + await page.keyboard.press('F'); // exit fullscreen + fullscreenElement = await page.evaluate(`document.fullscreenElement`); + expect(fullscreenElement).toBeFalsy(); + }); +}); diff --git a/test/server.js b/test/server.js index 6771c125d..c6c005078 100644 --- a/test/server.js +++ b/test/server.js @@ -7,7 +7,10 @@ const port = 30001; app.use(express.static(path.join(__dirname, '../dist'))); app.use(express.static(path.join(__dirname, 'e2e/core'))); app.use(express.static(path.join(__dirname, 'e2e/elements/map-extent'))); +app.use(express.static(path.join(__dirname, 'e2e/elements/mapml-viewer'))); +app.use(express.static(path.join(__dirname, 'e2e/elements/map'))); app.use(express.static(path.join(__dirname, 'e2e/elements/map-feature'))); +app.use(express.static(path.join(__dirname, 'e2e/elements/map-input'))); app.use(express.static(path.join(__dirname, 'e2e/elements/map-link'))); app.use(express.static(path.join(__dirname, 'e2e/elements/layer-'))); app.use(express.static(path.join(__dirname, 'e2e/api')));