diff --git a/src/map-extent.js b/src/map-extent.js index fea5d50b5..303be55cf 100644 --- a/src/map-extent.js +++ b/src/map-extent.js @@ -262,6 +262,11 @@ export class MapExtent extends HTMLElement { // this._layerControlHTML is the fieldset for the extent in the LayerControl this._layerControlHTML = this._createLayerControlExtentHTML(); this._calculateBounds(); + // instead of children using parents' whenReady which can be cyclic, + // when this element is ready, run stuff that is only available after + // initialization + this._runMutationObserver(this.children); + // make sure same thing happens when children are added this._bindMutationObserver(); } /* @@ -290,6 +295,16 @@ export class MapExtent extends HTMLElement { this._validateDisabled(); }); }; + const _addStylesheetLink = (mapLink) => { + this.whenReady().then(() => { + this._extentLayer.appendStyleLink(mapLink); + }); + }; + const _addStyleElement = (mapStyle) => { + this.whenReady().then(() => { + this._extentLayer.appendStyleElement(mapStyle); + }); + }; for (let i = 0; i < elementsGroup.length; ++i) { let element = elementsGroup[i]; switch (element.nodeName) { @@ -303,7 +318,14 @@ export class MapExtent extends HTMLElement { } break; case 'MAP-LINK': - // might need this in future, among others + if (element.link && !element.link.isConnected) + _addStylesheetLink(element); + break; + case 'MAP-STYLE': + if (element.styleElement && !element.styleElement.isConnected) { + _addStyleElement(element); + } + break; default: break; } diff --git a/src/map-link.js b/src/map-link.js index 73baadbc4..850302da9 100644 --- a/src/map-link.js +++ b/src/map-link.js @@ -316,6 +316,8 @@ export class MapLink extends HTMLElement { this._stylesheetHost._layer.appendStyleLink(this); } else if (this._stylesheetHost._templatedLayer) { this._stylesheetHost._templatedLayer.appendStyleLink(this); + } else if (this._stylesheetHost._extentLayer) { + this._stylesheetHost._extentLayer.appendStyleLink(this); } function copyAttributes(source, target) { diff --git a/src/map-style.js b/src/map-style.js index 3d8297571..6c050434a 100644 --- a/src/map-style.js +++ b/src/map-style.js @@ -27,11 +27,12 @@ export class MapStyle extends HTMLElement { this.styleElement.mapStyle = this; this.styleElement.textContent = this.textContent; copyAttributes(this, this.styleElement); - if (this._stylesheetHost._layer) { this._stylesheetHost._layer.appendStyleElement(this); } else if (this._stylesheetHost._templatedLayer) { this._stylesheetHost._templatedLayer.appendStyleElement(this); + } else if (this._stylesheetHost._extentLayer) { + this._stylesheetHost._extentLayer.appendStyleElement(this); } function copyAttributes(source, target) { diff --git a/src/mapml/layers/ExtentLayer.js b/src/mapml/layers/ExtentLayer.js index 9b275aff9..a97ae76dc 100644 --- a/src/mapml/layers/ExtentLayer.js +++ b/src/mapml/layers/ExtentLayer.js @@ -75,6 +75,31 @@ export var ExtentLayer = L.LayerGroup.extend({ this._extentEl._opacity = opacity; if (this._extentEl._opacitySlider) this._extentEl._opacitySlider.value = opacity; + }, + appendStyleLink: function (mapLink) { + if (!mapLink.link) return; + let positionAndNode = this._getStylePositionAndNode(); + positionAndNode.node.insertAdjacentElement( + positionAndNode.position, + mapLink.link + ); + }, + _getStylePositionAndNode: function () { + return this._container.lastChild && + (this._container.lastChild.nodeName.toUpperCase() === 'SVG' || + this._container.lastChild.classList.contains('mapml-vector-container')) + ? { position: 'beforebegin', node: this._container.lastChild } + : this._container.lastChild + ? { position: 'afterend', node: this._container.lastChild } + : { position: 'afterbegin', node: this._container }; + }, + appendStyleElement: function (mapStyle) { + if (!mapStyle.styleElement) return; + let positionAndNode = this._getStylePositionAndNode(); + positionAndNode.node.insertAdjacentElement( + positionAndNode.position, + mapStyle.styleElement + ); } }); export var extentLayer = function (options) { diff --git a/src/mapml/layers/TemplatedTileLayer.js b/src/mapml/layers/TemplatedTileLayer.js index 5b783d97b..db83346b6 100644 --- a/src/mapml/layers/TemplatedTileLayer.js +++ b/src/mapml/layers/TemplatedTileLayer.js @@ -196,14 +196,15 @@ export var TemplatedTileLayer = L.TileLayer.extend({ if (href) { if (!container.querySelector("link[href='" + href + "']")) { var linkElm = document.createElement('link'); + copyAttributes(stylesheets[i], linkElm); linkElm.setAttribute('href', href); - linkElm.setAttribute('rel', 'stylesheet'); ss.push(linkElm); } } } else { // var styleElm = document.createElement('style'); + copyAttributes(stylesheets[i], styleElm); styleElm.textContent = stylesheets[i].textContent; ss.push(styleElm); } @@ -215,6 +216,12 @@ export var TemplatedTileLayer = L.TileLayer.extend({ for (var s = ss.length - 1; s >= 0; s--) { container.insertAdjacentElement('afterbegin', ss[s]); } + function copyAttributes(source, target) { + return Array.from(source.attributes).forEach((attribute) => { + if (attribute.nodeName !== 'href') + target.setAttribute(attribute.nodeName, attribute.nodeValue); + }); + } }, _createFeatures: function (markup, coords, tile) { diff --git a/test/e2e/core/ArrowKeyNavContextMenu.test.js b/test/e2e/core/ArrowKeyNavContextMenu.test.js index ae89d7c6e..e57a7397e 100644 --- a/test/e2e/core/ArrowKeyNavContextMenu.test.js +++ b/test/e2e/core/ArrowKeyNavContextMenu.test.js @@ -4,7 +4,7 @@ test.describe('Using arrow keys to navigate context menu', () => { let page; let context; test.beforeAll(async function () { - context = await chromium.launchPersistentContext(''); + context = await chromium.launchPersistentContext('', { slowMo: 250 }); page = context.pages().find((page) => page.url() === 'about:blank') || (await context.newPage()); diff --git a/test/e2e/core/styleParsing.html b/test/e2e/core/styleParsing.html index 1d4946ad7..a33ec678e 100644 --- a/test/e2e/core/styleParsing.html +++ b/test/e2e/core/styleParsing.html @@ -22,19 +22,19 @@ - + - + - + .second { stroke: aqua; } - + .third { stroke: blue } @@ -88,7 +88,7 @@

Colorado

- +
- - - .secondVector { + + .firstVector { color: aliceblue; } + - - + + diff --git a/test/e2e/core/styleParsing.test.js b/test/e2e/core/styleParsing.test.js index aec54de50..c94847949 100644 --- a/test/e2e/core/styleParsing.test.js +++ b/test/e2e/core/styleParsing.test.js @@ -1,6 +1,6 @@ import { test, expect, chromium } from '@playwright/test'; -test.describe('Style Parsed and Implemented Test', () => { +test.describe('map-style and map-link[rel=stylesheet] tests', () => { let page; let context; test.beforeAll(async () => { @@ -12,95 +12,190 @@ test.describe('Style Parsed and Implemented Test', () => { await page.waitForTimeout(250); }); - test.beforeEach(async () => { - await page.waitForTimeout(250); - }); - test.afterAll(async function () { await context.close(); }); //tests using the 1st map in the page - test('CSS within html page added to inorder to overlay-pane container', async () => { - const styleContent = await page.$eval( - 'css=div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-overlay-pane > div', - (styleE) => styleE.innerHTML - ); - await expect(styleContent.indexOf('first')).toBeLessThan( - styleContent.indexOf('.second') - ); - await expect(styleContent.indexOf('.second')).toBeLessThan( - styleContent.indexOf('.third') - ); - await expect(styleContent.indexOf('.third')).toBeLessThan( - styleContent.indexOf('fourth') - ); + test(`Local-content (no src) styles rendered as style/link in same order as \ +found, in expected shadow root location`, async () => { + const localContentLayer = page.getByTestId('arizona'); + const ids = await localContentLayer.evaluate((layer) => { + const elementSequence = layer.querySelectorAll( + 'map-link[rel=stylesheet],map-style' + ); + let ids = ''; + for (let i = 0; i < elementSequence.length; i++) + ids += + elementSequence[i].getAttribute('id') + + (i < elementSequence.length - 1 ? ',' : ''); + return ids; + }); + const renderedIds = await localContentLayer.evaluate((layer) => { + const elementSequence = layer._layer._container.querySelectorAll( + 'link[rel=stylesheet],style' + ); + let ids = ''; + for (let i = 0; i < elementSequence.length; i++) + ids += + elementSequence[i].getAttribute('id') + + (i < elementSequence.length - 1 ? ',' : ''); + return ids; + }); + expect(ids).toEqual(renderedIds); }); - test('CSS from a retrieved MapML file added inorder inside templated-layer container', async () => { - const firstStyle = await page.$eval( - // this div * - 'css=div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-overlay-pane > div:nth-child(2) > div.leaflet-layer.mapml-extentlayer-container > .mapml-features-container.mapml-vector-container > link:nth-child(1)', - // no longer exists, (the svg is now the child of the first div) - // but the link isn't created either, and it should be - (styleE) => styleE.outerHTML - ); - const secondStyle = await page.$eval( - 'css=div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-overlay-pane > div:nth-child(2) > div.leaflet-layer.mapml-extentlayer-container > .mapml-features-container.mapml-vector-container > link:nth-child(2)', - (styleL) => styleL.outerHTML - ); - await expect(firstStyle).toMatch('canvec_cantopo'); - await expect(secondStyle).toMatch('canvec_feature'); + test(`Local layer content (no src) map-link[rel=features] remote styles \ +rendered as style/link in same order as found, in expected shadow root location`, async () => { + const featuresLink = page.getByTestId('alabama-features'); + const ids = await featuresLink.evaluate((link) => { + const elementSequence = link.shadowRoot.querySelectorAll( + 'map-link[rel=stylesheet],map-style' + ); + let ids = ''; + for (let i = 0; i < elementSequence.length; i++) + ids += + elementSequence[i].getAttribute('id') + + (i < elementSequence.length - 1 ? ',' : ''); + return ids; + }); + const renderedIds = await featuresLink.evaluate((link) => { + const elementSequence = link._templatedLayer._container.querySelectorAll( + 'link[rel=stylesheet],style' + ); + let ids = ''; + for (let i = 0; i < elementSequence.length; i++) + ids += + elementSequence[i].getAttribute('id') + + (i < elementSequence.length - 1 ? ',' : ''); + return ids; + }); + expect(ids).toEqual(renderedIds); }); - test('CSS within html page added to overlay-pane container', async () => { - const map1 = await page.getByTestId('map1'); - const foundStyleLinks = await map1.locator('#first'); - const foundStyleTags = await map1.locator( - '.mapml-layer:nth-child(1) > style' - ); - // expect the map-link#first to have been reflected as a into the shadow root - expect(foundStyleLinks).toHaveCount(2); - expect(foundStyleTags).toHaveCount(2); + test(`Local style children of map-extent rendered as style/link in same order \ +as found, in expected shadow root location`, async () => { + const mapExtent = page.getByTestId('map-ext1'); + const ids = await mapExtent.evaluate((e) => { + const elementSequence = e.querySelectorAll( + 'map-link[rel=stylesheet],map-style' + ); + let ids = ''; + for (let i = 0; i < elementSequence.length; i++) + ids += + elementSequence[i].getAttribute('id') + + (i < elementSequence.length - 1 ? ',' : ''); + return ids; + }); + const renderedIds = await mapExtent.evaluate((e) => { + const elementSequence = e._extentLayer._container.querySelectorAll( + ':scope > link[rel=stylesheet],:scope > style' + ); + let ids = ''; + for (let i = 0; i < elementSequence.length; i++) + ids += + elementSequence[i].getAttribute('id') + + (i < elementSequence.length - 1 ? ',' : ''); + return ids; + }); + expect(ids).toEqual(renderedIds); }); - - test('CSS from a retrieved MapML File added to templated-layer container', async () => { - const foundStyleLinkOne = await page.$( - 'css=div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-overlay-pane > div:nth-child(2) > div.leaflet-layer.mapml-extentlayer-container > .mapml-features-container.mapml-vector-container > link' - ); - const foundStyleLinkTwo = await page.$( - 'css=div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-overlay-pane > div:nth-child(2) > div.leaflet-layer.mapml-extentlayer-container > .mapml-features-container.mapml-vector-container > link:nth-child(2)' - ); - await expect(foundStyleLinkOne).toBeTruthy(); - await expect(foundStyleLinkTwo).toBeTruthy(); + test(`Local content map-link[rel=tile][type=text/mapml] with remote styles \ +embedded in text/mapml tiles are rendered in same order as found in tile`, async () => { + // it's a bit tricky to work with tiled mapml vectors because the map-feature + // is discarded and only the rendered path is kept. In this case, we're using + // the same layer twice in the styleParsing.html (vector-tile-test.mapml), + // which refers to the WGS84 countries' test tile data + // as a result, we get the same feature rendered whenever we add the layer + // in this case it is used inline and remotely. The inline countries are + // first in DOM order, so we'll take the first location of the rendered thing + // we're looking for (in this test, at least) + const renderedPath = await page.getByTestId('r0_c0').first(); + const renderedStyleIds = await renderedPath.evaluate((p) => { + const elementSequence = p + .closest('.mapml-tile-group.leaflet-tile') + .querySelectorAll(':scope > link[rel=stylesheet],:scope > style'); + let ids = ''; + for (let i = 0; i < elementSequence.length; i++) + ids += + elementSequence[i].getAttribute('id') + + (i < elementSequence.length - 1 ? ',' : ''); + return ids; + }); + expect(renderedStyleIds).toEqual('one,two,three'); }); - - //testing done on 2nd map in the page - test('CSS from a retrieved MapML file added inorder inside svg within templated-tile-container', async () => { - const firstStyle = await page.$eval( - 'xpath=//html/body/map[2]/div >> css=div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-overlay-pane > div > div.leaflet-layer.mapml-extentlayer-container .mapml-tile-group > style:nth-child(1)', - (styleE) => styleE.innerHTML - ); - const secondStyle = await page.$eval( - 'xpath=//html/body/map[2]/div >> css=div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-overlay-pane > div > div.leaflet-layer.mapml-extentlayer-container .mapml-tile-group > style:nth-child(2)', - (styleE) => styleE.innerHTML - ); - const foundStyleLink = await page.$( - 'xpath=//html/body/map[2]/div >> css=div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-overlay-pane > div > div.leaflet-layer.mapml-extentlayer-container .mapml-tile-group > link' - ); - await expect(firstStyle).toMatch('refStyleOne'); - await expect(secondStyle).toMatch('refStyleTwo'); - await expect(foundStyleLink).toBeTruthy(); + test(`Remote styles (layer- src) in the map-head rendered in the same source order\ +as children of layer-._layer._container`, async () => { + const remoteLayer = page.getByTestId('remote'); + const ids = await remoteLayer.evaluate((l) => { + const elementSequence = l.shadowRoot.querySelectorAll( + ':host > map-style,:host > [rel=stylesheet]' + ); + let ids = ''; + for (let i = 0; i < elementSequence.length; i++) + ids += + elementSequence[i].getAttribute('id') + + (i < elementSequence.length - 1 ? ',' : ''); + return ids; + }); + const renderedIds = await remoteLayer.evaluate((l) => { + const elementSequence = l._layer._container.querySelectorAll( + ':scope > link[rel=stylesheet],:scope > style' + ); + let ids = ''; + for (let i = 0; i < elementSequence.length; i++) + ids += + elementSequence[i].getAttribute('id') + + (i < elementSequence.length - 1 ? ',' : ''); + return ids; + }); + expect(renderedIds).toEqual(ids); }); - - test('CSS within html page added inorder to overlay-pane container', async () => { - const foundStyleLink = await page.$( - 'xpath=//html/body/map[2]/div >> css=div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-overlay-pane > div > link' - ); - const foundStyleTag = await page.$( - 'xpath=//html/body/map[2]/div >> css=div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-overlay-pane > div > style' - ); - await expect(foundStyleTag).toBeTruthy(); - await expect(foundStyleLink).toBeTruthy(); + test(`Remote (layer- src) styles in the remote map-extent should be rendered \ +in the same order as in remote source, in the map-extent._extentLayer._container`, async () => { + const remoteLayer = page.getByTestId('remote'); + const ids = await remoteLayer.evaluate((l) => { + const elementSequence = l.shadowRoot.querySelectorAll( + ':host > map-extent > map-style,:host > map-extent > [rel=stylesheet]' + ); + let ids = ''; + for (let i = 0; i < elementSequence.length; i++) + ids += + elementSequence[i].getAttribute('id') + + (i < elementSequence.length - 1 ? ',' : ''); + return ids; + }); + const renderedIds = await remoteLayer.evaluate((l) => { + const e = l.shadowRoot.querySelector('map-extent'); + const elementSequence = e._extentLayer._container.querySelectorAll( + ':scope > link[rel=stylesheet],:scope > style' + ); + let ids = ''; + for (let i = 0; i < elementSequence.length; i++) + ids += + elementSequence[i].getAttribute('id') + + (i < elementSequence.length - 1 ? ',' : ''); + return ids; + }); + expect(renderedIds).toEqual(ids); + }); + test(`Remote (layer- src) styles embedded within loaded tiles should be \ +rendered in the same order as in the tile, in the tile container`, async () => { + // there's more than one use of vector-tile-test.mapml here, so the test id + // is not unique. this is the second and last occurence of it though: + const renderedPath = await page.getByTestId('r0_c0').last(); + const renderedStyleIds = await renderedPath.evaluate((p) => { + const elementSequence = p + .closest('.mapml-tile-group.leaflet-tile') + .querySelectorAll(':scope > link[rel=stylesheet],:scope > style'); + let ids = ''; + for (let i = 0; i < elementSequence.length; i++) + ids += + elementSequence[i].getAttribute('id') + + (i < elementSequence.length - 1 ? ',' : ''); + return ids; + }); + // see e2e/data/tiles/wgs84/0/r0_c0.mapml for original order, it's one,two,three... + expect(renderedStyleIds).toEqual('one,two,three'); }); }); diff --git a/test/e2e/data/tiles/cbmt/alabama_feature.mapml b/test/e2e/data/tiles/cbmt/alabama_feature.mapml index 6576b9bb6..144959502 100644 --- a/test/e2e/data/tiles/cbmt/alabama_feature.mapml +++ b/test/e2e/data/tiles/cbmt/alabama_feature.mapml @@ -2,9 +2,12 @@ Natural Resources Canada's CanVec+ 031G - Map Markup Language - - - + + + + .all { fill-opacity: 0.7; stroke-width: 2; stroke: white; stroke-opacity: 1; stroke-dasharray: 3; } diff --git a/test/e2e/data/tiles/wgs84/0/r0_c0.mapml b/test/e2e/data/tiles/wgs84/0/r0_c0.mapml index 5b396c6c2..77c95951e 100644 --- a/test/e2e/data/tiles/wgs84/0/r0_c0.mapml +++ b/test/e2e/data/tiles/wgs84/0/r0_c0.mapml @@ -4,23 +4,32 @@ - + .refStyleOne{ color:black; } - + .refStyleTwo{ color:black; } - + - + + + + + -163.4686870536532 -83.0479765610694 -162.1722212049075 -83.1578465482512 -161.8082768723677 -83.0067753158762 -162.4688701702985 -82.8776780809375 -163.711774400293 -82.8405969602637 -163.4686870536532 -83.0479765610694 diff --git a/test/e2e/data/tiles/wgs84/vector-tile-test.mapml b/test/e2e/data/tiles/wgs84/vector-tile-test.mapml index 25cecf69a..1e49796e3 100644 --- a/test/e2e/data/tiles/wgs84/vector-tile-test.mapml +++ b/test/e2e/data/tiles/wgs84/vector-tile-test.mapml @@ -4,21 +4,26 @@ - - - + + .secondLayerVector{ color:black; } + + +