diff --git a/src/map-feature.js b/src/map-feature.js index 4bd3425b6..18b0633d4 100644 --- a/src/map-feature.js +++ b/src/map-feature.js @@ -280,7 +280,8 @@ export class MapFeature extends HTMLElement { if (!this.querySelector('map-geometry')) return; let fallbackCS = this._getFallbackCS(); let content = parentLayer.src ? parentLayer.shadowRoot : parentLayer; - this._geometry = layerToAddTo.createGeometry(this, fallbackCS); // side effect: extends `this` with this._groupEl, points to svg g element that renders to map SD + this._geometry = layerToAddTo.createGeometry(this, fallbackCS); // side effect: extends `this` with this._groupEl if successful, points to svg g element that renders to map SD + if (!this._geometry) return; layerToAddTo.addLayer(this._geometry); this._setUpEvents(); } diff --git a/src/mapml/layers/FeatureLayer.js b/src/mapml/layers/FeatureLayer.js index fad10fadb..b55a28f47 100644 --- a/src/mapml/layers/FeatureLayer.js +++ b/src/mapml/layers/FeatureLayer.js @@ -427,7 +427,7 @@ export var FeatureLayer = L.FeatureGroup.extend({ options.zoomBounds = feature.extent.zoom; } let geometry = this._geometryToLayer(feature, options, cs, +zoom, title); - if (geometry) { + if (geometry && Object.keys(geometry._layers).length !== 0) { // if the layer is being used as a query handler output, it will have // a color option set. Otherwise, copy classes from the feature if (!geometry.options.color && feature.hasAttribute('class')) { diff --git a/test/e2e/core/popupTabNavigation.html b/test/e2e/core/popupTabNavigation.html index 88fe9cdd5..7d7a8f02d 100644 --- a/test/e2e/core/popupTabNavigation.html +++ b/test/e2e/core/popupTabNavigation.html @@ -9,9 +9,9 @@ - - + - + diff --git a/test/e2e/core/popupTabNavigation.test.js b/test/e2e/core/popupTabNavigation.test.js index 57f5e552d..ce1d74e97 100644 --- a/test/e2e/core/popupTabNavigation.test.js +++ b/test/e2e/core/popupTabNavigation.test.js @@ -17,91 +17,84 @@ test.describe('Playwright Keyboard Navigation + Query Layer Tests', () => { test.describe('Feature Popup Tab Navigation Tests', () => { test('Inline features popup focus order', async () => { - await page.evaluateHandle(() => - document.getElementById('vector').removeAttribute('checked') - ); - await page.evaluateHandle(() => - document.getElementById('query').removeAttribute('checked') - ); - const body = page.locator('body'); - await body.click(); + const vectorLayer = page.getByTestId('vector'); + await vectorLayer.evaluate((l) => { + l.removeAttribute('checked'); + }); + const queryLayer = page.getByTestId('query'); + await queryLayer.evaluate((l) => { + l.removeAttribute('checked'); + }); + const viewer = page.locator('mapml-viewer'); + await viewer.focus(); await page.keyboard.press('Tab'); // focus map await page.keyboard.press('Tab'); // focus feature await page.keyboard.press('Enter'); // display popup with link in it - const viewer = page.locator('mapml-viewer'); - let f = await viewer.evaluate( - (viewer) => viewer.shadowRoot.activeElement.className - ); - expect(f).toEqual('mapml-popup-content'); - + const popupContainer = page.locator('.leaflet-popup'); + const popup = popupContainer.locator('.mapml-popup-content'); + await expect(popup).toBeFocused(); await page.keyboard.press('Tab'); // focus link - let f2 = await viewer.evaluate( - (viewer) => viewer.shadowRoot.activeElement.tagName - ); - expect(f2.toUpperCase()).toEqual('A'); + // there are actually 2 copies of the testid, so we scope relative to the popup + const anchor = popupContainer.getByTestId('anchor'); + await expect(anchor).toBeFocused(); await page.keyboard.press('Tab'); // focus on "zoom to here" link - let f3 = await viewer.evaluate( - (viewer) => viewer.shadowRoot.activeElement.tagName - ); - expect(f3.toUpperCase()).toEqual('A'); + const zoomToHereLink = popupContainer.getByText('Zoom to here'); + await expect(zoomToHereLink).toBeFocused(); await page.keyboard.press('Tab'); // focus on |< affordance - let f4 = await viewer.evaluate( - (viewer) => viewer.shadowRoot.activeElement.title - ); - expect(f4).toEqual('Focus Map'); + const focusMapButton = popupContainer.getByRole('button', { + name: 'Focus Map' + }); + await expect(focusMapButton).toBeFocused(); await page.keyboard.press('Tab'); // focus on < affordance - let f5 = await viewer.evaluate( - (viewer) => viewer.shadowRoot.activeElement.title - ); - expect(f5).toEqual('Previous Feature'); + const previousFeatureButton = popupContainer.getByRole('button', { + name: 'Previous Feature' + }); + await expect(previousFeatureButton).toBeFocused(); await page.keyboard.press('Tab'); // focus on > affordance - let f6 = await viewer.evaluate( - (viewer) => viewer.shadowRoot.activeElement.title - ); - expect(f6).toEqual('Next Feature'); + const nextFeatureButton = popupContainer.getByRole('button', { + name: 'Next Feature' + }); + await expect(nextFeatureButton).toBeFocused(); await page.keyboard.press('Tab'); // focus on >| affordance - let f7 = await viewer.evaluate( - (viewer) => viewer.shadowRoot.activeElement.title - ); - expect(f7).toEqual('Focus Controls'); + const focusControlsButton = popupContainer.getByRole('button', { + name: 'Focus Controls' + }); + await expect(focusControlsButton).toBeFocused(); await page.keyboard.press('Tab'); // focus on X dismiss popup affordance - let f8 = await viewer.evaluate( - (viewer) => viewer.shadowRoot.activeElement.className - ); - expect(f8).toEqual('leaflet-popup-close-button'); + const dismissButton = popupContainer.getByRole('button', { + name: 'Close popup' + }); + await expect(dismissButton).toBeFocused(); }); test('Tab to next feature after tabbing out of popup', async () => { + const viewer = page.getByTestId('viewer'); await page.keyboard.press('Escape'); // focus back on feature - const h = await page.evaluateHandle(() => - document.querySelector('mapml-viewer') + // according to https://github.com/microsoft/playwright/issues/15929 + // tests should locate the element and then check that it is focused + const bigSquare = viewer.getByTestId('big-square'); + const bigSquarePathData = await bigSquare.evaluate((f) => { + return f._groupEl.firstElementChild.getAttribute('d'); + }); + // no way to get a locator from another locator afaik, but this may work: + const bigSquareGroupEl = viewer.locator( + 'g:has( > path[d="' + bigSquarePathData + '"])' ); - const nh = await page.evaluateHandle((doc) => doc.shadowRoot, h); - const rh = await page.evaluateHandle( - (root) => root.activeElement.querySelector('.leaflet-interactive'), - nh - ); - const f = await ( - await page.evaluateHandle((elem) => elem.getAttribute('d'), rh) - ).jsonValue(); - expect(f).toEqual('M330 83L586 83L586 339L330 339z'); - await page.waitForTimeout(500); + await expect(bigSquareGroupEl).toBeFocused(); // that we have to do this to get the tooltip back is a bug #681 await page.keyboard.press('Shift+Tab'); await page.keyboard.press('Tab'); - - let tooltipCount = await page.$eval( - 'mapml-viewer .leaflet-tooltip-pane', + const toolTipPane = viewer.locator('.leaflet-tooltip-pane'); + let tooltipCount = await toolTipPane.evaluate( (div) => div.childElementCount ); - expect(tooltipCount).toEqual(1); }); @@ -110,26 +103,20 @@ test.describe('Playwright Keyboard Navigation + Query Layer Tests', () => { await page.waitForTimeout(500); await page.keyboard.press('Shift+Tab'); await page.waitForTimeout(500); - - const h = await page.evaluateHandle(() => - document.querySelector('mapml-viewer') - ); - const nh = await page.evaluateHandle((doc) => doc.shadowRoot, h); - const rh = await page.evaluateHandle( - (root) => root.activeElement.querySelector('.leaflet-interactive'), - nh + const viewer = page.getByTestId('viewer'); + const bigSquare = viewer.getByTestId('big-square'); + const bigSquarePathData = await bigSquare.evaluate((f) => { + return f._groupEl.firstElementChild.getAttribute('d'); + }); + const bigSquareGroupEl = viewer.locator( + 'g:has( > path[d="' + bigSquarePathData + '"])' ); - const f = await ( - await page.evaluateHandle((elem) => elem.getAttribute('d'), rh) - ).jsonValue(); - - let tooltipCount = await page.$eval( - 'mapml-viewer .leaflet-tooltip-pane', + await expect(bigSquareGroupEl).toBeFocused(); + const toolTipPane = viewer.locator('.leaflet-tooltip-pane'); + let tooltipCount = await toolTipPane.evaluate( (div) => div.childElementCount ); - expect(tooltipCount).toEqual(1); - expect(f).toEqual('M330 83L586 83L586 339L330 339z'); }); test('Previous feature button focuses previous feature', async () => { @@ -145,25 +132,20 @@ test.describe('Playwright Keyboard Navigation + Query Layer Tests', () => { await page.waitForTimeout(500); await page.keyboard.press('Enter'); // focus should fall on previous feature await page.waitForTimeout(500); - const h = await page.evaluateHandle(() => - document.querySelector('mapml-viewer') - ); - const nh = await page.evaluateHandle((doc) => doc.shadowRoot, h); - const rh = await page.evaluateHandle( - (root) => root.activeElement.querySelector('.leaflet-interactive'), - nh + const viewer = page.getByTestId('viewer'); + const bigSquare = viewer.getByTestId('big-square'); + const bigSquarePathData = await bigSquare.evaluate((f) => { + return f._groupEl.firstElementChild.getAttribute('d'); + }); + const bigSquareGroupEl = viewer.locator( + 'g:has( > path[d="' + bigSquarePathData + '"])' ); - const f = await ( - await page.evaluateHandle((elem) => elem.getAttribute('d'), rh) - ).jsonValue(); - - let tooltipCount = await page.$eval( - 'mapml-viewer .leaflet-tooltip-pane', + await expect(bigSquareGroupEl).toBeFocused(); + const toolTipPane = viewer.locator('.leaflet-tooltip-pane'); + let tooltipCount = await toolTipPane.evaluate( (div) => div.childElementCount ); - expect(tooltipCount).toEqual(1); - expect(f).toEqual('M330 83L586 83L586 339L330 339z'); }); test('Tooltip appears after pressing esc key', async () => { @@ -172,25 +154,20 @@ test.describe('Playwright Keyboard Navigation + Query Layer Tests', () => { await page.keyboard.down('Escape'); // focus back on feature await page.keyboard.up('Escape'); await page.waitForTimeout(500); - - const h = await page.evaluateHandle(() => - document.querySelector('mapml-viewer') + const viewer = page.getByTestId('viewer'); + const bigSquare = viewer.getByTestId('big-square'); + const bigSquarePathData = await bigSquare.evaluate((f) => { + return f._groupEl.firstElementChild.getAttribute('d'); + }); + const bigSquareGroupEl = viewer.locator( + 'g:has( > path[d="' + bigSquarePathData + '"])' ); - const nh = await page.evaluateHandle((doc) => doc.shadowRoot, h); - const rh = await page.evaluateHandle( - (root) => root.activeElement.querySelector('.leaflet-interactive'), - nh - ); - const f = await ( - await page.evaluateHandle((elem) => elem.getAttribute('d'), rh) - ).jsonValue(); - - let tooltipCount = await page.$eval( - 'mapml-viewer .leaflet-tooltip-pane', + await expect(bigSquareGroupEl).toBeFocused(); + const toolTipPane = viewer.locator('.leaflet-tooltip-pane'); + let tooltipCount = await toolTipPane.evaluate( (div) => div.childElementCount ); expect(tooltipCount).toEqual(1); - expect(f).toEqual('M330 83L586 83L586 339L330 339z'); }); test('Tooltip appears after pressing enter on close button', async () => { @@ -205,25 +182,20 @@ test.describe('Playwright Keyboard Navigation + Query Layer Tests', () => { await page.keyboard.down('Enter'); // press x button await page.keyboard.up('Enter'); await page.waitForTimeout(500); - - const h = await page.evaluateHandle(() => - document.querySelector('mapml-viewer') - ); - const nh = await page.evaluateHandle((doc) => doc.shadowRoot, h); - const rh = await page.evaluateHandle( - (root) => root.activeElement.querySelector('.leaflet-interactive'), - nh + const viewer = page.getByTestId('viewer'); + const bigSquare = viewer.getByTestId('big-square'); + const bigSquarePathData = await bigSquare.evaluate((f) => { + return f._groupEl.firstElementChild.getAttribute('d'); + }); + const bigSquareGroupEl = viewer.locator( + 'g:has( > path[d="' + bigSquarePathData + '"])' ); - const f = await ( - await page.evaluateHandle((elem) => elem.getAttribute('d'), rh) - ).jsonValue(); - - let tooltipCount = await page.$eval( - 'mapml-viewer .leaflet-tooltip-pane', + await expect(bigSquareGroupEl).toBeFocused(); + const toolTipPane = viewer.locator('.leaflet-tooltip-pane'); + let tooltipCount = await toolTipPane.evaluate( (div) => div.childElementCount ); expect(tooltipCount).toEqual(1); - expect(f).toEqual('M330 83L586 83L586 339L330 339z'); }); test('Next feature button focuses next feature', async () => { @@ -240,25 +212,21 @@ test.describe('Playwright Keyboard Navigation + Query Layer Tests', () => { await page.keyboard.press('Tab'); // focus > affordance (next feature) await page.waitForTimeout(500); await page.keyboard.press('Enter'); // focus falls on next feature - const h = await page.evaluateHandle(() => - document.querySelector('mapml-viewer') + const viewer = page.getByTestId('viewer'); + const smallTrapezoid = viewer.getByTestId('small-trapezoid'); + const smallTrapezoidPathData = await smallTrapezoid.evaluate((f) => { + return f._groupEl.firstElementChild.getAttribute('d'); + }); + // no way to get a locator from another locator afaik, but this may work: + const smallTrapezoidGroupEl = viewer.locator( + 'g:has( > path[d="' + smallTrapezoidPathData + '"])' ); - const nh = await page.evaluateHandle((doc) => doc.shadowRoot, h); - const rh = await page.evaluateHandle( - (root) => root.activeElement.querySelector('.leaflet-interactive'), - nh - ); - const f = await ( - await page.evaluateHandle((elem) => elem.getAttribute('d'), rh) - ).jsonValue(); - - let tooltipCount = await page.$eval( - 'mapml-viewer .leaflet-tooltip-pane', + await expect(smallTrapezoidGroupEl).toBeFocused(); + const toolTipPane = viewer.locator('.leaflet-tooltip-pane'); + let tooltipCount = await toolTipPane.evaluate( (div) => div.childElementCount ); - expect(tooltipCount).toEqual(1); - expect(f).toEqual('M285 373L460 380L468 477L329 459z'); }); test('Focus Controls focuses the first