From 4bdfdea1fefd1f91a137888bc549b1076126d6ac Mon Sep 17 00:00:00 2001 From: Peter Rushforth Date: Tue, 27 Oct 2026 13:39:19 -0400 Subject: [PATCH 1/2] Prepare edits for 0.18.1 release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update release notes to latest Get rid of slop from instructions to the best of my ability Update nvm version -> 22 Update package-lock.json Fix floating-point precision in context menu tests Replace exact equality (.toEqual) with approximate comparison (.toBeCloseTo) for floating-point coordinate values. This resolves intermittent test failures caused by tiny precision differences that arise from: - Node.js version changes affecting V8 optimizations - Different floating-point rounding in coordinate transformations - Dependency version updates Using toBeCloseTo with 6 decimal places (±0.0000005) provides meaningful validation while tolerating expected numerical variance. This pattern is already used in other coordinate comparison tests like axisInferring.test.js. Fixes: Coordinate precision errors in 'Context menu, back item' and 'Context menu, forward item' tests. Upgrade playwright (and consequently chromium). Fix / update broken tests mostly due to changes in last digits of coordinates returned by browser, and a few flakes de-flake long standing flaky tests --- .nvmrc | 2 +- RELEASE-NOTES-0.18.0.md | 33 -- RELEASE-NOTES-0.18.1.md | 60 ++ how-to-release.md | 20 +- package-lock.json | 553 +++++++++++++----- package.json | 6 +- test/e2e/core/mapContextMenu.test.js | 114 ++-- test/e2e/core/mapElement.test.js | 23 +- test/e2e/core/mapFeature.test.js | 27 +- test/e2e/core/metaDefault.test.js | 20 +- test/e2e/core/popupTabNavigation.test.js | 7 + test/e2e/core/styleParsing.test.js | 47 +- test/e2e/elements/map-tile/map-tile.test.js | 36 +- test/e2e/layers/featureLayer.test.js | 26 +- test/e2e/layers/general/extentProperty.js | 20 +- test/e2e/layers/queryLink.test.js | 76 +-- test/e2e/mapml-viewer/localization.test.js | 15 + test/e2e/mapml-viewer/mapml-viewer.test.js | 88 ++- .../mapml-viewer/viewerContextMenu.test.js | 58 +- 19 files changed, 848 insertions(+), 383 deletions(-) delete mode 100644 RELEASE-NOTES-0.18.0.md create mode 100644 RELEASE-NOTES-0.18.1.md diff --git a/.nvmrc b/.nvmrc index 33db3f0ea..2bd5a0a98 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -15.11.0 +22 diff --git a/RELEASE-NOTES-0.18.0.md b/RELEASE-NOTES-0.18.0.md deleted file mode 100644 index c215ffa34..000000000 --- a/RELEASE-NOTES-0.18.0.md +++ /dev/null @@ -1,33 +0,0 @@ -# Release Notes — v0.18.0 - -_Changes since [v0.17.0](https://github.com/Maps4HTML/MapML.js/releases/tag/v0.17.0)._ - -## Features -- Implement the search control UI, including a custom search handler with a - geonames.org example, localization for search messages, non-Latin character - search support, and multiple search-link handling. The search panel grabs - focus on mouse enter and stays open until dismissed. -- Add `HTMLMapmlViewerElement.zoomToExtent(xmin, ymin, xmax, ymax)` to support - custom search handler development. - -## Fixes -- Harden pasted-content handling -- Add missing UK locale keys. - -## Build / Tooling -- Update Node to v22 LTS, add Playwright browser caching, prevent `npm ci` from - installing browsers, and ignore postinstall scripts. - -## Other -- Make zoom/reload control icons bolder to match the search icon; update - screenshots accordingly. - ---- - -### Commits -- Harden pasted-content handling; add missing UK locale keys (`4f191331`) -- Fix search control placement when added via `controlslist="search"` after map - creation (`d99a1564`) -- Update Node to v22 LTS; add Playwright browser caching; prevent `npm ci` from - installing browsers; ignore postinstall scripts (`b0a44a28`) -- Implement search control UI (`e5f6d147`) diff --git a/RELEASE-NOTES-0.18.1.md b/RELEASE-NOTES-0.18.1.md new file mode 100644 index 000000000..e9913109f --- /dev/null +++ b/RELEASE-NOTES-0.18.1.md @@ -0,0 +1,60 @@ +# Release Notes — v0.18.1 + +_Changes since [v0.18.0](https://github.com/Maps4HTML/MapML.js/releases/tag/v0.18.0)._ + +## Fixes +- ``: fix inline ("static") tile rendering when tile content changes + while the parent `` is unchecked. Replaced tile layers are now + removed from the layer's internal layer group, and removing a tile layer now + clears its rendered tiles. Previously, re-checking the layer brought back + stale tiles and could render a duplicate set alongside the current ones. +- Layer control: on touch devices, expanding the collapsed layer control no + longer accidentally toggles the layer settings (hamburger) button. Tapping the map + now reliably collapses the control. +- Attribution control: the attribution (info) button now has a localized + tooltip and `aria-label`. + +## Features +- Add a "static" control: a lock button that toggles the viewer's existing + `static` attribute, freezing or restoring map interaction. Opt in with + `controlslist="static"` on `` or ``. The + button's pressed state follows the `static` attribute when it is changed + programmatically. This can be used to avoid scroll traps when multiple + maps exist on a long page. + +## Build / Tooling +- Vendor `media-query-parser` 3.0.2 and `media-query-solver` 0.1.3 under + `vendor/`, because `media-query-solver` was unpublished from npm on + 2026-09-30 and `npm ci` was failing. The vendored code is identical to the + published packages, and the built `dist/mapml.js` is unchanged. +- Sync workflow: supply the access token to git via a credential helper instead + of the clone URL. Newer libcurl versions in the `alpine` image caused git to + drop URL-embedded credentials, so pushes to the downstream repositories + failed. +- Sync workflow: the build is now synced to `dist/MapML.js/dist/` in the + experiments repository, so it can sit alongside other builds such as + MapML-MapLibre. +- Update `how-to-release.md`. + +## Other +- Test fixes: make the `` security test less flaky, and generate the + localization test's `` instead of hard-coding them. + +--- + +### Commits +- Fix inline "static" `` rendering: remove the old MapTileLayer from + the parent `` LayerGroup; call `GridLayer.prototype.onRemove` from + `MapTileLayer.onRemove` (`45401483`) +- Port static control from MapML-MapLibre (`7f515725`) +- Prevent ghost click on settings hamburger button during touch expand (`6581b5ae`) +- Add localized tooltip and aria-label to attribution summary (`e76971bc`) +- Vendor media-query-parser and media-query-solver because the latter was + unpublished from npm on 2026-09-30 (`63164cfc`) +- Pass the sync access token via a git credential helper, after the libcurl + change in the `alpine` image (`d16940ef`) +- Sync Leaflet build into experiments `dist/MapML.js/dist` subtree (`cdb752b6`) +- Update localization test so map-options are generated, not hard-coded + (`c7638a13`) +- Fix flaky test (`8e23fda0`) +- Update how-to-release.md (`2d03e35c`) diff --git a/how-to-release.md b/how-to-release.md index fbebebbc7..a4a5db08b 100644 --- a/how-to-release.md +++ b/how-to-release.md @@ -1,5 +1,5 @@ # Release Guide -**Publishing packages to NPM** +**Publishing packages to npmjs.com / NPM** In `package.json` verify the name and update the version. @@ -64,8 +64,6 @@ replacing `` with the actual prior release tag (e.g. `v0.18.0`): git log ..HEAD --pretty=format:'- %s (%h, %an)' ``` -Edit that file freely — it is disposable and never committed or shipped. - **GitHub release procedure** Create the release from the canonical `Maps4HTML/MapML.js` repository so the tag @@ -76,22 +74,6 @@ and click `Draft a new release`. In the `Choose a tag` dropdown, type the new `vX.Y.Z` tag (GitHub creates it on publish), set the target to the merged release commit on `main`, paste in your notes, and publish. -_Via the GitHub CLI_ (lets you create an editable **draft** from your notes -file, then review/publish): -```bash -gh release create vX.Y.Z --draft --title "vX.Y.Z" --notes-file RELEASE-NOTES-X.Y.Z.md -``` -Open the draft, make any final edits, then publish it. Publishing the release is -what creates and pushes the `vX.Y.Z` tag, so no separate `git push` of a tag is -required (useful when branch protection prevents pushing tags directly). - -After the tag exists upstream, publish to npm from that tagged state: -```bash -git fetch upstream --tags -git checkout vX.Y.Z -npm ci && npm publish --access=public -``` - **Publishing packages to GitHub** Create a personal access (classic) token on [GitHub](https://github.com/settings/tokens/new) diff --git a/package-lock.json b/package-lock.json index de5ccb037..a6c9dc375 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { "name": "@maps4html/mapml", - "version": "0.18.0", + "version": "0.18.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@maps4html/mapml", - "version": "0.18.0", + "version": "0.18.1", "hasInstallScript": true, "license": "W3C", "devDependencies": { - "@playwright/test": "^1.39.0", + "@playwright/test": "^1.64.1", "@rollup/plugin-alias": "^5.1.1", "@rollup/plugin-legacy": "^3.0.2", "@rollup/plugin-node-resolve": "^15.2.3", @@ -32,7 +32,7 @@ "media-query-parser": "file:vendor/media-query-parser", "media-query-solver": "file:vendor/media-query-solver", "path": "^0.12.7", - "playwright": "^1.39.0", + "playwright": "^1.64.1", "proj4": "^2.6.2", "proj4leaflet": "git+https://github.com/Maps4HTML/proj4leaflet.git#es-module", "protomaps-leaflet": "^4.0.1", @@ -120,17 +120,19 @@ } }, "node_modules/@playwright/test": { - "version": "1.48.1", + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.64.1.tgz", + "integrity": "sha512-IaYGml/nSOiZWROfPtuIL7neuu9qDOZ3TNvNljUIrhDMxiGTgPjzj/g9p9RxpS2tSepPDRY+UwAaEeiKtUvIAg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.48.1" + "playwright": "1.64.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/@rollup/plugin-alias": { @@ -169,7 +171,9 @@ } }, "node_modules/@rollup/plugin-node-resolve": { - "version": "15.3.0", + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz", + "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==", "dev": true, "license": "MIT", "dependencies": { @@ -311,6 +315,8 @@ }, "node_modules/argparse": { "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "license": "MIT", "dependencies": { @@ -359,28 +365,61 @@ } }, "node_modules/body-parser": { - "version": "1.20.3", + "version": "1.20.9", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.9.tgz", + "integrity": "sha512-FISIQxV5u91Y9gvuFq3q/tA/qTqLJn3AhRcEvyozyhJTh5OBFFtv827AdHU7Rbyct4r+Ba1/99oFa1Y3yuFsdw==", "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.16.0", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/body/node_modules/bytes": { "version": "1.0.0", "dev": true @@ -398,7 +437,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -426,22 +467,37 @@ }, "node_modules/bytes": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/call-bind": { - "version": "1.0.7", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0", "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -557,6 +613,8 @@ }, "node_modules/content-type": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, "license": "MIT", "engines": { @@ -613,22 +671,6 @@ "node": ">=0.10.0" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/depd": { "version": "2.0.0", "dev": true, @@ -655,7 +697,9 @@ } }, "node_modules/diff": { - "version": "5.2.0", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", + "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -710,6 +754,21 @@ "domelementtype": "1" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexer": { "version": "0.1.2", "dev": true, @@ -741,20 +800,34 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" } }, "node_modules/es-errors": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "dev": true, "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, "engines": { "node": ">= 0.4" } @@ -774,6 +847,8 @@ }, "node_modules/esprima": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, "license": "BSD-2-Clause", "bin": { @@ -809,6 +884,16 @@ "node": ">= 0.8.0" } }, + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/expand-tilde": { "version": "2.0.2", "dev": true, @@ -821,40 +906,40 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.3.tgz", + "integrity": "sha512-Bdcs4+3qlpVlx2NRn6fgX2Ue2/gGRaPeawebgclM0ERSCqDpA+owF1fdPwjJUTAJWMTuAaxjDf+hzb0/4eKvvw==", "dev": true, "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.13", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.16.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -884,7 +969,9 @@ } }, "node_modules/fflate": { - "version": "0.8.2", + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", "dev": true, "license": "MIT" }, @@ -1047,15 +1134,22 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -1064,6 +1158,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/getobject": { "version": "1.0.2", "dev": true, @@ -1143,18 +1251,22 @@ } }, "node_modules/gopd": { - "version": "1.0.1", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/grunt": { - "version": "1.6.1", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.6.3.tgz", + "integrity": "sha512-ng0CbpyNp/ox5e5CBY9TkZgVdAL4wK+nBHO6rNE8cXGa5kM++Lxlp0MpFBVZbpgOR2oCp/91V+T3CZVSWWLqMA==", "dev": true, "license": "MIT", "dependencies": { @@ -1163,14 +1275,14 @@ "exit": "~0.1.2", "findup-sync": "~5.0.0", "glob": "~7.1.6", - "grunt-cli": "~1.4.3", + "grunt-cli": "^1.4.3", "grunt-known-options": "~2.0.0", "grunt-legacy-log": "~3.0.0", "grunt-legacy-util": "~2.0.1", "iconv-lite": "~0.6.3", - "js-yaml": "~3.14.0", - "minimatch": "~3.0.4", - "nopt": "~3.0.6" + "js-yaml": "^3.15.0", + "minimatch": "^3.1.5", + "nopt": "^5.0.0" }, "bin": { "grunt": "bin/grunt" @@ -1404,26 +1516,29 @@ } }, "node_modules/grunt-legacy-log": { - "version": "3.0.0", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-3.0.1.tgz", + "integrity": "sha512-vytI3IUC8qUK9TcvvpHpGJzDojua/sfJV4TdLB4FtCFzospqduzBuL3+dEfpvO+tGECv7/273+33hjjMXSa92g==", "dev": true, "license": "MIT", "dependencies": { "colors": "~1.1.2", - "grunt-legacy-log-utils": "~2.1.0", + "grunt-legacy-log-utils": "^2.1.3", "hooker": "~0.2.3", - "lodash": "~4.17.19" + "lodash": "^4.18.0" }, "engines": { "node": ">= 0.10.0" } }, "node_modules/grunt-legacy-log-utils": { - "version": "2.1.0", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.3.tgz", + "integrity": "sha512-sgG+QvKmdb44wZyzJP+ejDsy3jYxG2wzohpol+JTMlXqMUBDoZb01JPQ5jKAedtZBFwhmABAc88T9hEBLy3U+Q==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "~4.1.0", - "lodash": "~4.17.19" + "chalk": "^4.1.0" }, "engines": { "node": ">=10" @@ -1431,6 +1546,8 @@ }, "node_modules/grunt-legacy-log-utils/node_modules/ansi-styles": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -1445,6 +1562,8 @@ }, "node_modules/grunt-legacy-log-utils/node_modules/chalk": { "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { @@ -1460,6 +1579,8 @@ }, "node_modules/grunt-legacy-log-utils/node_modules/supports-color": { "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { @@ -1469,16 +1590,25 @@ "node": ">=8" } }, + "node_modules/grunt-legacy-log/node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/grunt-legacy-util": { - "version": "2.0.1", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-2.0.2.tgz", + "integrity": "sha512-0xoDILyR4BVJel5uJwnhjdWN9evOQ8A0uXbQUIJ0hgVthIA6kloXHSoqATQPj6BRrHrHkcQtCeGVb0ixFoHyEQ==", "dev": true, "license": "MIT", "dependencies": { "async": "~3.2.0", - "exit": "~0.1.2", + "exit-x": "~0.2.2", "getobject": "~1.0.0", "hooker": "~0.2.3", - "lodash": "~4.17.21", + "lodash": "^4.18.0", "underscore.string": "~3.3.5", "which": "~2.0.2" }, @@ -1486,6 +1616,13 @@ "node": ">=10" } }, + "node_modules/grunt-legacy-util/node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/grunt-prettier": { "version": "2.2.0", "dev": true, @@ -1508,6 +1645,19 @@ "node": ">=0.10.0" } }, + "node_modules/grunt/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/gzip-size": { "version": "5.1.1", "dev": true, @@ -1539,30 +1689,10 @@ "node": ">=8" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { - "version": "1.0.3", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, "license": "MIT", "engines": { @@ -1635,6 +1765,8 @@ }, "node_modules/iconv-lite": { "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "license": "MIT", "dependencies": { @@ -1794,7 +1926,9 @@ } }, "node_modules/js-yaml": { - "version": "3.14.1", + "version": "3.15.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.2.tgz", + "integrity": "sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==", "dev": true, "license": "MIT", "dependencies": { @@ -1879,7 +2013,9 @@ "license": "MIT" }, "node_modules/lodash": { - "version": "4.17.21", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "dev": true, "license": "MIT" }, @@ -1908,6 +2044,16 @@ "dev": true, "license": "W3C" }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/maxmin": { "version": "3.0.0", "dev": true, @@ -1975,6 +2121,8 @@ }, "node_modules/media-typer": { "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true, "license": "MIT", "engines": { @@ -2015,7 +2163,9 @@ } }, "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -2080,7 +2230,9 @@ } }, "node_modules/nopt": { - "version": "3.0.6", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", "dev": true, "license": "ISC", "dependencies": { @@ -2088,6 +2240,9 @@ }, "bin": { "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" } }, "node_modules/object-assign": { @@ -2099,7 +2254,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.2", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, "license": "MIT", "engines": { @@ -2261,9 +2418,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "dev": true, "license": "MIT" }, @@ -2284,7 +2441,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.2", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", "engines": { @@ -2303,31 +2462,32 @@ } }, "node_modules/playwright": { - "version": "1.48.1", + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.64.1.tgz", + "integrity": "sha512-HCrVhHr5QQuwYoswEUGTb7gGp522yflbv/ST8nF4sFWGuLjQCt17Q/EubpGGSNKYn1tsTCPZxqHhHCfVURu7UA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.48.1" + "playwright-core": "1.64.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" + "node": ">=20" } }, "node_modules/playwright-core": { - "version": "1.48.1", + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.64.1.tgz", + "integrity": "sha512-BC4s8y6SEVA6Q1rSxZTto8hI1KUI3xXlHGZgvGqP4IqPdI/a6FX9H29IDNz7vKo9l21b6LfYosScPL6M3xxyDQ==", "dev": true, "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/pmtiles": { @@ -2404,7 +2564,9 @@ } }, "node_modules/protocol-buffers-schema": { - "version": "3.6.0", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.1.tgz", + "integrity": "sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==", "dev": true, "license": "MIT" }, @@ -2424,7 +2586,9 @@ } }, "node_modules/proxy-addr": { - "version": "2.0.7", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2433,14 +2597,21 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/qs": { - "version": "6.13.0", + "version": "6.16.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.2.tgz", + "integrity": "sha512-VYdnB5S7/0IeXzBacUtwNAaM6d7Y/PgYnNC7bJCI6meqA/OgnsQbZMtVW8Dt2D1PHgbHGtYmhSoEIgvt7xZ2ZA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -2473,15 +2644,48 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -2564,7 +2768,9 @@ } }, "node_modules/rollup": { - "version": "3.29.5", + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.30.0.tgz", + "integrity": "sha512-kQvGasUgN+AlWGliFn2POSajRQEsULVYFGTvOZmK06d7vCD+YhZztt70kGk3qaeAXeWYL5eO7zx+rAubBc55eA==", "dev": true, "license": "MIT", "bin": { @@ -2666,36 +2872,79 @@ "node": ">= 0.8.0" } }, - "node_modules/set-function-length": { - "version": "1.2.2", + "node_modules/setprototypeof": { + "version": "1.2.0", + "dev": true, + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "dev": true, "license": "MIT", "dependencies": { - "define-data-property": "^1.1.4", "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/setprototypeof": { - "version": "1.2.0", + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/side-channel": { - "version": "1.0.6", + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -2742,6 +2991,8 @@ }, "node_modules/sprintf-js": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true, "license": "BSD-3-Clause" }, @@ -2869,6 +3120,8 @@ }, "node_modules/type-is": { "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "dev": true, "license": "MIT", "dependencies": { @@ -2966,7 +3219,9 @@ } }, "node_modules/websocket-driver": { - "version": "0.7.4", + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.5.tgz", + "integrity": "sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==", "dev": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 5400446ec..7dc98a7d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@maps4html/mapml", - "version": "0.18.0", + "version": "0.18.1", "description": "Custom element suite", "keywords": [ "mapml-viewer", @@ -37,7 +37,7 @@ "test": "npx playwright test --retries=3 --workers=1" }, "devDependencies": { - "@playwright/test": "^1.39.0", + "@playwright/test": "^1.64.1", "@rollup/plugin-alias": "^5.1.1", "@rollup/plugin-legacy": "^3.0.2", "@rollup/plugin-node-resolve": "^15.2.3", @@ -59,7 +59,7 @@ "media-query-parser": "file:vendor/media-query-parser", "media-query-solver": "file:vendor/media-query-solver", "path": "^0.12.7", - "playwright": "^1.39.0", + "playwright": "^1.64.1", "proj4": "^2.6.2", "proj4leaflet": "git+https://github.com/Maps4HTML/proj4leaflet.git#es-module", "protomaps-leaflet": "^4.0.1", diff --git a/test/e2e/core/mapContextMenu.test.js b/test/e2e/core/mapContextMenu.test.js index 181a21052..96c462de0 100644 --- a/test/e2e/core/mapContextMenu.test.js +++ b/test/e2e/core/mapContextMenu.test.js @@ -144,18 +144,23 @@ test.describe('Playwright Map Context Menu Tests', () => { ); expect(await map.evaluate((map) => map.extent.zoom.minZoom)).toEqual(0); expect(await map.evaluate((map) => map.extent.zoom.maxZoom)).toEqual(3); - expect(await map.evaluate((map) => map.extent.topLeft.pcrs)).toEqual( - expectedPCRS[0] - ); - expect(await map.evaluate((map) => map.extent.topLeft.gcrs)).toEqual( - expectedGCRS[0] - ); + const topLeftPcrs = await map.evaluate((map) => map.extent.topLeft.pcrs); + expect(topLeftPcrs.horizontal).toBeCloseTo(expectedPCRS[0].horizontal, 6); + expect(topLeftPcrs.vertical).toBeCloseTo(expectedPCRS[0].vertical, 6); + const topLeftGcrs = await map.evaluate((map) => map.extent.topLeft.gcrs); + expect(topLeftGcrs.horizontal).toBeCloseTo(expectedGCRS[0].horizontal, 6); + expect(topLeftGcrs.vertical).toBeCloseTo(expectedGCRS[0].vertical, 6); expect( await map.evaluate((map) => map.extent.topLeft.tilematrix[0]) ).toEqual(expectedFirstTileMatrix[0]); - expect(await map.evaluate((map) => map.extent.topLeft.tcrs[0])).toEqual( - expectedFirstTCRS[0] + const topLeftTcrs0 = await map.evaluate( + (map) => map.extent.topLeft.tcrs[0] ); + expect(topLeftTcrs0.horizontal).toBeCloseTo( + expectedFirstTCRS[0].horizontal, + 6 + ); + expect(topLeftTcrs0.vertical).toBeCloseTo(expectedFirstTCRS[0].vertical, 6); }); test('Context menu, back and reload item at initial location disabled', async () => { await page.reload(); @@ -195,18 +200,23 @@ test.describe('Playwright Map Context Menu Tests', () => { expect(await map.evaluate((map) => map.extent.zoom.minZoom)).toEqual(0); expect(await map.evaluate((map) => map.extent.zoom.maxZoom)).toEqual(3); - expect(await map.evaluate((map) => map.extent.topLeft.pcrs)).toEqual( - expectedPCRS[1] - ); - expect(await map.evaluate((map) => map.extent.topLeft.gcrs)).toEqual( - expectedGCRS[1] - ); + const topLeftPcrs1 = await map.evaluate((map) => map.extent.topLeft.pcrs); + expect(topLeftPcrs1.horizontal).toBeCloseTo(expectedPCRS[1].horizontal, 6); + expect(topLeftPcrs1.vertical).toBeCloseTo(expectedPCRS[1].vertical, 6); + const topLeftGcrs1 = await map.evaluate((map) => map.extent.topLeft.gcrs); + expect(topLeftGcrs1.horizontal).toBeCloseTo(expectedGCRS[1].horizontal, 6); + expect(topLeftGcrs1.vertical).toBeCloseTo(expectedGCRS[1].vertical, 6); expect( await map.evaluate((map) => map.extent.topLeft.tilematrix[0]) ).toEqual(expectedFirstTileMatrix[1]); - expect(await map.evaluate((map) => map.extent.topLeft.tcrs[0])).toEqual( - expectedFirstTCRS[1] + const topLeftTcrs1 = await map.evaluate( + (map) => map.extent.topLeft.tcrs[0] + ); + expect(topLeftTcrs1.horizontal).toBeCloseTo( + expectedFirstTCRS[1].horizontal, + 6 ); + expect(topLeftTcrs1.vertical).toBeCloseTo(expectedFirstTCRS[1].vertical, 6); }); test('Context menu, forward item at most recent location disabled', async () => { const map = await page.getByTestId('firstmap'); @@ -516,11 +526,14 @@ test.describe('Playwright Map Context Menu Tests', () => { await page.keyboard.press('Shift+F10'); await page.keyboard.press('p'); - const layerLabel = await page.$eval( - 'body > map', - (map) => map.layers[1].label - ); - expect(layerLabel).toEqual('Pasted Layer'); + // pasting is asynchronous (navigator.clipboard.readText() resolves a + // promise, and Util._pasteLayer is itself async), so poll for the layer + await expect + .poll( + async () => + await page.$eval('body > map', (map) => map.layers[1]?.label ?? null) + ) + .toEqual('Pasted Layer'); // clean up await page.$eval('body > map', (map) => map.removeChild(map.querySelector('[label="Pasted Layer"]')) @@ -582,11 +595,12 @@ test.describe('Playwright Map Context Menu Tests', () => { await page.keyboard.press('Shift+F10'); await page.keyboard.press('p'); - const layerLabel = await page.$eval( - 'body > map', - (map) => map.layers[1].label - ); - expect(layerLabel).toEqual('Test Layer'); + await expect + .poll( + async () => + await page.$eval('body > map', (map) => map.layers[1]?.label ?? null) + ) + .toEqual('Test Layer'); }); test('Paste invalid element to map', async () => { @@ -597,6 +611,8 @@ test.describe('Playwright Map Context Menu Tests', () => { await page.click('body > map'); await page.keyboard.press('Shift+F10'); await page.keyboard.press('p'); + // nothing should be added; allow the paste to settle before asserting + await page.waitForTimeout(500); const layerCount = await page.$eval( 'body > map', (map) => map.children.length @@ -612,16 +628,19 @@ test.describe('Playwright Map Context Menu Tests', () => { await page.click('body > map'); await page.keyboard.press('Shift+F10'); await page.keyboard.press('p'); - await page.waitForTimeout(500); - const layerLabel = await page.$eval( - 'body > map', - (map) => map.layers[2].outerHTML - ); const expected = await page.$eval( 'body > textarea#geojsonExpected', (textarea) => textarea.value ); - expect(layerLabel).toEqual(expected); + await expect + .poll( + async () => + await page.$eval( + 'body > map', + (map) => map.layers[2]?.outerHTML ?? null + ) + ) + .toEqual(expected); }); test('Paste invalid geojson to map', async () => { @@ -632,6 +651,8 @@ test.describe('Playwright Map Context Menu Tests', () => { await page.click('body > map'); await page.keyboard.press('Shift+F10'); await page.keyboard.press('p'); + // nothing should be added; allow the paste to settle before asserting + await page.waitForTimeout(500); const layerCount = await page.$eval( 'body > map', (map) => map.children.length @@ -704,13 +725,38 @@ test.describe('Playwright Map Context Menu Tests', () => { test('Layer Context menu, Pressing enter on contextmenu focuses on checkbox element', async () => { const map = await page.getByTestId('firstmap'); + // The layer control starts out empty, and therefore hidden, until the + // inline registers itself. It is the 5th tab stop, so tabbing + // before it appears lands on the scale bar instead of the layer checkbox. + await expect(map.locator('.leaflet-control-layers')).toBeVisible(); await map.click(); + // An earlier context-menu interaction can leave a deferred map + // container focus() pending (ContextMenu.js schedules one in a + // setTimeout); if it lands mid-sequence it shifts every tab stop. + // Wait for focus to come to rest before counting tab stops. + await expect + .poll(async () => + page.evaluate((sel) => { + const active = eval(sel).shadowRoot.activeElement; + return active ? active.className : null; + }, "document.querySelector('.mapml-web-map')") + ) + .toContain('leaflet-container'); for (let i = 0; i < 5; i++) { await page.keyboard.press('Tab'); - await page.waitForTimeout(100); } await page.keyboard.press('Enter'); - await page.waitForTimeout(300); + // expanding the layer control moves focus to the layer's checkbox + // asynchronously, so poll rather than reading activeElement once + await expect + .poll(async () => + page.evaluate((hostSelector) => { + const active = + document.querySelector(hostSelector).shadowRoot.activeElement; + return active ? active.nodeName : null; + }, '.mapml-web-map') + ) + .toEqual('INPUT'); const aHandle = await page.evaluateHandle(() => document.querySelector('.mapml-web-map') ); diff --git a/test/e2e/core/mapElement.test.js b/test/e2e/core/mapElement.test.js index 2553dcb6c..60a8fbf18 100644 --- a/test/e2e/core/mapElement.test.js +++ b/test/e2e/core/mapElement.test.js @@ -46,9 +46,11 @@ test.describe('Playwright Map Element Tests', () => { expect(await map.evaluate((map) => map.extent.topLeft.pcrs)).toEqual( expectedPCRS[0] ); - expect(await map.evaluate((map) => map.extent.topLeft.gcrs)).toEqual( - expectedGCRS[0] - ); + // gcrs values go through proj4's inverse projection, whose transcendental + // math varies in the last ulp or two between browser/V8 versions + const topLeftGcrs = await map.evaluate((map) => map.extent.topLeft.gcrs); + expect(topLeftGcrs.horizontal).toBeCloseTo(expectedGCRS[0].horizontal, 6); + expect(topLeftGcrs.vertical).toBeCloseTo(expectedGCRS[0].vertical, 6); expect( await map.evaluate((map) => map.extent.topLeft.tilematrix[0]) ).toEqual(expectedFirstTileMatrix[0]); @@ -69,9 +71,9 @@ test.describe('Playwright Map Element Tests', () => { expect(await map.evaluate((map) => map.extent.topLeft.pcrs)).toEqual( expectedPCRS[1] ); - expect(await map.evaluate((map) => map.extent.topLeft.gcrs)).toEqual( - expectedGCRS[1] - ); + const pannedGcrs = await map.evaluate((map) => map.extent.topLeft.gcrs); + expect(pannedGcrs.horizontal).toBeCloseTo(expectedGCRS[1].horizontal, 6); + expect(pannedGcrs.vertical).toBeCloseTo(expectedGCRS[1].vertical, 6); expect( await map.evaluate((map) => map.extent.topLeft.tilematrix[0]) ).toEqual(expectedFirstTileMatrix[1]); @@ -93,7 +95,14 @@ test.describe('Playwright Map Element Tests', () => { await expect(extent.projection).toEqual('CBMTILE'); await expect(extent.zoom).toEqual({ minZoom: 0, maxZoom: 3 }); await expect(extent.topLeft.pcrs).toEqual(expectedPCRS[0]); - await expect(extent.topLeft.gcrs).toEqual(expectedGCRS[0]); + expect(extent.topLeft.gcrs.horizontal).toBeCloseTo( + expectedGCRS[0].horizontal, + 6 + ); + expect(extent.topLeft.gcrs.vertical).toBeCloseTo( + expectedGCRS[0].vertical, + 6 + ); await expect(extent.topLeft.tilematrix[0]).toEqual( expectedFirstTileMatrix[0] ); diff --git a/test/e2e/core/mapFeature.test.js b/test/e2e/core/mapFeature.test.js index 234cc8b2a..1fe571b2e 100644 --- a/test/e2e/core/mapFeature.test.js +++ b/test/e2e/core/mapFeature.test.js @@ -1,6 +1,17 @@ import { test, expect, chromium } from '@playwright/test'; const data = require('./mapFeature.data.js'); +// GeoJSON coordinates are produced by proj4's inverse projection, whose +// transcendental math varies in the last ulp or two between browser/V8 +// versions. Round every number to 9 decimal places (sub-millimetre for +// degrees) so the structure can still be compared with deep equality. +const roundCoordinates = (value) => + JSON.parse( + JSON.stringify(value, (key, v) => + typeof v === 'number' && Number.isFinite(v) ? Number(v.toFixed(9)) : v + ) + ); + test.describe('Playwright MapFeature Custom Element Tests', () => { let page; let context; @@ -133,13 +144,17 @@ test.describe('Playwright MapFeature Custom Element Tests', () => { let geojson = await page.$eval('body > map', (map) => map.querySelector('map-feature').mapml2geojson() ); - expect(geojson).toEqual(data.geojsonData.withDefOptions); + expect(roundCoordinates(geojson)).toEqual( + roundCoordinates(data.geojsonData.withDefOptions) + ); // options = {propertyFunction: null, transform: false} geojson = await page.$eval('body > map', (map) => map.querySelector('map-feature').mapml2geojson({ transform: false }) ); - expect(geojson).toEqual(data.geojsonData.withNoTransform); + expect(roundCoordinates(geojson)).toEqual( + roundCoordinates(data.geojsonData.withNoTransform) + ); // options = {propertyFunction: function (properties) {...}, transform: true} geojson = await page.$eval('body > map', (map) => { @@ -161,7 +176,9 @@ test.describe('Playwright MapFeature Custom Element Tests', () => { } }); }); - expect(geojson).toEqual(data.geojsonData.withPropertyFunc); + expect(roundCoordinates(geojson)).toEqual( + roundCoordinates(data.geojsonData.withPropertyFunc) + ); }); test('Get geojson representation of with multiple geometries', async () => { @@ -169,7 +186,9 @@ test.describe('Playwright MapFeature Custom Element Tests', () => { let geojson = await page.$eval('body > map', (map) => map.querySelector('.link').mapml2geojson() ); - expect(geojson).toEqual(data.geojsonData.withMultiGeo); + expect(roundCoordinates(geojson)).toEqual( + roundCoordinates(data.geojsonData.withMultiGeo) + ); }); test('Default click method test', async () => { diff --git a/test/e2e/core/metaDefault.test.js b/test/e2e/core/metaDefault.test.js index d9acd17fb..2bb326ae7 100644 --- a/test/e2e/core/metaDefault.test.js +++ b/test/e2e/core/metaDefault.test.js @@ -1,5 +1,13 @@ import { test, expect, chromium } from '@playwright/test'; +// gcrs values go through proj4's inverse projection, whose transcendental math +// varies in the last ulp or two between browser/V8 versions, so compare them +// approximately rather than with exact deep equality +const expectGcrsCloseTo = (received, expected) => { + expect(received.horizontal).toBeCloseTo(expected.horizontal, 6); + expect(received.vertical).toBeCloseTo(expected.vertical, 6); +}; + let expectedPCRSFirstLayer = { topLeft: { horizontal: -11940075.314370813, @@ -69,8 +77,11 @@ test.describe('Playwright Missing Min Max Attribute, Meta Default Tests', () => expect(extent.hasOwnProperty('projection')).toBeTruthy(); expect(extent.topLeft.pcrs).toEqual(expectedPCRSFirstLayer.topLeft); expect(extent.bottomRight.pcrs).toEqual(expectedPCRSFirstLayer.bottomRight); - expect(extent.topLeft.gcrs).toEqual(expectedGCRSFirstLayer.topLeft); - expect(extent.bottomRight.gcrs).toEqual(expectedGCRSFirstLayer.bottomRight); + expectGcrsCloseTo(extent.topLeft.gcrs, expectedGCRSFirstLayer.topLeft); + expectGcrsCloseTo( + extent.bottomRight.gcrs, + expectedGCRSFirstLayer.bottomRight + ); }); test('Inline layer extent test', async () => { const extent = await page.$eval( @@ -86,8 +97,9 @@ test.describe('Playwright Missing Min Max Attribute, Meta Default Tests', () => expect(extent.bottomRight.pcrs).toEqual( expectedPCRSSecondLayer.bottomRight ); - expect(extent.topLeft.gcrs).toEqual(expectedGCRSSecondLayer.topLeft); - expect(extent.bottomRight.gcrs).toEqual( + expectGcrsCloseTo(extent.topLeft.gcrs, expectedGCRSSecondLayer.topLeft); + expectGcrsCloseTo( + extent.bottomRight.gcrs, expectedGCRSSecondLayer.bottomRight ); }); diff --git a/test/e2e/core/popupTabNavigation.test.js b/test/e2e/core/popupTabNavigation.test.js index bb6a27f7d..824e3aa7e 100644 --- a/test/e2e/core/popupTabNavigation.test.js +++ b/test/e2e/core/popupTabNavigation.test.js @@ -152,6 +152,7 @@ test.describe('Playwright Keyboard Navigation + Query Layer Tests', () => { await page.keyboard.press('Enter'); await page.waitForTimeout(500); await page.keyboard.down('Escape'); // focus back on feature + await page.waitForTimeout(100); // hold the key, see note below await page.keyboard.up('Escape'); await page.waitForTimeout(500); const viewer = page.getByTestId('viewer'); @@ -179,7 +180,13 @@ test.describe('Playwright Keyboard Navigation + Query Layer Tests', () => { await page.keyboard.press('Tab'); await page.keyboard.press('Tab'); await page.keyboard.press('Tab'); // focus on x button + // The popup is closed and focus is restored to the feature from a + // setTimeout(0) scheduled on keydown; geometry.js only (re)opens the + // tooltip when the matching keyUP targets the feature's . Hold the + // key for a beat, like a real key press, so the close/refocus task runs + // before the keyup is delivered. await page.keyboard.down('Enter'); // press x button + await page.waitForTimeout(100); await page.keyboard.up('Enter'); await page.waitForTimeout(500); const viewer = page.getByTestId('viewer'); diff --git a/test/e2e/core/styleParsing.test.js b/test/e2e/core/styleParsing.test.js index 6844d55b2..9acc8d8f7 100644 --- a/test/e2e/core/styleParsing.test.js +++ b/test/e2e/core/styleParsing.test.js @@ -48,29 +48,30 @@ found, in expected shadow root location`, async () => { 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); + const idSequences = (link) => { + const sequence = (root, selector) => + Array.from(root.querySelectorAll(selector)) + .map((el) => el.getAttribute('id')) + .join(','); + return { + source: sequence(link.shadowRoot, 'map-link[rel=stylesheet],map-style'), + rendered: sequence( + link._templatedLayer._container, + 'link[rel=stylesheet],style' + ) + }; + }; + // The remote styles are fetched and grafted into the link's shadow root and + // then rendered into the layer container; both steps are async, so poll + // until each has content rather than racing them with a single read. + await expect + .poll(async () => { + const { source, rendered } = await featuresLink.evaluate(idSequences); + return source !== '' && rendered !== ''; + }) + .toBe(true); + const { source, rendered } = await featuresLink.evaluate(idSequences); + expect(source).toEqual(rendered); }); test(`Local style children of map-extent rendered as style/link in same order \ diff --git a/test/e2e/elements/map-tile/map-tile.test.js b/test/e2e/elements/map-tile/map-tile.test.js index f15cc3f25..daec79ed1 100644 --- a/test/e2e/elements/map-tile/map-tile.test.js +++ b/test/e2e/elements/map-tile/map-tile.test.js @@ -1,5 +1,11 @@ import { test, expect, chromium } from '@playwright/test'; +// These are whole-viewport golden images. Browser upgrades routinely shift +// anti-aliased/colour-converted pixels by +/-1 in a single channel, which is +// invisible but trips exact comparison. Allow a handful of such pixels: a real +// tile appearing or disappearing differs by tens of thousands of pixels. +const SNAPSHOT_OPTIONS = { maxDiffPixels: 100 }; + test.describe('Map Tile Dynamic Updates Tests', () => { let page; let context; @@ -54,7 +60,10 @@ test.describe('Map Tile Dynamic Updates Tests', () => { // Take screenshot before removal const beforeScreenshot = await page.screenshot({ fullPage: false }); - expect(beforeScreenshot).toMatchSnapshot('before-tile-removal.png'); + expect(beforeScreenshot).toMatchSnapshot( + 'before-tile-removal.png', + SNAPSHOT_OPTIONS + ); // Remove specific tiles that are currently at the map zoom level const removedTiles = await page.evaluate(() => { @@ -104,7 +113,10 @@ test.describe('Map Tile Dynamic Updates Tests', () => { // Take screenshot after removal to show visual difference const afterScreenshot = await page.screenshot({ fullPage: false }); - expect(afterScreenshot).toMatchSnapshot('after-tile-removal.png'); + expect(afterScreenshot).toMatchSnapshot( + 'after-tile-removal.png', + SNAPSHOT_OPTIONS + ); // The test should pass if we removed tiles successfully expect(removedTiles.removedCount).toBeGreaterThan(0); @@ -143,7 +155,10 @@ test.describe('Map Tile Dynamic Updates Tests', () => { // Take baseline screenshot const baselineScreenshot = await page.screenshot({ fullPage: false }); - expect(baselineScreenshot).toMatchSnapshot('baseline-before-adding.png'); + expect(baselineScreenshot).toMatchSnapshot( + 'baseline-before-adding.png', + SNAPSHOT_OPTIONS + ); // Add a new map-tile element to DOM at a visible location await page.evaluate(() => { @@ -177,7 +192,10 @@ test.describe('Map Tile Dynamic Updates Tests', () => { // Take screenshot after adding to show the tile was rendered const afterAddingScreenshot = await page.screenshot({ fullPage: false }); - expect(afterAddingScreenshot).toMatchSnapshot('after-adding-tile.png'); + expect(afterAddingScreenshot).toMatchSnapshot( + 'after-adding-tile.png', + SNAPSHOT_OPTIONS + ); }); test('bidirectional links are properly cleaned up on removal', async () => { @@ -252,7 +270,10 @@ test.describe('Map Tile Dynamic Updates Tests', () => { // Take screenshot before src change const beforeScreenshot = await page.screenshot({ fullPage: false }); - expect(beforeScreenshot).toMatchSnapshot('before-src-change.png'); + expect(beforeScreenshot).toMatchSnapshot( + 'before-src-change.png', + SNAPSHOT_OPTIONS + ); // Change the src attribute to a different color tile const newSrc = initialState.src.includes('green') @@ -291,7 +312,10 @@ test.describe('Map Tile Dynamic Updates Tests', () => { // Take screenshot after src change const afterScreenshot = await page.screenshot({ fullPage: false }); - expect(afterScreenshot).toMatchSnapshot('after-src-change.png'); + expect(afterScreenshot).toMatchSnapshot( + 'after-src-change.png', + SNAPSHOT_OPTIONS + ); }); test('coordinate collision - last tile wins', async () => { diff --git a/test/e2e/layers/featureLayer.test.js b/test/e2e/layers/featureLayer.test.js index 31879bb7f..fe873cdb9 100644 --- a/test/e2e/layers/featureLayer.test.js +++ b/test/e2e/layers/featureLayer.test.js @@ -103,18 +103,28 @@ test.describe('Playwright featureLayer (Static Features) Layer Tests', () => { horizontal: -34655800, vertical: 39310000 }); - expect(layerExtent.topLeft.gcrs).toEqual({ - horizontal: -169.78391348558873, - vertical: -60.79113663130127 - }); + // gcrs values go through proj4's inverse projection, whose transcendental + // math varies in the last ulp or two between browser/V8 versions + expect(layerExtent.topLeft.gcrs.horizontal).toBeCloseTo( + -169.78391348558873, + 6 + ); + expect(layerExtent.topLeft.gcrs.vertical).toBeCloseTo( + -60.79113663130127, + 6 + ); expect(layerExtent.bottomRight.pcrs).toEqual({ horizontal: 14450964.88019643, vertical: -9796764.88019643 }); - expect(layerExtent.bottomRight.gcrs).toEqual({ - horizontal: 79.6961805581841, - vertical: -60.79110984572508 - }); + expect(layerExtent.bottomRight.gcrs.horizontal).toBeCloseTo( + 79.6961805581841, + 6 + ); + expect(layerExtent.bottomRight.gcrs.vertical).toBeCloseTo( + -60.79110984572508, + 6 + ); // corrected logic for MapLayer._calculateBounds min/maxNativeZoom // there are a bunch of features loaded at map zoom=2. Two have default // (no) zoom attribute, all the others have zoom=0. So, the minNativeZoom diff --git a/test/e2e/layers/general/extentProperty.js b/test/e2e/layers/general/extentProperty.js index b6a32c6a9..e72e1ab85 100644 --- a/test/e2e/layers/general/extentProperty.js +++ b/test/e2e/layers/general/extentProperty.js @@ -29,8 +29,24 @@ exports.test = (path, expectedPCRS, expectedGCRS) => { expect(extent.hasOwnProperty('projection')).toBeTruthy(); expect(extent.topLeft.pcrs).toEqual(expectedPCRS.topLeft); expect(extent.bottomRight.pcrs).toEqual(expectedPCRS.bottomRight); - expect(extent.topLeft.gcrs).toEqual(expectedGCRS.topLeft); - expect(extent.bottomRight.gcrs).toEqual(expectedGCRS.bottomRight); + // gcrs values go through proj4's inverse projection, whose transcendental + // math varies in the last ulp or two between browser/V8 versions + expect(extent.topLeft.gcrs.horizontal).toBeCloseTo( + expectedGCRS.topLeft.horizontal, + 6 + ); + expect(extent.topLeft.gcrs.vertical).toBeCloseTo( + expectedGCRS.topLeft.vertical, + 6 + ); + expect(extent.bottomRight.gcrs.horizontal).toBeCloseTo( + expectedGCRS.bottomRight.horizontal, + 6 + ); + expect(extent.bottomRight.gcrs.vertical).toBeCloseTo( + expectedGCRS.bottomRight.vertical, + 6 + ); }); test('2nd .extent test', async () => { const extent = await page.$eval( diff --git a/test/e2e/layers/queryLink.test.js b/test/e2e/layers/queryLink.test.js index 7c5b70d2e..6450f2452 100644 --- a/test/e2e/layers/queryLink.test.js +++ b/test/e2e/layers/queryLink.test.js @@ -16,6 +16,40 @@ test.describe('Playwright Query Link Tests', () => { await context.close(); }); + const POPUP_IFRAME = + 'div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-popup-pane > div > div.leaflet-popup-content-wrapper > div > div > iframe'; + + // The popup's