diff --git a/src/mapml.css b/src/mapml.css index b6a92bf44..10f2139c5 100644 --- a/src/mapml.css +++ b/src/mapml.css @@ -95,6 +95,19 @@ the browser)' */ color: revert; } +.mapml-layer-item-legend-link { + display: inline-block; + margin-block-start: .25rem; +} + +.mapml-layer-item-legend-image { + display: block; + max-width: min(100%, 16rem); + height: auto; + border: 1px solid #e3e3e3; + border-radius: 2px; +} + .leaflet-top .leaflet-control { margin-top: 5px; } @@ -801,7 +814,7 @@ label.mapml-layer-item-toggle { padding-block-start: .25rem; padding-block-end: .25rem; padding-inline-start: .25rem; - padding-inline-end: 1rem; + display: inline-block; } .mapml-layer-item-settings > * { diff --git a/src/mapml/elementSupport/layers/createLayerControlForLayer.js b/src/mapml/elementSupport/layers/createLayerControlForLayer.js index 494a19a46..6f7bc572e 100644 --- a/src/mapml/elementSupport/layers/createLayerControlForLayer.js +++ b/src/mapml/elementSupport/layers/createLayerControlForLayer.js @@ -139,12 +139,38 @@ export var createLayerControlHTML = async function () { }; input.addEventListener('change', changeCheck.bind(this)); if (this._layer._legendUrl) { - var legendLink = document.createElement('a'); - legendLink.text = ' ' + this._layer._title; + layerItemName.innerText = this._layer._title; + + let legendControl = DomUtil.create( + 'details', + 'mapml-layer-item-legend mapml-control-layers', + layerItemSettings + ), + legendSummary = DomUtil.create('summary'), + legendLink = document.createElement('a'), + legendImage = document.createElement('img'); + + legendSummary.innerText = mapEl.locale.lmLegend; + legendControl.appendChild(legendSummary); + legendLink.href = this._layer._legendUrl; legendLink.target = '_blank'; + legendLink.rel = 'noopener noreferrer'; legendLink.draggable = false; - layerItemName.appendChild(legendLink); + legendLink.className = 'mapml-layer-item-legend-link'; + + legendImage.src = this._layer._legendUrl; + legendImage.alt = `${this._layer._title} legend`; + legendImage.loading = 'lazy'; + legendImage.decoding = 'async'; + legendImage.className = 'mapml-layer-item-legend-image'; + legendImage.addEventListener('error', () => { + legendLink.textContent = mapEl.locale.lmOpenInNewTab; + legendImage.remove(); + }); + + legendLink.appendChild(legendImage); + legendControl.appendChild(legendLink); } else { layerItemName.innerHTML = this._layer._title; } diff --git a/test/e2e/layers/layerLegend.html b/test/e2e/layers/layerLegend.html new file mode 100644 index 000000000..ea3425f1d --- /dev/null +++ b/test/e2e/layers/layerLegend.html @@ -0,0 +1,60 @@ + + + + + Layer Legend Control Tests + + + + + + + + + + + + + + + + + + + + + + + + -75.697193 45.421530 + + + + + + + diff --git a/test/e2e/layers/layerLegend.test.js b/test/e2e/layers/layerLegend.test.js new file mode 100644 index 000000000..6339da207 --- /dev/null +++ b/test/e2e/layers/layerLegend.test.js @@ -0,0 +1,93 @@ +import { test, expect, chromium } from '@playwright/test'; + +test.describe('Layer legend tests', () => { + let page; + let context; + + test.beforeAll(async () => { + context = await chromium.launchPersistentContext('', { slowMo: 250 }); + page = await context.newPage(); + await page.goto('layerLegend.html'); + await page.locator('mapml-viewer').hover(); + }); + + test.afterAll(async () => { + await context.close(); + }); + + test('Legend layer has legend details in layer settings', async () => { + // Get the first layer in the overlay list (the one with an img legend) + const layer = page + .locator('.leaflet-control-layers-overlays > fieldset') + .first(); + + // Open the settings for that layer and check that the legend details are present + const settings = layer.locator('.mapml-layer-item-settings'); + + // Check that the legend details, name, and link are present and correct + const legendDetails = settings.locator('details.mapml-layer-item-legend'); + await expect(legendDetails).toHaveCount(1); + await expect(legendDetails.locator('summary')).toHaveText('Legend'); + await expect( + legendDetails.locator('a.mapml-layer-item-legend-link') + ).toHaveAttribute( + 'href', + 'http://maps.geogratis.gc.ca/wms/toporama_en?SERVICE=WMS&REQUEST=GetLegendGraphic&LAYER=WMS-Toporama&VERSION=1.1&FORMAT=image/png' + ); + await expect( + legendDetails.locator('img.mapml-layer-item-legend-image') + ).toHaveAttribute( + 'src', + 'http://maps.geogratis.gc.ca/wms/toporama_en?SERVICE=WMS&REQUEST=GetLegendGraphic&LAYER=WMS-Toporama&VERSION=1.1&FORMAT=image/png' + ); + + // check that the legend details are the second details element in the settings + const secondDetails = settings.locator('> details').nth(1); + await expect(secondDetails).toHaveClass( + 'mapml-layer-item-legend mapml-control-layers' + ); + }); + + test('Layer without legend does not render legend details in settings', async () => { + // Get the second layer in the overlay list (the one without a legend) + const layer = page + .locator('.leaflet-control-layers-overlays > fieldset') + .nth(1); + + // check that the settings for that layer do not contain any legend details + const settings = layer.locator('.mapml-layer-item-settings'); + await expect( + settings.locator('details.mapml-layer-item-legend') + ).toHaveCount(0); + }); + + test('Layer with a non img legend renders a legend link', async () => { + // Get the third layer in the overlay list (the one with a non img legend) + const layer = page + .locator('.leaflet-control-layers-overlays > fieldset') + .nth(2); + + // check that the settings for that layer contain a legend link + const settings = layer.locator('.mapml-layer-item-settings'); + + // Check that the legend details, name, and link are present and correct + const legendDetails = settings.locator('details.mapml-layer-item-legend'); + await expect(legendDetails).toHaveCount(1); + await expect(legendDetails.locator('summary')).toHaveText('Legend'); + await expect( + legendDetails.locator('a.mapml-layer-item-legend-link') + ).toHaveAttribute('href', 'https://maps4html.org/web-map-doc/'); + + // not working, need to manually open the legend for it to update. + /*await expect( + legendDetails.locator('a.mapml-layer-item-legend-link') + ).toHaveText("Open Legend"); + */ + + // check that the legend details are the second details element in the settings + const secondDetails = settings.locator('> details').nth(1); + await expect(secondDetails).toHaveClass( + 'mapml-layer-item-legend mapml-control-layers' + ); + }); +});