Compare commits

..

26 Commits

Author SHA1 Message Date
dependabot[bot] 2b5f03f3c2 chore(deps): Bump github/codeql-action/analyze from 4.36.3 to 4.37.2 (#2029)
Bumps
[github/codeql-action/analyze](https://github.com/github/codeql-action)
from 4.36.3 to 4.37.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/analyze's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.2</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>v4.37.1</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>v4.37.0</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/analyze's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>4.36.1 - 02 Jun 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.0 - 22 May 2026</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle
version to 2.19.4. <a
href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a
href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5">2.25.5</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3926">#3926</a></li>
</ul>
<h2>4.35.5 - 15 May 2026</h2>
<ul>
<li>We have improved how the JavaScript bundles for the CodeQL Action
are generated to avoid duplication across bundles and reduce the size of
the repository by around 70%. This should have no effect on the runtime
behaviour of the CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3899">#3899</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/e0647621c2984b5ed2f768cb892365bf2a616ad1"><code>e064762</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4027">#4027</a>
from github/update-v4.37.2-385bcdc5a</li>
<li><a
href="https://github.com/github/codeql-action/commit/e0faed839190caa67a5cd42f1cc16246028ca3df"><code>e0faed8</code></a>
Add a couple of change notes</li>
<li><a
href="https://github.com/github/codeql-action/commit/73aad0eaa9df172668665a150d17b8bc5a650c20"><code>73aad0e</code></a>
Update changelog for v4.37.2</li>
<li><a
href="https://github.com/github/codeql-action/commit/385bcdc5afb0b5a06bd1dae7809fda5453c7664b"><code>385bcdc</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4026">#4026</a>
from github/dependabot/npm_and_yarn/tar-7.5.20</li>
<li><a
href="https://github.com/github/codeql-action/commit/de0229cea17fd9b594946f05dccc18e9c33a15d1"><code>de0229c</code></a>
Merge branch 'main' into dependabot/npm_and_yarn/tar-7.5.20</li>
<li><a
href="https://github.com/github/codeql-action/commit/115e8cbbe9419ab1ea002e5c0c6d4797d7820bf8"><code>115e8cb</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4010">#4010</a>
from github/mbg/ts/update-release-branch</li>
<li><a
href="https://github.com/github/codeql-action/commit/dbdf0b0c7d63778e4b807cb4cef00f090e7dcd4b"><code>dbdf0b0</code></a>
Bump tar from 7.5.16 to 7.5.20</li>
<li><a
href="https://github.com/github/codeql-action/commit/830c23121c8d6124ad817115f9d68da98f44eba0"><code>830c231</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4023">#4023</a>
from github/mbg/ff/remove-new-remote-file-addresses-ff</li>
<li><a
href="https://github.com/github/codeql-action/commit/69fd9e97ba2f4e95024e587c495eec0f1c9186ea"><code>69fd9e9</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4024">#4024</a>
from github/mbg/private-registry/make-docker-available</li>
<li><a
href="https://github.com/github/codeql-action/commit/b85568788a5dbb01732f9d4e0d0882e5429bb804"><code>b855687</code></a>
Always make <code>docker_registry</code> registries available</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/54f647b7e1bb85c95cddabcd46b0c578ec92bc1a...e0647621c2984b5ed2f768cb892365bf2a616ad1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action/analyze&package-manager=github_actions&previous-version=4.36.3&new-version=4.37.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-24 13:52:38 +00:00
dependabot[bot] ccfb9a3111 chore(deps-dev): Bump typescript-eslint from 8.64.0 to 8.65.0 (#2028)
Bumps
[typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint)
from 8.64.0 to 8.65.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">typescript-eslint's
releases</a>.</em></p>
<blockquote>
<h2>v8.65.0</h2>
<h2>8.65.0 (2026-07-20)</h2>
<h3>🚀 Features</h3>
<ul>
<li>add warning when TS 7 is detected (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12529">#12529</a>)</li>
<li><strong>eslint-plugin:</strong> [no-restricted-imports] deprecate
extension rule (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12527">#12527</a>,
<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/issues/19562">#19562</a>,
<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/issues/11889">#11889</a>)</li>
<li><strong>eslint-plugin:</strong> [no-shadow] specialized error on
enum declaration and member shadowing (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12578">#12578</a>)</li>
<li><strong>parser:</strong> add onUnsupportedTypeScriptVersion option
to error on unsupported TypeScript versions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12465">#12465</a>)</li>
<li><strong>typescript-estree:</strong> throw for invalid import defer
syntax (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12552">#12552</a>)</li>
</ul>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [prefer-string-starts-ends-with]
handle escaped $ ending regex literals (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12515">#12515</a>)</li>
<li><strong>eslint-plugin:</strong> [unbound-method] report unbound
methods accessed via member expression on union types (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12448">#12448</a>)</li>
<li><strong>eslint-plugin:</strong>
[no-unnecessary-parameter-property-assignment] don't flag computed
assignments with a variable key (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12568">#12568</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Evyatar Daud <a
href="https://github.com/StyleShit"><code>@​StyleShit</code></a></li>
<li>Josh Goldberg </li>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
<li>Serhii Leniv <a
href="https://github.com/Serhii-Leniv"><code>@​Serhii-Leniv</code></a></li>
<li>송재욱</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.65.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md">typescript-eslint's
changelog</a>.</em></p>
<blockquote>
<h2>8.65.0 (2026-07-20)</h2>
<h3>🚀 Features</h3>
<ul>
<li>add warning when TS 7 is detected (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12529">#12529</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Josh Goldberg </li>
<li>Kirk Waiblinger <a
href="https://github.com/kirkwaiblinger"><code>@​kirkwaiblinger</code></a></li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.65.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/63ba81b6acfa0d663c29aa0013d4672bf3b0426c"><code>63ba81b</code></a>
chore(release): publish 8.65.0</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/eaf457631ac381aadfee747c2d098c8ac4df9d63"><code>eaf4576</code></a>
feat: add warning when TS 7 is detected (<a
href="https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint/issues/12529">#12529</a>)</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/0d06406f3181f063041dbd1621d71984127ddae1"><code>0d06406</code></a>
chore: add attw validation to repo (<a
href="https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint/issues/12437">#12437</a>)</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/c2386e49ec6347aa92d53a1f32143ca45984cc13"><code>c2386e4</code></a>
chore(deps): update dependency prettier to v3.9.5 (<a
href="https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint/issues/12486">#12486</a>)</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.65.0/packages/typescript-eslint">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=typescript-eslint&package-manager=npm_and_yarn&previous-version=8.64.0&new-version=8.65.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-24 13:52:21 +00:00
dependabot[bot] 23ce685a91 chore(deps-dev): Bump postcss from 8.5.20 to 8.5.21 (#2027)
[//]: # (dependabot-start)
⚠️  **Dependabot is rebasing this PR** ⚠️ 

Rebasing might not happen immediately, so don't worry if this takes some
time.

Note: if you make any changes to this PR yourself, they will take
precedence over the rebase.

---

[//]: # (dependabot-end)

Bumps [postcss](https://github.com/postcss/postcss) from 8.5.20 to
8.5.21.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/postcss/postcss/releases">postcss's
releases</a>.</em></p>
<blockquote>
<h2>8.5.21</h2>
<ul>
<li>Fixed childless at-rule losing semicolon before comment (by <a
href="https://github.com/sarathfrancis90"><code>@​sarathfrancis90</code></a>).</li>
<li>Fixed docs (by <a
href="https://github.com/isker"><code>@​isker</code></a>).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/postcss/postcss/blob/main/CHANGELOG.md">postcss's
changelog</a>.</em></p>
<blockquote>
<h2>8.5.21</h2>
<ul>
<li>Fixed childless at-rule losing semicolon before comment (by <a
href="https://github.com/sarathfrancis90"><code>@​sarathfrancis90</code></a>).</li>
<li>Fixed docs (by <a
href="https://github.com/isker"><code>@​isker</code></a>).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/postcss/postcss/commit/28e0daf8f2fe5ba9e19ea3f8c27c8fe176f9419e"><code>28e0daf</code></a>
Release 8.5.21 version</li>
<li><a
href="https://github.com/postcss/postcss/commit/3d2b4e43e38274f233b5609d09687cadad8215d9"><code>3d2b4e4</code></a>
Update dependencies</li>
<li><a
href="https://github.com/postcss/postcss/commit/d197327e82e1a7d6dd9272effa3e4bfa91fdd20e"><code>d197327</code></a>
Fix childless at-rule losing its semicolon before a comment (<a
href="https://redirect.github.com/postcss/postcss/issues/2115">#2115</a>)</li>
<li><a
href="https://github.com/postcss/postcss/commit/0e360b749aa17a5a89baccf9c8e5db3c2978c21e"><code>0e360b7</code></a>
Fix mismatched JSDoc comments on Position (<a
href="https://redirect.github.com/postcss/postcss/issues/2114">#2114</a>)</li>
<li>See full diff in <a
href="https://github.com/postcss/postcss/compare/8.5.20...8.5.21">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=postcss&package-manager=npm_and_yarn&previous-version=8.5.20&new-version=8.5.21)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-24 13:52:06 +00:00
dependabot[bot] ea67387e65 chore(deps): Bump @maplibre/maplibre-gl-style-spec from 26.1.0 to 26.2.1 (#2026)
Bumps
[@maplibre/maplibre-gl-style-spec](https://github.com/maplibre/maplibre-style-spec)
from 26.1.0 to 26.2.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/maplibre/maplibre-style-spec/releases">@​maplibre/maplibre-gl-style-spec's
releases</a>.</em></p>
<blockquote>
<h2>v26.2.1</h2>
<h3> Features and improvements</h3>
<h3>🐞 Bug fixes</h3>
<ul>
<li>Fix <code>fill-extrusion-rounded-corner-distance</code> being
documented as an <code>paint</code> instead of an <code>layout</code>
property as implemnented (<a
href="https://redirect.github.com/maplibre/maplibre-style-spec/pull/1784">#1784</a>)
(by <a
href="https://github.com/CommanderStorm"><code>@​CommanderStorm</code></a>)</li>
</ul>
<h2>v26.2.0</h2>
<h3> Features and improvements</h3>
<ul>
<li>Add fill-extrusion-rounded-corner-distance (<a
href="https://redirect.github.com/maplibre/maplibre-style-spec/pull/1781">#1781</a>,
<a
href="https://redirect.github.com/maplibre/maplibre-style-spec/pull/1782">#1782</a>)
(by <a
href="https://github.com/louwers"><code>@​louwers</code></a>)</li>
</ul>
<h3>🐞 Bug fixes</h3>
<ul>
<li>Support <code>global-state</code> (and other
<code>value</code>-typed) expressions in
<code>projectionDefinition</code> properties such as
<code>projection.type</code> (<a
href="https://redirect.github.com/maplibre/maplibre-style-spec/pull/1772">#1772</a>)
(by <a
href="https://github.com/CommanderStorm"><code>@​CommanderStorm</code></a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/maplibre/maplibre-style-spec/blob/main/CHANGELOG.md">@​maplibre/maplibre-gl-style-spec's
changelog</a>.</em></p>
<blockquote>
<h2>26.2.1</h2>
<h3> Features and improvements</h3>
<h3>🐞 Bug fixes</h3>
<ul>
<li>Fix <code>fill-extrusion-rounded-corner-distance</code> being
documented as an <code>paint</code> instead of an <code>layout</code>
property as implemnented (<a
href="https://redirect.github.com/maplibre/maplibre-style-spec/pull/1784">#1784</a>)
(by <a
href="https://github.com/CommanderStorm"><code>@​CommanderStorm</code></a>)</li>
</ul>
<h2>26.2.0</h2>
<h3> Features and improvements</h3>
<ul>
<li>Add fill-extrusion-rounded-corner-distance (<a
href="https://redirect.github.com/maplibre/maplibre-style-spec/pull/1781">#1781</a>,
<a
href="https://redirect.github.com/maplibre/maplibre-style-spec/pull/1782">#1782</a>)
(by <a
href="https://github.com/louwers"><code>@​louwers</code></a>)</li>
</ul>
<h3>🐞 Bug fixes</h3>
<ul>
<li>Support <code>global-state</code> (and other
<code>value</code>-typed) expressions in
<code>projectionDefinition</code> properties such as
<code>projection.type</code> (<a
href="https://redirect.github.com/maplibre/maplibre-style-spec/pull/1772">#1772</a>)
(by <a
href="https://github.com/CommanderStorm"><code>@​CommanderStorm</code></a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/maplibre/maplibre-style-spec/commit/7a2420b0a076a1a013149720a511d63fc9502340"><code>7a2420b</code></a>
Bump js version to 26.2.1 (<a
href="https://redirect.github.com/maplibre/maplibre-style-spec/issues/1785">#1785</a>)</li>
<li><a
href="https://github.com/maplibre/maplibre-style-spec/commit/688a88cd1903cd8039a48ce8efc63b3f46f4008b"><code>688a88c</code></a>
fix: move fill-extrusion-rounded-corner-distance to layout properties
(<a
href="https://redirect.github.com/maplibre/maplibre-style-spec/issues/1784">#1784</a>)</li>
<li><a
href="https://github.com/maplibre/maplibre-style-spec/commit/08a02a3b9a5d9cdda9cfda1de309b812f712cdf8"><code>08a02a3</code></a>
docs: document global state support for paint and layout (<a
href="https://redirect.github.com/maplibre/maplibre-style-spec/issues/1770">#1770</a>)</li>
<li><a
href="https://github.com/maplibre/maplibre-style-spec/commit/dda297b6e3578068209e996ab716afc144759735"><code>dda297b</code></a>
Bump js version to 26.2.0 (<a
href="https://redirect.github.com/maplibre/maplibre-style-spec/issues/1783">#1783</a>)</li>
<li><a
href="https://github.com/maplibre/maplibre-style-spec/commit/add55f47717c9dcc7e8442be98c08f0cc356d243"><code>add55f4</code></a>
docs: document sky.* and light.* global state and rendering support (<a
href="https://redirect.github.com/maplibre/maplibre-style-spec/issues/1771">#1771</a>)</li>
<li><a
href="https://github.com/maplibre/maplibre-style-spec/commit/dc8a92b68d0818ce0848a16bc2cf6006e38a80f6"><code>dc8a92b</code></a>
implement global-state and projection interop (<a
href="https://redirect.github.com/maplibre/maplibre-style-spec/issues/1772">#1772</a>)</li>
<li><a
href="https://github.com/maplibre/maplibre-style-spec/commit/faaa9fe3b4f543decacb4015a88c8ef125348490"><code>faaa9fe</code></a>
Fix Android/iOS version
<code>fill-extrusion-rounded-corner-distance</code> property (<a
href="https://redirect.github.com/maplibre/maplibre-style-spec/issues/1">#1</a>...</li>
<li><a
href="https://github.com/maplibre/maplibre-style-spec/commit/28fe948d47b325ae0c2688257840b9881f87fa8a"><code>28fe948</code></a>
build(deps-dev): bump oxfmt from 0.58.0 to 0.59.0 (<a
href="https://redirect.github.com/maplibre/maplibre-style-spec/issues/1777">#1777</a>)</li>
<li><a
href="https://github.com/maplibre/maplibre-style-spec/commit/f20786eb870a9520a68ab39629bc45cb19211e9b"><code>f20786e</code></a>
build(deps-dev): bump <code>@​typescript-eslint/parser</code> from
8.64.0 to 8.65.0 (<a
href="https://redirect.github.com/maplibre/maplibre-style-spec/issues/1779">#1779</a>)</li>
<li><a
href="https://github.com/maplibre/maplibre-style-spec/commit/d885670d68e21e4e4921b77984e45d02743a4624"><code>d885670</code></a>
build(deps-dev): bump rolldown from 1.1.5 to 1.2.0 (<a
href="https://redirect.github.com/maplibre/maplibre-style-spec/issues/1775">#1775</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/maplibre/maplibre-style-spec/compare/v26.1.0...v26.2.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@maplibre/maplibre-gl-style-spec&package-manager=npm_and_yarn&previous-version=26.1.0&new-version=26.2.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-24 13:51:47 +00:00
dependabot[bot] 30bcfc2265 chore(deps-dev): Bump sass from 1.101.0 to 1.101.3 (#2025)
Bumps [sass](https://github.com/sass/dart-sass) from 1.101.0 to 1.101.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/sass/dart-sass/releases">sass's
releases</a>.</em></p>
<blockquote>
<h2>Dart Sass 1.101.3</h2>
<p>To install Sass 1.101.3, download one of the packages below and <a
href="https://katiek2.github.io/path-doc/">add it to your PATH</a>, or
see <a href="https://sass-lang.com/install">the Sass website</a> for
full installation instructions.</p>
<h1>Changes</h1>
<ul>
<li>No user-visible changes.</li>
</ul>
<p>See the <a
href="https://github.com/sass/dart-sass/blob/master/CHANGELOG.md#11013">full
changelog</a> for changes in earlier releases.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/sass/dart-sass/blob/main/CHANGELOG.md">sass's
changelog</a>.</em></p>
<blockquote>
<h2>1.101.3</h2>
<ul>
<li>No user-visible changes.</li>
</ul>
<h2>1.101.2</h2>
<ul>
<li>Fix a bug where the deprecation warning for vendor-prefixed
<code>expression()</code>
functions would incorrectly indicate whether or not the function would
be
invalid Sass in Dart Sass 2.0.0.</li>
</ul>
<h2>1.101.1</h2>
<ul>
<li>Sass stack trace entries are now always either absolute URLs,
absolute paths,
or paths relative to the current working directory. Previously, if a
stylesheet was loaded using a relative URL (as from a load path), that
relative URL was listed even if it couldn't be resolved relative to the
current working directory. However, this created potential ambiguities,
so
this behavior has been removed.</li>
</ul>
<h3>Command Line Interface</h3>
<ul>
<li><code>--watch</code> mode now handles atomically-written files more
gracefully.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/sass/dart-sass/commit/e8c12331ea5304a1d641d6a6bd4cb526cb3800b9"><code>e8c1233</code></a>
Fix some GitHub actions issues introduced by the Zizmor refactor (<a
href="https://redirect.github.com/sass/dart-sass/issues/2798">#2798</a>)</li>
<li><a
href="https://github.com/sass/dart-sass/commit/d676118ace4b6b196383f26cf5ffda4f9c61b239"><code>d676118</code></a>
Merge pull request <a
href="https://redirect.github.com/sass/dart-sass/issues/2797">#2797</a>
from sass/lints</li>
<li><a
href="https://github.com/sass/dart-sass/commit/548e6604c5eed5137b148a839513c7444582eb3c"><code>548e660</code></a>
Prefer interpolation to compose strings</li>
<li><a
href="https://github.com/sass/dart-sass/commit/b443ffe759f7235170b1fd6e540dcb8e459f3dca"><code>b443ffe</code></a>
Use function type syntax</li>
<li><a
href="https://github.com/sass/dart-sass/commit/3d28f6a83ff8c16e7b1980f6a4e46d62f1505fce"><code>3d28f6a</code></a>
Add <code>@override</code></li>
<li><a
href="https://github.com/sass/dart-sass/commit/e182ba21e1459c5efad0247e9bc5ba212a870db3"><code>e182ba2</code></a>
Expand dependency constraints (<a
href="https://redirect.github.com/sass/dart-sass/issues/2796">#2796</a>)</li>
<li><a
href="https://github.com/sass/dart-sass/commit/66f9d41f79231eebb4380a126bbc662c13f2d7d0"><code>66f9d41</code></a>
Bump analyzer from 10.2.0 to 13.3.0 (<a
href="https://redirect.github.com/sass/dart-sass/issues/2795">#2795</a>)</li>
<li><a
href="https://github.com/sass/dart-sass/commit/ab4026df5efe9eef1d7accc2653bdf050c3c1c14"><code>ab4026d</code></a>
Use the latest lints package (<a
href="https://redirect.github.com/sass/dart-sass/issues/2789">#2789</a>)</li>
<li><a
href="https://github.com/sass/dart-sass/commit/fbb1fe0533cc78c14b848c709b30d599c5bf86e1"><code>fbb1fe0</code></a>
Bump zizmorcore/zizmor-action from 0.5.6 to 0.5.7 (<a
href="https://redirect.github.com/sass/dart-sass/issues/2793">#2793</a>)</li>
<li><a
href="https://github.com/sass/dart-sass/commit/8abfa4d23d447bcada1efe415066458a08a0a155"><code>8abfa4d</code></a>
Fix <code>--watch</code> not recompiling files modified via atomic save
(<a
href="https://redirect.github.com/sass/dart-sass/issues/2791">#2791</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/sass/dart-sass/compare/1.101.0...1.101.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=sass&package-manager=npm_and_yarn&previous-version=1.101.0&new-version=1.101.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-24 13:50:21 +00:00
dependabot[bot] a408437cf8 chore(deps-dev): Bump stylelint from 17.14.0 to 17.14.1 (#2024)
Bumps [stylelint](https://github.com/stylelint/stylelint) from 17.14.0
to 17.14.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/stylelint/stylelint/releases">stylelint's
releases</a>.</em></p>
<blockquote>
<h2>17.14.1</h2>
<p>It fixes 4 bugs.</p>
<ul>
<li>Fixed: <code>quiet</code> option suppresses <code>report*</code>
warning reports (<a
href="https://redirect.github.com/stylelint/stylelint/issues/9387">#9387</a>)
(<a
href="https://github.com/ychampion"><code>@​ychampion</code></a>).</li>
<li>Fixed: reported range of unknown rules (<a
href="https://redirect.github.com/stylelint/stylelint/issues/9385">#9385</a>)
(<a
href="https://github.com/ybiquitous"><code>@​ybiquitous</code></a>).</li>
<li>Fixed:
<code>declaration-block-no-redundant-longhand-properties</code> autofix
producing invalid <code>background</code> shorthand when
<code>background-size</code> is present (<a
href="https://redirect.github.com/stylelint/stylelint/issues/9364">#9364</a>)
(<a
href="https://github.com/sarathfrancis90"><code>@​sarathfrancis90</code></a>).</li>
<li>Fixed: <code>rule-empty-line-before</code> false positives for
shared-line comments with <code>except:
[&quot;after-single-line-comment&quot;]</code> (<a
href="https://redirect.github.com/stylelint/stylelint/issues/9394">#9394</a>)
(<a
href="https://github.com/sarathfrancis90"><code>@​sarathfrancis90</code></a>).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md">stylelint's
changelog</a>.</em></p>
<blockquote>
<h2>17.14.1 - 2026-07-20</h2>
<p>It fixes 4 bugs.</p>
<ul>
<li>Fixed: <code>quiet</code> option suppresses <code>report*</code>
warning reports (<a
href="https://redirect.github.com/stylelint/stylelint/pull/9387">#9387</a>)
(<a
href="https://github.com/ychampion"><code>@​ychampion</code></a>).</li>
<li>Fixed: reported range of unknown rules (<a
href="https://redirect.github.com/stylelint/stylelint/pull/9385">#9385</a>)
(<a
href="https://github.com/ybiquitous"><code>@​ybiquitous</code></a>).</li>
<li>Fixed:
<code>declaration-block-no-redundant-longhand-properties</code> autofix
producing invalid <code>background</code> shorthand when
<code>background-size</code> is present (<a
href="https://redirect.github.com/stylelint/stylelint/pull/9364">#9364</a>)
(<a
href="https://github.com/sarathfrancis90"><code>@​sarathfrancis90</code></a>).</li>
<li>Fixed: <code>rule-empty-line-before</code> false positives for
shared-line comments with <code>except:
[&quot;after-single-line-comment&quot;]</code> (<a
href="https://redirect.github.com/stylelint/stylelint/pull/9394">#9394</a>)
(<a
href="https://github.com/sarathfrancis90"><code>@​sarathfrancis90</code></a>).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/stylelint/stylelint/commit/cd66b035087270dd62d33542154463266cc5e81a"><code>cd66b03</code></a>
Release 17.14.1 (<a
href="https://redirect.github.com/stylelint/stylelint/issues/9397">#9397</a>)</li>
<li><a
href="https://github.com/stylelint/stylelint/commit/7c30fc5987c18c7c017416299d84ad162a53e725"><code>7c30fc5</code></a>
Refactor to replace <code>void</code> with <code>undefined</code> in
union types (<a
href="https://redirect.github.com/stylelint/stylelint/issues/9395">#9395</a>)</li>
<li><a
href="https://github.com/stylelint/stylelint/commit/57c8d0e3c54d83342d23895991d0400ae47e2c14"><code>57c8d0e</code></a>
Fix <code>rule-empty-line-before</code> false positives for shared-line
comments with `e...</li>
<li><a
href="https://github.com/stylelint/stylelint/commit/226f10a68f8fe420f4f32060a23920723c0943ba"><code>226f10a</code></a>
Bump <code>@​vscode/windows-process-tree</code> from 0.7.0 to 0.8.0 (<a
href="https://redirect.github.com/stylelint/stylelint/issues/9392">#9392</a>)</li>
<li><a
href="https://github.com/stylelint/stylelint/commit/13c3c5c7e95853d58247f77e84f342e60d9f88ca"><code>13c3c5c</code></a>
Bump globby from 16.2.0 to 16.2.1 (<a
href="https://redirect.github.com/stylelint/stylelint/issues/9393">#9393</a>)</li>
<li><a
href="https://github.com/stylelint/stylelint/commit/6dafcf5cfd7bbd8c62e19601cca3be7235ce75d4"><code>6dafcf5</code></a>
Document unknown-effort contributions (<a
href="https://redirect.github.com/stylelint/stylelint/issues/9391">#9391</a>)</li>
<li><a
href="https://github.com/stylelint/stylelint/commit/54c2bc8ac9e1008419ea5fd8f3ee4fddb8ff4a96"><code>54c2bc8</code></a>
Fix test for Yarn PnP with npm v12 (<a
href="https://redirect.github.com/stylelint/stylelint/issues/9390">#9390</a>)</li>
<li><a
href="https://github.com/stylelint/stylelint/commit/3e7eb56f721a28cc60070cec25d3776715bfb1a5"><code>3e7eb56</code></a>
Fix <code>declaration-block-no-redundant-longhand-properties</code>
invalid <code>background</code>...</li>
<li><a
href="https://github.com/stylelint/stylelint/commit/438196ede78f9a8be3413b0dedecc25f10dd8ab0"><code>438196e</code></a>
Bump postcss from 8.5.15 to 8.5.16 in the postcss group across 1
directory (#...</li>
<li><a
href="https://github.com/stylelint/stylelint/commit/c1e1489dee8d214f9df51945bc9bac0e66b666a6"><code>c1e1489</code></a>
Fix <code>quiet</code> option to suppress <code>report*</code> warning
reports (<a
href="https://redirect.github.com/stylelint/stylelint/issues/9387">#9387</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/stylelint/stylelint/compare/17.14.0...17.14.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=stylelint&package-manager=npm_and_yarn&previous-version=17.14.0&new-version=17.14.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-23 13:51:28 +00:00
dependabot[bot] 01e58e0895 chore(deps): Bump fast-uri from 3.1.2 to 3.1.4 (#2023)
Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.2 to
3.1.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/fastify/fast-uri/releases">fast-uri's
releases</a>.</em></p>
<blockquote>
<h2>v3.1.4</h2>
<h2>⚠️ Security Release</h2>
<p>Fix for <a
href="https://github.com/fastify/fast-uri/security/advisories/GHSA-v2hh-gcrm-f6hx">https://github.com/fastify/fast-uri/security/advisories/GHSA-v2hh-gcrm-f6hx</a></p>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/fastify/fast-uri/compare/v3.1.3...v3.1.4">https://github.com/fastify/fast-uri/compare/v3.1.3...v3.1.4</a></p>
<h2>v3.1.3</h2>
<h2>⚠️ Security Release</h2>
<ul>
<li>Fixes: <a
href="https://github.com/fastify/fast-uri/security/advisories/GHSA-4c8g-83qw-93j6">https://github.com/fastify/fast-uri/security/advisories/GHSA-4c8g-83qw-93j6</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/fastify/fast-uri/compare/v3.1.2...v3.1.3">https://github.com/fastify/fast-uri/compare/v3.1.2...v3.1.3</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/fastify/fast-uri/commit/6aeece669e4166b2446a89f17c07a3b15dfb7ed4"><code>6aeece6</code></a>
Bumped v3.1.4</li>
<li><a
href="https://github.com/fastify/fast-uri/commit/2d50fbabc80e4d0884fe0f6a98fe118ce6faa353"><code>2d50fba</code></a>
fix: reject literal backslash in URI authority</li>
<li><a
href="https://github.com/fastify/fast-uri/commit/0549fe35b0d482233f3be2816439f3ec803603fa"><code>0549fe3</code></a>
Bumped v3.1.3</li>
<li><a
href="https://github.com/fastify/fast-uri/commit/2a6d357a18a68e6d812824379fd3388a1ae50d05"><code>2a6d357</code></a>
Merge commit from fork</li>
<li>See full diff in <a
href="https://github.com/fastify/fast-uri/compare/v3.1.2...v3.1.4">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=fast-uri&package-manager=npm_and_yarn&previous-version=3.1.2&new-version=3.1.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/maplibre/maputnik/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-22 14:01:04 +00:00
dependabot[bot] 8128fef161 chore(deps-dev): Bump immutable from 5.1.5 to 5.1.9 (#2022)
Bumps [immutable](https://github.com/immutable-js/immutable-js) from
5.1.5 to 5.1.9.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/immutable-js/immutable-js/releases">immutable's
releases</a>.</em></p>
<blockquote>
<h2>v5.1.9</h2>
<h2>What's Changed</h2>
<ul>
<li>fix(List): preserve undefined values when grown past 32 elements by
<a href="https://github.com/spokodev"><code>@​spokodev</code></a> / <a
href="https://github.com/jdeniau"><code>@​jdeniau</code></a> in <a
href="https://redirect.github.com/immutable-js/immutable-js/pull/2235">immutable-js/immutable-js#2235</a>
(originated from <a
href="https://redirect.github.com/immutable-js/immutable-js/pull/2230">immutable-js/immutable-js#2230</a>
)</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/immutable-js/immutable-js/compare/v5.1.8...v5.1.9">https://github.com/immutable-js/immutable-js/compare/v5.1.8...v5.1.9</a></p>
<h2>v5.1.8</h2>
<h2>What's Changed</h2>
<ul>
<li>fix(List): guard oversized bounds in setListBounds. Fixes CVE <a
href="https://github.com/immutable-js/immutable-js/security/advisories/GHSA-v56q-mh7h-f735">https://github.com/immutable-js/immutable-js/security/advisories/GHSA-v56q-mh7h-f735</a></li>
<li>perf(Map): index large hash-collision buckets for faster lookups.
Fixes CVE <a
href="https://github.com/immutable-js/immutable-js/security/advisories/GHSA-xvcm-6775-5m9r">https://github.com/immutable-js/immutable-js/security/advisories/GHSA-xvcm-6775-5m9r</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/immutable-js/immutable-js/compare/v5.1.7...v5.1.8">https://github.com/immutable-js/immutable-js/compare/v5.1.7...v5.1.8</a></p>
<h2>v5.1.7</h2>
<h2>What's Changed</h2>
<ul>
<li>fix(Repeat): lastIndexOf returned size instead of size - 1 by <a
href="https://github.com/chatman-media"><code>@​chatman-media</code></a>
in <a
href="https://redirect.github.com/immutable-js/immutable-js/pull/2227">immutable-js/immutable-js#2227</a></li>
</ul>
<h2>internal</h2>
<ul>
<li>Backport tests from 6.x branch by <a
href="https://github.com/jdeniau"><code>@​jdeniau</code></a> in <a
href="https://redirect.github.com/immutable-js/immutable-js/pull/2199">immutable-js/immutable-js#2199</a></li>
<li>fix(IndexedCollection): <code>has(index)</code> on a lazy
<code>Seq</code> of unknown size now checks index existence instead of
searching for a value equal to the index by <a
href="https://github.com/jdeniau"><code>@​jdeniau</code></a> in <a
href="https://redirect.github.com/immutable-js/immutable-js/pull/2203">immutable-js/immutable-js#2203</a></li>
<li>Better type for reduce if not ininitial value is given by <a
href="https://github.com/jdeniau"><code>@​jdeniau</code></a> in <a
href="https://redirect.github.com/immutable-js/immutable-js/pull/2205">immutable-js/immutable-js#2205</a></li>
<li>Backport test from <a
href="https://redirect.github.com/immutable-js/immutable-js/issues/2193">#2193</a>
by <a href="https://github.com/jdeniau"><code>@​jdeniau</code></a> in <a
href="https://redirect.github.com/immutable-js/immutable-js/pull/2207">immutable-js/immutable-js#2207</a></li>
<li>backport tests from 6.x by <a
href="https://github.com/jdeniau"><code>@​jdeniau</code></a> in <a
href="https://redirect.github.com/immutable-js/immutable-js/pull/2213">immutable-js/immutable-js#2213</a></li>
<li>Firefox link to non-French page by <a
href="https://github.com/jdeniau"><code>@​jdeniau</code></a> in <a
href="https://redirect.github.com/immutable-js/immutable-js/pull/2219">immutable-js/immutable-js#2219</a></li>
<li>Bump esbuild and <code>@​size-limit/preset-small-lib</code> by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/immutable-js/immutable-js/pull/2217">immutable-js/immutable-js#2217</a></li>
<li>backport tests from <a
href="https://redirect.github.com/immutable-js/immutable-js/issues/2210">#2210</a>
by <a href="https://github.com/jdeniau"><code>@​jdeniau</code></a> in <a
href="https://redirect.github.com/immutable-js/immutable-js/pull/2214">immutable-js/immutable-js#2214</a></li>
<li>Bump shell-quote from 1.8.2 to 1.8.4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/immutable-js/immutable-js/pull/2211">immutable-js/immutable-js#2211</a></li>
<li>Bump <code>@​tootallnate/once</code> from 2.0.0 to 2.0.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/immutable-js/immutable-js/pull/2190">immutable-js/immutable-js#2190</a></li>
<li>Bump minimatch from 9.0.5 to 9.0.9 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/immutable-js/immutable-js/pull/2189">immutable-js/immutable-js#2189</a></li>
<li>Bump <code>@​codemirror/theme-one-dark</code> from 6.1.2 to 6.1.3 by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/immutable-js/immutable-js/pull/2168">immutable-js/immutable-js#2168</a></li>
<li>Bump form-data from 4.0.5 to 4.0.6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/immutable-js/immutable-js/pull/2220">immutable-js/immutable-js#2220</a></li>
<li>Bump typescript-eslint from 8.33.0 to 8.61.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/immutable-js/immutable-js/pull/2222">immutable-js/immutable-js#2222</a></li>
<li>Bump <code>@​rollup/plugin-commonjs</code> from 28.0.2 to 29.0.3 by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/immutable-js/immutable-js/pull/2224">immutable-js/immutable-js#2224</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/chatman-media"><code>@​chatman-media</code></a>
made their first contribution in <a
href="https://redirect.github.com/immutable-js/immutable-js/pull/2227">immutable-js/immutable-js#2227</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/immutable-js/immutable-js/compare/v5.1.6...v5.1.7">https://github.com/immutable-js/immutable-js/compare/v5.1.6...v5.1.7</a></p>
<h2>v5.1.6</h2>
<h2>What's Changed</h2>
<ul>
<li>fix(reverseFactory): read reversedSequence.size in __iterator
instead of this by <a
href="https://github.com/jdeniau"><code>@​jdeniau</code></a> in <a
href="https://redirect.github.com/immutable-js/immutable-js/pull/2196">immutable-js/immutable-js#2196</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/immutable-js/immutable-js/blob/main/CHANGELOG.md">immutable's
changelog</a>.</em></p>
<blockquote>
<h2>5.1.9</h2>
<ul>
<li>fix(List): a <code>List</code> grown past 32 elements while all its
values are <code>undefined</code> no longer reads those values back as
<code>null</code> (affected <code>get</code>, iteration,
<code>toArray</code>, <code>equals</code> and
<code>hashCode</code>)</li>
</ul>
<h2>5.1.8</h2>
<ul>
<li>fix(List): guard oversized bounds in setListBounds. Fixes CVE <a
href="https://github.com/immutable-js/immutable-js/security/advisories/GHSA-v56q-mh7h-f735">https://github.com/immutable-js/immutable-js/security/advisories/GHSA-v56q-mh7h-f735</a></li>
<li>perf(Map): index large hash-collision buckets for faster lookups.
Fixes CVE <a
href="https://github.com/immutable-js/immutable-js/security/advisories/GHSA-xvcm-6775-5m9r">https://github.com/immutable-js/immutable-js/security/advisories/GHSA-xvcm-6775-5m9r</a></li>
</ul>
<h2>4.3.9</h2>
<ul>
<li>fix(List): guard oversized bounds in setListBounds. Fixes CVE <a
href="https://github.com/immutable-js/immutable-js/security/advisories/GHSA-v56q-mh7h-f735">https://github.com/immutable-js/immutable-js/security/advisories/GHSA-v56q-mh7h-f735</a></li>
<li>perf(Map): index large hash-collision buckets for faster lookups.
Fixes CVE <a
href="https://github.com/immutable-js/immutable-js/security/advisories/GHSA-xvcm-6775-5m9r">https://github.com/immutable-js/immutable-js/security/advisories/GHSA-xvcm-6775-5m9r</a></li>
</ul>
<h2>5.1.7</h2>
<ul>
<li>fix(Repeat): lastIndexOf returned size instead of size - 1 by <a
href="https://github.com/chatman-media"><code>@​chatman-media</code></a>
in <a
href="https://redirect.github.com/immutable-js/immutable-js/pull/2227">immutable-js/immutable-js#2227</a>.
Fixes CVE <a
href="https://github.com/immutable-js/immutable-js/security/advisories/GHSA-wf6x-7x77-mvgw">CVE-2026-29063
</a></li>
<li>fix(IndexedCollection): <code>has(index)</code> on a lazy
<code>Seq</code> of unknown size now checks index existence instead of
searching for a value equal to the index <a
href="https://redirect.github.com/immutable-js/immutable-js/pull/2203">#2203</a></li>
<li>[TypeScript]: <code>reduce</code>/<code>reduceRight</code> without
an initial value now infer the result type from the collection's values
when the reducer returns a value (e.g. <code>list.reduce((a, b) =&gt; a
+ b)</code> infers <code>number</code>), matching
<code>Array#reduce</code>. Previously an explicit type argument was
required. <a
href="https://redirect.github.com/immutable-js/immutable-js/pull/2205">#2205</a></li>
</ul>
<h2>5.1.6</h2>
<ul>
<li>fix(reverseFactory): read <code>reversedSequence.size</code> in
<code>__iterator</code> instead of this <a
href="https://redirect.github.com/immutable-js/immutable-js/pull/2196">#2196</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/immutable-js/immutable-js/commit/329f7a680efa262c310b938a343295880eefe4fc"><code>329f7a6</code></a>
5.1.9</li>
<li><a
href="https://github.com/immutable-js/immutable-js/commit/21fabd92bc6cd6771918fc966004034fc10a06e0"><code>21fabd9</code></a>
changelog</li>
<li><a
href="https://github.com/immutable-js/immutable-js/commit/009164facb3c69870c6ad4c6472b1baeb02fcdf7"><code>009164f</code></a>
Merge pull request <a
href="https://redirect.github.com/immutable-js/immutable-js/issues/2235">#2235</a>
from immutable-js/fix/avoid-null-when-setsize33</li>
<li><a
href="https://github.com/immutable-js/immutable-js/commit/5b65bfbd583472eaa9686137a61db2a29d63cd36"><code>5b65bfb</code></a>
fix(List): preserve undefined values when grown past 32 elements</li>
<li><a
href="https://github.com/immutable-js/immutable-js/commit/50bf39e94105f83f1132cce02b9ee6c45cc7d9ca"><code>50bf39e</code></a>
Update CHANGELOG.md for versions 5.1.8 and 4.3.9</li>
<li><a
href="https://github.com/immutable-js/immutable-js/commit/6496539bd1e9dfa75ce11b053e7d70d79e9ac6f7"><code>6496539</code></a>
5.1.8</li>
<li><a
href="https://github.com/immutable-js/immutable-js/commit/808a83a071c9e22b8199ac256dc7e6e764198aac"><code>808a83a</code></a>
Merge commit from fork</li>
<li><a
href="https://github.com/immutable-js/immutable-js/commit/e51d49fc612ded5ec4dfb94ff294d22074269b0f"><code>e51d49f</code></a>
perf(Map): index large hash-collision buckets for faster lookups</li>
<li><a
href="https://github.com/immutable-js/immutable-js/commit/25c58b094f112dc32e9b0bca79c84f448d0d2a38"><code>25c58b0</code></a>
fix typescript in tests</li>
<li><a
href="https://github.com/immutable-js/immutable-js/commit/a1a1ee412dcaa380ab325196283d06594ffe4b84"><code>a1a1ee4</code></a>
Merge commit from fork</li>
<li>Additional commits viewable in <a
href="https://github.com/immutable-js/immutable-js/compare/v5.1.5...v5.1.9">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=immutable&package-manager=npm_and_yarn&previous-version=5.1.5&new-version=5.1.9)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/maplibre/maputnik/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-22 14:00:36 +00:00
dependabot[bot] 283ef008a6 chore(deps-dev): Bump postcss from 8.5.19 to 8.5.20 (#2021)
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.19 to
8.5.20.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/postcss/postcss/releases">postcss's
releases</a>.</em></p>
<blockquote>
<h2>8.5.20</h2>
<ul>
<li>Fixed missing space if <code>AtRule#params</code> is set after (by
<a
href="https://github.com/sarathfrancis90"><code>@​sarathfrancis90</code></a>).</li>
<li>Fixed mixing AST error on warnings (by <a
href="https://github.com/MahinAnowar"><code>@​MahinAnowar</code></a>).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/postcss/postcss/blob/main/CHANGELOG.md">postcss's
changelog</a>.</em></p>
<blockquote>
<h2>8.5.20</h2>
<ul>
<li>Fixed missing space if <code>AtRule#params</code> is set after (by
<a
href="https://github.com/sarathfrancis90"><code>@​sarathfrancis90</code></a>).</li>
<li>Fixed mixing AST error on warnings (by <a
href="https://github.com/MahinAnowar"><code>@​MahinAnowar</code></a>).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/postcss/postcss/commit/c4ac725d5920916d35be44002b49b7f66f8b1dc8"><code>c4ac725</code></a>
Release 8.5.20 version</li>
<li><a
href="https://github.com/postcss/postcss/commit/c30586d7863d0563e2f2707bd89461636e37f6f6"><code>c30586d</code></a>
Fix missing space when AtRule#params is set after parsing (<a
href="https://redirect.github.com/postcss/postcss/issues/2113">#2113</a>)</li>
<li><a
href="https://github.com/postcss/postcss/commit/5bfc3b9e7463936fdd4898f92dd43c358bfdef62"><code>5bfc3b9</code></a>
Update dependencies</li>
<li><a
href="https://github.com/postcss/postcss/commit/24733fdbfe9abe4a949eb0a10e53db8c87fc9277"><code>24733fd</code></a>
Move back to latest 11 pnpm</li>
<li><a
href="https://github.com/postcss/postcss/commit/728127c427e076ae15f4b4107cae29f9e60d2db4"><code>728127c</code></a>
Update pnpm to check that CI is fixed</li>
<li><a
href="https://github.com/postcss/postcss/commit/806fe21198e210cc85fd87d4afd0c949b04e0c1f"><code>806fe21</code></a>
Rebuild nodes from another PostCSS copy in Warning (<a
href="https://redirect.github.com/postcss/postcss/issues/2112">#2112</a>)</li>
<li><a
href="https://github.com/postcss/postcss/commit/337cb7ef092da6066ec845973af243ed8e378550"><code>337cb7e</code></a>
Improve CI security</li>
<li><a
href="https://github.com/postcss/postcss/commit/7a8ca2d0e5044fbde3df33e2b9730c14528270e3"><code>7a8ca2d</code></a>
Remove patch after updating dependency</li>
<li>See full diff in <a
href="https://github.com/postcss/postcss/compare/8.5.19...8.5.20">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=postcss&package-manager=npm_and_yarn&previous-version=8.5.19&new-version=8.5.20)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-22 13:51:15 +00:00
dependabot[bot] d021155991 chore(deps): Bump actions/checkout from 7.0.0 to 7.0.1 (#2020)
Bumps [actions/checkout](https://github.com/actions/checkout) from 7.0.0
to 7.0.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/releases">actions/checkout's
releases</a>.</em></p>
<blockquote>
<h2>v7.0.1</h2>
<h2>What's Changed</h2>
<ul>
<li>skip running unsafe pr check if input is default by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2518">actions/checkout#2518</a></li>
<li>trim only ascii whitespace for branch by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2521">actions/checkout#2521</a></li>
<li>escape values passed to --unset by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2530">actions/checkout#2530</a></li>
<li>Various dependency updates</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v7...v7.0.1">https://github.com/actions/checkout/compare/v7...v7.0.1</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/blob/main/CHANGELOG.md">actions/checkout's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>v7.0.1</h2>
<ul>
<li>Skip running unsafe pr check if input is default by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2518">actions/checkout#2518</a></li>
<li>Trim only ascii whitespace for branch by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2521">actions/checkout#2521</a></li>
<li>Escape values passed to --unset by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2530">actions/checkout#2530</a></li>
<li>Various dependency updates</li>
</ul>
<h2>v7.0.0</h2>
<ul>
<li>Block checking out fork PR for pull_request_target and workflow_run
by <a href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2454">actions/checkout#2454</a></li>
<li>Various dependency updates</li>
</ul>
<h2>v6.0.3</h2>
<ul>
<li>Fix checkout init for SHA-256 repositories by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2439">actions/checkout#2439</a></li>
<li>fix: expand merge commit SHA regex and add SHA-256 test cases by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li>
</ul>
<h2>v6.0.2</h2>
<ul>
<li>Fix tag handling: preserve annotations and explicit fetch-tags by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2356">actions/checkout#2356</a></li>
</ul>
<h2>v6.0.1</h2>
<ul>
<li>Add worktree support for persist-credentials includeIf by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2327">actions/checkout#2327</a></li>
</ul>
<h2>v6.0.0</h2>
<ul>
<li>Persist creds to a separate file by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2286">actions/checkout#2286</a></li>
<li>Update README to include Node.js 24 support details and requirements
by <a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2248">actions/checkout#2248</a></li>
</ul>
<h2>v5.0.1</h2>
<ul>
<li>Port v6 cleanup to v5 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2301">actions/checkout#2301</a></li>
</ul>
<h2>v5.0.0</h2>
<ul>
<li>Update actions checkout to use node 24 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2226">actions/checkout#2226</a></li>
</ul>
<h2>v4.3.1</h2>
<ul>
<li>Port v6 cleanup to v4 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2305">actions/checkout#2305</a></li>
</ul>
<h2>v4.3.0</h2>
<ul>
<li>docs: update README.md by <a
href="https://github.com/motss"><code>@​motss</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li>
<li>Add internal repos for checking out multiple repositories by <a
href="https://github.com/mouismail"><code>@​mouismail</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li>
<li>Documentation update - add recommended permissions to Readme by <a
href="https://github.com/benwells"><code>@​benwells</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li>
<li>Adjust positioning of user email note and permissions heading by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2044">actions/checkout#2044</a></li>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li>
<li>Update CODEOWNERS for actions by <a
href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2224">actions/checkout#2224</a></li>
<li>Update package dependencies by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li>
</ul>
<h2>v4.2.2</h2>
<ul>
<li><code>url-helper.ts</code> now leverages well-known environment
variables by <a href="https://github.com/jww3"><code>@​jww3</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/1941">actions/checkout#1941</a></li>
<li>Expand unit test coverage for <code>isGhes</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1946">actions/checkout#1946</a></li>
</ul>
<h2>v4.2.1</h2>
<ul>
<li>Check out other refs/* by commit if provided, fall back to ref by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1924">actions/checkout#1924</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/checkout/commit/3d3c42e5aac5ba805825da76410c181273ba90b1"><code>3d3c42e</code></a>
prep v7.0.1 release (<a
href="https://redirect.github.com/actions/checkout/issues/2531">#2531</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/28802689a136bfcdb721715abd713740beecbe07"><code>2880268</code></a>
escape values passed to --unset (<a
href="https://redirect.github.com/actions/checkout/issues/2530">#2530</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/12cd2235efa0937479335606d7c3ac9f6c0973b1"><code>12cd223</code></a>
trim only ascii whitespace for branch (<a
href="https://redirect.github.com/actions/checkout/issues/2521">#2521</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/62661c4e71a304b2823ed026347b8d34c3eac541"><code>62661c4</code></a>
skip running unsafe pr check if input is default (<a
href="https://redirect.github.com/actions/checkout/issues/2518">#2518</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/e8d4307400f9427dba7cb98e488d6ab85f1cec5f"><code>e8d4307</code></a>
Bump the minor-actions-dependencies group with 2 updates (<a
href="https://redirect.github.com/actions/checkout/issues/2499">#2499</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/631c942040754b6e095e929c1677c07e10ed4f87"><code>631c942</code></a>
eslint 9 (<a
href="https://redirect.github.com/actions/checkout/issues/2474">#2474</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/4f1f4aec02e41874fa0262ea8ff5172d7978ad1e"><code>4f1f4ae</code></a>
Bump actions/upload-artifact from 4 to 7 (<a
href="https://redirect.github.com/actions/checkout/issues/2476">#2476</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/ba097532fb203f7e88c9c3c0b899b49469908a92"><code>ba09753</code></a>
Bump actions/checkout from 6 to 7 (<a
href="https://redirect.github.com/actions/checkout/issues/2488">#2488</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/b9e0990d219a03df7633c93f6f005a8fecbcab22"><code>b9e0990</code></a>
Bump docker/login-action from 3.3.0 to 4.2.0 (<a
href="https://redirect.github.com/actions/checkout/issues/2479">#2479</a>)</li>
<li><a
href="https://github.com/actions/checkout/commit/e8cb398be4a550817e382abf69e4c12c76fce1f2"><code>e8cb398</code></a>
Bump docker/build-push-action from 6.5.0 to 7.2.0 (<a
href="https://redirect.github.com/actions/checkout/issues/2478">#2478</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/checkout/compare/9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0...3d3c42e5aac5ba805825da76410c181273ba90b1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=7.0.0&new-version=7.0.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-21 13:53:31 +00:00
dependabot[bot] d06a3e7e1e chore(deps): Bump actions/setup-go from 6.5.0 to 7.0.0 (#2016)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 6.5.0
to 7.0.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/setup-go/releases">actions/setup-go's
releases</a>.</em></p>
<blockquote>
<h2>v7.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Migrate to ESM and upgrade dependencies by <a
href="https://github.com/priyagupta108"><code>@​priyagupta108</code></a>
in <a
href="https://redirect.github.com/actions/setup-go/pull/763">actions/setup-go#763</a></li>
<li>chore(deps): bump <code>@​actions/cache</code> to 6.2.0 by <a
href="https://github.com/philip-gai"><code>@​philip-gai</code></a> in <a
href="https://redirect.github.com/actions/setup-go/pull/771">actions/setup-go#771</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/philip-gai"><code>@​philip-gai</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/setup-go/pull/771">actions/setup-go#771</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-go/compare/v6...v7.0.0">https://github.com/actions/setup-go/compare/v6...v7.0.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/setup-go/commit/b7ad1dad31e06c5925ef5d2fc7ad053ef454303e"><code>b7ad1da</code></a>
chore(deps): bump <code>@​actions/cache</code> to 6.2.0 (<a
href="https://redirect.github.com/actions/setup-go/issues/771">#771</a>)</li>
<li><a
href="https://github.com/actions/setup-go/commit/0778a10ce47b5d450cf60fb94fafad4330008a35"><code>0778a10</code></a>
Migrate to ESM and upgrade dependencies (<a
href="https://redirect.github.com/actions/setup-go/issues/763">#763</a>)</li>
<li>See full diff in <a
href="https://github.com/actions/setup-go/compare/924ae3a1cded613372ab5595356fb5720e22ba16...b7ad1dad31e06c5925ef5d2fc7ad053ef454303e">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/setup-go&package-manager=github_actions&previous-version=6.5.0&new-version=7.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 13:53:46 +00:00
dependabot[bot] e0f27214b7 chore(deps): Bump github/codeql-action/init from 4.37.0 to 4.37.1 (#2015)
Bumps
[github/codeql-action/init](https://github.com/github/codeql-action)
from 4.37.0 to 4.37.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/init's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.1</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/init's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>4.36.1 - 02 Jun 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.0 - 22 May 2026</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle
version to 2.19.4. <a
href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a
href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5">2.25.5</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3926">#3926</a></li>
</ul>
<h2>4.35.5 - 15 May 2026</h2>
<ul>
<li>We have improved how the JavaScript bundles for the CodeQL Action
are generated to avoid duplication across bundles and reduce the size of
the repository by around 70%. This should have no effect on the runtime
behaviour of the CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3899">#3899</a></li>
<li>For performance and accuracy reasons, <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> will now only be enabled on a pull request when
diff-informed analysis is also enabled for that run. If diff-informed
analysis is unavailable (for example, because the PR diff ranges could
not be computed), the action will fall back to a full analysis. <a
href="https://redirect.github.com/github/codeql-action/pull/3791">#3791</a></li>
<li>If multiple inputs are provided for the GitHub-internal
<code>analysis-kinds</code> input, only <code>code-scanning</code> will
be enabled. The <code>analysis-kinds</code> input is experimental, for
GitHub-internal use only, and may change without notice at any time. <a
href="https://redirect.github.com/github/codeql-action/pull/3892">#3892</a></li>
<li>Added an experimental change which, when running a Code Scanning
analysis for a PR with <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> enabled, prefers CodeQL CLI versions that have
a cached overlay-base database for the configured languages. This speeds
up analysis for a repository when there is not yet a cached overlay-base
database for the latest CLI version. We expect to roll this change out
to everyone in May. <a
href="https://redirect.github.com/github/codeql-action/pull/3880">#3880</a></li>
</ul>
<h2>4.35.4 - 07 May 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.4">2.25.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3881">#3881</a></li>
</ul>
<h2>4.35.3 - 01 May 2026</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/7188fc363630916deb702c7fdcf4e481b751f97a"><code>7188fc3</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4020">#4020</a>
from github/update-v4.37.1-9e7c07009</li>
<li><a
href="https://github.com/github/codeql-action/commit/c8b5f69be686908c3dfd844428137d56fe80c936"><code>c8b5f69</code></a>
Update changelog for v4.37.1</li>
<li><a
href="https://github.com/github/codeql-action/commit/9e7c070092090e89e8b3d62f977d4456e0732cd7"><code>9e7c070</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4014">#4014</a>
from github/mbg/explicit-remote-prefix</li>
<li><a
href="https://github.com/github/codeql-action/commit/3492b7e9ab96e28b1d8b971345d30e929c6f8fee"><code>3492b7e</code></a>
Change <code>REMOTE_PATH_PREFIX</code> to <code>remote=</code></li>
<li><a
href="https://github.com/github/codeql-action/commit/3654baa924bc6456db54002581cb7c1c877548c4"><code>3654baa</code></a>
Merge remote-tracking branch 'origin/main' into
mbg/explicit-remote-prefix</li>
<li><a
href="https://github.com/github/codeql-action/commit/2d682ac05f1b3588aaff3814826bede39b9ba6bb"><code>2d682ac</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4017">#4017</a>
from github/dependabot/github_actions/dot-github/wor...</li>
<li><a
href="https://github.com/github/codeql-action/commit/23f6a50753a88efd9b7ae8687b29f6bdb65f6250"><code>23f6a50</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4009">#4009</a>
from github/mbg/action-state/additions</li>
<li><a
href="https://github.com/github/codeql-action/commit/1ee3c75d1988ab8621f01ebb165115c38d56df91"><code>1ee3c75</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4018">#4018</a>
from github/dependabot/github_actions/dot-github/wor...</li>
<li><a
href="https://github.com/github/codeql-action/commit/e053684dc500899b0b5520edc8549ac0f1ed730b"><code>e053684</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4015">#4015</a>
from github/dependabot/npm_and_yarn/npm-minor-fd2e83...</li>
<li><a
href="https://github.com/github/codeql-action/commit/6803c5671d2f87a83ed96e151c441b1cb3bdc66a"><code>6803c56</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4019">#4019</a>
from github/update-bundle/codeql-bundle-v2.26.1</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/99df26d4f13ea111d4ec1a7dddef6063f76b97e9...7188fc363630916deb702c7fdcf4e481b751f97a">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action/init&package-manager=github_actions&previous-version=4.37.0&new-version=4.37.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 13:53:09 +00:00
dependabot[bot] 167f83abe9 chore(deps-dev): Bump i18next-cli from 1.65.0 to 1.66.2 (#2014)
Bumps [i18next-cli](https://github.com/i18next/i18next-cli) from 1.65.0
to 1.66.2.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/i18next/i18next-cli/blob/main/CHANGELOG.md">i18next-cli's
changelog</a>.</em></p>
<blockquote>
<h2>1.66.2</h2>
<ul>
<li>fix(extract): ignore <code>&lt;Trans&gt;</code> components that have
neither an <code>i18nKey</code>
expression nor non-whitespace children, preventing <code>&lt;Trans
/&gt;</code> and
spread-only variants from creating an empty-string translation key
(<a
href="https://redirect.github.com/i18next/i18next-cli/issues/274">#274</a>).</li>
</ul>
<h2>1.66.1</h2>
<ul>
<li>fix(logger): <code>ConsoleLogger.warn</code>/<code>error</code> no
longer swallow their optional
second argument — plugin-hook failures (e.g.
<code>Plugin my-plugin onVisitNode failed:</code>) now print the caught
error instead
of a bare message
(<a
href="https://redirect.github.com/i18next/i18next-cli/pull/273">#273</a>,
thanks
<a
href="https://github.com/pjrobertson"><code>@​pjrobertson</code></a>).</li>
</ul>
<h2>1.66.0</h2>
<ul>
<li>fix(funnel): never print locize funnel/tip messages in
non-interactive
contexts — they are now suppressed when <code>CI=true</code> or stdout
is not a TTY,
and the once-per-24h cooldown applies unconditionally (newly created
translation files no longer bypass it). CI pipelines and first
extractions
no longer get promotional output
(follow-up to <a
href="https://redirect.github.com/i18next/i18next-cli/issues/20">#20</a>
/
<a
href="https://redirect.github.com/i18next/i18next-cli/issues/222">#222</a>).</li>
<li>feat(extract): new <code>sort: 'locize'</code> preset that mirrors
the key order of
locize-published files (plain UTF-16 code-unit sort, case-sensitive, no
canonical plural-form reordering), making <code>extract</code> followed
by
<code>locize-sync</code>/<code>locize-download</code> round-trip without
spurious reorder diffs
(follow-up to <a
href="https://redirect.github.com/i18next/i18next-cli/issues/106">#106</a>).</li>
<li>test(locize): assert <code>--cdn-type</code> is forwarded to
<code>download</code> and <code>migrate</code>,
guarding against the <a
href="https://redirect.github.com/i18next/i18next-cli/issues/156">#156</a>
class of regression.</li>
<li>docs(readme): new &quot;Dynamic Keys&quot; section documenting the
built-in TS
type-aware resolution of finite dynamic keys shipped in v1.49
(<a
href="https://redirect.github.com/i18next/i18next-cli/issues/210">#210</a>)
with
<code>preservePatterns</code> reserved for truly runtime-dynamic keys;
note in
<code>migrate-config</code> that i18next-cli requires Node.js &gt;= 22
(i18next-parser
still supported Node 18/20).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/i18next/i18next-cli/commit/34f2b96dca5502c536b3e181ddfea80b43a66dcb"><code>34f2b96</code></a>
1.66.2</li>
<li><a
href="https://github.com/i18next/i18next-cli/commit/6761334ce2f4a4a105be3163c8b2acaa1e8b7796"><code>6761334</code></a>
fix(extract): skip empty Trans component keys (<a
href="https://redirect.github.com/i18next/i18next-cli/issues/274">#274</a>)</li>
<li><a
href="https://github.com/i18next/i18next-cli/commit/66acdcb18094054c8703fb73d7e82fbf6db9ddb1"><code>66acdcb</code></a>
1.66.1</li>
<li><a
href="https://github.com/i18next/i18next-cli/commit/8a5a136a0902063ef573156ccb9227c32527a3af"><code>8a5a136</code></a>
fix(logger): simplify error signature to unknown; changelog for 1.66.1
(<a
href="https://redirect.github.com/i18next/i18next-cli/issues/273">#273</a>)</li>
<li><a
href="https://github.com/i18next/i18next-cli/commit/cada9ca78fe0c52f0172f6e90a25842ccaaaa36a"><code>cada9ca</code></a>
Allow passing extra args (e.g. exceptions to the logger warn/error) (<a
href="https://redirect.github.com/i18next/i18next-cli/issues/273">#273</a>)</li>
<li><a
href="https://github.com/i18next/i18next-cli/commit/6555ff065e0de0aa42e9f350008e6d0de98e9833"><code>6555ff0</code></a>
docs: Code-Splitting Translations section (one namespace per route, lazy
chun...</li>
<li><a
href="https://github.com/i18next/i18next-cli/commit/69bb99618527ddfd91f5003dfc128f177b05dbe0"><code>69bb996</code></a>
1.66.0</li>
<li><a
href="https://github.com/i18next/i18next-cli/commit/d94a1468fac9a83cffa6c8c1241b3b9ec0cd1258"><code>d94a146</code></a>
feat(extract): sort: 'locize' preset for locize round-trip key order (<a
href="https://redirect.github.com/i18next/i18next-cli/issues/106">#106</a>);
...</li>
<li><a
href="https://github.com/i18next/i18next-cli/commit/3a278ee443805ea1468e7e377004d7ede968381a"><code>3a278ee</code></a>
fix(funnel): suppress promotional output in CI/non-TTY and always
respect the...</li>
<li><a
href="https://github.com/i18next/i18next-cli/commit/8c69ab921ba394a5f09fcac878a3feaf345f5d97"><code>8c69ab9</code></a>
docs: surface localize --print-agent-prompt in the top callout</li>
<li>See full diff in <a
href="https://github.com/i18next/i18next-cli/compare/v1.65.0...v1.66.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=i18next-cli&package-manager=npm_and_yarn&previous-version=1.65.0&new-version=1.66.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-20 13:51:49 +00:00
dependabot[bot] 0936afc2f9 chore(deps-dev): Bump typescript-eslint from 8.62.1 to 8.64.0 (#2013)
Bumps
[typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint)
from 8.62.1 to 8.64.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/releases">typescript-eslint's
releases</a>.</em></p>
<blockquote>
<h2>v8.64.0</h2>
<h2>8.64.0 (2026-07-13)</h2>
<h3>🚀 Features</h3>
<ul>
<li>support parsing <code>import defer</code> (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12513">#12513</a>)</li>
<li><strong>eslint-plugin:</strong> [no-loop-func] support
<code>using</code> / <code>await using</code> declarations and deprecate
the rule (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12500">#12500</a>)</li>
<li><strong>typescript-estree:</strong> throw for invalid definite
assignment in class properties (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12543">#12543</a>)</li>
</ul>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [require-array-sort-compare] handle
constrained arrays (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12512">#12512</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Evyatar Daud <a
href="https://github.com/StyleShit"><code>@​StyleShit</code></a></li>
<li>송재욱</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.64.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
<h2>v8.63.0</h2>
<h2>8.63.0 (2026-07-06)</h2>
<h3>🚀 Features</h3>
<ul>
<li><strong>eslint-plugin:</strong> [no-misused-promises] detect async
usage of a sync dispose usage (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12426">#12426</a>)</li>
</ul>
<h3>🩹 Fixes</h3>
<ul>
<li><strong>eslint-plugin:</strong> [method-signature-style] suggest
converting readonly function properties instead of emitting invalid
syntax (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12447">#12447</a>,
<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/issues/12446">#12446</a>)</li>
<li><strong>eslint-plugin:</strong> [no-unnecessary-type-assertion]
handle optional-chained calls to overloaded functions (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12491">#12491</a>,
<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/issues/12485">#12485</a>)</li>
<li><strong>eslint-plugin:</strong> [no-base-to-string] don't flag a
shadowed String() call (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12492">#12492</a>)</li>
<li><strong>scope-manager:</strong> export ClassStaticBlockScope (<a
href="https://redirect.github.com/typescript-eslint/typescript-eslint/pull/12460">#12460</a>)</li>
</ul>
<h3>❤️ Thank You</h3>
<ul>
<li>Evyatar Daud <a
href="https://github.com/StyleShit"><code>@​StyleShit</code></a></li>
<li>Kristjan <a
href="https://github.com/KristjanTammekivi"><code>@​KristjanTammekivi</code></a></li>
<li>Michael Naumov <a
href="https://github.com/mnaoumov"><code>@​mnaoumov</code></a></li>
<li>Serhii Leniv <a
href="https://github.com/Serhii-Leniv"><code>@​Serhii-Leniv</code></a></li>
<li>송재욱</li>
</ul>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.63.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md">typescript-eslint's
changelog</a>.</em></p>
<blockquote>
<h2>8.64.0 (2026-07-13)</h2>
<p>This was a version bump only for typescript-eslint to align it with
other projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.64.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
<h2>8.63.0 (2026-07-06)</h2>
<p>This was a version bump only for typescript-eslint to align it with
other projects, there were no code changes.</p>
<p>See <a
href="https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.63.0">GitHub
Releases</a> for more information.</p>
<p>You can read about our <a
href="https://typescript-eslint.io/users/versioning">versioning
strategy</a> and <a
href="https://typescript-eslint.io/users/releases">releases</a> on our
website.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/414d9abbf66f77796ab12ec7e75b07722e592832"><code>414d9ab</code></a>
chore(release): publish 8.64.0</li>
<li><a
href="https://github.com/typescript-eslint/typescript-eslint/commit/290cf6cdcc5ffb00c5b8f3e1e0e9f2fd8cc96374"><code>290cf6c</code></a>
chore(release): publish 8.63.0</li>
<li>See full diff in <a
href="https://github.com/typescript-eslint/typescript-eslint/commits/v8.64.0/packages/typescript-eslint">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=typescript-eslint&package-manager=npm_and_yarn&previous-version=8.62.1&new-version=8.64.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-17 13:51:37 +00:00
dependabot[bot] b664caa13e chore(deps-dev): Bump postcss from 8.5.17 to 8.5.19 (#2011)
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.17 to
8.5.19.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/postcss/postcss/releases">postcss's
releases</a>.</em></p>
<blockquote>
<h2>8.5.19</h2>
<ul>
<li>Fixed cleaning <code>before</code> for new nodes inserted to
<code>Root</code> (by <a
href="https://github.com/MahinAnowar"><code>@​MahinAnowar</code></a>).</li>
</ul>
<h2>8.5.18</h2>
<ul>
<li>Restricted loading previous source maps file to the
<code>opts.from</code> folder for security reasons (use <code>unsafeMap:
true</code> to disable the check).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/postcss/postcss/blob/main/CHANGELOG.md">postcss's
changelog</a>.</em></p>
<blockquote>
<h2>8.5.19</h2>
<ul>
<li>Fixed cleaning <code>before</code> for new nodes inserted to
<code>Root</code> (by <a
href="https://github.com/MahinAnowar"><code>@​MahinAnowar</code></a>).</li>
</ul>
<h2>8.5.18</h2>
<ul>
<li>Restricted loading previous source maps file to the
<code>opts.from</code> folder for security reasons (use <code>unsafeMap:
true</code> to disable the check).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/postcss/postcss/commit/9543b22769bef5bcd47600fbca752204c106cda8"><code>9543b22</code></a>
Release 8.5.19 version</li>
<li><a
href="https://github.com/postcss/postcss/commit/3d13bf9360652922de0535f6257e1648187363f0"><code>3d13bf9</code></a>
Fix CI on Windows too</li>
<li><a
href="https://github.com/postcss/postcss/commit/00d0dd2322162f6083d507ea6954685e1c92f165"><code>00d0dd2</code></a>
Keep explicitly set raws.before when inserting nodes into root (<a
href="https://redirect.github.com/postcss/postcss/issues/2111">#2111</a>)</li>
<li><a
href="https://github.com/postcss/postcss/commit/7a05b33e7a15d6f80d90098784170ad3dca39180"><code>7a05b33</code></a>
Temporary fix CI</li>
<li><a
href="https://github.com/postcss/postcss/commit/4c0d194c136fd374495d0993c890d794cab65b81"><code>4c0d194</code></a>
Release 8.5.18 version</li>
<li><a
href="https://github.com/postcss/postcss/commit/92b4e7891ec7b811821d01acc8aa0f010caf41e2"><code>92b4e78</code></a>
Update dependencies</li>
<li><a
href="https://github.com/postcss/postcss/commit/95663d3eb7ba26f4854dd19d3b4f4425760cf56c"><code>95663d3</code></a>
Limit where source map can be loaded for security reasons</li>
<li>See full diff in <a
href="https://github.com/postcss/postcss/compare/8.5.17...8.5.19">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=postcss&package-manager=npm_and_yarn&previous-version=8.5.17&new-version=8.5.19)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-16 13:51:58 +00:00
dependabot[bot] d90d7b743b chore(deps): Bump actions/setup-node from 6.4.0 to 7.0.0 (#2012)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from
6.4.0 to 7.0.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/setup-node/releases">actions/setup-node's
releases</a>.</em></p>
<blockquote>
<h2>v7.0.0</h2>
<h2>What's Changed</h2>
<h3>Enhancements:</h3>
<ul>
<li>Add cache-primary-key and cache-matched-key as outputs by <a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a> in
<a
href="https://redirect.github.com/actions/setup-node/pull/1577">actions/setup-node#1577</a></li>
<li>Migrate to ESM and upgrade dependencies by <a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a> in
<a
href="https://redirect.github.com/actions/setup-node/pull/1574">actions/setup-node#1574</a></li>
</ul>
<h3>Bug fixes:</h3>
<ul>
<li>Remove dummy NODE_AUTH_TOKEN export by <a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a> in
<a
href="https://redirect.github.com/actions/setup-node/pull/1558">actions/setup-node#1558</a></li>
<li>Only use <code>mirrorToken</code> in <code>getManifest</code> if
it's provided by <a
href="https://github.com/deiga"><code>@​deiga</code></a> in <a
href="https://redirect.github.com/actions/setup-node/pull/1548">actions/setup-node#1548</a></li>
</ul>
<h3>Documentation updates:</h3>
<ul>
<li>Add documentation for publishing to npm with Trusted Publisher
(OIDC) by <a
href="https://github.com/chiranjib-swain"><code>@​chiranjib-swain</code></a>
in <a
href="https://redirect.github.com/actions/setup-node/pull/1536">actions/setup-node#1536</a></li>
<li>docs: Update restore-only cache documentation by <a
href="https://github.com/priya-kinthali"><code>@​priya-kinthali</code></a>
in <a
href="https://redirect.github.com/actions/setup-node/pull/1550">actions/setup-node#1550</a></li>
<li>docs: Update caching recommendations to mitigate cache poisoning
risks by <a
href="https://github.com/chiranjib-swain"><code>@​chiranjib-swain</code></a>
in <a
href="https://redirect.github.com/actions/setup-node/pull/1567">actions/setup-node#1567</a></li>
</ul>
<h3>Dependency update:</h3>
<ul>
<li>Upgrade <code>@​actions/cache</code> to 5.1.0, log cache write
denied by <a
href="https://github.com/jasongin"><code>@​jasongin</code></a> in <a
href="https://redirect.github.com/actions/setup-node/pull/1569">actions/setup-node#1569</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/chiranjib-swain"><code>@​chiranjib-swain</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-node/pull/1536">actions/setup-node#1536</a></li>
<li><a href="https://github.com/deiga"><code>@​deiga</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/setup-node/pull/1548">actions/setup-node#1548</a></li>
<li><a href="https://github.com/jasongin"><code>@​jasongin</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-node/pull/1569">actions/setup-node#1569</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-node/compare/v6...v7.0.0">https://github.com/actions/setup-node/compare/v6...v7.0.0</a></p>
<h2>v6.5.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update <code>@​actions/cache</code> to 5.1.0 and add security
overrides for undici and fast-xml-parser by <a
href="https://github.com/HarithaVattikuti"><code>@​HarithaVattikuti</code></a>
in <a
href="https://redirect.github.com/actions/setup-node/pull/1579">actions/setup-node#1579</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-node/compare/v6.4.0...v6.5.0">https://github.com/actions/setup-node/compare/v6.4.0...v6.5.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/setup-node/commit/820762786026740c76f36085b0efc47a31fe5020"><code>8207627</code></a>
Migrate to ESM and upgrade dependencies (<a
href="https://redirect.github.com/actions/setup-node/issues/1574">#1574</a>)</li>
<li><a
href="https://github.com/actions/setup-node/commit/04be95cf3511ea51ebf9f224ddfb99cc7ab87cd4"><code>04be95c</code></a>
Add cache-primary-key and cache-matched-key as outputs (<a
href="https://redirect.github.com/actions/setup-node/issues/1577">#1577</a>)</li>
<li><a
href="https://github.com/actions/setup-node/commit/7c2c68d20d402ed6a201ada70a81341941093140"><code>7c2c68d</code></a>
docs: Update caching recommendations to mitigate cache poisoning risks
(<a
href="https://redirect.github.com/actions/setup-node/issues/1567">#1567</a>)</li>
<li><a
href="https://github.com/actions/setup-node/commit/6a61c0375d66246de94630495909f12cf8dac84d"><code>6a61c03</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/setup-node/issues/1569">#1569</a>
from jasongin/update-actions-cache-5.1.0</li>
<li><a
href="https://github.com/actions/setup-node/commit/30eb73b41ded577900c1ebf968ef95cdf8f7434f"><code>30eb73b</code></a>
Resolve high-severity audit issues</li>
<li><a
href="https://github.com/actions/setup-node/commit/4e1a87a501d0302f99e30e2748568adcb388d09f"><code>4e1a87a</code></a>
Update dist</li>
<li><a
href="https://github.com/actions/setup-node/commit/360237f0c01778d0c17291f75c56d6feae4f7574"><code>360237f</code></a>
Strict equality</li>
<li><a
href="https://github.com/actions/setup-node/commit/4f8aac5beb2f0854bc79651567a18c67eb0b9de3"><code>4f8aac5</code></a>
Bump <code>@​actions/cache</code> to 5.1.0, log cache write denied</li>
<li><a
href="https://github.com/actions/setup-node/commit/f4a67bbeca970f103397d3d2b9462cf787cd2980"><code>f4a67bb</code></a>
Only use <code>mirrorToken</code> in <code>getManifest</code> if it's
provided (<a
href="https://redirect.github.com/actions/setup-node/issues/1548">#1548</a>)</li>
<li><a
href="https://github.com/actions/setup-node/commit/0355742c943ddb13ca8a6b700f824231caa91e75"><code>0355742</code></a>
Remove dummy NODE_AUTH_TOKEN export (<a
href="https://redirect.github.com/actions/setup-node/issues/1558">#1558</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/setup-node/compare/48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e...820762786026740c76f36085b0efc47a31fe5020">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/setup-node&package-manager=github_actions&previous-version=6.4.0&new-version=7.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-16 13:51:07 +00:00
dependabot[bot] 0152a02858 chore(deps-dev): Bump postcss from 8.5.16 to 8.5.17 (#2009)
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.16 to
8.5.17.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/postcss/postcss/releases">postcss's
releases</a>.</em></p>
<blockquote>
<h2>8.5.17</h2>
<ul>
<li>Fixed <code>Maximum call stack size exceeded</code> error.</li>
<li>Fixed Prototype hijacking for <code>postcss.fromJSON()</code>.</li>
<li>Fixed <code>Input#origin()</code> for unmapped end position (by <a
href="https://github.com/chatman-media"><code>@​chatman-media</code></a>).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/postcss/postcss/blob/main/CHANGELOG.md">postcss's
changelog</a>.</em></p>
<blockquote>
<h2>8.5.17</h2>
<ul>
<li>Fixed <code>Maximum call stack size exceeded</code> error.</li>
<li>Fixed Prototype hijacking for <code>postcss.fromJSON()</code>.</li>
<li>Fixed <code>Input#origin()</code> for unmapped end position (by <a
href="https://github.com/chatman-media"><code>@​chatman-media</code></a>).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/postcss/postcss/commit/74e25ae9f4efaa56a41a449064a655d7da78072c"><code>74e25ae</code></a>
Release 8.5.17 version</li>
<li><a
href="https://github.com/postcss/postcss/commit/d1518afd5a88f42728b30b87f8917210f363f9f1"><code>d1518af</code></a>
Fix Maximum call stack size exceeded error</li>
<li><a
href="https://github.com/postcss/postcss/commit/2421312ffea96ba77b35ce24a1b2d9c2e22b5e83"><code>2421312</code></a>
Fix linter</li>
<li><a
href="https://github.com/postcss/postcss/commit/a50352c583df991710f92ccac25b36304695161a"><code>a50352c</code></a>
Fix CI</li>
<li><a
href="https://github.com/postcss/postcss/commit/33948f0969bb858acdd52c9692e3a785a3ed0a73"><code>33948f0</code></a>
Prevent prototype hijacking in fromJSON</li>
<li><a
href="https://github.com/postcss/postcss/commit/2131909351161cd2c5fc2be58b14919a873ea824"><code>2131909</code></a>
Update dependencies</li>
<li><a
href="https://github.com/postcss/postcss/commit/93440abcca92793b31c5d1fdf5f2da7b58b27599"><code>93440ab</code></a>
Fix non-closed <code>\&lt;div align=&quot;center&quot;&gt;</code> in
README (<a
href="https://redirect.github.com/postcss/postcss/issues/2110">#2110</a>)</li>
<li><a
href="https://github.com/postcss/postcss/commit/1bf9076867e70f5a01776aebebee5aacbf9771ce"><code>1bf9076</code></a>
Remove old sponsors</li>
<li><a
href="https://github.com/postcss/postcss/commit/2d44be0923873ea6af2c3a1a191148e2ce9d733f"><code>2d44be0</code></a>
docs: fix invalid JS in writing-a-plugin examples (<a
href="https://redirect.github.com/postcss/postcss/issues/2107">#2107</a>)</li>
<li><a
href="https://github.com/postcss/postcss/commit/3298727755a676beced3cbe67015d42e70396e4f"><code>3298727</code></a>
Fix <code>Input#origin()</code> mixing null and undefined for unmapped
end position (<a
href="https://redirect.github.com/postcss/postcss/issues/2106">#2106</a>)</li>
<li>See full diff in <a
href="https://github.com/postcss/postcss/compare/8.5.16...8.5.17">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=postcss&package-manager=npm_and_yarn&previous-version=8.5.16&new-version=8.5.17)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-15 13:50:43 +00:00
dependabot[bot] 0b076e3896 chore(deps-dev): Bump eslint from 10.6.0 to 10.7.0 (#2007)
Bumps [eslint](https://github.com/eslint/eslint) from 10.6.0 to 10.7.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/eslint/eslint/releases">eslint's
releases</a>.</em></p>
<blockquote>
<h2>v10.7.0</h2>
<h2>Features</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/cf2a9bf2d982642f760370c15f1196f4658f4e27"><code>cf2a9bf</code></a>
feat: add errorClassNames option to preserve-caught-error rule (<a
href="https://redirect.github.com/eslint/eslint/issues/21032">#21032</a>)
(sethamus)</li>
<li><a
href="https://github.com/eslint/eslint/commit/f8b873aae53610b80b5c1005606716e05ed3b91f"><code>f8b873a</code></a>
feat: max-nested-callbacks option for constructor callbacks (<a
href="https://redirect.github.com/eslint/eslint/issues/21063">#21063</a>)
(fnx)</li>
<li><a
href="https://github.com/eslint/eslint/commit/557fde8bc633d26ea4fe287d4483f287e8e6080f"><code>557fde8</code></a>
feat: support computed <code>Number.parseInt</code> member access in
<code>radix</code> rule (<a
href="https://redirect.github.com/eslint/eslint/issues/21041">#21041</a>)
(Pixel)</li>
<li><a
href="https://github.com/eslint/eslint/commit/0b4a73b65808f5a8fd192f15db2bf5e47002b9f7"><code>0b4a73b</code></a>
feat: add suggestions to no-compare-neg-zero (<a
href="https://redirect.github.com/eslint/eslint/issues/21034">#21034</a>)
(den$)</li>
<li><a
href="https://github.com/eslint/eslint/commit/96cdd427db5f26c6403fba92b0f7ae4556c595ec"><code>96cdd42</code></a>
feat: report invalid signed numeric radix values in <code>radix</code>
rule (<a
href="https://redirect.github.com/eslint/eslint/issues/21030">#21030</a>)
(Pixel)</li>
</ul>
<h2>Bug Fixes</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/3e7bf15e69e6d3a2c8832356bcc2e9903cc4eede"><code>3e7bf15</code></a>
fix: apply <code>ignoreClassesWithImplements</code> to class expressions
(<a
href="https://redirect.github.com/eslint/eslint/issues/21069">#21069</a>)
(Pixel)</li>
<li><a
href="https://github.com/eslint/eslint/commit/0d7d70cf013ec5bf1e4f5a0fcf7128bdf059b2d4"><code>0d7d70c</code></a>
fix: insert cause outside wrapping parens in preserve-caught-error (<a
href="https://redirect.github.com/eslint/eslint/issues/21062">#21062</a>)
(Mahin Anowar)</li>
<li><a
href="https://github.com/eslint/eslint/commit/75ec753226010867270787b412f3dae412e421e6"><code>75ec753</code></a>
fix: handle static template literals in <code>eqeqeq</code> rule (<a
href="https://redirect.github.com/eslint/eslint/issues/21058">#21058</a>)
(Pixel)</li>
<li><a
href="https://github.com/eslint/eslint/commit/b717a22e2408389a2ad40af66543e46390669eb6"><code>b717a22</code></a>
fix: prevent <code>eqeqeq</code> null option from reporting non-equality
operators (<a
href="https://redirect.github.com/eslint/eslint/issues/21057">#21057</a>)
(Pixel)</li>
<li><a
href="https://github.com/eslint/eslint/commit/e35b05f1961dcd691611bd68b6ff8a87072d6f76"><code>e35b05f</code></a>
fix: avoid <code>no-invalid-regexp</code> false positive for shadowed
RegExp (<a
href="https://redirect.github.com/eslint/eslint/issues/21051">#21051</a>)
(Pixel)</li>
<li><a
href="https://github.com/eslint/eslint/commit/a3172b69c7db63ea0321355543e3f527c7d8b76a"><code>a3172b6</code></a>
fix: avoid <code>no-control-regex</code> false positive for shadowed
RegExp (<a
href="https://redirect.github.com/eslint/eslint/issues/21050">#21050</a>)
(Pixel)</li>
<li><a
href="https://github.com/eslint/eslint/commit/d1f637eca27e523d613991c6bea5b8726b810e4c"><code>d1f637e</code></a>
fix: parenthesize sequence expression operands in no-implicit-coercion
(<a
href="https://redirect.github.com/eslint/eslint/issues/21045">#21045</a>)
(spokodev)</li>
<li><a
href="https://github.com/eslint/eslint/commit/8859bafb018e2b23b5110d52e491b69b94ad890a"><code>8859baf</code></a>
fix: avoid prefer-numeric-literals false positive for shadowed globals
(<a
href="https://redirect.github.com/eslint/eslint/issues/21047">#21047</a>)
(한국)</li>
<li><a
href="https://github.com/eslint/eslint/commit/a9e5961050676ef29dba9649dfcd7233d21760c7"><code>a9e5961</code></a>
fix: use-isnan false positive on shadowed NaN/Number (<a
href="https://redirect.github.com/eslint/eslint/issues/20958">#20958</a>)
(sethamus)</li>
<li><a
href="https://github.com/eslint/eslint/commit/8a240a76108f30f2476e61a925e4d32d378cce5e"><code>8a240a7</code></a>
fix: avoid false positives in <code>radix</code> rule for spread
arguments (<a
href="https://redirect.github.com/eslint/eslint/issues/21044">#21044</a>)
(Pixel)</li>
</ul>
<h2>Documentation</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/c30d80801ca561bdf65c6c3eba4ee57dced278cf"><code>c30d808</code></a>
docs: Update README (GitHub Actions Bot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/5139800b23e77fdd1a76664e1b36f478942eae55"><code>5139800</code></a>
docs: document ESLint migration codemods in v9 and v10 guides (<a
href="https://redirect.github.com/eslint/eslint/issues/20980">#20980</a>)
(Alex Bit)</li>
<li><a
href="https://github.com/eslint/eslint/commit/04174cbfaac1f9555400b5332d69056e6aab9cc6"><code>04174cb</code></a>
docs: Update README (GitHub Actions Bot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/026e1304080512bcddfd2af3557fff86474ff949"><code>026e130</code></a>
docs: update semver policy for bug fixes (<a
href="https://redirect.github.com/eslint/eslint/issues/21048">#21048</a>)
(Milos Djermanovic)</li>
<li><a
href="https://github.com/eslint/eslint/commit/9d42fefb0e05ecd0b256c2ebed479fc868862f0c"><code>9d42fef</code></a>
docs: Update README (GitHub Actions Bot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/b23015955c8d6e6516076190730f538c86927f26"><code>b230159</code></a>
docs: Update README (GitHub Actions Bot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/0129972ba122e95bece6040fda44d20f6799246a"><code>0129972</code></a>
docs: correct <code>**/.js</code> glob to <code>**/*.js</code> in config
files guide (<a
href="https://redirect.github.com/eslint/eslint/issues/21036">#21036</a>)
(EduardF1)</li>
</ul>
<h2>Chores</h2>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/948937986f2aa3cccb27a4c80694a85e5b0a5f64"><code>9489379</code></a>
chore: update dependency <code>@​eslint/eslintrc</code> to ^3.3.6 (<a
href="https://redirect.github.com/eslint/eslint/issues/21076">#21076</a>)
(renovate[bot])</li>
<li><a
href="https://github.com/eslint/eslint/commit/81a4774a928211bd0c01a06a1517736ece594522"><code>81a4774</code></a>
chore: updates for v9.39.5 release (Jenkins)</li>
<li><a
href="https://github.com/eslint/eslint/commit/983541491393323d17717683f6a2e6dbef4bc3f4"><code>9835414</code></a>
chore: enable <code>$ExpectType</code> annotations in all TypeScript
files (<a
href="https://redirect.github.com/eslint/eslint/issues/21071">#21071</a>)
(Francesco Trotta)</li>
<li><a
href="https://github.com/eslint/eslint/commit/72adf6bd214c142b38aabb4b8f6f908079c5301a"><code>72adf6b</code></a>
chore: restrict <code>markdownlint-cli2</code> updates in renovate (<a
href="https://redirect.github.com/eslint/eslint/issues/21067">#21067</a>)
(lumir)</li>
<li><a
href="https://github.com/eslint/eslint/commit/833ec10fd702644e94334edd3cd2aa313174a958"><code>833ec10</code></a>
chore: update dependency prettier to v3.9.4 (<a
href="https://redirect.github.com/eslint/eslint/issues/21061">#21061</a>)
(renovate[bot])</li>
<li><a
href="https://github.com/eslint/eslint/commit/7ea106ddcb6af7db8f36d62a28dbaba94d33598e"><code>7ea106d</code></a>
chore: update ecosystem plugins (<a
href="https://redirect.github.com/eslint/eslint/issues/21059">#21059</a>)
(ESLint Bot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/8fb550e7c257fbdc4108fc8e63db43c1d914d472"><code>8fb550e</code></a>
chore: add prettier update commit to <code>.git-blame-ignore-revs</code>
(<a
href="https://redirect.github.com/eslint/eslint/issues/21056">#21056</a>)
(lumir)</li>
<li><a
href="https://github.com/eslint/eslint/commit/e4e11668ead6b950328c71e894563a13ef72bf84"><code>e4e1166</code></a>
chore: update dependency prettier to v3.9.1 (<a
href="https://redirect.github.com/eslint/eslint/issues/21055">#21055</a>)
(renovate[bot])</li>
<li><a
href="https://github.com/eslint/eslint/commit/0493f53f3fcec4ed69a1808dffa153dcff1380d3"><code>0493f53</code></a>
chore: update prettier to v3.9.0 (<a
href="https://redirect.github.com/eslint/eslint/issues/21054">#21054</a>)
(Pixel)</li>
<li><a
href="https://github.com/eslint/eslint/commit/1056a996c657b377dc16a58c28890d2940394408"><code>1056a99</code></a>
chore: update dependency prettier to v3.8.5 (<a
href="https://redirect.github.com/eslint/eslint/issues/21049">#21049</a>)
(renovate[bot])</li>
<li><a
href="https://github.com/eslint/eslint/commit/4d4155df4a21d711e8dd21a467de085e770bad35"><code>4d4155d</code></a>
ci: run ecosystem tests on pull requests (<a
href="https://redirect.github.com/eslint/eslint/issues/21027">#21027</a>)
(sethamus)</li>
<li><a
href="https://github.com/eslint/eslint/commit/993539fd45d3d01aad3fbc13963e2245fdf7ee0d"><code>993539f</code></a>
chore: update dependency <code>@​eslint/json</code> to ^2.0.1 (<a
href="https://redirect.github.com/eslint/eslint/issues/21042">#21042</a>)
(renovate[bot])</li>
<li><a
href="https://github.com/eslint/eslint/commit/53f8b69c15af7398c989071ee4788b88edd48bdd"><code>53f8b69</code></a>
test: add error locations to <code>no-constant-binary-expression</code>
(<a
href="https://redirect.github.com/eslint/eslint/issues/21039">#21039</a>)
(lumir)</li>
<li><a
href="https://github.com/eslint/eslint/commit/5ab71d52f9d860df04e626fcd819a36257f21f9e"><code>5ab71d5</code></a>
refactor: clean up radix rule internals (<a
href="https://redirect.github.com/eslint/eslint/issues/21015">#21015</a>)
(Pixel)</li>
<li><a
href="https://github.com/eslint/eslint/commit/a80a9a461478838930c5545f840473840b26b0e6"><code>a80a9a4</code></a>
chore: update ecosystem plugins (<a
href="https://redirect.github.com/eslint/eslint/issues/21035">#21035</a>)
(ESLint Bot)</li>
<li><a
href="https://github.com/eslint/eslint/commit/7c9a02933cf1c231237babb8a7a32150ab72e263"><code>7c9a029</code></a>
ci: add Node.js 26 to CI (<a
href="https://redirect.github.com/eslint/eslint/issues/20847">#20847</a>)
(lumir)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/eslint/eslint/commit/fabd99bb74eedcb950df51ec3280414ff1d68d38"><code>fabd99b</code></a>
10.7.0</li>
<li><a
href="https://github.com/eslint/eslint/commit/37c5e75949ff2ae1f0085f547e35ce2bdf9d524d"><code>37c5e75</code></a>
Build: changelog update for 10.7.0</li>
<li><a
href="https://github.com/eslint/eslint/commit/948937986f2aa3cccb27a4c80694a85e5b0a5f64"><code>9489379</code></a>
chore: update dependency <code>@​eslint/eslintrc</code> to ^3.3.6 (<a
href="https://redirect.github.com/eslint/eslint/issues/21076">#21076</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/81a4774a928211bd0c01a06a1517736ece594522"><code>81a4774</code></a>
chore: updates for v9.39.5 release</li>
<li><a
href="https://github.com/eslint/eslint/commit/3e7bf15e69e6d3a2c8832356bcc2e9903cc4eede"><code>3e7bf15</code></a>
fix: apply <code>ignoreClassesWithImplements</code> to class expressions
(<a
href="https://redirect.github.com/eslint/eslint/issues/21069">#21069</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/983541491393323d17717683f6a2e6dbef4bc3f4"><code>9835414</code></a>
chore: enable <code>$ExpectType</code> annotations in all TypeScript
files (<a
href="https://redirect.github.com/eslint/eslint/issues/21071">#21071</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/cf2a9bf2d982642f760370c15f1196f4658f4e27"><code>cf2a9bf</code></a>
feat: add errorClassNames option to preserve-caught-error rule (<a
href="https://redirect.github.com/eslint/eslint/issues/21032">#21032</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/c30d80801ca561bdf65c6c3eba4ee57dced278cf"><code>c30d808</code></a>
docs: Update README</li>
<li><a
href="https://github.com/eslint/eslint/commit/f8b873aae53610b80b5c1005606716e05ed3b91f"><code>f8b873a</code></a>
feat: max-nested-callbacks option for constructor callbacks (<a
href="https://redirect.github.com/eslint/eslint/issues/21063">#21063</a>)</li>
<li><a
href="https://github.com/eslint/eslint/commit/72adf6bd214c142b38aabb4b8f6f908079c5301a"><code>72adf6b</code></a>
chore: restrict <code>markdownlint-cli2</code> updates in renovate (<a
href="https://redirect.github.com/eslint/eslint/issues/21067">#21067</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/eslint/eslint/compare/v10.6.0...v10.7.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=eslint&package-manager=npm_and_yarn&previous-version=10.6.0&new-version=10.7.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-14 13:52:48 +00:00
Harel M cf54b6fdeb Update style-spec package to 26.1 (#2005)
## Launch Checklist

Update style-spec package to version 26.1
This should solve a problem related to expression validation.

 - [x] Briefly describe the changes in this PR.
 - [x] Link to related issues.
2026-07-14 14:14:28 +03:00
dependabot[bot] e5e09e846a chore(deps): Bump github/codeql-action/init from 4.36.3 to 4.37.0 (#2002)
Bumps
[github/codeql-action/init](https://github.com/github/codeql-action)
from 4.36.3 to 4.37.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/init's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.0</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/init's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>4.36.1 - 02 Jun 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.0 - 22 May 2026</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle
version to 2.19.4. <a
href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a
href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5">2.25.5</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3926">#3926</a></li>
</ul>
<h2>4.35.5 - 15 May 2026</h2>
<ul>
<li>We have improved how the JavaScript bundles for the CodeQL Action
are generated to avoid duplication across bundles and reduce the size of
the repository by around 70%. This should have no effect on the runtime
behaviour of the CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3899">#3899</a></li>
<li>For performance and accuracy reasons, <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> will now only be enabled on a pull request when
diff-informed analysis is also enabled for that run. If diff-informed
analysis is unavailable (for example, because the PR diff ranges could
not be computed), the action will fall back to a full analysis. <a
href="https://redirect.github.com/github/codeql-action/pull/3791">#3791</a></li>
<li>If multiple inputs are provided for the GitHub-internal
<code>analysis-kinds</code> input, only <code>code-scanning</code> will
be enabled. The <code>analysis-kinds</code> input is experimental, for
GitHub-internal use only, and may change without notice at any time. <a
href="https://redirect.github.com/github/codeql-action/pull/3892">#3892</a></li>
<li>Added an experimental change which, when running a Code Scanning
analysis for a PR with <a
href="https://redirect.github.com/github/roadmap/issues/1158">improved
incremental analysis</a> enabled, prefers CodeQL CLI versions that have
a cached overlay-base database for the configured languages. This speeds
up analysis for a repository when there is not yet a cached overlay-base
database for the latest CLI version. We expect to roll this change out
to everyone in May. <a
href="https://redirect.github.com/github/codeql-action/pull/3880">#3880</a></li>
</ul>
<h2>4.35.4 - 07 May 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.4">2.25.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3881">#3881</a></li>
</ul>
<h2>4.35.3 - 01 May 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.19.3 and earlier. These versions of
CodeQL were discontinued on 9 April 2026 alongside GitHub Enterprise
Server 3.15, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3837">#3837</a></li>
<li>Configurations for private registries that use Cloudsmith or GCP
OIDC are now accepted. <a
href="https://redirect.github.com/github/codeql-action/pull/3850">#3850</a></li>
<li>Best-effort connection tests for private registries now use
<code>GET</code> requests instead of <code>HEAD</code> for better
compatibility with various registry implementations. For NuGet feeds,
the test is now always performed against the service index. <a
href="https://redirect.github.com/github/codeql-action/pull/3853">#3853</a></li>
<li>Fixed a bug where two diagnostics produced within the same
millisecond could overwrite each other on disk, causing one of them to
be lost. <a
href="https://redirect.github.com/github/codeql-action/pull/3852">#3852</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/99df26d4f13ea111d4ec1a7dddef6063f76b97e9"><code>99df26d</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3996">#3996</a>
from github/update-v4.37.0-c7c896d71</li>
<li><a
href="https://github.com/github/codeql-action/commit/31c27074fda95256cda077009907f8a6022dd7c0"><code>31c2707</code></a>
Add changenote for <a
href="https://redirect.github.com/github/codeql-action/issues/3973">#3973</a></li>
<li><a
href="https://github.com/github/codeql-action/commit/72df2181aac054d1f4b44264399d2aac12cf11c6"><code>72df218</code></a>
Update changelog for v4.37.0</li>
<li><a
href="https://github.com/github/codeql-action/commit/c7c896d71b3055d36f2aff93b16bcc6c69923b91"><code>c7c896d</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3995">#3995</a>
from github/update-bundle/codeql-bundle-v2.26.0</li>
<li><a
href="https://github.com/github/codeql-action/commit/3f34ff0ea3f5153c96071437b7cbf71ea3757146"><code>3f34ff0</code></a>
Add changelog note</li>
<li><a
href="https://github.com/github/codeql-action/commit/43bec09f1dc368b430cab4b5d69799bc904079d1"><code>43bec09</code></a>
Update default bundle to codeql-bundle-v2.26.0</li>
<li><a
href="https://github.com/github/codeql-action/commit/f58f0d11ebf5dedd870fab2f999275f7602cfa46"><code>f58f0d1</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/3973">#3973</a>
from github/mbg/repo-props/config-file-shorthands</li>
<li><a
href="https://github.com/github/codeql-action/commit/7dc37cbb5b3e37f0e1cd1f18b61e0ea849898fb8"><code>7dc37cb</code></a>
Merge remote-tracking branch 'origin/main' into
mbg/repo-props/config-file-sh...</li>
<li><a
href="https://github.com/github/codeql-action/commit/8e22350a7e28c34c82a5a499fc241923301c2c4f"><code>8e22350</code></a>
Thread <code>ActionState</code> to <code>initConfig</code></li>
<li><a
href="https://github.com/github/codeql-action/commit/69c9e8c7d918cf2fee13b8b72fdde15883ff155b"><code>69c9e8c</code></a>
Mark some <code>status-report</code> imports as <code>type</code>-only
to avoid circular dependencies</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/54f647b7e1bb85c95cddabcd46b0c578ec92bc1a...99df26d4f13ea111d4ec1a7dddef6063f76b97e9">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action/init&package-manager=github_actions&previous-version=4.36.3&new-version=4.37.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 13:53:39 +00:00
dependabot[bot] 43bb23801c chore(deps): Bump i18next from 26.3.4 to 26.3.6 (#2001)
Bumps [i18next](https://github.com/i18next/i18next) from 26.3.4 to
26.3.6.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/i18next/i18next/releases">i18next's
releases</a>.</em></p>
<blockquote>
<h2>v26.3.6</h2>
<ul>
<li>fix: allow TypeScript 7 in the optional <code>typescript</code> peer
dependency range (<code>^5 || ^6 || ^7</code>). With
<code>typescript@7.0.2</code> in a project, <code>npm install</code>
failed with an <code>ERESOLVE</code> peer conflict. The published types
are TS7-compatible as-is: every <code>test/typescript</code> suite
produces identical results under 6.0 and 7.0.2. Reported in <a
href="https://redirect.github.com/i18next/react-i18next/issues/1927">react-i18next#1927</a>,
thanks <a
href="https://github.com/andikapradanaarif"><code>@​andikapradanaarif</code></a>.</li>
</ul>
<h2>v26.3.5</h2>
<ul>
<li>fix: <code>$t()</code> nesting options blocks that span multiple
lines are now parsed. <code>nest()</code> decided where the nested key
ends by testing <code>match[1]</code> with <code>/{.*}/</code>, whose
dot does not cross line breaks — so a <code>$t(key, { ... })</code>
options object containing a newline was treated as having no options,
mis-split as formatters, and the nested lookup ran without its options
(placeholders stayed unresolved). The nesting regexp itself already
matches newlines inside <code>$t(...)</code>; adding the <code>s</code>
(dotAll) flag makes multiline options behave like the single-line form.
Thanks <a href="https://github.com/spokodev"><code>@​spokodev</code></a>
(<a
href="https://redirect.github.com/i18next/i18next/pull/2440">#2440</a>).</li>
<li>fix: <code>getUsedParamsDetails</code> (the <code>returnDetails:
true</code> path) no longer mutates the passed <code>replace</code>
object. It wrote <code>count</code> straight onto
<code>options.replace</code> so the returned <code>usedParams</code>
would include it — a caller reusing one <code>replace</code> object
across <code>t()</code> calls then carried a stale <code>count</code>
into later interpolations (e.g. a previous call's <code>count: 5</code>
rendered instead of the current call's value). The details are now built
from a copy; <code>usedParams</code> still includes <code>count</code>.
Thanks <a href="https://github.com/spokodev"><code>@​spokodev</code></a>
(<a
href="https://redirect.github.com/i18next/i18next/pull/2441">#2441</a>).</li>
<li>fix: with the default <code>skipOnVariables: true</code> +
<code>escapeValue: true</code>, a <code>{{placeholder}}</code> carried
inside an interpolated value now stays literal even when the value
contains escapable characters. The skip logic advanced the regex
<code>lastIndex</code> by the raw value length, but the escaped text
written into the string is longer, so <code>lastIndex</code> landed
inside the inserted value and a trailing <code>{{placeholder}}</code> in
it got interpolated — leaking another in-scope variable that should have
stayed literal (values without escapable characters were already skipped
correctly). The advance now uses the escaped length that is actually
written, and the regex-safe <code>$</code>-doubling is applied only at
the <code>String.replace</code> call so it can't distort the length
arithmetic. Thanks <a
href="https://github.com/spokodev"><code>@​spokodev</code></a> (<a
href="https://redirect.github.com/i18next/i18next/pull/2442">#2442</a>).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/i18next/i18next/blob/master/CHANGELOG.md">i18next's
changelog</a>.</em></p>
<blockquote>
<h2>26.3.6</h2>
<ul>
<li>fix: allow TypeScript 7 in the optional <code>typescript</code> peer
dependency range (<code>^5 || ^6 || ^7</code>). With
<code>typescript@7.0.2</code> in a project, <code>npm install</code>
failed with an <code>ERESOLVE</code> peer conflict. The published types
are TS7-compatible as-is: every <code>test/typescript</code> suite
produces identical results under 6.0 and 7.0.2. Reported in <a
href="https://redirect.github.com/i18next/react-i18next/issues/1927">react-i18next#1927</a>,
thanks <a
href="https://github.com/andikapradanaarif"><code>@​andikapradanaarif</code></a>.</li>
</ul>
<h2>26.3.5</h2>
<ul>
<li>fix: <code>$t()</code> nesting options blocks that span multiple
lines are now parsed. <code>nest()</code> decided where the nested key
ends by testing <code>match[1]</code> with <code>/{.*}/</code>, whose
dot does not cross line breaks — so a <code>$t(key, { ... })</code>
options object containing a newline was treated as having no options,
mis-split as formatters, and the nested lookup ran without its options
(placeholders stayed unresolved). The nesting regexp itself already
matches newlines inside <code>$t(...)</code>; adding the <code>s</code>
(dotAll) flag makes multiline options behave like the single-line form.
Thanks <a href="https://github.com/spokodev"><code>@​spokodev</code></a>
(<a
href="https://redirect.github.com/i18next/i18next/pull/2440">#2440</a>).</li>
<li>fix: <code>getUsedParamsDetails</code> (the <code>returnDetails:
true</code> path) no longer mutates the passed <code>replace</code>
object. It wrote <code>count</code> straight onto
<code>options.replace</code> so the returned <code>usedParams</code>
would include it — a caller reusing one <code>replace</code> object
across <code>t()</code> calls then carried a stale <code>count</code>
into later interpolations (e.g. a previous call's <code>count: 5</code>
rendered instead of the current call's value). The details are now built
from a copy; <code>usedParams</code> still includes <code>count</code>.
Thanks <a href="https://github.com/spokodev"><code>@​spokodev</code></a>
(<a
href="https://redirect.github.com/i18next/i18next/pull/2441">#2441</a>).</li>
<li>fix: with the default <code>skipOnVariables: true</code> +
<code>escapeValue: true</code>, a <code>{{placeholder}}</code> carried
inside an interpolated value now stays literal even when the value
contains escapable characters. The skip logic advanced the regex
<code>lastIndex</code> by the raw value length, but the escaped text
written into the string is longer, so <code>lastIndex</code> landed
inside the inserted value and a trailing <code>{{placeholder}}</code> in
it got interpolated — leaking another in-scope variable that should have
stayed literal (values without escapable characters were already skipped
correctly). The advance now uses the escaped length that is actually
written, and the regex-safe <code>$</code>-doubling is applied only at
the <code>String.replace</code> call so it can't distort the length
arithmetic. Thanks <a
href="https://github.com/spokodev"><code>@​spokodev</code></a> (<a
href="https://redirect.github.com/i18next/i18next/pull/2442">#2442</a>).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/i18next/i18next/commit/e1c60d4dd28a16f91be7f55b3685ffcf9760619b"><code>e1c60d4</code></a>
26.3.6</li>
<li><a
href="https://github.com/i18next/i18next/commit/04da43e08cb799f2e6f9a5578f3674fe2ef49595"><code>04da43e</code></a>
fix: allow typescript 7 in optional peerDependencies range
(react-i18next#1927)</li>
<li><a
href="https://github.com/i18next/i18next/commit/8eed4accc6c69398e9e25f79b617249fd3f7b1ec"><code>8eed4ac</code></a>
build</li>
<li><a
href="https://github.com/i18next/i18next/commit/573ae735682a082ad5a581dea3f2d5f517c53772"><code>573ae73</code></a>
26.3.5</li>
<li><a
href="https://github.com/i18next/i18next/commit/cc54b05b5cab9bf76cf66557c222c978bbb3c082"><code>cc54b05</code></a>
docs(changelog): 26.3.5 — multiline $t() options, replace mutation,
escaped-l...</li>
<li><a
href="https://github.com/i18next/i18next/commit/3180d67291a8fff10950f79660420c106c047f94"><code>3180d67</code></a>
fix: skip interpolation of placeholders inside escaped values (<a
href="https://redirect.github.com/i18next/i18next/issues/2442">#2442</a>)</li>
<li><a
href="https://github.com/i18next/i18next/commit/d16f5a2da748082155007ba0677d32b877b1102e"><code>d16f5a2</code></a>
fix: stop mutating the passed replace object when returning details (<a
href="https://redirect.github.com/i18next/i18next/issues/2441">#2441</a>)</li>
<li><a
href="https://github.com/i18next/i18next/commit/bed56c115979de52bd85c46d9b7a7b13361bf736"><code>bed56c1</code></a>
fix: parse $t() nesting options block that spans multiple lines (<a
href="https://redirect.github.com/i18next/i18next/issues/2440">#2440</a>)</li>
<li><a
href="https://github.com/i18next/i18next/commit/c19e45864f3abbbe90e30be44cc131f2248394bf"><code>c19e458</code></a>
docs(changelog): link GHSA advisory for deepExtend fix</li>
<li><a
href="https://github.com/i18next/i18next/commit/7bb87d09f907b4395ed0825638d7d2c4356f7a89"><code>7bb87d0</code></a>
docs(changelog): reference security advisory for deepExtend fix</li>
<li>Additional commits viewable in <a
href="https://github.com/i18next/i18next/compare/v26.3.4...v26.3.6">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=i18next&package-manager=npm_and_yarn&previous-version=26.3.4&new-version=26.3.6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-13 13:52:30 +00:00
Harel M f14eeae38b Increase coverage (#1997)
## Launch Checklist

This PR increases coverage by adding unit tests to lib folde, replace
the skipped end to end placeholder with actual tests and adds more end
to end tests.

This was mostly done by AI (Claude opus 4.8) and I reviewed it and
requested changes where needed.

 - [x] Briefly describe the changes in this PR.
 - [x] Link to related issues.
- [x] Include before/after visuals or gifs if this PR includes visual
changes.
 - [x] Write tests for all new functionality.
 - [x] Add an entry to `CHANGELOG.md` under the `## main` section.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-12 15:46:30 +03:00
Harel M 9c1499b805 Replace default export with named exports (#1998)
## Launch Checklist

See title,
Also removed some "_" from some file names.
This is a pure refactoring, no logic changes.


 - [x] Briefly describe the changes in this PR.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-12 14:07:28 +03:00
dependabot[bot] 21e141542a chore(deps): Bump qs from 6.14.2 to 6.15.3 (#1996)
Bumps [qs](https://github.com/ljharb/qs) from 6.14.2 to 6.15.3.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/ljharb/qs/blob/main/CHANGELOG.md">qs's
changelog</a>.</em></p>
<blockquote>
<h2><strong>6.15.3</strong></h2>
<ul>
<li>[Fix] <code>parse</code>: enforce <code>throwOnLimitExceeded</code>
for cumulative array growth via
<code>combine</code>/<code>merge</code></li>
<li>[Fix] <code>utils</code>: respect encoding of surrogate pairs across
chunks (<a
href="https://redirect.github.com/ljharb/qs/issues/559">#559</a>)</li>
<li>[Robustness] <code>parse</code>: throw the <code>arrayLimit</code>
error before splitting oversized comma values</li>
<li>[Robustness] <code>utils.merge</code> / <code>utils.assign</code>:
avoid invoking <code>__proto__</code> setter when copying own
properties</li>
<li>[Robustness] <code>utils</code>: enforce <code>arrayLimit</code>
consistently across <code>merge</code>'s array paths</li>
<li>[Perf] <code>utils</code>: make <code>compact</code> O(n) via a
side-channel visited-set instead of <code>Array.indexOf</code></li>
<li>[Deps] update <code>side-channel</code></li>
<li>[Dev Deps] update <code>eslint</code>, <code>mock-property</code>,
<code>tape</code></li>
<li>[Tests] <code>parse</code>: characterize current lenient handling of
unbalanced bracket keys (<a
href="https://redirect.github.com/ljharb/qs/issues/558">#558</a>)</li>
</ul>
<h2><strong>6.15.2</strong></h2>
<ul>
<li>[Fix] <code>stringify</code>: skip null/undefined entries in
<code>arrayFormat: 'comma'</code> + <code>encodeValuesOnly</code>
instead of crashing in <code>encoder</code></li>
<li>[Fix] <code>stringify</code>: use configured <code>delimiter</code>
after <code>charsetSentinel</code> (<a
href="https://redirect.github.com/ljharb/qs/issues/555">#555</a>)</li>
<li>[Fix] <code>stringify</code>: apply <code>formatter</code> to
encoded key under <code>strictNullHandling</code> (<a
href="https://redirect.github.com/ljharb/qs/issues/554">#554</a>)</li>
<li>[Fix] <code>stringify</code>: skip null/undefined filter-array
entries instead of crashing in <code>encoder</code> (<a
href="https://redirect.github.com/ljharb/qs/issues/551">#551</a>)</li>
<li>[Fix] <code>parse</code>: handle nested bracket groups and add
regression tests (<a
href="https://redirect.github.com/ljharb/qs/issues/530">#530</a>)</li>
<li>[readme] fix grammar (<a
href="https://redirect.github.com/ljharb/qs/issues/550">#550</a>)</li>
<li>[Dev Deps] update <code>@ljharb/eslint-config</code></li>
<li>[Tests] add regression tests for keys containing percent-encoded
bracket text</li>
</ul>
<h2><strong>6.15.1</strong></h2>
<ul>
<li>[Fix] <code>parse</code>: <code>parameterLimit: Infinity</code> with
<code>throwOnLimitExceeded: true</code> silently drops all
parameters</li>
<li>[Deps] update <code>@ljharb/eslint-config</code></li>
<li>[Dev Deps] update <code>@ljharb/eslint-config</code>,
<code>iconv-lite</code></li>
<li>[Tests] increase coverage</li>
</ul>
<h2><strong>6.15.0</strong></h2>
<ul>
<li>[New] <code>parse</code>: add <code>strictMerge</code> option to
wrap object/primitive conflicts in an array (<a
href="https://redirect.github.com/ljharb/qs/issues/425">#425</a>, <a
href="https://redirect.github.com/ljharb/qs/issues/122">#122</a>)</li>
<li>[Fix] <code>duplicates</code> option should not apply to bracket
notation keys (<a
href="https://redirect.github.com/ljharb/qs/issues/514">#514</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/ljharb/qs/commit/18d085e919dae70c8f1b200ab99323058edab2c2"><code>18d085e</code></a>
v6.15.3</li>
<li><a
href="https://github.com/ljharb/qs/commit/c38af42039d8cbf83f3e0d486c924eaa5a5d1712"><code>c38af42</code></a>
[Deps] update <code>side-channel</code></li>
<li><a
href="https://github.com/ljharb/qs/commit/adce539489fcd34cc10830cf8c4a43e90b9300b0"><code>adce539</code></a>
[Dev Deps] update <code>eslint</code>, <code>mock-property</code>,
<code>tape</code></li>
<li><a
href="https://github.com/ljharb/qs/commit/74a0f6a83af59245b4fa78785fbe709438580c97"><code>74a0f6a</code></a>
[Robustness] <code>utils</code>: enforce <code>arrayLimit</code>
consistently across <code>merge</code>'s arra...</li>
<li><a
href="https://github.com/ljharb/qs/commit/f4938f594a0a2773d3c99c0c6f5c96f30d909a2c"><code>f4938f5</code></a>
[Tests] <code>parse</code>: characterize current lenient handling of
unbalanced bracket ...</li>
<li><a
href="https://github.com/ljharb/qs/commit/5d5f723afe55a861c52cff6327f62269df35fdd1"><code>5d5f723</code></a>
[Perf] <code>utils</code>: make <code>compact</code> O(n) via a
side-channel visited-set instead of...</li>
<li><a
href="https://github.com/ljharb/qs/commit/52afe00abdb36416ee01c8d5243919b47a59af67"><code>52afe00</code></a>
[Robustness] <code>parse</code>: throw the <code>arrayLimit</code> error
before splitting oversized...</li>
<li><a
href="https://github.com/ljharb/qs/commit/963e538c740961a35ecaa0b38ee1f5c16697e208"><code>963e538</code></a>
[Fix] <code>parse</code>: enforce <code>throwOnLimitExceeded</code> for
cumulative array growth via...</li>
<li><a
href="https://github.com/ljharb/qs/commit/59da434d5de8c3d2564e4d75aeedde2e8af72369"><code>59da434</code></a>
[Fix] <code>utils</code>: respect encoding of surrogate pairs across
chunks</li>
<li><a
href="https://github.com/ljharb/qs/commit/9532969635a4bc96506370b28dd28dcab446e7ee"><code>9532969</code></a>
[Robustness] <code>utils.merge</code> / <code>utils.assign</code>: avoid
invoking <code>__proto__</code> sette...</li>
<li>Additional commits viewable in <a
href="https://github.com/ljharb/qs/compare/v6.14.2...v6.15.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=qs&package-manager=npm_and_yarn&previous-version=6.14.2&new-version=6.15.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/maplibre/maputnik/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-10 18:25:44 +00:00
dependabot[bot] 320f94b707 chore(deps): Bump uuid and istanbul-lib-processinfo (#1995)
Bumps [uuid](https://github.com/uuidjs/uuid) and
[istanbul-lib-processinfo](https://github.com/istanbuljs/istanbul-lib-processinfo).
These dependencies needed to be updated together.
Updates `uuid` from 8.3.2 to 14.0.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/uuidjs/uuid/releases">uuid's
releases</a>.</em></p>
<blockquote>
<h2>v14.0.1</h2>
<h2><a
href="https://github.com/uuidjs/uuid/compare/v14.0.0...v14.0.1">14.0.1</a>
(2026-06-20)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>add types condition to node export for moduleResolution bundler (<a
href="https://redirect.github.com/uuidjs/uuid/issues/961">#961</a>) (<a
href="https://github.com/uuidjs/uuid/commit/27ffae5e867823b8c7db255975d65358fbdb1a7e">27ffae5</a>)</li>
</ul>
<h2>v14.0.0</h2>
<h2><a
href="https://github.com/uuidjs/uuid/compare/v13.0.0...v14.0.0">14.0.0</a>
(2026-04-19)</h2>
<h3>⚠ BREAKING CHANGES</h3>
<ul>
<li>expect <code>crypto</code> to be global everywhere (requires
node@20+) (<a
href="https://redirect.github.com/uuidjs/uuid/issues/935">#935</a>)</li>
<li>drop node@18 support (<a
href="https://redirect.github.com/uuidjs/uuid/issues/934">#934</a>)</li>
</ul>
<h3>Features</h3>
<ul>
<li>drop node@18 support (<a
href="https://redirect.github.com/uuidjs/uuid/issues/934">#934</a>) (<a
href="https://github.com/uuidjs/uuid/commit/dc4ddb87272ed2843faccd130bcc41d492688bd3">dc4ddb8</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>expect <code>crypto</code> to be global everywhere (requires
node@20+) (<a
href="https://redirect.github.com/uuidjs/uuid/issues/935">#935</a>) (<a
href="https://github.com/uuidjs/uuid/commit/f2c235f93059325fa43e1106e624b5291bb523c4">f2c235f</a>)</li>
<li>Use GITHUB_TOKEN for release-please and enable npm provenance (<a
href="https://redirect.github.com/uuidjs/uuid/issues/925">#925</a>) (<a
href="https://github.com/uuidjs/uuid/commit/ffa31383e8e4e1f0b4e22e504561272041b8738c">ffa3138</a>)</li>
</ul>
<h2>v13.0.2</h2>
<h2><a
href="https://github.com/uuidjs/uuid/compare/v13.0.1...v13.0.2">13.0.2</a>
(2026-05-04)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>rerelease to fix provenance. (<a
href="https://github.com/uuidjs/uuid/commit/49ccb35f78c0c4ce1409dd2f1d89f83caadba10b">49ccb35</a>)</li>
</ul>
<h2>v13.0.1</h2>
<h2><a
href="https://github.com/uuidjs/uuid/compare/v13.0.0...v13.0.1">13.0.1</a>
(2026-04-27)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>backport fix for GHSA-w5hq-g745-h8pq (<a
href="https://github.com/uuidjs/uuid/commit/9d27ddf7046ce496ef39569ff84d948eeff9cb2a">9d27ddf</a>)</li>
</ul>
<h2>v13.0.0</h2>
<h2><a
href="https://github.com/uuidjs/uuid/compare/v12.0.0...v13.0.0">13.0.0</a>
(2025-09-08)</h2>
<h3>⚠ BREAKING CHANGES</h3>
<ul>
<li>make browser exports the default (<a
href="https://redirect.github.com/uuidjs/uuid/issues/901">#901</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md">uuid's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/uuidjs/uuid/compare/v14.0.0...v14.0.1">14.0.1</a>
(2026-06-20)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>add types condition to node export for moduleResolution bundler (<a
href="https://redirect.github.com/uuidjs/uuid/issues/961">#961</a>) (<a
href="https://github.com/uuidjs/uuid/commit/27ffae5e867823b8c7db255975d65358fbdb1a7e">27ffae5</a>)</li>
</ul>
<h2><a
href="https://github.com/uuidjs/uuid/compare/v13.0.0...v14.0.0">14.0.0</a>
(2026-04-19)</h2>
<h3>Security</h3>
<ul>
<li>Fixes <a
href="https://github.com/uuidjs/uuid/security/advisories/GHSA-w5hq-g745-h8pq">GHSA-w5hq-g745-h8pq</a>:
<code>v3()</code>, <code>v5()</code>, and <code>v6()</code> did not
validate that writes would remain within the bounds of a caller-supplied
buffer, allowing out-of-bounds writes when an invalid
<code>offset</code> was provided. A <code>RangeError</code> is now
thrown if <code>offset &lt; 0</code> or <code>offset + 16 &gt;
buf.length</code>.</li>
</ul>
<h3>⚠ BREAKING CHANGES</h3>
<ul>
<li><code>crypto</code> is now expected to be globally defined (requires
node@20+) (<a
href="https://redirect.github.com/uuidjs/uuid/issues/935">#935</a>)</li>
<li>drop node@18 support (<a
href="https://redirect.github.com/uuidjs/uuid/issues/934">#934</a>)</li>
<li>upgrade minimum supported TypeScript version to 5.4.3, in keeping
with the project's policy of supporting TypeScript versions released
within the last two years</li>
</ul>
<h2><a
href="https://github.com/uuidjs/uuid/compare/v12.0.0...v13.0.0">13.0.0</a>
(2025-09-08)</h2>
<h3>⚠ BREAKING CHANGES</h3>
<ul>
<li>make browser exports the default (<a
href="https://redirect.github.com/uuidjs/uuid/issues/901">#901</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li>make browser exports the default (<a
href="https://redirect.github.com/uuidjs/uuid/issues/901">#901</a>) (<a
href="https://github.com/uuidjs/uuid/commit/bce9d72a3ae5b9a3dcd8eb21ef6d1820288a427a">bce9d72</a>)</li>
</ul>
<h2><a
href="https://github.com/uuidjs/uuid/compare/v11.1.0...v12.0.0">12.0.0</a>
(2025-09-05)</h2>
<h3>⚠ BREAKING CHANGES</h3>
<ul>
<li>update to typescript@5.2 (<a
href="https://redirect.github.com/uuidjs/uuid/issues/887">#887</a>)</li>
<li>remove CommonJS support (<a
href="https://redirect.github.com/uuidjs/uuid/issues/886">#886</a>)</li>
<li>drop node@16 support (<a
href="https://redirect.github.com/uuidjs/uuid/issues/883">#883</a>)</li>
</ul>
<h3>Features</h3>
<ul>
<li>add node@24 to ci matrix (<a
href="https://redirect.github.com/uuidjs/uuid/issues/879">#879</a>) (<a
href="https://github.com/uuidjs/uuid/commit/42b6178aa21a593257f0a72abacd220f0b7b8a92">42b6178</a>)</li>
<li>drop node@16 support (<a
href="https://redirect.github.com/uuidjs/uuid/issues/883">#883</a>) (<a
href="https://github.com/uuidjs/uuid/commit/0f38cf10366ab074f9328ae2021eea04d5f2e530">0f38cf1</a>)</li>
<li>remove CommonJS support (<a
href="https://redirect.github.com/uuidjs/uuid/issues/886">#886</a>) (<a
href="https://github.com/uuidjs/uuid/commit/ae786e27265f50bcf7cead196c29f1869297c42f">ae786e2</a>)</li>
<li>update to typescript@5.2 (<a
href="https://redirect.github.com/uuidjs/uuid/issues/887">#887</a>) (<a
href="https://github.com/uuidjs/uuid/commit/c7ee40598ed78584d81ab78dffded9fe5ff20b01">c7ee405</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/uuidjs/uuid/commit/70177807e9229dfacde2038dc1e722f1828f358a"><code>7017780</code></a>
chore(main): release 14.0.1 (<a
href="https://redirect.github.com/uuidjs/uuid/issues/964">#964</a>)</li>
<li><a
href="https://github.com/uuidjs/uuid/commit/f2c3e4b513e47968f568ad1bf96417c6924e83a7"><code>f2c3e4b</code></a>
chore: fix release-please workflow (<a
href="https://redirect.github.com/uuidjs/uuid/issues/963">#963</a>)</li>
<li><a
href="https://github.com/uuidjs/uuid/commit/27ffae5e867823b8c7db255975d65358fbdb1a7e"><code>27ffae5</code></a>
fix: add types condition to node export for moduleResolution bundler (<a
href="https://redirect.github.com/uuidjs/uuid/issues/961">#961</a>)</li>
<li><a
href="https://github.com/uuidjs/uuid/commit/664cb3163531f49d3a5125f732d38724621bfcaa"><code>664cb31</code></a>
Remove outdated security contact information (<a
href="https://redirect.github.com/uuidjs/uuid/issues/959">#959</a>)</li>
<li><a
href="https://github.com/uuidjs/uuid/commit/d729016037c4e1b09a522f4854a96f4f4add9047"><code>d729016</code></a>
fix(ci): checkout PR head commit in browser workflow (<a
href="https://redirect.github.com/uuidjs/uuid/issues/957">#957</a>)</li>
<li><a
href="https://github.com/uuidjs/uuid/commit/89a5ebcc56999fc25c95350f922693b71fb11d32"><code>89a5ebc</code></a>
Workflows (<a
href="https://redirect.github.com/uuidjs/uuid/issues/948">#948</a>)</li>
<li><a
href="https://github.com/uuidjs/uuid/commit/196e208db33d568d8d43d5b549d5d0eb3d9b0fbf"><code>196e208</code></a>
chore: fix workflow (<a
href="https://redirect.github.com/uuidjs/uuid/issues/947">#947</a>)</li>
<li><a
href="https://github.com/uuidjs/uuid/commit/95af4489be459d6ba14d137b6fcd87ffd0953e91"><code>95af448</code></a>
chore: update workflows (<a
href="https://redirect.github.com/uuidjs/uuid/issues/946">#946</a>)</li>
<li><a
href="https://github.com/uuidjs/uuid/commit/3b57f95555ab1b8432213264b5eaa318958fb8fe"><code>3b57f95</code></a>
chore: add workflow_dispatch (<a
href="https://redirect.github.com/uuidjs/uuid/issues/944">#944</a>)</li>
<li><a
href="https://github.com/uuidjs/uuid/commit/a433096a74e1d1815432fb1bed8115369785a1a9"><code>a433096</code></a>
chore: add 12.x and 13.x maintenance release branches (<a
href="https://redirect.github.com/uuidjs/uuid/issues/941">#941</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/uuidjs/uuid/compare/v8.3.2...v14.0.1">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a
href="https://www.npmjs.com/~GitHub%20Actions">GitHub Actions</a>, a new
releaser for uuid since your current version.</p>
</details>
<details>
<summary>Install script changes</summary>
<p>This version adds <code>prepare</code> script that runs during
installation. Review the package contents before updating.</p>
</details>
<br />

Updates `istanbul-lib-processinfo` from 3.0.0 to 3.0.1
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/istanbuljs/istanbul-lib-processinfo/blob/master/CHANGELOG.md">istanbul-lib-processinfo's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<p>All notable changes to this project will be documented in this file.
See <a
href="https://github.com/conventional-changelog/standard-version">standard-version</a>
for commit guidelines.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/istanbuljs/istanbul-lib-processinfo/commit/bd2fd64c87f956f6e5d8ede9cc3f47f37f047145"><code>bd2fd64</code></a>
3.0.1</li>
<li><a
href="https://github.com/istanbuljs/istanbul-lib-processinfo/commit/1100563778e6d1c9c3ce0181c912df750d40475f"><code>1100563</code></a>
chore: remove <code>uuid</code> dependency</li>
<li><a
href="https://github.com/istanbuljs/istanbul-lib-processinfo/commit/79c691abb60dbb7c134c4bf220546250ebb40755"><code>79c691a</code></a>
chore(deps): update uuid</li>
<li>See full diff in <a
href="https://github.com/istanbuljs/istanbul-lib-processinfo/compare/istanbul-lib-processinfo-v3.0.0...v3.0.1">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/maplibre/maputnik/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-10 18:25:26 +00:00
Harel M 2e0b7cdb8c Replace cypress with playwright (#1988)
## Launch Checklist

This PR replaces cypress with playwright.
<img width="1907" height="933" alt="image"
src="https://github.com/user-attachments/assets/b52075b3-eb3b-45dc-93dc-8c5e9cfd35dd"
/>

I hope this will make the end-to-end debugging and fixing easier due to
the async await support of playwright which is missing in cypress.

This is a pure refactoring change.

 - [x] Briefly describe the changes in this PR.
 - [x] Add an entry to `CHANGELOG.md` under the `## main` section.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-07-10 18:18:48 +00:00
151 changed files with 4273 additions and 5000 deletions
+40 -29
View File
@@ -21,9 +21,9 @@ jobs:
os: [ ubuntu-latest, windows-latest, macos-latest ] os: [ ubuntu-latest, windows-latest, macos-latest ]
steps: steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: { persist-credentials: false } with: { persist-credentials: false }
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
- run: npm ci - run: npm ci
@@ -41,9 +41,9 @@ jobs:
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' }} if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' }}
steps: steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: { persist-credentials: false } with: { persist-credentials: false }
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
- run: npm ci - run: npm ci
@@ -56,7 +56,7 @@ jobs:
# Build and upload desktop CLI artifacts # Build and upload desktop CLI artifacts
- name: Set up Go - name: Set up Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with: with:
go-version: ^1.23.x go-version: ^1.23.x
cache-dependency-path: desktop/go.sum cache-dependency-path: desktop/go.sum
@@ -90,9 +90,9 @@ jobs:
contents: read contents: read
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: { persist-credentials: false } with: { persist-credentials: false }
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
- run: npm ci - run: npm ci
@@ -111,23 +111,27 @@ jobs:
contents: read contents: read
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: { persist-credentials: false } with: { persist-credentials: false }
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
- run: npm ci - run: npm ci
- name: Cypress run - run: npx playwright install --with-deps chromium
uses: cypress-io/github-action@fa4a118725a8f001170d49631ea89e5d66fee626 # v7.4.1 - name: Playwright run
with: run: npm run test-e2e
build: npm run build
start: npm run start
browser: chrome
- name: Upload coverage reports to Codecov - name: Upload coverage reports to Codecov
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with: with:
files: ${{ github.workspace }}/.nyc_output/out.json files: ${{ github.workspace }}/coverage/coverage-final.json
verbose: true verbose: true
- name: Upload Playwright report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: playwright-report
path: playwright-report/
retention-days: 7
e2e-tests-docker: e2e-tests-docker:
name: "E2E tests using chrome and docker" name: "E2E tests using chrome and docker"
@@ -135,20 +139,27 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: { persist-credentials: false } with: { persist-credentials: false }
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
- run: npm ci - run: npm ci
- name: Cypress run - run: npx playwright install --with-deps chromium
uses: cypress-io/github-action@fa4a118725a8f001170d49631ea89e5d66fee626 # v7.4.1 - name: Build docker image
with: run: docker build -t maputnik .
build: docker build -t maputnik . - name: Start maputnik container
start: docker run --rm --network host maputnik --port=8888 run: docker run -d --network host --name maputnik maputnik --port=8888
browser: chrome - name: Wait for maputnik to be ready
- name: Upload coverage reports to Codecov run: |
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 for i in $(seq 1 60); do
with: if curl -sSf http://localhost:8888/ > /dev/null; then
files: ${{ github.workspace }}/.nyc_output/out.json echo "maputnik is up"; exit 0
verbose: true fi
sleep 1
done
echo "maputnik did not start in time"; docker logs maputnik; exit 1
- name: Playwright run
run: npm run test-e2e
env:
E2E_NO_WEBSERVER: "1"
+3 -3
View File
@@ -38,11 +38,11 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3 uses: github/codeql-action/init@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@@ -67,4 +67,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3 uses: github/codeql-action/analyze@e0647621c2984b5ed2f768cb892365bf2a616ad1 # v4.37.2
+2 -2
View File
@@ -16,13 +16,13 @@ jobs:
run: run:
shell: bash shell: bash
steps: steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: with:
fetch-depth: 0 fetch-depth: 0
ref: main ref: main
- name: Use Node.js from nvmrc - name: Use Node.js from nvmrc
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
+3 -3
View File
@@ -12,11 +12,11 @@ jobs:
contents: write contents: write
if: ${{ github.event_name == 'push' }} if: ${{ github.event_name == 'push' }}
steps: steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: { persist-credentials: false } with: { persist-credentials: false }
- name: Use Node.js from nvmrc - name: Use Node.js from nvmrc
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
@@ -50,6 +50,6 @@ jobs:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- run: docker build -t ghcr.io/maplibre/maputnik:main . - run: docker build -t ghcr.io/maplibre/maputnik:main .
- run: docker push ghcr.io/maplibre/maputnik:main - run: docker push ghcr.io/maplibre/maputnik:main
+5 -5
View File
@@ -15,14 +15,14 @@ jobs:
permissions: permissions:
contents: read contents: read
steps: steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: with:
fetch-depth: 0 fetch-depth: 0
ref: main ref: main
persist-credentials: false persist-credentials: false
- name: Use Node.js from nvmrc - name: Use Node.js from nvmrc
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
@@ -42,19 +42,19 @@ jobs:
run: run:
shell: bash shell: bash
steps: steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: with:
fetch-depth: 0 fetch-depth: 0
ref: main ref: main
- name: Use Node.js from nvmrc - name: Use Node.js from nvmrc
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
registry-url: "https://registry.npmjs.org" registry-url: "https://registry.npmjs.org"
- name: Set up Go for desktop build - name: Set up Go for desktop build
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with: with:
go-version: ^1.23.x go-version: ^1.23.x
cache-dependency-path: desktop/go.sum cache-dependency-path: desktop/go.sum
+5 -1
View File
@@ -33,10 +33,14 @@ node_modules
public public
/errorShots /errorShots
/old /old
/cypress/screenshots
/dist/ /dist/
/desktop/version.go /desktop/version.go
# Playwright
/test-results/
/playwright-report/
/playwright/.cache/
# IDE # IDE
.vscode/ .vscode/
.idea/ .idea/
+5 -4
View File
@@ -4,12 +4,13 @@
"check-coverage": false, "check-coverage": false,
"include": ["src/**/*.ts", "src/**/*.tsx"], "include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": [ "exclude": [
"cypress/**/*.*", "e2e/**/*.*",
"**/*.d.ts", "**/*.d.ts",
"**/*.cy.tsx", "**/*.test.ts",
"**/*.cy.ts", "**/*.test.tsx",
"**/*.browser.test.tsx",
"./coverage/**", "./coverage/**",
"./cypress/**", "./e2e/**",
"./dist/**", "./dist/**",
"node_modules" "node_modules"
], ],
+109 -6
View File
@@ -24,24 +24,127 @@ The project type checked and built with:
npm run build npm run build
``` ```
To run the tests make sure that xvfb is installed: Install the Playwright browser (first time only):
``` ```
apt install xvfb npx playwright install --with-deps chromium
``` ```
Run the development server in the background with Vite: Then run the end-to-end tests (Playwright starts the dev server automatically):
``` ```
nohup npm run start & npm run test
``` ```
Then start the Cypress tests with: Run the unit tests with Vitest:
``` ```
xvfb-run -a npm run test npm run test-unit
``` ```
## Pull Requests ## Pull Requests
- Pull requests should update `CHANGELOG.md` with a short description of the change. - Pull requests should update `CHANGELOG.md` with a short description of the change.
## Testing
### Prefer end-to-end tests
Most of this codebase is React components, and they are only reachable from an
end-to-end test. E2E coverage is the primary signal.
Reach for a unit test only for pure logic that e2e cannot cheaply reach (parsers,
sorting, watchers, stores). Before writing one, check whether e2e already covers
the file — a unit test that duplicates existing e2e coverage adds test code and
almost no coverage:
```
npx nyc report --reporter=text --include="src/libs/style.ts"
```
Do **not** merge the Vitest (v8) and e2e (istanbul) coverage reports locally. They
produce conflicting statement maps for the same files and the combined percentage
is meaningless. Codecov merges the two uploads server-side; that is the number to
trust. Locally, read them separately:
- e2e: `npx playwright test` then `npx nyc report --reporter=text-summary` (reads `.nyc_output/`)
- unit: `npx vitest run --coverage` (writes `coverage/`)
### E2E layering
Three layers, and the boundaries matter:
- `e2e/playwright-helper.ts` — generic, app-agnostic browser actions. **The only
file allowed to import `@playwright/test`** (besides `e2e/utils/fixtures.ts`).
- `e2e/maputnik-driver.ts` — domain actions (layers, filters, functions, the
style). Knows nothing about `page` or Playwright.
- `e2e/modal-driver.ts` — actions scoped to a modal, exposed as `when.modal.*`.
Specs get a driver at describe scope and assert fluently:
```ts
describe("layer editor", () => {
const { given, get, when, then } = new MaputnikDriver();
...
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ layers: [{ id, type: "fill" }] });
});
```
New UI interactions belong in a driver, not inline in a spec.
### Writing assertions
- `shouldDeepNestedInclude` is a recursive partial match (`toMatchObject`): nested
objects are matched as subsets, arrays and primitives must match exactly
(including array length).
- Assert against the whole style, not an extracted slice. Avoid
`get.styleFromLocalStorage().then(style => style.layers.find(...))` — it moves
test logic into the test. Compare the real object instead.
- `Query.then()` is lazy and returns a new `Query`, **not** a Promise. `await
get.styleFromLocalStorage()` hangs forever. Use `.get()` to await it directly,
or pass the Query to `then(...)`.
### One behaviour per test
If a test needs comments narrating "and now this…", it is several tests. Split it,
and hoist the shared setup into a nested `describe` + `beforeEach`.
### Test ids
Test ids use the `data-wd-key` attribute and are read via `get.elementByTestId`.
The `Input*` components already accept `data-wd-key` and render it on the real
`<input>`; the `Field*` wrappers forward it through their `{...props}` spread. So
passing `data-wd-key` to a `Field*` component is usually enough. Do **not** also
add it to `Block`/`Fieldset` — the id then matches two elements and locators fail
in strict mode.
Note `InputNumber` renders `<key>-text` and `<key>-range` when `allowRange` is set,
and `<key>` otherwise.
### Input commit semantics (common source of "the value didn't save")
- `InputString` only fires its `onChange` on **blur** or **Enter**. Typing alone
fires `onInput`. A driver that calls `fill()` must then call `blur()`, or the
value never reaches the style.
- `InputNumber` commits on every change; no blur needed.
- The autocomplete inputs (layer source, add-layer source) are controlled
downshift comboboxes. Keystroke typing is dropped/reordered — `{selectall}` then
typing `raster` yields `"exampleaster"`. Use `fill()`, which dispatches a single
input event, then pick from the filtered menu.
- CodeMirror auto-closes brackets and quotes, and types over its own closers, so
inserting a well-formed JSON fragment stays well-formed. To break JSON on
purpose, insert a bare word.
### Fixtures
Style fixtures live in `e2e/fixtures/`. A new one must be registered in two places
in `maputnik-driver.ts`: the list in `given.setupMockBackedResponses` and the
`styleFileByKey` map in `when.setStyle`.
### Verify a new test can fail
A test that passes for the wrong reason is worse than no test. After writing one,
mutate the expected value and confirm it fails. This has caught real mistakes
(e.g. a driver that never committed its input, so the assertion was matching a
value written by the *previous* step).
+1
View File
@@ -1,6 +1,7 @@
## main ## main
### ✨ Features and improvements ### ✨ Features and improvements
- Replace Cypress with Playwright for end-to-end tests and Vitest browser mode (Playwright provider) for component tests; drop the `@shellygo/cypress-test-utils` helper in favour of a `MaputnikDriver` page object
- _...Add new stuff here..._ - _...Add new stuff here..._
### 🐞 Bug fixes ### 🐞 Bug fixes
+30 -11
View File
@@ -79,26 +79,45 @@ npm run sort-styles
``` ```
## Tests ## Tests
For E2E testing we use [Cypress](https://www.cypress.io/)
[Cypress](https://www.cypress.io/) doesn't start a server so you'll need to start one manually by running `npm run start`. ### End-to-end tests
Now open a terminal and run the following using *chrome*: For E2E testing we use [Playwright](https://playwright.dev/). The tests live in the [`e2e`](/e2e) directory and drive the app through the `MaputnikDriver` page object.
The first time you run the tests, install the browser:
```
npx playwright install chromium
```
Playwright automatically starts the dev server (`npm run start`) for you, so you can just run:
``` ```
npm run test npm run test
``` ```
or *firefox*:
```
npm run test -- --browser firefox
```
See the following docs for more info: (Launching Browsers)[https://docs.cypress.io/guides/guides/launching-browsers] Some useful options:
You can also see the tests as they run or select which suites to run by executing:
``` ```
npm run cy:open # see the tests run in a headed browser
npm run test -- --headed
# run a single spec / filter by title
npm run test -- e2e/map.spec.ts
npm run test -- -g "zoom level"
# open the interactive UI mode
npx playwright test --ui
```
Running the E2E tests also produces a code-coverage report in `coverage/` (collected via istanbul instrumentation of the dev server).
### Unit & component tests
Unit tests and component tests run with [Vitest](https://vitest.dev/); component tests (`*.browser.test.tsx`) use Vitest's browser mode with the Playwright provider.
```
npm run test-unit
``` ```
## Release process ## Release process
-42
View File
@@ -1,42 +0,0 @@
import { defineConfig } from "cypress";
import { createRequire } from "module";
const require = createRequire(import.meta.url);
export default defineConfig({
env: {
codeCoverage: {
exclude: "cypress/**/*.*",
},
},
e2e: {
specPattern: "e2e/**/*.spec.ts",
setupNodeEvents(on, config) {
// implement node event listeners here
require("@cypress/code-coverage/task")(on, config);
on("before:browser:launch", (browser, launchOptions) => {
if (browser.family !== "chromium") {
return;
}
launchOptions.args.push("--disable-gpu");
launchOptions.args.push("--enable-features=AllowSwiftShaderFallback,AllowSoftwareGLFallbackDueToCrashes");
launchOptions.args.push("--enable-unsafe-swiftshader");
return launchOptions;
});
return config;
},
baseUrl: "http://localhost:8888",
scrollBehavior: "center",
retries: {
runMode: 2,
openMode: 0,
},
},
component: {
devServer: {
framework: "react",
bundler: "vite",
},
},
});
-37
View File
@@ -1,37 +0,0 @@
/// <reference types="cypress" />
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
//
// declare global {
// namespace Cypress {
// interface Chainable {
// login(email: string, password: string): Chainable<void>
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
// }
// }
// }
-12
View File
@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Components App</title>
</head>
<body>
<div data-cy-root></div>
</body>
</html>
-37
View File
@@ -1,37 +0,0 @@
// ***********************************************************
// This example support/component.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import "./commands";
import { mount } from "cypress/react";
// Augment the Cypress namespace to include type definitions for
// your custom command.
// Alternatively, can be defined in cypress/support/component.d.ts
// with a <reference path="./component" /> at the top of your spec.
declare global {
/* eslint-disable @typescript-eslint/no-namespace */
namespace Cypress {
interface Chainable {
mount: typeof mount
}
}
}
Cypress.Commands.add("mount", mount);
// Example use:
// cy.mount(<MyComponent />)
-22
View File
@@ -1,22 +0,0 @@
// ***********************************************************
// This example support/e2e.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import "@cypress/code-coverage/support";
import "cypress-plugin-tab";
import "./commands";
// Alternatively you can use CommonJS syntax:
// require('./commands')
+60
View File
@@ -0,0 +1,60 @@
import { test, expect, describe, beforeEach } from "./utils/fixtures";
import { MaputnikDriver } from "./maputnik-driver";
import tokens from "../src/config/tokens.json" with { type: "json" };
describe("access tokens", () => {
const { given, when } = new MaputnikDriver();
const tileJson = {
tilejson: "2.2.0",
tiles: ["https://example.local/{z}/{x}/{y}.pbf"],
minzoom: 0,
maxzoom: 14,
};
beforeEach(async () => {
await given.setupMockBackedResponses();
});
test("uses the thunderforest token for a thunderforest source", async () => {
await given.interceptAndMockResponse({
method: "GET",
url: /tile\.thunderforest\.com\/.*/,
response: tileJson,
alias: "thunderforest",
});
await when.setStyle("access_tokens");
const request = await when.waitForResponse("thunderforest");
expect(request.url()).toContain(`apikey=${tokens.thunderforest}`);
});
test("uses the locationiq token for a locationiq source", async () => {
await given.interceptAndMockResponse({
method: "GET",
url: /tiles\.locationiq\.com\/.*/,
response: tileJson,
alias: "locationiq",
});
await when.setStyle("access_tokens");
const request = await when.waitForResponse("locationiq");
expect(request.url()).toContain(`key=${tokens.locationiq}`);
});
test("appends the stadia token as a query parameter", async () => {
await given.interceptAndMockResponse({
method: "GET",
url: /tiles\.stadiamaps\.com\/.*/,
response: tileJson,
alias: "stadia",
});
await when.setStyle("access_tokens");
const request = await when.waitForResponse("stadia");
expect(request.url()).toContain("?api_key=stadia-test-token");
});
});
+31 -25
View File
@@ -1,42 +1,48 @@
import { test, describe, beforeEach } from "./utils/fixtures";
import { MaputnikDriver } from "./maputnik-driver"; import { MaputnikDriver } from "./maputnik-driver";
const test = it;
describe("accessibility", () => { describe("accessibility", () => {
const { beforeAndAfter, get, when, then } = new MaputnikDriver(); const { given, get, when, then } = new MaputnikDriver();
beforeAndAfter();
beforeEach(async () => {
await given.setupMockBackedResponses();
await when.setStyle("both");
});
describe("skip links", () => { describe("skip links", () => {
beforeEach(() => { beforeEach(async () => {
when.setStyle("layer"); await when.setStyle("layer");
}); });
test("skip link to layer list", () => { test("skip link to layer list", async () => {
const selector = "root:skip:layer-list"; const selector = "root:skip:layer-list";
then(get.elementByTestId(selector)).shouldExist(); await then(get.elementByTestId(selector)).shouldExist();
when.tab(); await when.tab();
then(get.elementByTestId(selector)).shouldBeFocused(); await then(get.elementByTestId(selector)).shouldBeFocused();
when.click(selector); await when.click(selector);
then(get.skipTargetLayerList()).shouldBeFocused(); await then(get.skipTargetLayerList()).shouldBeFocused();
}); });
test("skip link to layer editor", () => { test("skip link to layer editor", async () => {
const selector = "root:skip:layer-editor"; const selector = "root:skip:layer-editor";
then(get.elementByTestId(selector)).shouldExist(); await then(get.elementByTestId(selector)).shouldExist();
then(get.elementByTestId("skip-target-layer-editor")).shouldExist(); await then(get.elementByTestId("skip-target-layer-editor")).shouldExist();
when.tab().tab(); await when.tab();
then(get.elementByTestId(selector)).shouldBeFocused(); await when.tab();
when.click(selector); await then(get.elementByTestId(selector)).shouldBeFocused();
then(get.skipTargetLayerEditor()).shouldBeFocused(); await when.click(selector);
await then(get.skipTargetLayerEditor()).shouldBeFocused();
}); });
test("skip link to map view", () => { test("skip link to map view", async () => {
const selector = "root:skip:map-view"; const selector = "root:skip:map-view";
then(get.elementByTestId(selector)).shouldExist(); await then(get.elementByTestId(selector)).shouldExist();
when.tab().tab().tab(); await when.tab();
then(get.elementByTestId(selector)).shouldBeFocused(); await when.tab();
when.click(selector); await when.tab();
then(get.canvas()).shouldBeFocused(); await then(get.elementByTestId(selector)).shouldBeFocused();
await when.click(selector);
await then(get.canvas()).shouldBeFocused();
}); });
}); });
}); });
+15 -12
View File
@@ -1,20 +1,23 @@
import { beforeEach, describe, test } from "./utils/fixtures";
import { MaputnikDriver } from "./maputnik-driver"; import { MaputnikDriver } from "./maputnik-driver";
const test = it;
describe("code editor", () => { describe("code editor", () => {
const { beforeAndAfter, when, get, then } = new MaputnikDriver(); const { given, get, when, then } = new MaputnikDriver();
beforeAndAfter();
test("open code editor", () => { beforeEach(async () => {
when.click("nav:code-editor"); await given.setupMockBackedResponses();
then(get.element(".maputnik-code-editor")).shouldExist(); await when.setStyle("both");
}); });
test("closes code editor", () => { test("open code editor", async () => {
when.click("nav:code-editor"); await when.click("nav:code-editor");
then(get.element(".maputnik-code-editor")).shouldExist(); await then(get.element(".maputnik-code-editor")).shouldExist();
when.click("nav:code-editor"); });
then(get.element(".maputnik-code-editor")).shouldNotExist();
test("closes code editor", async () => {
await when.click("nav:code-editor");
await then(get.element(".maputnik-code-editor")).shouldExist();
await when.click("nav:code-editor");
await then(get.element(".maputnik-code-editor")).shouldNotExist();
}); });
}); });
+22
View File
@@ -0,0 +1,22 @@
{
"version": 8,
"name": "Access token style",
"metadata": {
"maputnik:stadia_access_token": "stadia-test-token"
},
"sources": {
"thunderforest_transport": {
"type": "vector",
"url": "https://tile.thunderforest.com/thunderforest.transport-v2.json?apikey={key}"
},
"stadia_outdoors": {
"type": "vector",
"url": "https://tiles.stadiamaps.com/data/openmaptiles.json"
},
"locationiq": {
"type": "vector",
"url": "https://tiles.locationiq.com/v3/pbf/tiles.json?key={key}"
}
},
"layers": []
}
+44 -80
View File
@@ -1,126 +1,90 @@
import { beforeEach, describe, test } from "./utils/fixtures";
import { MaputnikDriver } from "./maputnik-driver"; import { MaputnikDriver } from "./maputnik-driver";
const test = it;
describe("history", () => { describe("history", () => {
const { beforeAndAfter, when, get, then } = new MaputnikDriver(); const { given, get, when, then } = new MaputnikDriver();
beforeAndAfter();
let undoKeyCombo: string; const undoKeyCombo = process.platform === "darwin" ? "{meta}z" : "{ctrl}z";
let redoKeyCombo: string; const redoKeyCombo = process.platform === "darwin" ? "{meta}{shift}z" : "{ctrl}y";
before(() => { beforeEach(async () => {
const isMac = get.isMac(); await given.setupMockBackedResponses();
undoKeyCombo = isMac ? "{meta}z" : "{ctrl}z"; await when.setStyle("both");
redoKeyCombo = isMac ? "{meta}{shift}z" : "{ctrl}y";
}); });
test("undo/redo", () => { test("undo/redo", async () => {
when.setStyle("geojson"); await when.setStyle("geojson");
when.modal.open(); await when.modal.open();
when.modal.fillLayers({ await when.modal.fillLayers({
id: "step 1", id: "step 1",
type: "background", type: "background",
}); });
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [ layers: [{ id: "step 1", type: "background" }],
{
id: "step 1",
type: "background",
},
],
}); });
when.modal.open(); await when.modal.open();
when.modal.fillLayers({ await when.modal.fillLayers({
id: "step 2", id: "step 2",
type: "background", type: "background",
}); });
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [ layers: [
{ { id: "step 1", type: "background" },
id: "step 1", { id: "step 2", type: "background" },
type: "background",
},
{
id: "step 2",
type: "background",
},
], ],
}); });
when.typeKeys(undoKeyCombo); await when.typeKeys(undoKeyCombo);
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [ layers: [{ id: "step 1", type: "background" }],
{
id: "step 1",
type: "background",
},
],
}); });
when.typeKeys(undoKeyCombo); await when.typeKeys(undoKeyCombo);
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ layers: [] }); await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ layers: [] });
when.typeKeys(redoKeyCombo); await when.typeKeys(redoKeyCombo);
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [ layers: [{ id: "step 1", type: "background" }],
{
id: "step 1",
type: "background",
},
],
}); });
when.typeKeys(redoKeyCombo); await when.typeKeys(redoKeyCombo);
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [ layers: [
{ { id: "step 1", type: "background" },
id: "step 1", { id: "step 2", type: "background" },
type: "background",
},
{
id: "step 2",
type: "background",
},
], ],
}); });
}); });
test("should not redo after undo and value change", () => { test("should not redo after undo and value change", async () => {
when.setStyle("geojson"); await when.setStyle("geojson");
when.modal.open(); await when.modal.open();
when.modal.fillLayers({ await when.modal.fillLayers({
id: "step 1", id: "step 1",
type: "background", type: "background",
}); });
when.modal.open(); await when.modal.open();
when.modal.fillLayers({ await when.modal.fillLayers({
id: "step 2", id: "step 2",
type: "background", type: "background",
}); });
when.typeKeys(undoKeyCombo); await when.typeKeys(undoKeyCombo);
when.typeKeys(undoKeyCombo); await when.typeKeys(undoKeyCombo);
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ layers: [] }); await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ layers: [] });
when.modal.open(); await when.modal.open();
when.modal.fillLayers({ await when.modal.fillLayers({
id: "step 3", id: "step 3",
type: "background", type: "background",
}); });
when.typeKeys(redoKeyCombo); await when.typeKeys(redoKeyCombo);
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [ layers: [{ id: "step 3", type: "background" }],
{
id: "step 3",
type: "background",
},
],
}); });
}); });
}); });
+20 -19
View File
@@ -1,37 +1,38 @@
import { beforeEach, describe, test } from "./utils/fixtures";
import { MaputnikDriver } from "./maputnik-driver"; import { MaputnikDriver } from "./maputnik-driver";
const test = it;
describe("i18n", () => { describe("i18n", () => {
const { beforeAndAfter, get, when, then } = new MaputnikDriver(); const { given, get, when, then } = new MaputnikDriver();
beforeAndAfter();
beforeEach(async () => {
await given.setupMockBackedResponses();
await when.setStyle("both");
});
describe("language detector", () => { describe("language detector", () => {
test("English", () => { test("English", async () => {
const url = "?lng=en"; await when.visit("?lng=en");
when.visit(url); await then(get.elementByTestId("maputnik-lang-select")).shouldHaveValue("en");
then(get.elementByTestId("maputnik-lang-select")).shouldHaveValue("en");
}); });
test("Japanese", () => { test("Japanese", async () => {
const url = "?lng=ja"; await when.visit("?lng=ja");
when.visit(url); await then(get.elementByTestId("maputnik-lang-select")).shouldHaveValue("ja");
then(get.elementByTestId("maputnik-lang-select")).shouldHaveValue("ja");
}); });
}); });
describe("language switcher", () => { describe("language switcher", () => {
beforeEach(() => { beforeEach(async () => {
when.setStyle("layer"); await when.setStyle("layer");
}); });
test("the language switcher switches to Japanese", () => { test("the language switcher switches to Japanese", async () => {
const selector = "maputnik-lang-select"; const selector = "maputnik-lang-select";
then(get.elementByTestId(selector)).shouldExist(); await then(get.elementByTestId(selector)).shouldExist();
when.select(selector, "ja"); await when.select(selector, "ja");
then(get.elementByTestId(selector)).shouldHaveValue("ja"); await then(get.elementByTestId(selector)).shouldHaveValue("ja");
then(get.elementByTestId("nav:settings")).shouldHaveText("スタイル設定"); await then(get.elementByTestId("nav:settings")).shouldHaveText("スタイル設定");
}); });
}); });
}); });
+40 -35
View File
@@ -1,61 +1,66 @@
import { beforeEach, describe, test } from "./utils/fixtures";
import { MaputnikDriver } from "./maputnik-driver"; import { MaputnikDriver } from "./maputnik-driver";
const test = it;
describe("keyboard", () => { describe("keyboard", () => {
const { beforeAndAfter, given, when, get, then } = new MaputnikDriver(); const { given, get, when, then } = new MaputnikDriver();
beforeAndAfter();
beforeEach(async () => {
await given.setupMockBackedResponses();
await when.setStyle("both");
});
describe("shortcuts", () => { describe("shortcuts", () => {
beforeEach(() => { beforeEach(async () => {
given.setupMockBackedResponses(); await given.setupMockBackedResponses();
when.setStyle(""); await when.setStyle("");
}); });
test("ESC should unfocus", () => { test("ESC should unfocus", async () => {
const targetSelector = "maputnik-select"; const targetSelector = "maputnik-select";
when.focus(targetSelector); await when.focus(targetSelector);
then(get.elementByTestId(targetSelector)).shouldBeFocused(); await then(get.elementByTestId(targetSelector)).shouldBeFocused();
when.typeKeys("{esc}"); await when.typeKeys("{esc}");
then(get.elementByTestId(targetSelector)).shouldNotBeFocused(); await then(get.elementByTestId(targetSelector)).shouldNotBeFocused();
}); });
test("'?' should show shortcuts modal", () => { test("'?' should show shortcuts modal", async () => {
when.typeKeys("?"); await when.typeKeys("?");
then(get.elementByTestId("modal:shortcuts")).shouldBeVisible(); await then(get.elementByTestId("modal:shortcuts")).shouldBeVisible();
}); });
test("'o' should show open modal", () => { test("'o' should show open modal", async () => {
when.typeKeys("o"); await when.typeKeys("o");
then(get.elementByTestId("modal:open")).shouldBeVisible(); await then(get.elementByTestId("modal:open")).shouldBeVisible();
}); });
test("'e' should show export modal", () => { test("'e' should show export modal", async () => {
when.typeKeys("e"); await when.typeKeys("e");
then(get.elementByTestId("modal:export")).shouldBeVisible(); await then(get.elementByTestId("modal:export")).shouldBeVisible();
}); });
test("'d' should show sources modal", () => { test("'d' should show sources modal", async () => {
when.typeKeys("d"); await when.typeKeys("d");
then(get.elementByTestId("modal:sources")).shouldBeVisible(); await then(get.elementByTestId("modal:sources")).shouldBeVisible();
}); });
test("'s' should show settings modal", () => { test("'s' should show settings modal", async () => {
when.typeKeys("s"); await when.typeKeys("s");
then(get.elementByTestId("modal:settings")).shouldBeVisible(); await then(get.elementByTestId("modal:settings")).shouldBeVisible();
}); });
test("'i' should change map to inspect mode", () => { test("'i' should change map to inspect mode", async () => {
when.typeKeys("i"); await when.typeKeys("i");
then(get.inputValue("maputnik-select")).shouldEqual("inspect"); await then(get.inputValue("maputnik-select")).shouldEqual("inspect");
}); });
test("'m' should focus map", () => { test("'m' should focus map", async () => {
when.typeKeys("m"); await when.typeKeys("m");
then(get.canvas()).shouldBeFocused(); await then(get.canvas()).shouldBeFocused();
}); });
test("'!' should show debug modal", () => { test("'!' should show debug modal", async () => {
when.typeKeys("!"); await when.typeKeys("!");
then(get.elementByTestId("modal:debug")).shouldBeVisible(); await then(get.elementByTestId("modal:debug")).shouldBeVisible();
}); });
}); });
}); });
+478 -170
View File
@@ -1,98 +1,98 @@
import { MaputnikDriver } from "./maputnik-driver";
import { v1 as uuid } from "uuid"; import { v1 as uuid } from "uuid";
import { beforeEach, describe, test } from "./utils/fixtures";
const test = it; import { MaputnikDriver } from "./maputnik-driver";
describe("layer editor", () => { describe("layer editor", () => {
const { beforeAndAfter, get, when, then } = new MaputnikDriver(); const { given, get, when, then } = new MaputnikDriver();
beforeAndAfter();
beforeEach(() => { beforeEach(async () => {
when.setStyle("both"); await given.setupMockBackedResponses();
when.modal.open(); await when.setStyle("both");
await when.modal.open();
}); });
function createBackground() { async function createBackground() {
const id = uuid(); const id = uuid();
when.selectWithin("add-layer.layer-type", "background"); await when.selectWithin("add-layer.layer-type", "background");
when.setValue("add-layer.layer-id.input", "background:" + id); await when.setValue("add-layer.layer-id.input", "background:" + id);
when.click("add-layer"); await when.click("add-layer");
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [ layers: [{ id: "background:" + id, type: "background" }],
{
id: "background:" + id,
type: "background",
},
],
}); });
return id; return id;
} }
test("expand/collapse"); test("expand/collapse", async () => {
test("id", () => { const bgId = await createBackground();
const bgId = createBackground(); await when.click("layer-list-item:background:" + bgId);
when.click("layer-list-item:background:" + bgId); await then(get.elementByTestId("layer-editor.layer-id.input")).shouldBeVisible();
await when.toggleGroupInLayerEditor("Layer");
await then(get.elementByTestId("layer-editor.layer-id.input")).shouldNotBeVisible();
await when.toggleGroupInLayerEditor("Layer");
await then(get.elementByTestId("layer-editor.layer-id.input")).shouldBeVisible();
});
test("id", async () => {
const bgId = await createBackground();
await when.click("layer-list-item:background:" + bgId);
const id = uuid(); const id = uuid();
when.setValue("layer-editor.layer-id.input", "foobar:" + id); await when.setValue("layer-editor.layer-id.input", "foobar:" + id);
when.click("min-zoom"); await when.click("min-zoom");
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [ layers: [{ id: "foobar:" + id, type: "background" }],
{
id: "foobar:" + id,
type: "background",
},
],
}); });
}); });
describe("source", () => { describe("source", () => {
test("should show error when the source is invalid", () => { test("should show error when the source is invalid", async () => {
when.modal.fillLayers({ await when.modal.fillLayers({
type: "circle", type: "circle",
layer: "invalid", layer: "invalid",
}); });
then(get.element(".maputnik-input-block--error .maputnik-input-block-label")).shouldHaveCss("color", "rgb(207, 74, 74)"); await then(
get.element(".maputnik-input-block--error .maputnik-input-block-label")
).shouldHaveCss("color", "rgb(207, 74, 74)");
}); });
}); });
describe("min-zoom", () => { describe("min-zoom", () => {
let bgId: string; let bgId: string;
beforeEach(() => { beforeEach(async () => {
bgId = createBackground(); bgId = await createBackground();
when.click("layer-list-item:background:" + bgId); await when.click("layer-list-item:background:" + bgId);
when.setValue("min-zoom.input-text", "1"); await when.setValue("min-zoom.input-text", "1");
when.click("layer-editor.layer-id"); await when.click("layer-editor.layer-id");
}); });
test("should update min-zoom in local storage", () => { test("should update min-zoom in local storage", async () => {
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [ layers: [{ id: "background:" + bgId, type: "background", minzoom: 1 }],
{
id: "background:" + bgId,
type: "background",
minzoom: 1,
},
],
}); });
}); });
it("when clicking next layer should update style on local storage", () => { test("when clicking next layer should update style on local storage", async () => {
when.type("min-zoom.input-text", "{backspace}"); await when.type("min-zoom.input-text", "{backspace}");
when.click("max-zoom.input-text"); await when.click("max-zoom.input-text");
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [ layers: [{ id: "background:" + bgId, type: "background", minzoom: 1 }],
{ });
id: "background:" + bgId, });
type: "background",
minzoom: 1, test("the range slider adjusts min-zoom", async () => {
}, await when.focus("min-zoom.input-range");
], await when.typeKeys("{rightarrow}");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id: "background:" + bgId, type: "background", minzoom: 2 }],
}); });
}); });
}); });
@@ -100,22 +100,16 @@ describe("layer editor", () => {
describe("max-zoom", () => { describe("max-zoom", () => {
let bgId: string; let bgId: string;
beforeEach(() => { beforeEach(async () => {
bgId = createBackground(); bgId = await createBackground();
when.click("layer-list-item:background:" + bgId); await when.click("layer-list-item:background:" + bgId);
when.setValue("max-zoom.input-text", "1"); await when.setValue("max-zoom.input-text", "1");
when.click("layer-editor.layer-id"); await when.click("layer-editor.layer-id");
}); });
test("should update style in local storage", () => { test("should update style in local storage", async () => {
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [ layers: [{ id: "background:" + bgId, type: "background", maxzoom: 1 }],
{
id: "background:" + bgId,
type: "background",
maxzoom: 1,
},
],
}); });
}); });
}); });
@@ -124,41 +118,34 @@ describe("layer editor", () => {
let bgId: string; let bgId: string;
const comment = "42"; const comment = "42";
beforeEach(() => { beforeEach(async () => {
bgId = createBackground(); bgId = await createBackground();
when.click("layer-list-item:background:" + bgId); await when.click("layer-list-item:background:" + bgId);
when.setValue("layer-comment.input", comment); await when.setValue("layer-comment.input", comment);
when.click("layer-editor.layer-id"); await when.click("layer-editor.layer-id");
}); });
test("should update style in local storage", () => { test("should update style in local storage", async () => {
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [ layers: [
{ {
id: "background:" + bgId, id: "background:" + bgId,
type: "background", type: "background",
metadata: { metadata: { "maputnik:comment": comment },
"maputnik:comment": comment,
},
}, },
], ],
}); });
}); });
describe("when unsetting", () => { describe("when unsetting", () => {
beforeEach(() => { beforeEach(async () => {
when.clear("layer-comment.input"); await when.clear("layer-comment.input");
when.click("min-zoom.input-text"); await when.click("min-zoom.input-text");
}); });
test("should update style in local storage", () => { test("should update style in local storage", async () => {
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [ layers: [{ id: "background:" + bgId, type: "background" }],
{
id: "background:" + bgId,
type: "background",
},
],
}); });
}); });
}); });
@@ -166,131 +153,452 @@ describe("layer editor", () => {
describe("color", () => { describe("color", () => {
let bgId: string; let bgId: string;
beforeEach(() => { beforeEach(async () => {
bgId = createBackground(); bgId = await createBackground();
when.click("layer-list-item:background:" + bgId); await when.click("layer-list-item:background:" + bgId);
when.click("spec-field:background-color"); await when.click("spec-field:background-color");
}); });
test("should update style in local storage", () => { test("should update style in local storage", async () => {
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [ layers: [{ id: "background:" + bgId, type: "background" }],
{ });
id: "background:" + bgId, });
type: "background",
}, test("typing a hex value updates the paint color", async () => {
], await when.setColorValue("background-color", "#ff0000");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id: "background:" + bgId, type: "background", paint: { "background-color": "#ff0000" } }],
}); });
}); });
}); });
describe("opacity", () => { describe("opacity", () => {
let bgId: string; let bgId: string;
beforeEach(() => { beforeEach(async () => {
bgId = createBackground(); bgId = await createBackground();
when.click("layer-list-item:background:" + bgId); await when.click("layer-list-item:background:" + bgId);
when.type("spec-field-input:background-opacity", "0."); await when.type("spec-field-input:background-opacity", "0.");
}); });
test("should keep '.' in the input field", () => { test("should keep '.' in the input field", async () => {
then(get.elementByTestId("spec-field-input:background-opacity")).shouldHaveValue("0."); await then(get.elementByTestId("spec-field-input:background-opacity")).shouldHaveValue("0.");
}); });
test("should revert to a valid value when focus out", () => { test("should revert to a valid value when focus out", async () => {
when.click("layer-list-item:background:" + bgId); await when.click("layer-list-item:background:" + bgId);
then(get.elementByTestId("spec-field-input:background-opacity")).shouldHaveValue("0"); await then(get.elementByTestId("spec-field-input:background-opacity")).shouldHaveValue("0");
}); });
}); });
describe("filter", () => { describe("filter", () => {
test("expand/collapse"); let id: string;
test("compound filter");
beforeEach(async () => {
id = await when.modal.fillLayers({ type: "fill", layer: "example" });
await when.addFilter();
});
test("should add a filter item", async () => {
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, type: "fill", source: "example", filter: ["all", ["==", "name", ""]] }],
});
});
test("should change the filter operator", async () => {
await when.selectFilterOperator("!=");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, filter: ["all", ["!=", "name", ""]] }],
});
});
test("should extend the compound filter with a second item", async () => {
await when.addFilter();
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, filter: ["all", ["==", "name", ""], ["==", "name", ""]] }],
});
});
test("should change the combining operator", async () => {
await when.selectFilterCombiningOperator("any");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, filter: ["any", ["==", "name", ""]] }],
});
});
test("should delete a filter item", async () => {
await when.addFilter();
await when.deleteFilterItem();
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, filter: ["all", ["==", "name", ""]] }],
});
});
describe("when converted to an expression", () => {
beforeEach(async () => {
await when.convertFilterToExpression();
});
test("should migrate the filter to an expression", async () => {
// A single-item "all" collapses to the bare comparison when migrated.
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, filter: ["==", ["get", "name"], ""] }],
});
});
test("should restore the default filter when the expression is deleted", async () => {
await when.deleteFilterExpression();
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, filter: ["all"] }],
});
});
});
});
describe("functions", () => {
let id: string;
describe("zoom function", () => {
beforeEach(async () => {
id = await when.modal.fillLayers({ type: "circle", layer: "example" });
await when.makeZoomFunction("circle-radius");
});
test("should convert the property to a zoom function", async () => {
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [
{ id, type: "circle", source: "example", paint: { "circle-radius": { stops: [[6, 5], [10, 5]] } } },
],
});
});
test("should add a stop", async () => {
await when.addFunctionStop("circle-radius");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, paint: { "circle-radius": { stops: [[6, 5], [10, 5], [11, 5]] } } }],
});
});
test("should delete the first stop", async () => {
// A function needs more than two stops, otherwise deleting one collapses
// it back into a plain value.
await when.addFunctionStop("circle-radius");
await when.deleteFunctionStop("circle-radius");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, paint: { "circle-radius": { stops: [[10, 5], [11, 5]] } } }],
});
});
test("should set the base", async () => {
await when.setFunctionBase("circle-radius", "2");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, paint: { "circle-radius": { base: 2, stops: [[6, 5], [10, 5]] } } }],
});
});
test("should edit the zoom of a stop", async () => {
await when.setFunctionStopValue("circle-radius", "Zoom", 0, "3");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, paint: { "circle-radius": { stops: [[3, 5], [10, 5]] } } }],
});
});
test("should edit the output value of a stop", async () => {
await when.setFunctionStopValue("circle-radius", "Output value", 0, "9");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, paint: { "circle-radius": { stops: [[6, 9], [10, 5]] } } }],
});
});
test("should convert to an expression", async () => {
await when.makeExpression("circle-radius");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, paint: { "circle-radius": ["interpolate", ["linear"], ["zoom"], 6, 5, 10, 5] } }],
});
});
describe("when converted to a data function", () => {
beforeEach(async () => {
// Any non-interpolate scale turns the zoom function into a data one.
await when.selectFunctionType("circle-radius", "categorical");
});
test("should carry the stops over as zoom/value pairs", async () => {
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [
{
id,
paint: {
"circle-radius": {
property: "",
type: "exponential",
stops: [[{ zoom: 6, value: 0 }, 5], [{ zoom: 10, value: 0 }, 5]],
},
},
},
],
});
});
test("should convert back to a zoom function", async () => {
await when.selectFunctionType("circle-radius", "interpolate");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, paint: { "circle-radius": { stops: [[6, 5], [10, 5]] } } }],
});
});
});
});
describe("data function", () => {
beforeEach(async () => {
id = await when.modal.fillLayers({ type: "circle", layer: "example" });
await when.setValue("spec-field-input:circle-blur", "1");
await when.makeDataFunction("circle-blur");
});
test("should convert the property to a data function", async () => {
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [
{
id,
type: "circle",
source: "example",
paint: {
"circle-blur": {
property: "",
type: "exponential",
stops: [[{ zoom: 6, value: 0 }, 1], [{ zoom: 10, value: 0 }, 1]],
},
},
},
],
});
});
test("should add a stop", async () => {
await when.addFunctionStop("circle-blur");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [
{
id,
paint: {
"circle-blur": {
stops: [[{ zoom: 6, value: 0 }, 1], [{ zoom: 10, value: 0 }, 1], [{ zoom: 11, value: 0 }, 1]],
},
},
},
],
});
});
test("should delete the first stop", async () => {
await when.addFunctionStop("circle-blur");
await when.deleteFunctionStop("circle-blur");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [
{
id,
paint: {
"circle-blur": {
stops: [[{ zoom: 10, value: 0 }, 1], [{ zoom: 11, value: 0 }, 1]],
},
},
},
],
});
});
test("should set the property", async () => {
await when.setFunctionProperty("circle-blur", "myprop");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, paint: { "circle-blur": { property: "myprop" } } }],
});
});
test("should set the default", async () => {
await when.setFunctionDefault("circle-blur", "0.5");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, paint: { "circle-blur": { default: 0.5 } } }],
});
});
test("should edit the input value of a stop", async () => {
await when.setFunctionStopValue("circle-blur", "Input value", 0, "7");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [
{
id,
paint: {
"circle-blur": {
stops: [[{ zoom: 6, value: 7 }, 1], [{ zoom: 10, value: 0 }, 1]],
},
},
},
],
});
});
test("should change the function type", async () => {
await when.selectFunctionType("circle-blur", "categorical");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, paint: { "circle-blur": { type: "categorical" } } }],
});
});
});
describe("expression", () => {
beforeEach(async () => {
id = await when.modal.fillLayers({ type: "circle", layer: "example" });
await when.setValue("spec-field-input:circle-blur", "1");
await when.makeExpression("circle-blur");
});
test("should wrap the property value in a literal expression", async () => {
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, paint: { "circle-blur": ["literal", 1] } }],
});
});
test("should restore the plain value when reverted", async () => {
await when.undoExpression("circle-blur");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, paint: { "circle-blur": 1 } }],
});
});
test("should fall back to the spec default when deleted", async () => {
await when.deleteExpression("circle-blur");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, paint: { "circle-blur": 0 } }],
});
});
});
}); });
describe("layout", () => { describe("layout", () => {
test("text-font", () => { test("text-font", async () => {
when.setStyle("font"); await when.setStyle("font");
when.collapseGroupInLayerEditor(); await when.collapseGroupInLayerEditor();
when.collapseGroupInLayerEditor(1); await when.collapseGroupInLayerEditor(1);
when.collapseGroupInLayerEditor(2); await when.collapseGroupInLayerEditor(2);
when.doWithin("spec-field:text-font", () => { await when.clickWithin("spec-field:text-font", ".maputnik-autocomplete input");
get.element(".maputnik-autocomplete input").first().click(); await then(get.element(".maputnik-autocomplete-menu-item")).shouldBeVisible();
}); await then(get.element(".maputnik-autocomplete-menu-item")).shouldHaveLength(3);
then(get.element(".maputnik-autocomplete-menu-item")).shouldBeVisible();
then(get.element(".maputnik-autocomplete-menu-item")).shouldHaveLength(3);
}); });
}); });
describe("paint", () => { describe("paint", () => {
test("expand/collapse"); let id: string;
test("color");
test("pattern"); beforeEach(async () => {
test("opacity"); id = await when.modal.fillLayers({ type: "fill", layer: "example" });
});
test("expand/collapse", async () => {
await then(get.elementByTestId("spec-field:fill-color")).shouldBeVisible();
await when.toggleGroupInLayerEditor("Paint properties");
await then(get.elementByTestId("spec-field:fill-color")).shouldNotBeVisible();
await when.toggleGroupInLayerEditor("Paint properties");
await then(get.elementByTestId("spec-field:fill-color")).shouldBeVisible();
});
test("color", async () => {
await when.setColorValue("fill-color", "#ff0000");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, type: "fill", source: "example", paint: { "fill-color": "#ff0000" } }],
});
});
test("pattern", async () => {
await when.setStringValue("fill-pattern", "some-pattern");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, type: "fill", source: "example", paint: { "fill-pattern": "some-pattern" } }],
});
});
test("opacity", async () => {
await when.setValue("spec-field-input:fill-opacity", "0.4");
await when.click("layer-editor.layer-id");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, type: "fill", source: "example", paint: { "fill-opacity": 0.4 } }],
});
});
}); });
describe("json-editor", () => { describe("json-editor", () => {
test("add", () => { test("add", async () => {
const id = when.modal.fillLayers({ const id = await when.modal.fillLayers({
type: "circle", type: "circle",
layer: "example", layer: "example",
}); });
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [ layers: [{ id, type: "circle", source: "example" }],
{
id: id,
type: "circle",
source: "example",
},
],
}); });
const sourceText = get.elementByText('"source"'); await when.clickByText('"source"');
await when.typeKeys('"');
sourceText.click(); await then(get.element(".cm-lint-marker-error")).shouldExist();
sourceText.type("\"");
then(get.element(".cm-lint-marker-error")).shouldExist();
}); });
test("expand/collapse", async () => {
const bgId = await createBackground();
await when.click("layer-list-item:background:" + bgId);
test("expand/collapse"); await then(get.element(".cm-content")).shouldBeVisible();
test("modify");
test("parse error", () => { await when.toggleGroupInLayerEditor("JSON Editor");
const bgId = createBackground(); await then(get.element(".cm-content")).shouldNotBeVisible();
when.click("layer-list-item:background:" + bgId); await when.toggleGroupInLayerEditor("JSON Editor");
when.collapseGroupInLayerEditor(); await then(get.element(".cm-content")).shouldBeVisible();
when.collapseGroupInLayerEditor(1); });
then(get.element(".cm-lint-marker-error")).shouldNotExist();
when.appendTextInJsonEditor( test("modify", async () => {
"\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013\uE013 {" const bgId = await createBackground();
); await when.click("layer-list-item:background:" + bgId);
then(get.element(".cm-lint-marker-error")).shouldExist();
await when.appendToJsonEditorLine('"background"', ',\n"minzoom": 5');
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id: "background:" + bgId, type: "background", minzoom: 5 }],
});
});
test("parse error", async () => {
const bgId = await createBackground();
await when.click("layer-list-item:background:" + bgId);
await when.collapseGroupInLayerEditor();
await when.collapseGroupInLayerEditor(1);
await then(get.element(".cm-lint-marker-error")).shouldNotExist();
// Inject an invalid token (CodeMirror auto-closes brackets/quotes, so a
// bare word reliably breaks the JSON) and expect a lint error.
await when.appendTextInJsonEditor("zzz");
await then(get.element(".cm-lint-marker-error")).shouldExist();
}); });
}); });
describe("sticky header", () => { describe("sticky header", () => {
test("should keep layer header visible when scrolling properties", () => { test("should keep layer header visible when scrolling properties", async () => {
// Setup: Create a layer with many properties (e.g., symbol layer) // Setup: Create a layer with many properties (e.g. symbol layer)
when.modal.fillLayers({ await when.modal.fillLayers({
type: "symbol", type: "symbol",
layer: "example", layer: "example",
}); });
when.wait(500); await when.wait(500);
const header = get.elementByTestId("layer-editor.header"); const header = get.elementByTestId("layer-editor.header");
then(header).shouldBeVisible(); await then(header).shouldBeVisible();
get.element(".maputnik-scroll-container").scrollTo("bottom", { ensureScrollable: false }); await when.scrollToBottom(get.element(".maputnik-scroll-container"));
when.wait(200); await when.wait(200);
then(header).shouldBeVisible(); await then(header).shouldBeVisible();
then(get.elementByTestId("skip-target-layer-editor")).shouldBeVisible(); await then(get.elementByTestId("skip-target-layer-editor")).shouldBeVisible();
}); });
}); });
}); });
+209 -387
View File
@@ -1,412 +1,278 @@
import { beforeEach, describe, test } from "./utils/fixtures";
import { MaputnikDriver } from "./maputnik-driver"; import { MaputnikDriver } from "./maputnik-driver";
const test = it;
describe("layers list", () => { describe("layers list", () => {
const { beforeAndAfter, get, when, then } = new MaputnikDriver(); const { given, get, when, then } = new MaputnikDriver();
beforeAndAfter();
beforeEach(() => { beforeEach(async () => {
when.setStyle("both"); await given.setupMockBackedResponses();
when.modal.open(); await when.setStyle("both");
await when.modal.open();
}); });
describe("ops", () => { describe("ops", () => {
let id: string; let id: string;
beforeEach(() => { beforeEach(async () => {
id = when.modal.fillLayers({ id = await when.modal.fillLayers({ type: "background" });
type: "background",
});
}); });
test("should update layers in local storage", () => { test("should update layers in local storage", async () => {
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [ layers: [{ id, type: "background" }],
{
id: id,
type: "background",
},
],
}); });
}); });
describe("when clicking delete", () => { describe("when clicking delete", () => {
beforeEach(() => { beforeEach(async () => {
when.click("layer-list-item:" + id + ":delete"); await when.click("layer-list-item:" + id + ":delete");
}); });
test("should empty layers in local storage", () => { test("should empty layers in local storage", async () => {
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [], layers: [],
}); });
}); });
}); });
describe("when clicking duplicate", () => { describe("when clicking duplicate", () => {
beforeEach(() => { beforeEach(async () => {
when.click("layer-list-item:" + id + ":copy"); await when.click("layer-list-item:" + id + ":copy");
}); });
test("should add copy layer in local storage", () => { test("should add copy layer in local storage", async () => {
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [ layers: [
{ { id: id + "-copy", type: "background" },
id: id + "-copy", { id, type: "background" },
type: "background",
},
{
id: id,
type: "background",
},
], ],
}); });
}); });
}); });
describe("when clicking hide", () => { describe("when clicking hide", () => {
beforeEach(() => { beforeEach(async () => {
when.click("layer-list-item:" + id + ":toggle-visibility"); await when.click("layer-list-item:" + id + ":toggle-visibility");
}); });
test("should update visibility to none in local storage", () => { test("should update visibility to none in local storage", async () => {
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [ layers: [{ id, type: "background", layout: { visibility: "none" } }],
{
id: id,
type: "background",
layout: {
visibility: "none",
},
},
],
}); });
}); });
describe("when clicking show", () => { describe("when clicking show", () => {
beforeEach(() => { beforeEach(async () => {
when.click("layer-list-item:" + id + ":toggle-visibility"); await when.click("layer-list-item:" + id + ":toggle-visibility");
}); });
test("should update visibility to visible in local storage", () => { test("should update visibility to visible in local storage", async () => {
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [ layers: [{ id, type: "background", layout: { visibility: "visible" } }],
{
id: id,
type: "background",
layout: {
visibility: "visible",
},
},
],
}); });
}); });
}); });
describe("when selecting a layer", () => { describe("when selecting a layer", () => {
let secondId: string; let secondId: string;
beforeEach(() => { beforeEach(async () => {
when.modal.open(); await when.modal.open();
secondId = when.modal.fillLayers({ secondId = await when.modal.fillLayers({
id: "second-layer", id: "second-layer",
type: "background", type: "background",
}); });
}); });
test("should show the selected layer in the editor", () => { test("should show the selected layer in the editor", async () => {
when.realClick("layer-list-item:" + secondId); await when.realClick("layer-list-item:" + secondId);
then(get.elementByTestId("layer-editor.layer-id.input")).shouldHaveValue(secondId); await then(get.elementByTestId("layer-editor.layer-id.input")).shouldHaveValue(secondId);
when.realClick("layer-list-item:" + id); await when.realClick("layer-list-item:" + id);
then(get.elementByTestId("layer-editor.layer-id.input")).shouldHaveValue(id); await then(get.elementByTestId("layer-editor.layer-id.input")).shouldHaveValue(id);
}); });
}); });
}); });
}); });
describe("background", () => { describe("background", () => {
test("add", () => { test("add", async () => {
const id = when.modal.fillLayers({ const id = await when.modal.fillLayers({ type: "background" });
type: "background", await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
}); layers: [{ id, type: "background" }],
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [
{
id: id,
type: "background",
},
],
}); });
}); });
describe("modify", () => {}); test("modify", async () => {
const id = await when.modal.fillLayers({ type: "background" });
await when.click("layer-list-item:" + id);
await when.setValue("spec-field-input:background-opacity", "0.4");
await when.click("layer-editor.layer-id");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, type: "background", paint: { "background-opacity": 0.4 } }],
});
});
}); });
describe("fill", () => { describe("fill", () => {
test("add", () => { test("add", async () => {
const id = when.modal.fillLayers({ const id = await when.modal.fillLayers({ type: "fill", layer: "example" });
type: "fill", await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layer: "example", layers: [{ id, type: "fill", source: "example" }],
});
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [
{
id: id,
type: "fill",
source: "example",
},
],
}); });
}); });
// TODO: Change source test("change source", async () => {
test("change source"); const id = await when.modal.fillLayers({ type: "fill", layer: "example" });
await when.changeLayerSource("raster");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [{ id, type: "fill", source: "raster" }],
});
});
}); });
describe("line", () => { describe("line", () => {
test("add", () => { test("add", async () => {
const id = when.modal.fillLayers({ const id = await when.modal.fillLayers({ type: "line", layer: "example" });
type: "line", await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layer: "example", layers: [{ id, type: "line", source: "example" }],
});
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [
{
id: id,
type: "line",
source: "example",
},
],
}); });
}); });
test("groups", () => { test("groups", async () => {
when.modal.open(); const id1 = await when.modal.fillLayers({ id: "aa", type: "line", layer: "example" });
const id1 = when.modal.fillLayers({
id: "aa",
type: "line",
layer: "example",
});
when.modal.open(); await when.modal.open();
const id2 = when.modal.fillLayers({ const id2 = await when.modal.fillLayers({ id: "aa-2", type: "line", layer: "example" });
id: "aa-2",
type: "line",
layer: "example",
});
when.modal.open(); await when.modal.open();
const id3 = when.modal.fillLayers({ const id3 = await when.modal.fillLayers({ id: "b", type: "line", layer: "example" });
id: "b",
type: "line",
layer: "example",
});
then(get.elementByTestId("layer-list-item:" + id1)).shouldBeVisible(); await then(get.elementByTestId("layer-list-item:" + id1)).shouldBeVisible();
then(get.elementByTestId("layer-list-item:" + id2)).shouldNotBeVisible(); await then(get.elementByTestId("layer-list-item:" + id2)).shouldNotBeVisible();
then(get.elementByTestId("layer-list-item:" + id3)).shouldBeVisible(); await then(get.elementByTestId("layer-list-item:" + id3)).shouldBeVisible();
when.click("layer-list-group:aa-0"); await when.click("layer-list-group:aa-0");
then(get.elementByTestId("layer-list-item:" + id1)).shouldBeVisible(); await then(get.elementByTestId("layer-list-item:" + id1)).shouldBeVisible();
then(get.elementByTestId("layer-list-item:" + id2)).shouldBeVisible(); await then(get.elementByTestId("layer-list-item:" + id2)).shouldBeVisible();
then(get.elementByTestId("layer-list-item:" + id3)).shouldBeVisible(); await then(get.elementByTestId("layer-list-item:" + id3)).shouldBeVisible();
when.click("layer-list-item:" + id2); await when.click("layer-list-item:" + id2);
when.click("skip-target-layer-editor"); await when.click("skip-target-layer-editor");
when.click("menu-move-layer-down"); await when.click("menu-move-layer-down");
then(get.elementByTestId("layer-list-group:aa-0")).shouldNotExist(); await then(get.elementByTestId("layer-list-group:aa-0")).shouldNotExist();
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [ layers: [
{ { id: "aa", type: "line", source: "example" },
id: "aa", { id: "b", type: "line", source: "example" },
type: "line", { id: "aa-2", type: "line", source: "example" },
source: "example",
},
{
id: "b",
type: "line",
source: "example",
},
{
id: "aa-2",
type: "line",
source: "example",
},
], ],
}); });
}); });
}); });
describe("symbol", () => { describe("symbol", () => {
test("add", () => { test("add", async () => {
const id = when.modal.fillLayers({ const id = await when.modal.fillLayers({ type: "symbol", layer: "example" });
type: "symbol", await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layer: "example", layers: [{ id, type: "symbol", source: "example" }],
});
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [
{
id: id,
type: "symbol",
source: "example",
},
],
}); });
}); });
test("should show spec info when hovering and clicking single line property", () => { test("should show spec info when hovering and clicking single line property", async () => {
when.modal.fillLayers({ await when.modal.fillLayers({ type: "symbol", layer: "example" });
type: "symbol",
layer: "example",
});
when.hover("spec-field-container:text-rotate"); await when.hover("spec-field-container:text-rotate");
then(get.elementByTestId("field-doc-button-Rotate")).shouldBeVisible(); await then(get.elementByTestId("field-doc-button-Rotate")).shouldBeVisible();
when.click("field-doc-button-Rotate", 0); await when.click("field-doc-button-Rotate", 0);
then(get.elementByTestId("spec-field-doc")).shouldContainText("Rotates the "); await then(get.elementByTestId("spec-field-doc")).shouldContainText("Rotates the ");
}); });
test("should show spec info when hovering and clicking multi line property", () => { test("should show spec info when hovering and clicking multi line property", async () => {
when.modal.fillLayers({ await when.modal.fillLayers({ type: "symbol", layer: "example" });
type: "symbol",
layer: "example",
});
when.hover("spec-field-container:text-offset"); await when.hover("spec-field-container:text-offset");
then(get.elementByTestId("field-doc-button-Offset")).shouldBeVisible(); await then(get.elementByTestId("field-doc-button-Offset")).shouldBeVisible();
when.click("field-doc-button-Offset", 0); await when.click("field-doc-button-Offset", 0);
then(get.elementByTestId("spec-field-doc")).shouldContainText("Offset distance"); await then(get.elementByTestId("spec-field-doc")).shouldContainText("Offset distance");
}); });
test("should hide spec info when clicking a second time", () => { test("should hide spec info when clicking a second time", async () => {
when.modal.fillLayers({ await when.modal.fillLayers({ type: "symbol", layer: "example" });
type: "symbol",
layer: "example",
});
when.hover("spec-field-container:text-rotate"); await when.hover("spec-field-container:text-rotate");
then(get.elementByTestId("field-doc-button-Rotate")).shouldBeVisible(); await then(get.elementByTestId("field-doc-button-Rotate")).shouldBeVisible();
when.click("field-doc-button-Rotate", 0); await when.click("field-doc-button-Rotate", 0);
when.wait(200); await when.wait(200);
when.click("field-doc-button-Rotate", 0); await when.click("field-doc-button-Rotate", 0);
then(get.elementByTestId("spec-field-doc")).shouldNotBeVisible(); await then(get.elementByTestId("spec-field-doc")).shouldNotBeVisible();
}); });
}); });
describe("raster", () => { describe("raster", () => {
test("add", () => { test("add", async () => {
const id = when.modal.fillLayers({ const id = await when.modal.fillLayers({ type: "raster", layer: "raster" });
type: "raster", await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layer: "raster", layers: [{ id, type: "raster", source: "raster" }],
});
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [
{
id: id,
type: "raster",
source: "raster",
},
],
}); });
}); });
}); });
describe("circle", () => { describe("circle", () => {
test("add", () => { test("add", async () => {
const id = when.modal.fillLayers({ const id = await when.modal.fillLayers({ type: "circle", layer: "example" });
type: "circle", await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layer: "example", layers: [{ id, type: "circle", source: "example" }],
});
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [
{
id: id,
type: "circle",
source: "example",
},
],
}); });
}); });
}); });
describe("fill extrusion", () => { describe("fill extrusion", () => {
test("add", () => { test("add", async () => {
const id = when.modal.fillLayers({ const id = await when.modal.fillLayers({ type: "fill-extrusion", layer: "example" });
type: "fill-extrusion", await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layer: "example", layers: [{ id, type: "fill-extrusion", source: "example" }],
});
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [
{
id: id,
type: "fill-extrusion",
source: "example",
},
],
}); });
}); });
}); });
describe("hillshade", () => { describe("hillshade", () => {
test("add", () => { test("add", async () => {
const id = when.modal.fillLayers({ const id = await when.modal.fillLayers({ type: "hillshade", layer: "example" });
type: "hillshade", await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layer: "example", layers: [{ id, type: "hillshade", source: "example" }],
}); });
});
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ test("set hillshade illumination direction array", async () => {
const id = await when.modal.fillLayers({ type: "hillshade", layer: "example" });
await when.collapseGroupInLayerEditor();
await when.collapseGroupInLayerEditor(1);
await when.setValueToPropertyArray("spec-field:hillshade-illumination-direction", "1");
await when.addValueToPropertyArray("spec-field:hillshade-illumination-direction", "2");
await when.addValueToPropertyArray("spec-field:hillshade-illumination-direction", "3");
await when.addValueToPropertyArray("spec-field:hillshade-illumination-direction", "4");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [ layers: [
{ {
id: id, id,
type: "hillshade", type: "hillshade",
source: "example", source: "example",
paint: { "hillshade-illumination-direction": [1, 2, 3, 4] },
}, },
], ],
}); });
}); });
test("set hillshade illumination direction array", () => { test("set hillshade highlight color array", async () => {
const id = when.modal.fillLayers({ const id = await when.modal.fillLayers({ type: "hillshade", layer: "example" });
type: "hillshade", await when.collapseGroupInLayerEditor();
layer: "example", await when.setValueToPropertyArray("spec-field:hillshade-highlight-color", "blue");
}); await when.addValueToPropertyArray("spec-field:hillshade-highlight-color", "#00ff00");
when.collapseGroupInLayerEditor(); await when.addValueToPropertyArray("spec-field:hillshade-highlight-color", "rgba(255, 255, 0, 1)");
when.collapseGroupInLayerEditor(1);
when.setValueToPropertyArray("spec-field:hillshade-illumination-direction", "1");
when.addValueToPropertyArray("spec-field:hillshade-illumination-direction", "2");
when.addValueToPropertyArray("spec-field:hillshade-illumination-direction", "3");
when.addValueToPropertyArray("spec-field:hillshade-illumination-direction", "4");
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [ layers: [
{ {
id: id, id,
type: "hillshade", type: "hillshade",
source: "example", source: "example",
paint: { paint: {
"hillshade-illumination-direction": [ 1, 2, 3, 4 ] "hillshade-highlight-color": ["blue", "#00ff00", "rgba(255, 255, 0, 1)"],
} },
},
],
});
});
test("set hillshade highlight color array", () => {
const id = when.modal.fillLayers({
type: "hillshade",
layer: "example",
});
when.collapseGroupInLayerEditor();
when.setValueToPropertyArray("spec-field:hillshade-highlight-color", "blue");
when.addValueToPropertyArray("spec-field:hillshade-highlight-color", "#00ff00");
when.addValueToPropertyArray("spec-field:hillshade-highlight-color", "rgba(255, 255, 0, 1)");
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [
{
id: id,
type: "hillshade",
source: "example",
paint: {
"hillshade-highlight-color": [ "blue", "#00ff00", "rgba(255, 255, 0, 1)" ]
}
}, },
], ],
}); });
@@ -414,129 +280,85 @@ describe("layers list", () => {
}); });
describe("color-relief", () => { describe("color-relief", () => {
test("add", () => { test("add", async () => {
const id = when.modal.fillLayers({ const id = await when.modal.fillLayers({ type: "color-relief", layer: "example" });
type: "color-relief", await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layer: "example", layers: [{ id, type: "color-relief", source: "example" }],
});
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [
{
id: id,
type: "color-relief",
source: "example",
},
],
}); });
}); });
test("adds elevation expression when clicking the elevation button", () => { test("adds elevation expression when clicking the elevation button", async () => {
when.modal.fillLayers({ await when.modal.fillLayers({ type: "color-relief", layer: "example" });
type: "color-relief", await when.collapseGroupInLayerEditor();
layer: "example", await when.click("make-elevation-function");
}); await then(
when.collapseGroupInLayerEditor(); get.element("[data-wd-key='spec-field-container:color-relief-color'] .cm-line")
when.click("make-elevation-function");
then(get.element("[data-wd-key='spec-field-container:color-relief-color'] .cm-line")).shouldBeVisible();
});
});
describe("groups", () => {
test("simple", () => {
when.setStyle("geojson");
when.modal.open();
when.modal.fillLayers({
id: "foo",
type: "background",
});
when.modal.open();
when.modal.fillLayers({
id: "foo_bar",
type: "background",
});
when.modal.open();
when.modal.fillLayers({
id: "foo_bar_baz",
type: "background",
});
then(get.elementByTestId("layer-list-item:foo")).shouldBeVisible();
then(get.elementByTestId("layer-list-item:foo_bar")).shouldNotBeVisible();
then(
get.elementByTestId("layer-list-item:foo_bar_baz")
).shouldNotBeVisible();
when.click("layer-list-group:foo-0");
then(get.elementByTestId("layer-list-item:foo")).shouldBeVisible();
then(get.elementByTestId("layer-list-item:foo_bar")).shouldBeVisible();
then(
get.elementByTestId("layer-list-item:foo_bar_baz")
).shouldBeVisible(); ).shouldBeVisible();
}); });
}); });
describe("groups", () => {
test("simple", async () => {
await when.setStyle("geojson");
await when.modal.open();
await when.modal.fillLayers({ id: "foo", type: "background" });
await when.modal.open();
await when.modal.fillLayers({ id: "foo_bar", type: "background" });
await when.modal.open();
await when.modal.fillLayers({ id: "foo_bar_baz", type: "background" });
await then(get.elementByTestId("layer-list-item:foo")).shouldBeVisible();
await then(get.elementByTestId("layer-list-item:foo_bar")).shouldNotBeVisible();
await then(get.elementByTestId("layer-list-item:foo_bar_baz")).shouldNotBeVisible();
await when.click("layer-list-group:foo-0");
await then(get.elementByTestId("layer-list-item:foo")).shouldBeVisible();
await then(get.elementByTestId("layer-list-item:foo_bar")).shouldBeVisible();
await then(get.elementByTestId("layer-list-item:foo_bar_baz")).shouldBeVisible();
});
});
describe("drag and drop", () => { describe("drag and drop", () => {
test("move layer should update local storage", () => { test("move layer should update local storage", async () => {
when.modal.open(); const firstId = await when.modal.fillLayers({ id: "a", type: "background" });
const firstId = when.modal.fillLayers({ await when.modal.open();
id: "a", const secondId = await when.modal.fillLayers({ id: "b", type: "background" });
type: "background", await when.modal.open();
}); const thirdId = await when.modal.fillLayers({ id: "c", type: "background" });
when.modal.open();
const secondId = when.modal.fillLayers({
id: "b",
type: "background",
});
when.modal.open();
const thirdId = when.modal.fillLayers({
id: "c",
type: "background",
});
when.dragAndDropWithWait("layer-list-item:" + firstId, "layer-list-item:" + thirdId); await when.dragAndDropWithWait("layer-list-item:" + firstId, "layer-list-item:" + thirdId);
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
layers: [ layers: [
{ { id: secondId, type: "background" },
id: secondId, { id: thirdId, type: "background" },
type: "background", { id: firstId, type: "background" },
},
{
id: thirdId,
type: "background",
},
{
id: firstId,
type: "background",
},
], ],
}); });
}); });
}); });
describe("sticky header", () => { describe("sticky header", () => {
test("should keep header visible when scrolling layer list", () => { test("should keep header visible when scrolling layer list", async () => {
// Setup: Create multiple layers to enable scrolling // Setup: Create multiple layers to enable scrolling
for (let i = 0; i < 20; i++) { // The modal is already open (beforeEach) for the first layer.
when.modal.open(); await when.modal.fillLayers({ id: "layer-0", type: "background" });
when.modal.fillLayers({ for (let i = 1; i < 20; i++) {
id: `layer-${i}`, await when.modal.open();
type: "background", await when.modal.fillLayers({ id: `layer-${i}`, type: "background" });
});
} }
when.wait(500); await when.wait(500);
const header = get.elementByTestId("layer-list.header"); const header = get.elementByTestId("layer-list.header");
then(header).shouldBeVisible(); await then(header).shouldBeVisible();
// Scroll the layer list container (use ensureScrollable: false to avoid flakiness) // Scroll the layer list container
get.elementByTestId("layer-list").scrollTo("bottom", { ensureScrollable: false }); await when.scrollToBottom(get.elementByTestId("layer-list"));
when.wait(200); await when.wait(200);
then(header).shouldBeVisible(); await then(header).shouldBeVisible();
then(get.elementByTestId("layer-list:add-layer")).shouldBeVisible(); await then(get.elementByTestId("layer-list:add-layer")).shouldBeVisible();
}); });
}); });
}); });
+41 -43
View File
@@ -1,69 +1,67 @@
import { beforeEach, describe, test } from "./utils/fixtures";
import { MaputnikDriver } from "./maputnik-driver"; import { MaputnikDriver } from "./maputnik-driver";
const test = it;
describe("map", () => { describe("map", () => {
const { beforeAndAfter, get, when, then } = new MaputnikDriver(); const { given, get, when, then } = new MaputnikDriver();
beforeAndAfter();
beforeEach(async () => {
await given.setupMockBackedResponses();
await when.setStyle("both");
});
describe("zoom level", () => { describe("zoom level", () => {
test("via url", () => { test("via url", async () => {
const zoomLevel = 12.37; const zoomLevel = 12.37;
when.setStyle("geojson", zoomLevel); await when.setStyle("geojson", zoomLevel);
then(get.elementByTestId("maplibre:ctrl-zoom")).shouldBeVisible(); await then(get.elementByTestId("maplibre:ctrl-zoom")).shouldBeVisible();
then(get.elementByTestId("maplibre:ctrl-zoom")).shouldContainText( await then(get.elementByTestId("maplibre:ctrl-zoom")).shouldContainText("Zoom: " + zoomLevel);
"Zoom: " + zoomLevel
);
}); });
test("via map controls", () => { test("via map controls", async () => {
const zoomLevel = 12.37; const zoomLevel = 12.37;
when.setStyle("geojson", zoomLevel); await when.setStyle("geojson", zoomLevel);
then(get.elementByTestId("maplibre:ctrl-zoom")).shouldBeVisible(); await then(get.elementByTestId("maplibre:ctrl-zoom")).shouldBeVisible();
when.clickZoomIn(); await when.clickZoomIn();
then(get.elementByTestId("maplibre:ctrl-zoom")).shouldContainText( await then(get.elementByTestId("maplibre:ctrl-zoom")).shouldContainText("Zoom: " + (zoomLevel + 1));
"Zoom: " + (zoomLevel + 1)
);
}); });
test("via style file definition", () => { test("via style file definition", async () => {
when.setStyle("zoom_7_center_0_51"); await when.setStyle("zoom_7_center_0_51");
then(get.elementByTestId("maplibre:ctrl-zoom")).shouldBeVisible(); await then(get.elementByTestId("maplibre:ctrl-zoom")).shouldBeVisible();
then(get.elementByTestId("maplibre:ctrl-zoom")).shouldContainText( await then(get.elementByTestId("maplibre:ctrl-zoom")).shouldContainText("Zoom: " + 7);
"Zoom: " + (7) await then(get.locationHash()).shouldInclude("#7/51/0");
);
then(get.locationHash().should("contain", "#7/51/0"));
// opening another stylefile does not update the map view again // opening another stylefile does not update the map view again
// as discussed in https://github.com/maplibre/maputnik/issues/1546 // as discussed in https://github.com/maplibre/maputnik/issues/1546
when.openASecondStyleWithDifferentZoomAndCenter(); await when.openASecondStyleWithDifferentZoomAndCenter();
then(get.locationHash().should("contain", "#7/51/0")); await then(get.locationHash()).shouldInclude("#7/51/0");
}); });
}); });
describe("search", () => { describe("search", () => {
test("should exist", () => { test("should exist", async () => {
then(get.searchControl()).shouldBeVisible(); await then(get.searchControl()).shouldBeVisible();
}); });
}); });
describe("popup", () => { describe("popup", () => {
beforeEach(() => { beforeEach(async () => {
when.setStyle("rectangles"); await when.setStyle("rectangles");
then(get.locationHash().should("exist")); await then(get.locationHash()).shouldExist();
});
test("should open on feature click", () => {
when.clickCenter("maplibre:map");
then(get.elementByTestId("feature-layer-popup")).shouldBeVisible();
}); });
test("should open a second feature after closing popup", () => { test("should open on feature click", async () => {
when.clickCenter("maplibre:map"); await when.clickCenter("maplibre:map");
then(get.elementByTestId("feature-layer-popup")).shouldBeVisible(); await then(get.elementByTestId("feature-layer-popup")).shouldBeVisible();
when.closePopup(); });
then(get.elementByTestId("feature-layer-popup")).shouldNotExist();
when.clickCenter("maplibre:map"); test("should open a second feature after closing popup", async () => {
then(get.elementByTestId("feature-layer-popup")).shouldBeVisible(); await when.clickCenter("maplibre:map");
await then(get.elementByTestId("feature-layer-popup")).shouldBeVisible();
await when.closePopup();
await then(get.elementByTestId("feature-layer-popup")).shouldNotExist();
await when.clickCenter("maplibre:map");
await then(get.elementByTestId("feature-layer-popup")).shouldBeVisible();
}); });
}); });
}); });
-56
View File
@@ -1,56 +0,0 @@
/// <reference types="cypress-real-events" />
import { CypressHelper } from "@shellygo/cypress-test-utils";
import "cypress-real-events/support";
export default class MaputnikCypressHelper {
private helper = new CypressHelper({ defaultDataAttribute: "data-wd-key" });
public given = {
...this.helper.given,
};
public get = {
locationHash: (): Cypress.Chainable<string> => cy.location("hash"),
...this.helper.get,
};
public when = {
dragAndDropWithWait: (element: string, targetElement: string) => {
this.helper.get.elementByTestId(element).realMouseDown({ button: "left", position: "center" });
this.helper.get.elementByTestId(element).realMouseMove(0, 10, { position: "center" });
this.helper.get.elementByTestId(targetElement).realMouseMove(0, 0, { position: "center" });
this.helper.when.wait(1);
this.helper.get.elementByTestId(targetElement).realMouseUp();
},
clickCenter: (element: string) => {
this.helper.get.elementByTestId(element).realMouseDown({ button: "left", position: "center" });
this.helper.when.wait(200);
this.helper.get.elementByTestId(element).realMouseUp();
},
openFileByFixture: (fixture: string, buttonTestId: string, inputTestId: string) => {
cy.window().then((win) => {
const file = {
text: cy.stub().resolves(cy.fixture(fixture).then(JSON.stringify)),
};
const fileHandle = {
getFile: cy.stub().resolves(file),
};
if (!win.showOpenFilePicker) {
this.helper.get.elementByTestId(inputTestId).selectFile("cypress/fixtures/" + fixture, { force: true });
} else {
cy.stub(win, "showOpenFilePicker").resolves([fileHandle]);
this.helper.get.elementByTestId(buttonTestId).click();
}
});
},
dropFileByFixture: (fixture: string, dropzoneTestId: string) => {
this.helper.get.elementByTestId(dropzoneTestId).selectFile("cypress/fixtures/" + fixture, {
action: "drag-drop",
force: true,
});
},
...this.helper.when,
};
public beforeAndAfter = this.helper.beforeAndAfter;
}
+301 -215
View File
@@ -1,122 +1,56 @@
/// <reference types="cypress-plugin-tab" /> import { PlaywrightHelper } from "./playwright-helper";
import { ModalDriver } from "./modal-driver";
import { CypressHelper } from "@shellygo/cypress-test-utils";
import { Assertable, then } from "@shellygo/cypress-test-utils/assertable";
import MaputnikCypressHelper from "./maputnik-cypress-helper";
import ModalDriver from "./modal-driver";
const baseUrl = "http://localhost:8888/"; const baseUrl = "http://localhost:8888/";
const isMac = process.platform === "darwin";
const styleFromWindow = (win: Window) => { /**
const styleId = win.localStorage.getItem("maputnik:latest_style"); * The maputnik-specific driver. It builds on the generic {@link PlaywrightHelper}
const styleItemKey = `maputnik:style:${styleId}`; * — spreading its `given`/`when`/`get` primitives and adding domain concepts
const styleItem = win.localStorage.getItem(styleItemKey); * (loading a style, the add-layer modal, the JSON editor, …). All Playwright
if (!styleItem) throw new Error("Could not get styleItem from localStorage"); * access goes through the helper; the driver never touches `page` directly.
const obj = JSON.parse(styleItem); */
return obj;
};
export class MaputnikAssertable<T> extends Assertable<T> {
shouldEqualToStoredStyle = () =>
then(
new CypressHelper().get.window().then((win: Window) => {
const style = styleFromWindow(win);
then(this.chainable).shouldDeepNestedInclude(style);
})
);
}
export class MaputnikDriver { export class MaputnikDriver {
private helper = new MaputnikCypressHelper(); private readonly helper = new PlaywrightHelper();
private modalDriver = new ModalDriver(); private readonly modalDriver = new ModalDriver();
public beforeAndAfter = () => { then = this.helper.then;
beforeEach(() => {
this.given.setupMockBackedResponses();
this.when.setStyle("both");
});
};
public then = (chainable: Cypress.Chainable<any>) => /** Reads the maputnik style currently persisted in localStorage. */
new MaputnikAssertable(chainable); private async readStoredStyle(): Promise<any> {
const styleId = await this.helper.get.localStorageItem("maputnik:latest_style");
const styleItem = await this.helper.get.localStorageItem(`maputnik:style:${styleId}`);
if (!styleItem) throw new Error("Could not get styleItem from localStorage");
return JSON.parse(styleItem);
}
public given = { public given = {
...this.helper.given, ...this.helper.given,
setupMockBackedResponses: () => {
this.helper.given.interceptAndMockResponse({ setupMockBackedResponses: async () => {
method: "GET", const styleFixtures = [
url: baseUrl + "example-style.json", "example-style.json",
response: { "example-layer-style.json",
fixture: "example-style.json", "geojson-style.json",
}, "raster-style.json",
alias: "example-style.json", "geojson-raster-style.json",
}); "rectangles-style.json",
this.helper.given.interceptAndMockResponse({ "example-style-with-fonts.json",
method: "GET", "example-style-with-zoom-7-and-center-0-51.json",
url: baseUrl + "example-layer-style.json", "example-style-with-zoom-5-and-center-50-50.json",
response: { "access-token-style.json",
fixture: "example-layer-style.json", ];
}, for (const fixture of styleFixtures) {
}); await this.helper.given.interceptAndMockResponse({
this.helper.given.interceptAndMockResponse({ method: "GET",
method: "GET", url: baseUrl + fixture,
url: baseUrl + "geojson-style.json", response: { fixture },
response: { alias: fixture === "example-style.json" ? "example-style.json" : undefined,
fixture: "geojson-style.json", });
}, }
}); await this.helper.given.interceptAndMockResponse({ method: "GET", url: /example\.local\//, response: [] });
this.helper.given.interceptAndMockResponse({ await this.helper.given.interceptAndMockResponse({ method: "GET", url: /example\.com\//, response: [] });
method: "GET", await this.helper.given.interceptAndMockResponse({
url: baseUrl + "raster-style.json",
response: {
fixture: "raster-style.json",
},
});
this.helper.given.interceptAndMockResponse({
method: "GET",
url: baseUrl + "geojson-raster-style.json",
response: {
fixture: "geojson-raster-style.json",
},
});
this.helper.given.interceptAndMockResponse({
method: "GET",
url: baseUrl + "rectangles-style.json",
response: {
fixture: "rectangles-style.json",
},
});
this.helper.given.interceptAndMockResponse({
method: "GET",
url: baseUrl + "example-style-with-fonts.json",
response: {
fixture: "example-style-with-fonts.json",
},
});
this.helper.given.interceptAndMockResponse({
method: "GET",
url: baseUrl + "example-style-with-zoom-7-and-center-0-51.json",
response: {
fixture: "example-style-with-zoom-7-and-center-0-51.json",
},
});
this.helper.given.interceptAndMockResponse({
method: "GET",
url: baseUrl + "example-style-with-zoom-5-and-center-50-50.json",
response: {
fixture: "example-style-with-zoom-5-and-center-50-50.json",
},
});
this.helper.given.interceptAndMockResponse({
method: "GET",
url: "*example.local/*",
response: [],
});
this.helper.given.interceptAndMockResponse({
method: "GET",
url: "*example.com/*",
response: [],
});
this.helper.given.interceptAndMockResponse({
method: "GET", method: "GET",
url: "https://www.glyph-server.com/*", url: "https://www.glyph-server.com/*",
response: ["Font 1", "Font 2", "Font 3"], response: ["Font 1", "Font 2", "Font 3"],
@@ -126,147 +60,299 @@ export class MaputnikDriver {
public when = { public when = {
...this.helper.when, ...this.helper.when,
modal: this.modalDriver.when, modal: this.modalDriver.when,
doWithin: (selector: string, fn: () => void) => {
this.helper.when.doWithin(fn, selector); setStyle: async (
}, styleProperties:
tab: () => this.helper.get.element("body").tab(), | "geojson"
waitForExampleFileResponse: () => { | "raster"
this.helper.when.waitForResponse("example-style.json"); | "both"
}, | "layer"
openASecondStyleWithDifferentZoomAndCenter: () => { | "rectangles"
cy.contains("button", "Open").click(); | "font"
cy.get('[data-wd-key="modal:open.url.input"]') | "zoom_7_center_0_51"
.should("be.enabled") | "access_tokens"
.clear() | "",
.type("http://localhost:8888/example-style-with-zoom-5-and-center-50-50.json{enter}");
},
chooseExampleFile: () => {
this.helper.given.fixture("example-style.json", "example-style.json");
this.helper.when.openFileByFixture("example-style.json", "modal:open.dropzone", "modal:open.file.input");
this.helper.when.wait(200);
},
dropExampleFile: () => {
this.helper.given.fixture("example-style.json", "example-style.json");
this.helper.when.dropFileByFixture("example-style.json", "modal:open.dropzone");
this.helper.when.wait(200);
},
setStyle: (
styleProperties: "geojson" | "raster" | "both" | "layer" | "rectangles" | "font" | "zoom_7_center_0_51" | "",
zoom?: number zoom?: number
) => { ) => {
const url = new URL(baseUrl); const styleFileByKey: Record<string, string> = {
switch (styleProperties) { geojson: "geojson-style.json",
case "geojson": raster: "raster-style.json",
url.searchParams.set("style", baseUrl + "geojson-style.json"); both: "geojson-raster-style.json",
break; layer: "example-layer-style.json",
case "raster": rectangles: "rectangles-style.json",
url.searchParams.set("style", baseUrl + "raster-style.json"); font: "example-style-with-fonts.json",
break; zoom_7_center_0_51: "example-style-with-zoom-7-and-center-0-51.json",
case "both": access_tokens: "access-token-style.json",
url.searchParams.set("style", baseUrl + "geojson-raster-style.json"); };
break;
case "layer":
url.searchParams.set("style", baseUrl + "example-layer-style.json");
break;
case "rectangles":
url.searchParams.set("style", baseUrl + "rectangles-style.json");
break;
case "font":
url.searchParams.set("style", baseUrl + "example-style-with-fonts.json");
break;
case "zoom_7_center_0_51":
url.searchParams.set("style", baseUrl + "example-style-with-zoom-7-and-center-0-51.json");
break;
}
const url = new URL(baseUrl);
if (styleProperties && styleFileByKey[styleProperties]) {
url.searchParams.set("style", baseUrl + styleFileByKey[styleProperties]);
}
if (zoom) { if (zoom) {
url.hash = `${zoom}/41.3805/2.1635`; url.hash = `${zoom}/41.3805/2.1635`;
} }
this.helper.when.visit(url.toString());
if (styleProperties) { await this.helper.when.visit(url.toString());
this.helper.when.acceptConfirm();
}
// when methods should not include assertions
const toolbarLink = this.helper.get.elementByTestId("toolbar:link"); const toolbarLink = this.helper.get.elementByTestId("toolbar:link");
toolbarLink.scrollIntoView(); await toolbarLink.scrollIntoViewIfNeeded();
toolbarLink.should("be.visible"); await this.then(toolbarLink).shouldBeVisible();
}, },
typeKeys: (keys: string) => this.helper.get.element("body").type(keys), openASecondStyleWithDifferentZoomAndCenter: async () => {
await this.helper.when.clickButtonByName("Open");
clickZoomIn: () => { const input = this.helper.get.elementByTestId("modal:open.url.input");
this.helper.get.element(".maplibregl-ctrl-zoom-in").click(); await input.fill("http://localhost:8888/example-style-with-zoom-5-and-center-50-50.json");
await input.press("Enter");
}, },
selectWithin: (selector: string, value: string) => { chooseExampleFile: async () => {
this.when.doWithin(selector, () => { await this.helper.when.openFileByFixture("example-style.json", "modal:open.dropzone");
this.helper.get.element("select").select(value); await this.helper.when.wait(200);
});
}, },
select: (selector: string, value: string) => { /** Picks the example style through the browser's native file chooser. */
this.helper.get.elementByTestId(selector).select(value); chooseExampleFileFromPicker: async () => {
await this.helper.when.chooseFileFromPicker("example-style.json", "modal:open.dropzone");
await this.helper.when.wait(200);
}, },
focus: (selector: string) => { dropExampleFile: async () => {
this.helper.when.focus(selector); await this.helper.when.dropFileByFixture("example-style.json", "modal:open.dropzone");
await this.helper.when.wait(200);
}, },
setValue: (selector: string, text: string) => { clickZoomIn: async () => {
this.helper.get await this.helper.get.element(".maplibregl-ctrl-zoom-in").click();
.elementByTestId(selector)
.clear()
.type(text, { parseSpecialCharSequences: false });
}, },
setValueToPropertyArray: (selector: string, value: string) => { closePopup: async () => {
this.when.doWithin(selector, () => { await this.helper.get.element(".maplibregl-popup-close-button").click();
this.helper.get.element(".maputnik-array-block-content input").last().type("{selectall}"+value, {force: true });
});
}, },
addValueToPropertyArray: (selector: string, value: string) => { collapseGroupInLayerEditor: async (index = 0) => {
this.when.doWithin(selector, () => { await this.helper.get.element(".maputnik-layer-editor-group__button").nth(index).click();
this.helper.get.element(".maputnik-array-add-value").click({ force: true });
this.helper.get.element(".maputnik-array-block-content input").last().type("{selectall}"+value, {force: true });
});
}, },
closePopup: () => { /** Expands/collapses a layer-editor group by its title, e.g. "Paint properties". */
this.helper.get.element(".maplibregl-popup-close-button").click(); toggleGroupInLayerEditor: async (title: string) => {
await this.helper.when.click("layer-editor-group:" + title);
}, },
collapseGroupInLayerEditor: (index = 0) => { /**
this.helper.get.element(".maputnik-layer-editor-group__button").eq(index).realClick(); * Picks a source for the selected layer from the source autocomplete.
* The autocomplete is a controlled (downshift) input, so the value has to be
* filled rather than typed key by key, then chosen from the filtered menu.
*/
changeLayerSource: async (sourceId: string) => {
const input = this.helper.get.elementByTestId("layer-editor.layer-source").locator("input");
await input.fill(sourceId);
await this.helper.get.element(".maputnik-autocomplete-menu-item").first().click();
}, },
appendTextInJsonEditor: (text: string) => { appendTextInJsonEditor: async (text: string) => {
this.helper.get.element(".cm-line").first().click().type(text, { parseSpecialCharSequences: false }); await this.helper.get.element(".cm-line").first().click();
// Move to the very start of the document so the inserted text breaks the
// root JSON structure (CodeMirror auto-closes brackets otherwise).
await this.helper.when.typeKeys("{home}");
await this.helper.when.typeText(text);
}, },
setTextInJsonEditor: (text: string) => { setTextInJsonEditor: async (text: string) => {
this.helper.get.element(".cm-line").first().click().clear().type(text, { parseSpecialCharSequences: false }); await this.helper.get.element(".cm-line").first().click();
} await this.helper.when.typeKeys("{selectall}");
await this.helper.when.typeText(text);
},
setValueToPropertyArray: async (selector: string, value: string) => {
const input = this.helper.get.elementByTestId(selector).locator(".maputnik-array-block-content input").last();
await input.focus();
await this.helper.when.typeKeys("{selectall}");
await this.helper.when.typeText(value);
},
addValueToPropertyArray: async (selector: string, value: string) => {
const block = this.helper.get.elementByTestId(selector);
await block.locator(".maputnik-array-add-value").click();
const input = block.locator(".maputnik-array-block-content input").last();
await input.focus();
await this.helper.when.typeKeys("{selectall}");
await this.helper.when.typeText(value);
},
makeZoomFunction: async (fieldName: string) => {
const container = this.helper.get.elementByTestId("spec-field-container:" + fieldName);
await container.scrollIntoViewIfNeeded();
await container.locator(".maputnik-make-zoom-function").last().click({ force: true });
},
makeDataFunction: async (fieldName: string) => {
const container = this.helper.get.elementByTestId("spec-field-container:" + fieldName);
await container.scrollIntoViewIfNeeded();
await container.locator(".maputnik-make-data-function").click({ force: true });
},
addFunctionStop: async (fieldName: string) => {
const container = this.helper.get.elementByTestId("spec-field-container:" + fieldName);
await container.locator(".maputnik-add-stop").first().click({ force: true });
},
deleteFunctionStop: async (fieldName: string) => {
const container = this.helper.get.elementByTestId("spec-field-container:" + fieldName);
await container.locator(".maputnik-delete-stop").first().click({ force: true });
},
/** Turns the property into a raw style expression. */
makeExpression: async (fieldName: string) => {
const container = this.helper.get.elementByTestId("spec-field-container:" + fieldName);
await container.scrollIntoViewIfNeeded();
// In the plain spec field the expression button shares the zoom-function
// class and comes first; inside a function editor it has its own test id.
const inFunctionEditor = container.locator("[data-wd-key='convert-to-expression']");
const button =
(await inFunctionEditor.count()) > 0
? inFunctionEditor
: container.locator(".maputnik-make-zoom-function").first();
await button.click({ force: true });
},
/** Reverts an expression back to a plain value. */
undoExpression: async (fieldName: string) => {
const container = this.helper.get.elementByTestId("spec-field-container:" + fieldName);
await container.locator("[data-wd-key='undo-expression']").click({ force: true });
},
/** Removes an expression, restoring the property's spec default. */
deleteExpression: async (fieldName: string) => {
const container = this.helper.get.elementByTestId("spec-field-container:" + fieldName);
await container.locator("[data-wd-key='delete-expression']").click({ force: true });
},
/** Picks the function scale (categorical/interval/exponential/identity/interpolate). */
selectFunctionType: async (fieldName: string, type: string) => {
const container = this.helper.get.elementByTestId("spec-field-container:" + fieldName);
await container.locator("[data-wd-key='function-type'] select").selectOption(type);
},
/** Sets the "Base" input of a zoom/data function. */
setFunctionBase: async (fieldName: string, value: string) => {
const container = this.helper.get.elementByTestId("spec-field-container:" + fieldName);
await container.locator("[data-wd-key='function-base'] input").fill(value);
},
/**
* Sets the data property a data function keys off of. This is an InputString,
* which only commits its value on blur, so typing alone is not enough.
*/
setFunctionProperty: async (fieldName: string, value: string) => {
const container = this.helper.get.elementByTestId("spec-field-container:" + fieldName);
const input = container.locator("[data-wd-key='function-property'] input");
await input.fill(value);
await input.blur();
},
/** Sets the fallback value used when a feature has no matching stop. */
setFunctionDefault: async (fieldName: string, value: string) => {
const container = this.helper.get.elementByTestId("spec-field-container:" + fieldName);
await container.locator("[data-wd-key='function-default'] input").fill(value);
},
/** Edits one cell of a function's stop table ("Zoom", "Input value" or "Output value"). */
setFunctionStopValue: async (fieldName: string, column: string, index: number, value: string) => {
const container = this.helper.get.elementByTestId("spec-field-container:" + fieldName);
await container.locator(`[aria-label="${column}"]`).nth(index).fill(value);
},
addFilter: async () => {
const button = this.helper.get.elementByTestId("layer-filter-button");
await button.scrollIntoViewIfNeeded();
await button.click({ force: true });
},
selectFilterOperator: async (value: string) => {
await this.helper.get.element(".maputnik-filter-editor-operator select").first().selectOption(value);
},
/** Chooses how the filter items combine: all / none / any. */
selectFilterCombiningOperator: async (value: string) => {
await this.helper.when.selectWithin("filter-combining-operator", value);
},
deleteFilterItem: async (index = 0) => {
await this.helper.get
.element(".maputnik-filter-editor-block-action .maputnik-icon-button")
.nth(index)
.click();
},
/** Converts the simple filter editor into a raw expression editor. */
convertFilterToExpression: async () => {
await this.helper.when.click("filter-convert-to-expression");
},
/**
* Deletes the filter expression, restoring the simple filter editor.
* The filter group precedes the paint group, so its button comes first.
*/
deleteFilterExpression: async () => {
await this.helper.get.element("[data-wd-key='delete-expression']").first().click();
},
setColorValue: async (fieldName: string, value: string) => {
const input = this.helper.get.elementByTestId("spec-field:" + fieldName).locator(".maputnik-color");
await input.fill(value);
},
/** Sets a plain string spec field (e.g. a pattern), which has no dedicated input test id. */
setStringValue: async (fieldName: string, value: string) => {
const input = this.helper.get.elementByTestId("spec-field:" + fieldName).locator("input.maputnik-string");
await input.fill(value);
await input.blur();
},
/**
* Appends text to the end of the JSON editor line holding `lineText`.
* CodeMirror types over its own auto-inserted closing quotes/brackets, so a
* well-formed fragment stays well-formed.
*/
appendToJsonEditorLine: async (lineText: string, text: string) => {
await this.helper.when.clickByText(lineText);
await this.helper.when.typeKeys("{end}");
await this.helper.when.typeText(text);
},
waitForExampleFileResponse: () => this.helper.when.waitForResponse("example-style.json"),
/** Fill localStorage until we get a QuotaExceededError. */
fillLocalStorage: () => this.helper.when.fillLocalStorageUntilQuota("maputnik:fill-"),
}; };
public get = { public get = {
...this.helper.get, ...this.helper.get,
isMac: () => {
return Cypress.platform === "darwin";
},
styleFromLocalStorage: () => isMac: () => isMac,
this.helper.get.window().then((win) => styleFromWindow(win)),
exampleFileUrl: () => {
return baseUrl + "example-style.json";
},
skipTargetLayerList: () =>
this.helper.get.elementByTestId("skip-target-layer-list"),
skipTargetLayerEditor: () =>
this.helper.get.elementByTestId("skip-target-layer-editor"),
canvas: () => this.helper.get.element("canvas"), canvas: () => this.helper.get.element("canvas"),
searchControl: () => this.helper.get.element(".maplibregl-ctrl-geocoder")
searchControl: () => this.helper.get.element(".maplibregl-ctrl-geocoder"),
skipTargetLayerList: () => this.helper.get.elementByTestId("skip-target-layer-list"),
skipTargetLayerEditor: () => this.helper.get.elementByTestId("skip-target-layer-editor"),
styleFromLocalStorage: () => this.helper.query(() => this.readStoredStyle()),
fixture: (name: string) => this.helper.readFixture(name),
responseBody: (alias: string) => {
// Our mocked style responses always return the matching fixture.
const name = alias.endsWith(".json") ? alias : `${alias}.json`;
return this.helper.readFixture(name);
},
exampleFileUrl: () => baseUrl + "example-style.json",
}; };
} }
+63 -25
View File
@@ -1,40 +1,78 @@
import { v1 as uuid } from "uuid"; import { v1 as uuid } from "uuid";
import MaputnikCypressHelper from "./maputnik-cypress-helper"; import { PlaywrightHelper } from "./playwright-helper";
export default class ModalDriver { export class ModalDriver {
private helper = new MaputnikCypressHelper(); private readonly helper = new PlaywrightHelper();
public when = { public when = {
fillLayers: (opts: { type: string; layer?: string; id?: string }) => { fillLayers: async (opts: { type: string; layer?: string; id?: string }) => {
// Having logic in test code is an anti pattern. const { when, get, then } = this.helper;
// This should be split to multiple single responsibility functions const id = opts.id ?? `${opts.type}:${uuid()}`;
const type = opts.type;
const layer = opts.layer;
let id;
if (opts.id) {
id = opts.id;
} else {
id = `${type}:${uuid()}`;
}
this.helper.when.selectOption("add-layer.layer-type.select", type);
this.helper.when.type("add-layer.layer-id.input", id);
if (layer) { await when.select("add-layer.layer-type.select", opts.type);
this.helper.when.doWithin(() => { await when.type("add-layer.layer-id.input", id);
this.helper.get.element("input").clear().type(layer!);
}, "add-layer.layer-source-block"); if (opts.layer) {
const input = get.elementByTestId("add-layer.layer-source-block").locator("input");
await input.click();
await input.fill(opts.layer);
// The source input is a controlled downshift combobox; wait for React to
// settle on the typed value before submitting.
await then(input).shouldHaveValue(opts.layer);
// Close the autocomplete menu so it does not intercept the add button.
await get.elementByTestId("add-layer.layer-id.input").click();
} }
this.helper.when.click("add-layer"); await when.click("add-layer");
return id; return id;
}, },
open: () => { open: async () => {
this.helper.when.click("layer-list:add-layer"); await this.helper.when.click("layer-list:add-layer");
}, },
close: (key: string) => { close: async (key: string) => {
this.helper.when.click(key + ".close-modal"); await this.helper.when.click(key + ".close-modal");
},
/**
* Adds a source of the given type from the sources modal, keeping whatever
* defaults that type's editor prefills.
*/
addSource: async (sourceId: string, sourceType: string) => {
const { when } = this.helper;
await when.setValue("modal:sources.add.source_id", sourceId);
await when.select("modal:sources.add.source_type", sourceType);
await when.click("modal:sources.add.add_source");
await when.wait(200);
},
/** Adds one of the predefined public sources listed in the sources modal. */
addPublicSource: async (index = 0) => {
await this.helper.get.element(".maputnik-public-source-select").nth(index).click();
},
deleteFirstActiveSource: async () => {
await this.helper.get.element(".maputnik-active-source-type-editor-header-delete").first().click();
},
/** Fills one number box of a coordinate pair in the image/video source editor. */
setCoordinateValue: async (index: number, value: string) => {
const input = this.helper.get
.elementByTestId("modal:sources")
.locator(".maputnik-array input")
.nth(index);
await input.fill(value);
await input.blur();
},
exportCreateHtml: async () => {
await this.helper.get.element(".maputnik-modal-export-buttons button").last().click();
},
exportSaveStyle: async () => {
await this.helper.stubSaveFilePicker();
await this.helper.get.element(".maputnik-modal-export-buttons button").first().click();
}, },
}; };
} }
+398 -278
View File
@@ -1,103 +1,142 @@
import { test, expect, describe, beforeEach } from "./utils/fixtures";
import { MaputnikDriver } from "./maputnik-driver"; import { MaputnikDriver } from "./maputnik-driver";
import tokens from "../src/config/tokens.json" with {type: "json"}; import tokens from "../src/config/tokens.json" with { type: "json" };
const test = it;
describe("modals", () => { describe("modals", () => {
const { beforeAndAfter, when, get, given, then } = new MaputnikDriver(); const { given, get, when, then } = new MaputnikDriver();
beforeAndAfter();
beforeEach(() => { beforeEach(async () => {
when.setStyle(""); await given.setupMockBackedResponses();
await when.setStyle("both");
await when.setStyle("");
}); });
describe("open", () => { describe("open", () => {
beforeEach(() => { beforeEach(async () => {
when.click("nav:open"); await when.click("nav:open");
}); });
test("close", () => { test("close", async () => {
when.modal.close("modal:open"); await when.modal.close("modal:open");
then(get.elementByTestId("modal:open")).shouldNotExist(); await then(get.elementByTestId("modal:open")).shouldNotExist();
}); });
test("upload", () => { test("upload", async () => {
when.chooseExampleFile(); await when.chooseExampleFile();
then(get.fixture("example-style.json")).shouldEqualToStoredStyle(); await then(get.styleFromLocalStorage()).shouldDeepNestedInclude(get.fixture("example-style.json"));
}); });
test("upload via drag and drop", () => { test("upload via drag and drop", async () => {
when.dropExampleFile(); await when.dropExampleFile();
then(get.fixture("example-style.json")).shouldEqualToStoredStyle(); await then(get.styleFromLocalStorage()).shouldDeepNestedInclude(get.fixture("example-style.json"));
}); });
describe("when click open url", () => { describe("when click open url", () => {
beforeEach(() => { beforeEach(async () => {
const styleFileUrl = get.exampleFileUrl(); const styleFileUrl = get.exampleFileUrl();
when.setValue("modal:open.url.input", styleFileUrl); await when.setValue("modal:open.url.input", styleFileUrl);
when.click("modal:open.url.button"); await when.click("modal:open.url.button");
when.wait(200); await when.wait(200);
}); });
test("load from url", () => { test("load from url", async () => {
then(get.responseBody("example-style.json")).shouldEqualToStoredStyle(); await then(get.styleFromLocalStorage()).shouldDeepNestedInclude(get.responseBody("example-style.json"));
});
});
describe("without the File System Access API", () => {
test("upload via the file chooser", async () => {
await given.noFileSystemAccessApi();
await when.setStyle("");
await when.click("nav:open");
await when.chooseExampleFileFromPicker();
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude(get.fixture("example-style.json"));
}); });
}); });
}); });
describe("shortcuts", () => { describe("shortcuts", () => {
test("open/close", () => { test("open/close", async () => {
when.setStyle(""); await when.setStyle("");
when.typeKeys("?"); await when.typeKeys("?");
when.modal.close("modal:shortcuts"); await when.modal.close("modal:shortcuts");
then(get.elementByTestId("modal:shortcuts")).shouldNotExist(); await then(get.elementByTestId("modal:shortcuts")).shouldNotExist();
}); });
}); });
describe("export", () => { describe("export", () => {
beforeEach(() => { beforeEach(async () => {
when.click("nav:export"); await when.click("nav:export");
}); });
test("close", () => { test("close", async () => {
when.modal.close("modal:export"); await when.modal.close("modal:export");
then(get.elementByTestId("modal:export")).shouldNotExist(); await then(get.elementByTestId("modal:export")).shouldNotExist();
}); });
// TODO: Work out how to download a file and check the contents test("download HTML and save the style", async () => {
test("download"); // Generate the standalone HTML export (triggers a file download).
await when.modal.exportCreateHtml();
await then(get.elementByTestId("modal:export")).shouldExist();
// Saving the style closes the export modal.
await when.modal.exportSaveStyle();
await then(get.elementByTestId("modal:export")).shouldNotExist();
});
}); });
describe("sources", () => { describe("sources", () => {
beforeEach(() => { beforeEach(async () => {
when.setStyle("layer"); await when.setStyle("layer");
when.click("nav:sources"); await when.click("nav:sources");
}); });
test("active sources"); test("active sources are listed and can be deleted", async () => {
test("public source"); await when.setStyle("both");
await when.click("nav:sources");
const before = Object.keys(get.fixture("geojson-raster-style.json").sources).length;
await when.modal.deleteFirstActiveSource();
await then(
get.styleFromLocalStorage().then((style) => Object.keys(style.sources).length)
).shouldEqual(before - 1);
});
test("add new source", () => { test("public source", async () => {
const sourceId = "n1z2v3r"; await when.modal.addPublicSource();
when.setValue("modal:sources.add.source_id", sourceId); await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
when.select("modal:sources.add.source_type", "tile_vector"); sources: {
when.select("modal:sources.add.scheme_type", "tms"); openmaptiles: {
when.click("modal:sources.add.add_source"); type: "vector",
when.wait(200); url: `https://api.maptiler.com/tiles/v3-openmaptiles/tiles.json?key=${tokens.openmaptiles}`,
then( },
get.styleFromLocalStorage().then((style) => style.sources[sourceId]) },
).shouldInclude({
scheme: "tms",
}); });
}); });
test("add new pmtiles source", () => { test("add new source", async () => {
const sourceId = "n1z2v3r";
await when.setValue("modal:sources.add.source_id", sourceId);
await when.select("modal:sources.add.source_type", "tile_vector");
await when.select("modal:sources.add.scheme_type", "tms");
await when.click("modal:sources.add.add_source");
await when.wait(200);
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
sources: { [sourceId]: { scheme: "tms" } },
});
});
test("add new pmtiles source", async () => {
const sourceId = "pmtilestest"; const sourceId = "pmtilestest";
when.setValue("modal:sources.add.source_id", sourceId); await when.setValue("modal:sources.add.source_id", sourceId);
when.select("modal:sources.add.source_type", "pmtiles_vector"); await when.select("modal:sources.add.source_type", "pmtiles_vector");
when.setValue("modal:sources.add.source_url", "https://data.source.coop/protomaps/openstreetmap/v4.pmtiles"); await when.setValue(
when.click("modal:sources.add.add_source"); "modal:sources.add.source_url",
when.click("modal:sources.add.add_source"); "https://data.source.coop/protomaps/openstreetmap/v4.pmtiles"
when.wait(200); );
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await when.click("modal:sources.add.add_source");
await when.click("modal:sources.add.add_source");
await when.wait(200);
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
sources: { sources: {
pmtilestest: { pmtilestest: {
type: "vector", type: "vector",
@@ -107,351 +146,432 @@ describe("modals", () => {
}); });
}); });
test("add new raster source", () => { test("add new raster source", async () => {
const sourceId = "rastertest"; const sourceId = "rastertest";
when.setValue("modal:sources.add.source_id", sourceId); await when.setValue("modal:sources.add.source_id", sourceId);
when.select("modal:sources.add.source_type", "tile_raster"); await when.select("modal:sources.add.source_type", "tile_raster");
when.select("modal:sources.add.scheme_type", "xyz"); await when.select("modal:sources.add.scheme_type", "xyz");
when.setValue("modal:sources.add.tile_size", "128"); await when.setValue("modal:sources.add.tile_size", "128");
when.click("modal:sources.add.add_source"); await when.click("modal:sources.add.add_source");
when.wait(200); await when.wait(200);
then( await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
get.styleFromLocalStorage().then((style) => style.sources[sourceId]) sources: { [sourceId]: { tileSize: 128 } },
).shouldInclude({ });
tileSize: 128, });
test("add new geojson url source", async () => {
await when.modal.addSource("geojsonurl", "geojson_url");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
sources: {
geojsonurl: { type: "geojson", data: "http://localhost:3000/geojson.json" },
},
});
});
test("add new geojson json source", async () => {
await when.modal.addSource("geojsonjson", "geojson_json");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
sources: {
geojsonjson: { type: "geojson", cluster: false, data: "" },
},
});
});
test("add new tilejson vector source", async () => {
await when.modal.addSource("tilejsonvector", "tilejson_vector");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
sources: {
tilejsonvector: { type: "vector", url: "http://localhost:3000/tilejson.json" },
},
});
});
test("add new tilejson raster source", async () => {
await when.modal.addSource("tilejsonraster", "tilejson_raster");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
sources: {
tilejsonraster: { type: "raster", url: "http://localhost:3000/tilejson.json" },
},
});
});
test("add new tilejson raster-dem source", async () => {
await when.modal.addSource("tilejsonrasterdem", "tilejson_raster-dem");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
sources: {
tilejsonrasterdem: { type: "raster-dem", url: "http://localhost:3000/tilejson.json" },
},
});
});
test("add new tile xyz raster-dem source", async () => {
await when.modal.addSource("tilexyzrasterdem", "tilexyz_raster-dem");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
sources: {
tilexyzrasterdem: {
type: "raster-dem",
tiles: ["http://localhost:3000/{x}/{y}/{z}.png"],
minzoom: 0,
maxzoom: 14,
tileSize: 512,
},
},
});
});
test("add new image source", async () => {
await when.modal.addSource("imagesource", "image");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
sources: {
imagesource: {
type: "image",
url: "http://localhost:3000/image.png",
coordinates: [[0, 0], [0, 0], [0, 0], [0, 0]],
},
},
});
});
test("add new video source", async () => {
await when.modal.addSource("videosource", "video");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
sources: {
videosource: {
type: "video",
urls: ["http://localhost:3000/movie.mp4"],
coordinates: [[0, 0], [0, 0], [0, 0], [0, 0]],
},
},
});
});
test("edit the corner coordinates of an image source", async () => {
const sourceId = "imagecoords";
await when.setValue("modal:sources.add.source_id", sourceId);
await when.select("modal:sources.add.source_type", "image");
// The first corner is the first two number boxes of the coordinate arrays.
await when.modal.setCoordinateValue(0, "1");
await when.modal.setCoordinateValue(1, "2");
await when.click("modal:sources.add.add_source");
await when.wait(200);
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
sources: {
[sourceId]: { type: "image", coordinates: [[1, 2], [0, 0], [0, 0], [0, 0]] },
},
}); });
}); });
}); });
describe("inspect", () => { describe("inspect", () => {
test("toggle", () => { test("toggle", async () => {
// There is no assertion in this test // There is no assertion in this test
when.setStyle("geojson"); await when.setStyle("geojson");
when.select("maputnik-select", "inspect"); await when.select("maputnik-select", "inspect");
}); });
}); });
describe("style settings", () => { describe("style settings", () => {
beforeEach(() => { beforeEach(async () => {
when.click("nav:settings"); await when.click("nav:settings");
}); });
describe("when click name filed spec information", () => { describe("when click name filed spec information", () => {
beforeEach(() => { beforeEach(async () => {
when.click("field-doc-button-Name"); await when.click("field-doc-button-Name");
}); });
test("should show the spec information", () => { test("should show the spec information", async () => {
then(get.elementsText("spec-field-doc")).shouldInclude( await then(get.elementsText("spec-field-doc")).shouldInclude("name for the style");
"name for the style"
);
}); });
}); });
describe("when set name and click owner", () => { describe("when set name and click owner", () => {
beforeEach(() => { beforeEach(async () => {
when.setValue("modal:settings.name", "foobar"); await when.setValue("modal:settings.name", "foobar");
when.click("modal:settings.owner"); await when.click("modal:settings.owner");
when.wait(200); await when.wait(200);
}); });
test("show name specifications", () => { test("show name specifications", async () => {
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
name: "foobar", name: "foobar",
}); });
}); });
}); });
describe("when set owner and click name", () => { describe("when set owner and click name", () => {
beforeEach(() => { beforeEach(async () => {
when.setValue("modal:settings.owner", "foobar"); await when.setValue("modal:settings.owner", "foobar");
when.click("modal:settings.name"); await when.click("modal:settings.name");
when.wait(200); await when.wait(200);
}); });
test("should update owner in local storage", () => { test("should update owner in local storage", async () => {
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
owner: "foobar", owner: "foobar",
}); });
}); });
}); });
test("sprite url", () => { test("sprite url", async () => {
when.setTextInJsonEditor("\"http://example.com\""); await when.setTextInJsonEditor('"http://example.com"');
when.click("modal:settings.name"); await when.click("modal:settings.name");
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
sprite: "http://example.com", sprite: "http://example.com",
}); });
}); });
test("sprite object", () => { test("sprite object", async () => {
when.setTextInJsonEditor(JSON.stringify([{ id: "1", url: "2" }])); await when.setTextInJsonEditor(JSON.stringify([{ id: "1", url: "2" }]));
when.click("modal:settings.name"); await when.click("modal:settings.name");
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
sprite: [{ id: "1", url: "2" }], sprite: [{ id: "1", url: "2" }],
}); });
}); });
test("glyphs url", () => { test("glyphs url", async () => {
const glyphsUrl = "http://example.com/{fontstack}/{range}.pbf"; const glyphsUrl = "http://example.com/{fontstack}/{range}.pbf";
when.setValue("modal:settings.glyphs", glyphsUrl); await when.setValue("modal:settings.glyphs", glyphsUrl);
when.click("modal:settings.name"); await when.click("modal:settings.name");
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
glyphs: glyphsUrl, glyphs: glyphsUrl,
}); });
}); });
test("maptiler access token", () => { test("maptiler access token", async () => {
const apiKey = "testing123"; const apiKey = "testing123";
when.setValue( await when.setValue("modal:settings.maputnik:openmaptiles_access_token", apiKey);
"modal:settings.maputnik:openmaptiles_access_token", await when.click("modal:settings.name");
apiKey await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
); metadata: { "maputnik:openmaptiles_access_token": apiKey },
when.click("modal:settings.name");
then(
get.styleFromLocalStorage().then((style) => style.metadata)
).shouldInclude({
"maputnik:openmaptiles_access_token": apiKey,
}); });
}); });
test("thunderforest access token", () => { test("thunderforest access token", async () => {
const apiKey = "testing123"; const apiKey = "testing123";
when.setValue( await when.setValue("modal:settings.maputnik:thunderforest_access_token", apiKey);
"modal:settings.maputnik:thunderforest_access_token", await when.click("modal:settings.name");
apiKey await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
); metadata: { "maputnik:thunderforest_access_token": apiKey },
when.click("modal:settings.name"); });
then(
get.styleFromLocalStorage().then((style) => style.metadata)
).shouldInclude({ "maputnik:thunderforest_access_token": apiKey });
}); });
test("stadia access token", () => { test("stadia access token", async () => {
const apiKey = "testing123"; const apiKey = "testing123";
when.setValue( await when.setValue("modal:settings.maputnik:stadia_access_token", apiKey);
"modal:settings.maputnik:stadia_access_token", await when.click("modal:settings.name");
apiKey await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
); metadata: { "maputnik:stadia_access_token": apiKey },
when.click("modal:settings.name"); });
then(
get.styleFromLocalStorage().then((style) => style.metadata)
).shouldInclude({ "maputnik:stadia_access_token": apiKey });
}); });
test("locationiq access token", () => { test("locationiq access token", async () => {
const apiKey = "testing123"; const apiKey = "testing123";
when.setValue( await when.setValue("modal:settings.maputnik:locationiq_access_token", apiKey);
"modal:settings.maputnik:locationiq_access_token", await when.click("modal:settings.name");
apiKey await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
); metadata: { "maputnik:locationiq_access_token": apiKey },
when.click("modal:settings.name"); });
then(
get.styleFromLocalStorage().then((style) => style.metadata)
).shouldInclude({ "maputnik:locationiq_access_token": apiKey });
}); });
test("style projection mercator", () => { test("map view defaults", async () => {
when.select("modal:settings.projection", "mercator"); await when.setValue("modal:settings.zoom", "4");
then( await when.setValue("modal:settings.bearing", "12");
get.styleFromLocalStorage().then((style) => style.projection) await when.setValue("modal:settings.pitch", "30");
).shouldInclude({ type: "mercator" }); await when.click("modal:settings.name");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
zoom: 4,
bearing: 12,
pitch: 30,
});
}); });
test("style projection globe", () => { test("light intensity", async () => {
when.select("modal:settings.projection", "globe"); await when.setValue("modal:settings.light-intensity", "0.7");
then( await when.click("modal:settings.name");
get.styleFromLocalStorage().then((style) => style.projection) await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
).shouldInclude({ type: "globe" }); light: { intensity: 0.7 },
});
}); });
test("terrain source and exaggeration", async () => {
test("style projection vertical-perspective", () => { await when.setValue("modal:settings.maputnik:terrain_source", "terrain");
when.select("modal:settings.projection", "vertical-perspective"); await when.setValue("modal:settings.terrain-exaggeration", "1.5");
then( await when.click("modal:settings.name");
get.styleFromLocalStorage().then((style) => style.projection) await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
).shouldInclude({ type: "vertical-perspective" }); terrain: { source: "terrain", exaggeration: 1.5 },
});
}); });
test("style renderer", () => { test("transition delay and duration", async () => {
cy.on("uncaught:exception", () => false); // this is due to the fact that this is an invalid style for openlayers await when.setValue("modal:settings.transition-delay", "100");
when.select("modal:settings.maputnik:renderer", "ol"); await when.setValue("modal:settings.transition-duration", "500");
then(get.inputValue("modal:settings.maputnik:renderer")).shouldEqual( await when.click("modal:settings.name");
"ol" await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
); transition: { delay: 100, duration: 500 },
});
});
when.click("modal:settings.name"); test("style projection mercator", async () => {
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await when.select("modal:settings.projection", "mercator");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
projection: { type: "mercator" },
});
});
test("style projection globe", async () => {
await when.select("modal:settings.projection", "globe");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
projection: { type: "globe" },
});
});
test("style projection vertical-perspective", async () => {
await when.select("modal:settings.projection", "vertical-perspective");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
projection: { type: "vertical-perspective" },
});
});
test("style renderer", async () => {
await when.select("modal:settings.maputnik:renderer", "ol");
await then(get.inputValue("modal:settings.maputnik:renderer")).shouldEqual("ol");
await when.click("modal:settings.name");
await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
metadata: { "maputnik:renderer": "ol" }, metadata: { "maputnik:renderer": "ol" },
}); });
}); });
test("include API key when change renderer", async () => {
await when.click("modal:settings.close-modal");
await when.click("nav:open");
await when.clickByAttribute("aria-label", "MapTiler Basic");
await when.wait(1000);
await when.click("nav:settings");
test("include API key when change renderer", () => { await when.select("modal:settings.maputnik:renderer", "mlgljs");
await then(get.inputValue("modal:settings.maputnik:renderer")).shouldEqual("mlgljs");
when.click("modal:settings.close-modal"); await when.select("modal:settings.maputnik:renderer", "ol");
when.click("nav:open"); await then(get.inputValue("modal:settings.maputnik:renderer")).shouldEqual("ol");
get.elementByAttribute("aria-label", "MapTiler Basic").should("exist").click(); await given.intercept(
when.wait(1000); /https:\/\/api\.maptiler\.com\/tiles\/v3-openmaptiles\/tiles\.json\?key=.*/,
when.click("nav:settings"); "tileRequest",
"GET"
when.select("modal:settings.maputnik:renderer", "mlgljs");
then(get.inputValue("modal:settings.maputnik:renderer")).shouldEqual(
"mlgljs"
); );
when.select("modal:settings.maputnik:renderer", "ol"); await when.select("modal:settings.maputnik:renderer", "mlgljs");
then(get.inputValue("modal:settings.maputnik:renderer")).shouldEqual( await then(get.inputValue("modal:settings.maputnik:renderer")).shouldEqual("mlgljs");
"ol"
const request = await when.waitForResponse("tileRequest");
expect(request.url()).toContain(
`https://api.maptiler.com/tiles/v3-openmaptiles/tiles.json?key=${tokens.openmaptiles}`
); );
given.intercept("https://api.maptiler.com/tiles/v3-openmaptiles/tiles.json?key=*", "tileRequest", "GET");
when.select("modal:settings.maputnik:renderer", "mlgljs");
then(get.inputValue("modal:settings.maputnik:renderer")).shouldEqual(
"mlgljs"
);
when.waitForResponse("tileRequest").its("request").its("url").should("include", `https://api.maptiler.com/tiles/v3-openmaptiles/tiles.json?key=${tokens.openmaptiles}`);
when.waitForResponse("tileRequest").its("request").its("url").should("include", `https://api.maptiler.com/tiles/v3-openmaptiles/tiles.json?key=${tokens.openmaptiles}`);
when.waitForResponse("tileRequest").its("request").its("url").should("include", `https://api.maptiler.com/tiles/v3-openmaptiles/tiles.json?key=${tokens.openmaptiles}`);
}); });
}); });
describe("add layer", () => { describe("add layer", () => {
beforeEach(() => { beforeEach(async () => {
when.setStyle("layer"); await when.setStyle("layer");
when.modal.open(); await when.modal.open();
}); });
test("shows duplicate id error", () => { test("shows duplicate id error", async () => {
when.setValue("add-layer.layer-id.input", "background"); await when.setValue("add-layer.layer-id.input", "background");
when.click("add-layer"); await when.click("add-layer");
then(get.elementByTestId("modal:add-layer")).shouldExist(); await then(get.elementByTestId("modal:add-layer")).shouldExist();
then(get.element(".maputnik-modal-error")).shouldContainText( await then(get.element(".maputnik-modal-error")).shouldContainText("Layer ID already exists");
"Layer ID already exists"
);
}); });
}); });
describe("sources", () => {
test("toggle");
});
describe("global state", () => { describe("global state", () => {
beforeEach(() => { beforeEach(async () => {
when.click("nav:global-state"); await when.click("nav:global-state");
}); });
test("add variable", () => { test("add variable", async () => {
when.wait(100); await when.wait(100);
when.click("global-state-add-variable"); await when.click("global-state-add-variable");
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
state: { key1: { default: "value" } }, state: { key1: { default: "value" } },
}); });
}); });
test("add multiple variables", async () => {
test("add multiple variables", () => { await when.click("global-state-add-variable");
when.click("global-state-add-variable"); await when.click("global-state-add-variable");
when.click("global-state-add-variable"); await when.click("global-state-add-variable");
when.click("global-state-add-variable"); await when.wait(100);
when.wait(100); await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
state: { key1: { default: "value" }, key2: { default: "value" }, key3: { default: "value" } }, state: { key1: { default: "value" }, key2: { default: "value" }, key3: { default: "value" } },
}); });
}); });
test("remove variable", () => { test("remove variable", async () => {
when.click("global-state-add-variable"); await when.click("global-state-add-variable");
when.click("global-state-add-variable"); await when.click("global-state-add-variable");
when.click("global-state-add-variable"); await when.click("global-state-add-variable");
when.click("global-state-remove-variable", 0); await when.click("global-state-remove-variable", 0);
when.wait(100); await when.wait(100);
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
state: { key2: { default: "value" }, key3: { default: "value" } }, state: { key2: { default: "value" }, key3: { default: "value" } },
}); });
}); });
test("edit variable key", () => { test("edit variable key", async () => {
when.click("global-state-add-variable"); await when.click("global-state-add-variable");
when.wait(100); await when.wait(100);
when.setValue("global-state-variable-key:0", "mykey"); await when.setValue("global-state-variable-key:0", "mykey");
when.typeKeys("{enter}"); await when.typeKeys("{enter}");
when.wait(100); await when.wait(100);
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
state: { mykey: { default: "value" } }, state: { mykey: { default: "value" } },
}); });
}); });
test("edit variable value", () => { test("edit variable value", async () => {
when.click("global-state-add-variable"); await when.click("global-state-add-variable");
when.wait(100); await when.wait(100);
when.setValue("global-state-variable-value:0", "myvalue"); await when.setValue("global-state-variable-value:0", "myvalue");
when.typeKeys("{enter}"); await when.typeKeys("{enter}");
when.wait(100); await when.wait(100);
then(get.styleFromLocalStorage()).shouldDeepNestedInclude({ await then(get.styleFromLocalStorage()).shouldDeepNestedInclude({
state: { key1: { default: "myvalue" } }, state: { key1: { default: "myvalue" } },
}); });
}); });
}); });
describe("error panel", () => { describe("error panel", () => {
test("not visible when no errors", () => { test("not visible when no errors", async () => {
then(get.element("maputnik-message-panel-error")).shouldNotExist(); await then(get.element("maputnik-message-panel-error")).shouldNotExist();
}); });
test("visible on style error", () => { test("visible on style error", async () => {
when.modal.open(); await when.modal.open();
when.modal.fillLayers({ await when.modal.fillLayers({
type: "circle", type: "circle",
layer: "invalid", layer: "invalid",
}); });
then(get.element(".maputnik-message-panel-error")).shouldBeVisible(); await then(get.element(".maputnik-message-panel-error")).shouldBeVisible();
}); });
}); });
describe("Handle localStorage QuotaExceededError", () => { describe("Handle localStorage QuotaExceededError", () => {
test("handles quota exceeded error when opening style from URL", () => { test("handles quota exceeded error when opening style from URL", async () => {
// Clear localStorage to start fresh // Clear localStorage to start fresh
cy.clearLocalStorage(); await when.clearLocalStorage();
await when.fillLocalStorage();
// fill localStorage until we get a QuotaExceededError
cy.window().then(win => {
let chunkSize = 1000;
const chunk = new Array(chunkSize).join("x");
let index = 0;
// Keep adding until we hit the quota
while (true) {
try {
const key = `maputnik:fill-${index++}`;
win.localStorage.setItem(key, chunk);
} catch (e: any) {
// Verify it's a quota error
if (e.name === "QuotaExceededError") {
if (chunkSize <= 1) return;
else {
chunkSize /= 2;
continue;
}
}
throw e; // Unexpected error
}
}
});
// Open the style via URL input // Open the style via URL input
when.click("nav:open"); await when.click("nav:open");
when.setValue("modal:open.url.input", get.exampleFileUrl()); await when.setValue("modal:open.url.input", get.exampleFileUrl());
when.click("modal:open.url.button"); await when.click("modal:open.url.button");
then(get.responseBody("example-style.json")).shouldEqualToStoredStyle(); await then(get.styleFromLocalStorage()).shouldDeepNestedInclude(get.responseBody("example-style.json"));
then(get.styleFromLocalStorage()).shouldExist(); await then(get.styleFromLocalStorage()).shouldExist();
}); });
}); });
}); });
+453
View File
@@ -0,0 +1,453 @@
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { expect, type Locator, type Page, type Request } from "@playwright/test";
import { currentPage, recordCoverageChunk } from "./utils/fixtures";
const DATA_ATTRIBUTE = "data-wd-key";
const FIXTURES_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), "fixtures");
const isMac = process.platform === "darwin";
function testIdSelector(testId: string): string {
return `[${DATA_ATTRIBUTE}="${testId}"]`;
}
/** Retries `assertion` until it stops throwing */
async function retry(
assertion: () => Promise<void> | void,
timeout = 10000,
interval = 100
): Promise<void> {
const start = Date.now();
let lastError: unknown;
while (true) {
try {
await assertion();
return;
} catch (error) {
lastError = error;
if (Date.now() - start > timeout) throw lastError;
await new Promise((resolve) => setTimeout(resolve, interval));
}
}
}
/**
* A lazily-evaluated value (e.g. the style in localStorage). Assertions on a
* Query re-read the value until they pass.
*/
class Query<T> {
readonly __query = true as const;
constructor(private readonly getter: () => Promise<T>) {}
get(): Promise<T> {
return this.getter();
}
then<U>(mapper: (value: T) => U | Promise<U>): Query<U> {
return new Query<U>(async () => mapper(await this.getter()));
}
}
function isQuery(target: unknown): target is Query<unknown> {
return typeof target === "object" && target !== null && (target as Query<unknown>).__query === true;
}
function isLocator(target: unknown): target is Locator {
return (
typeof target === "object" &&
target !== null &&
typeof (target as Locator).count === "function" &&
typeof (target as Locator).boundingBox === "function"
);
}
/**
* Asserts that `actual` recursively contains everything in `expected`: nested
* objects are matched as subsets, while arrays and primitives must match exactly.
*/
function assertDeepNestedInclude(actual: any, expected: Record<string, unknown> | unknown[]): void {
expect(actual).toMatchObject(expected);
}
/**
* Fluent, auto-retrying assertions over a Playwright Locator or a lazily
* evaluated value/Query. This is the generic base that the maputnik-specific
* assertable extends.
*/
export class Assertable<T> {
constructor(private readonly target: T) {}
private locator(): Locator {
if (!isLocator(this.target)) throw new Error("Expected a Locator target for this assertion");
return this.target;
}
protected async assertValue(assertion: (value: any) => void): Promise<void> {
const target = this.target;
if (isQuery(target)) {
await retry(async () => assertion(await target.get()));
} else {
assertion(await (target as any));
}
}
// Element assertions (auto-retrying via Playwright web-first assertions).
shouldBeVisible = () => expect(this.locator().first()).toBeVisible();
// Some testids resolve to many elements that are always rendered but hidden
// (e.g. per-field documentation panels); "not visible" means none is visible.
shouldNotBeVisible = () => expect(this.locator().filter({ visible: true })).toHaveCount(0);
shouldExist = async () => {
if (isLocator(this.target)) {
await expect(this.locator().first()).toBeAttached();
} else {
await this.assertValue((value) => expect(value).toBeTruthy());
}
};
shouldNotExist = () => expect(this.locator()).toHaveCount(0);
shouldBeFocused = () => expect(this.locator().first()).toBeFocused();
shouldNotBeFocused = () => expect(this.locator().first()).not.toBeFocused();
shouldHaveValue = (value: string) => expect(this.locator().first()).toHaveValue(value);
shouldContainText = async (text: string) => {
const locator = this.locator();
// Prefer the visible element when a testid resolves to several (only the
// open documentation panel is visible; the rest are hidden in the DOM).
const target = (await locator.count()) > 1 ? locator.filter({ visible: true }).first() : locator.first();
await expect(target).toContainText(text);
};
shouldHaveText = (text: string) => expect(this.locator().first()).toHaveText(text);
shouldHaveLength = (length: number) => expect(this.locator()).toHaveCount(length);
shouldHaveCss = (property: string, value: string) => expect(this.locator().first()).toHaveCSS(property, value);
// Value assertions (auto-retrying for Query targets).
shouldEqual = (value: any) => this.assertValue((actual) => expect(actual).toBe(value));
shouldInclude = (value: any) =>
this.assertValue((actual) => {
if (typeof value === "object" && value !== null) {
expect(actual).toMatchObject(value);
} else {
expect(String(actual)).toContain(String(value));
}
});
shouldDeepNestedInclude = (value: Record<string, unknown> | unknown[]) =>
this.assertValue((actual) => assertDeepNestedInclude(actual, value));
}
async function typeSequence(page: Page, text: string): Promise<void> {
const tokens = text.match(/\{[^}]+\}|[^{]+/g) ?? [];
const modifierMap: Record<string, string> = { meta: "Meta", ctrl: "Control", shift: "Shift", alt: "Alt" };
const namedKeys: Record<string, string> = {
esc: "Escape",
enter: "Enter",
backspace: "Backspace",
del: "Delete",
tab: "Tab",
home: "Home",
end: "End",
rightarrow: "ArrowRight",
};
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i];
if (!token.startsWith("{") || !token.endsWith("}")) {
await page.keyboard.type(token);
continue;
}
const name = token.slice(1, -1).toLowerCase();
if (name === "selectall") {
await page.keyboard.press(isMac ? "Meta+a" : "Control+a");
} else if (namedKeys[name]) {
await page.keyboard.press(namedKeys[name]);
} else if (modifierMap[name]) {
const modifiers = [modifierMap[name]];
let j = i + 1;
while (j < tokens.length && /^\{(meta|ctrl|shift|alt)\}$/i.test(tokens[j])) {
modifiers.push(modifierMap[tokens[j].slice(1, -1).toLowerCase()]);
j++;
}
const key = tokens[j] ?? "";
await page.keyboard.press([...modifiers, key].join("+"));
i = j;
}
}
}
async function centerOf(locator: Locator): Promise<{ x: number; y: number }> {
const box = await locator.boundingBox();
if (!box) throw new Error("Element has no bounding box");
return { x: box.x + box.width / 2, y: box.y + box.height / 2 };
}
/**
* This is where all plywright-specific test helpers live.
* It is used by the MaputnikDriver to implement the Maputnik-specific test helpers.
*/
export class PlaywrightHelper {
private readonly recordedRequests = new Map<string, Request[]>();
private get page(): Page {
return currentPage();
}
private testId(testId: string): Locator {
return this.page.locator(testIdSelector(testId));
}
/** Reads and parses a JSON fixture from the fixtures directory. */
public readFixture(name: string): any {
return JSON.parse(fs.readFileSync(path.join(FIXTURES_DIR, name), "utf-8"));
}
/** Wraps a lazily-evaluated value so assertions on it auto-retry. */
public query<T>(getter: () => Promise<T>): Query<T> {
return new Query<T>(getter);
}
/** Stubs the File System Access "save" picker so file saves complete headlessly. */
public stubSaveFilePicker(): Promise<void> {
return this.page.evaluate(() => {
(window as any).showSaveFilePicker = async () => ({
createWritable: async () => ({ write: async () => {}, close: async () => {} }),
});
});
}
/** Entry point for fluent assertions over a Locator or a value/Query. */
public then = <T>(target: T): Assertable<T> => new Assertable(target);
public given = {
/**
* Removes the File System Access API so the app falls back to a plain
* <input type="file">, the way Firefox and Safari behave. Must be called
* before the page under test is loaded.
*/
noFileSystemAccessApi: async () => {
await this.page.addInitScript(() => {
delete (window as any).showOpenFilePicker;
delete (window as any).showSaveFilePicker;
});
},
intercept: async (pattern: RegExp, alias: string, _method = "GET") => {
this.recordedRequests.set(alias, []);
await this.page.route(pattern, (route) => {
this.recordedRequests.get(alias)!.push(route.request());
route.continue();
});
},
interceptAndMockResponse: async (options: {
method?: string;
url: string | RegExp;
response: unknown | { fixture: string };
alias?: string;
}) => {
const { url, response, alias } = options;
if (alias) this.recordedRequests.set(alias, []);
await this.page.route(url, (route) => {
if (alias) this.recordedRequests.get(alias)!.push(route.request());
const body =
response && typeof response === "object" && "fixture" in (response as any)
? this.readFixture((response as { fixture: string }).fixture)
: response;
route.fulfill({ json: body });
});
},
};
public when = {
visit: async (url: string) => {
// Snapshot coverage before navigating, since a full page load resets it.
await recordCoverageChunk(this.page);
await this.page.goto(url);
},
wait: (ms: number) => this.page.waitForTimeout(ms),
tab: () => this.page.keyboard.press("Tab"),
typeKeys: (keys: string) => typeSequence(this.page, keys),
/** Types raw text into the focused element (no "{key}" sequence parsing). */
typeText: (text: string) => this.page.keyboard.type(text),
clickButtonByName: async (name: string) => {
await this.page.getByRole("button", { name }).click();
},
click: async (testId: string, index = 0) => {
// Documentation buttons are wrapped in a <label>/.maputnik-doc-target that
// Playwright treats as intercepting the click; bypass the check for them.
const force = testId.startsWith("field-doc-button-");
await this.testId(testId).nth(index).click({ force });
},
realClick: async (testId: string) => {
await this.testId(testId).click();
},
hover: async (testId: string) => {
await this.testId(testId).hover();
},
focus: async (testId: string) => {
await this.testId(testId).focus();
},
clear: async (testId: string) => {
await this.testId(testId).clear();
},
select: async (testId: string, value: string) => {
await this.testId(testId).selectOption(value);
},
selectWithin: async (parentTestId: string, value: string) => {
await this.testId(parentTestId).locator("select").selectOption(value);
},
clickWithin: async (parentTestId: string, selector: string) => {
await this.testId(parentTestId).locator(selector).first().click();
},
clickByText: async (text: string) => {
await this.page.getByText(text).click();
},
clickByAttribute: async (attribute: string, value: string) => {
await this.page.locator(`[${attribute}="${value}"]`).click();
},
scrollToBottom: async (element: Locator) => {
await element.evaluate((el) => el.scrollTo(0, el.scrollHeight));
},
setValue: async (testId: string, text: string) => {
const input = this.testId(testId);
await input.fill("");
await input.fill(text);
},
type: async (testId: string, text: string) => {
await this.testId(testId).focus();
// Place the caret at the start of the field, so a leading "{backspace}"
// is a no-op rather than clearing an already-committed value.
await this.page.keyboard.press("Home");
await typeSequence(this.page, text);
},
dragAndDropWithWait: async (source: string, target: string) => {
const from = await centerOf(this.testId(source));
const to = await centerOf(this.testId(target));
await this.page.mouse.move(from.x, from.y);
await this.page.mouse.down();
await this.page.mouse.move(from.x, from.y + 10);
await this.page.mouse.move(to.x, to.y, { steps: 10 });
await this.page.waitForTimeout(100);
await this.page.mouse.up();
},
clickCenter: async (testId: string) => {
const { x, y } = await centerOf(this.testId(testId));
await this.page.mouse.move(x, y);
await this.page.mouse.down();
await this.page.waitForTimeout(200);
await this.page.mouse.up();
},
/**
* Opens a fixture through the File System Access API, which raises no
* "filechooser" event and so has to be stubbed. For the <input type="file">
* fallback that browsers without the API use, see chooseFileFromPicker.
*/
openFileByFixture: async (fixture: string, buttonTestId: string) => {
const content = JSON.stringify(this.readFixture(fixture));
await this.page.evaluate((fileContent) => {
(window as any).showOpenFilePicker = async () => [
{ getFile: async () => ({ text: async () => fileContent }) },
];
}, content);
await this.testId(buttonTestId).click();
},
/**
* Clicks a control that opens the browser's native file chooser and answers
* it with a fixture. Only works on the <input type="file"> path — the File
* System Access API does not raise a "filechooser" event, so pair this with
* given.noFileSystemAccessApi().
*/
chooseFileFromPicker: async (fixture: string, triggerTestId: string) => {
const content = JSON.stringify(this.readFixture(fixture));
const [fileChooser] = await Promise.all([
this.page.waitForEvent("filechooser"),
this.testId(triggerTestId).click(),
]);
await fileChooser.setFiles({
name: fixture,
mimeType: "application/json",
buffer: Buffer.from(content),
});
},
dropFileByFixture: async (fixture: string, dropzoneTestId: string) => {
const content = JSON.stringify(this.readFixture(fixture));
const dataTransfer = await this.page.evaluateHandle((fileContent) => {
const dt = new DataTransfer();
dt.items.add(new File([fileContent], "example-style.json", { type: "application/json" }));
return dt;
}, content);
const dropzone = this.testId(dropzoneTestId);
await dropzone.dispatchEvent("dragenter", { dataTransfer });
await dropzone.dispatchEvent("dragover", { dataTransfer });
await dropzone.dispatchEvent("drop", { dataTransfer });
},
waitForResponse: async (alias: string) => {
const requests = this.recordedRequests.get(alias);
if (!requests) throw new Error(`No intercept registered for alias "${alias}"`);
await retry(async () => {
if (requests.length === 0) throw new Error(`No request recorded for alias "${alias}"`);
});
return requests[requests.length - 1];
},
clearLocalStorage: () => this.page.evaluate(() => window.localStorage.clear()),
/** Writes to localStorage under `keyPrefix` until a QuotaExceededError is hit. */
fillLocalStorageUntilQuota: (keyPrefix: string) =>
this.page.evaluate((prefix) => {
let chunkSize = 1000;
const chunk = new Array(chunkSize).join("x");
let index = 0;
while (true) {
try {
window.localStorage.setItem(`${prefix}${index++}`, chunk);
} catch (e: any) {
if (e.name === "QuotaExceededError") {
if (chunkSize <= 1) return;
chunkSize /= 2;
continue;
}
throw e; // Unexpected error
}
}
}, keyPrefix),
};
public get = {
element: (selector: string) => this.page.locator(selector),
localStorageItem: (key: string) =>
this.page.evaluate((k) => window.localStorage.getItem(k), key),
elementByTestId: (testId: string) => this.testId(testId),
inputValue: (testId: string) => new Query<string>(() => this.testId(testId).first().inputValue()),
elementsText: (testId: string) => new Query<string>(() => this.testId(testId).first().innerText()),
locationHash: () => new Query<string>(async () => new URL(this.page.url()).hash),
};
}
+12
View File
@@ -0,0 +1,12 @@
import fs from "node:fs";
import path from "node:path";
/**
* Clears the istanbul coverage output directory before the e2e run so stale
* coverage from previous runs is not merged into the report.
*/
export default function globalSetup(): void {
const dir = path.resolve(process.cwd(), ".nyc_output");
fs.rmSync(dir, { recursive: true, force: true });
fs.mkdirSync(dir, { recursive: true });
}
+23
View File
@@ -0,0 +1,23 @@
import fs from "node:fs";
import path from "node:path";
import { execFileSync } from "node:child_process";
/**
* Merges the per-test istanbul coverage chunks collected in `.nyc_output` into a
* report (configured by `.nycrc.json`) once the whole e2e run has finished.
*/
export default function globalTeardown(): void {
const dir = path.resolve(process.cwd(), ".nyc_output");
const hasCoverage = fs.existsSync(dir) && fs.readdirSync(dir).some((f) => f.endsWith(".json"));
if (!hasCoverage) {
console.warn("No coverage data collected; skipping coverage report.");
return;
}
try {
execFileSync("npx", ["nyc", "report"], { stdio: "inherit" });
} catch (error) {
// Don't fail the whole run if the report can't be generated (e.g. when
// running against a container whose source paths differ from the host).
console.warn("Failed to generate coverage report:", error);
}
}
+82
View File
@@ -0,0 +1,82 @@
import { test, expect, type Page } from "@playwright/test";
import fs from "node:fs";
import path from "node:path";
let activePage: Page | undefined;
const coverageChunks: unknown[] = [];
/** The page for the currently running test. Throws if used outside a test. */
export function currentPage(): Page {
if (!activePage) {
throw new Error("No active page: a MaputnikDriver method was called outside of a running test.");
}
return activePage;
}
const OUTPUT_DIR = path.resolve(process.cwd(), ".nyc_output");
/**
* Reads the istanbul coverage object (injected by vite-plugin-istanbul) from the
* given page. Returns `null` when the page has not been instrumented.
*/
async function readCoverage(page: Page): Promise<unknown | null> {
try {
return await page.evaluate(() => (window as unknown as { __coverage__?: unknown }).__coverage__ ?? null);
} catch {
// Page might be navigating/closed.
return null;
}
}
/**
* Persists a coverage chunk to `.nyc_output` so that `nyc report` can merge it.
* istanbul-lib-coverage (used by nyc) sums the hit counts across every file it
* finds, so writing one file per chunk is enough to accumulate coverage across
* navigations and tests.
*/
export function writeCoverage(coverage: unknown, id: string): void {
if (!coverage) return;
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
fs.writeFileSync(path.join(OUTPUT_DIR, `playwright-${id}.json`), JSON.stringify(coverage));
}
/** Records a coverage snapshot (called before navigations, which reset __coverage__). */
export async function recordCoverageChunk(page: Page): Promise<void> {
const chunk = await readCoverage(page);
if (chunk) coverageChunks.push(chunk);
}
/**
* Auto fixture that binds the current test's page for the (page-lazy)
* MaputnikDriver, auto-accepts confirm dialogs, and writes the istanbul
* coverage collected during the test to `.nyc_output`.
*/
const extendedTest = test.extend<{ maputnikPage: void }>({
maputnikPage: [
async ({ page }, use, testInfo) => {
activePage = page;
coverageChunks.length = 0;
// Accept confirm dialogs (e.g. the "replace current style" prompt). These
// are dismissed by default, which would cancel loading a style via URL.
page.on("dialog", (dialog) => dialog.accept().catch(() => undefined));
await use();
const finalCoverage = await readCoverage(page);
if (finalCoverage) coverageChunks.push(finalCoverage);
coverageChunks.forEach((chunk, index) => writeCoverage(chunk, `${testInfo.testId}-${index}`));
coverageChunks.length = 0;
activePage = undefined;
},
{ auto: true },
],
});
const describe = extendedTest.describe;
const beforeEach = extendedTest.beforeEach;
export {
expect,
describe,
extendedTest as test,
beforeEach,
};
+3 -1
View File
@@ -34,7 +34,9 @@ export default defineConfig({
rules: { rules: {
"react-refresh/only-export-components": [ "react-refresh/only-export-components": [
"warn", "warn",
{ allowConstantExport: true } // Many components are exported as withTranslation()(Component); without
// this the rule cannot tell the HOC's result is still a component.
{ allowConstantExport: true, extraHOCs: ["withTranslation"] }
], ],
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": [ "@typescript-eslint/no-unused-vars": [
+1005 -2973
View File
File diff suppressed because it is too large Load Diff
+13 -15
View File
@@ -11,10 +11,10 @@
"build-linux": "tsc && vite build --mode=desktop && cd desktop && make bin/linux/maputnik", "build-linux": "tsc && vite build --mode=desktop && cd desktop && make bin/linux/maputnik",
"i18n:extract": "npx i18next-cli extract", "i18n:extract": "npx i18next-cli extract",
"lint": "eslint", "lint": "eslint",
"test": "cypress run", "test": "playwright test",
"test-e2e": "playwright test",
"test-unit": "vitest", "test-unit": "vitest",
"test-unit-ci": "vitest run --coverage --reporter=json", "test-unit-ci": "vitest run --coverage --reporter=json",
"cy:open": "cypress open",
"lint-css": "stylelint \"src/styles/*.scss\"", "lint-css": "stylelint \"src/styles/*.scss\"",
"sort-styles": "jq 'sort_by(.id)' src/config/styles.json > tmp.json && mv tmp.json src/config/styles.json" "sort-styles": "jq 'sort_by(.id)' src/config/styles.json > tmp.json && mv tmp.json src/config/styles.json"
}, },
@@ -37,7 +37,7 @@
"@mapbox/mapbox-gl-rtl-text": "^0.4.0", "@mapbox/mapbox-gl-rtl-text": "^0.4.0",
"@maplibre/maplibre-gl-geocoder": "^1.9.4", "@maplibre/maplibre-gl-geocoder": "^1.9.4",
"@maplibre/maplibre-gl-inspect": "^1.8.2", "@maplibre/maplibre-gl-inspect": "^1.8.2",
"@maplibre/maplibre-gl-style-spec": "^25.0.2", "@maplibre/maplibre-gl-style-spec": "^26.2.1",
"array-move": "^4.0.0", "array-move": "^4.0.0",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"classnames": "^2.5.1", "classnames": "^2.5.1",
@@ -47,7 +47,7 @@
"downshift": "^9.4.0", "downshift": "^9.4.0",
"events": "^3.3.0", "events": "^3.3.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"i18next": "^26.3.4", "i18next": "^26.3.6",
"i18next-browser-languagedetector": "^8.2.1", "i18next-browser-languagedetector": "^8.2.1",
"i18next-resources-to-backend": "^1.2.1", "i18next-resources-to-backend": "^1.2.1",
"json-stringify-pretty-compact": "^4.0.0", "json-stringify-pretty-compact": "^4.0.0",
@@ -98,10 +98,9 @@
} }
}, },
"devDependencies": { "devDependencies": {
"@cypress/code-coverage": "^4.0.3",
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@istanbuljs/nyc-config-typescript": "^1.0.2", "@istanbuljs/nyc-config-typescript": "^1.0.2",
"@shellygo/cypress-test-utils": "^6.0.6", "@playwright/test": "^1.61.1",
"@stylistic/eslint-plugin": "^5.10.0", "@stylistic/eslint-plugin": "^5.10.0",
"@types/codemirror": "^5.60.17", "@types/codemirror": "^5.60.17",
"@types/color": "^4.2.1", "@types/color": "^4.2.1",
@@ -127,25 +126,24 @@
"@vitejs/plugin-react": "5.2", "@vitejs/plugin-react": "5.2",
"@vitest/coverage-v8": "^4.1.10", "@vitest/coverage-v8": "^4.1.10",
"cors": "^2.8.6", "cors": "^2.8.6",
"cypress": "^15.18.0", "eslint": "^10.7.0",
"cypress-plugin-tab": "^2.0.0",
"eslint": "^10.6.0",
"eslint-plugin-react": "^7.37.5", "eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.3", "eslint-plugin-react-refresh": "^0.5.3",
"i18next-cli": "^1.65.0", "i18next-cli": "^1.66.2",
"istanbul": "^0.4.5", "istanbul": "^0.4.5",
"istanbul-lib-coverage": "^3.2.2", "istanbul-lib-coverage": "^3.2.2",
"postcss": "^8.5.16", "nyc": "^18.0.0",
"postcss": "^8.5.21",
"react-hot-loader": "^4.13.1", "react-hot-loader": "^4.13.1",
"sass": "^1.101.0", "sass": "^1.101.3",
"stylelint": "^17.14.0", "stylelint": "^17.14.1",
"stylelint-config-recommended-scss": "^17.0.1", "stylelint-config-recommended-scss": "^17.0.1",
"stylelint-scss": "^7.2.0", "stylelint-scss": "^7.2.0",
"typescript": "^6.0.3", "typescript": "^6.0.3",
"typescript-eslint": "^8.62.1", "typescript-eslint": "^8.65.0",
"uuid": "^14.0.1", "uuid": "^14.0.1",
"vite": "^8.1.3", "vite": "^7.3.2",
"vite-plugin-istanbul": "^9.0.1", "vite-plugin-istanbul": "^9.0.1",
"vitest": "^4.1.10" "vitest": "^4.1.10"
} }
+46
View File
@@ -0,0 +1,46 @@
import { defineConfig, devices } from "@playwright/test";
const isCI = !!process.env.CI;
// When the app is already served elsewhere (e.g. the docker e2e job) set
// E2E_NO_WEBSERVER=1 so Playwright does not start its own dev server.
const useExternalServer = !!process.env.E2E_NO_WEBSERVER;
const baseURL = process.env.E2E_BASE_URL ?? "http://localhost:8888/";
export default defineConfig({
testDir: "./e2e",
testMatch: "**/*.spec.ts",
globalSetup: "./e2e/utils/e2e-setup.ts",
globalTeardown: "./e2e/utils/e2e-teardown.ts",
fullyParallel: true,
forbidOnly: isCI,
retries: isCI ? 2 : 0,
reporter: isCI ? [["list"], ["html", { open: "never" }]] : "list",
use: {
baseURL,
trace: "on-first-retry",
},
projects: [
{
name: "chromium",
use: {
...devices["Desktop Chrome"],
launchOptions: {
// Allow WebGL (maplibre) to fall back to software rendering in headless.
args: [
"--disable-gpu",
"--enable-features=AllowSwiftShaderFallback,AllowSoftwareGLFallbackDueToCrashes",
"--enable-unsafe-swiftshader",
],
},
},
},
],
webServer: useExternalServer
? undefined
: {
command: "npm run start",
url: "http://localhost:8888/maputnik/",
reuseExistingServer: !isCI,
timeout: 120000,
},
});
+23 -23
View File
@@ -11,29 +11,29 @@ import {type Map, type LayerSpecification, type StyleSpecification, type Validat
import {validateStyleMin} from "@maplibre/maplibre-gl-style-spec"; import {validateStyleMin} from "@maplibre/maplibre-gl-style-spec";
import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json"; import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json";
import MapMaplibreGl from "./MapMaplibreGl"; import { MapMaplibreGl } from "./MapMaplibreGl";
import MapOpenLayers from "./MapOpenLayers"; import { MapOpenLayers } from "./MapOpenLayers";
import CodeEditor from "./CodeEditor"; import { CodeEditor } from "./CodeEditor";
import LayerList from "./LayerList"; import { LayerList } from "./LayerList";
import LayerEditor from "./LayerEditor"; import { LayerEditor } from "./LayerEditor";
import AppToolbar, { type MapState } from "./AppToolbar"; import { AppToolbar, type MapState } from "./AppToolbar";
import AppLayout from "./AppLayout"; import { AppLayout } from "./AppLayout";
import MessagePanel from "./AppMessagePanel"; import { AppMessagePanel as MessagePanel } from "./AppMessagePanel";
import ModalSettings from "./modals/ModalSettings"; import { ModalSettings } from "./modals/ModalSettings";
import ModalExport from "./modals/ModalExport"; import { ModalExport } from "./modals/ModalExport";
import ModalSources from "./modals/ModalSources"; import { ModalSources } from "./modals/ModalSources";
import ModalOpen from "./modals/ModalOpen"; import { ModalOpen } from "./modals/ModalOpen";
import ModalShortcuts from "./modals/ModalShortcuts"; import { ModalShortcuts } from "./modals/ModalShortcuts";
import ModalDebug from "./modals/ModalDebug"; import { ModalDebug } from "./modals/ModalDebug";
import ModalGlobalState from "./modals/ModalGlobalState"; import { ModalGlobalState } from "./modals/ModalGlobalState";
import {downloadGlyphsMetadata, downloadSpriteMetadata} from "../libs/metadata"; import {downloadGlyphsMetadata, downloadSpriteMetadata} from "../libs/metadata";
import style from "../libs/style"; import { emptyStyle, getAccessToken, replaceAccessTokens } from "../libs/style";
import { undoMessages, redoMessages } from "../libs/diffmessage"; import { undoMessages, redoMessages } from "../libs/diffmessage";
import { createStyleStore, type IStyleStore } from "../libs/store/style-store-factory"; import { createStyleStore, type IStyleStore } from "../libs/store/style-store-factory";
import { RevisionStore } from "../libs/revisions"; import { RevisionStore } from "../libs/revisions";
import LayerWatcher from "../libs/layerwatcher"; import { LayerWatcher } from "../libs/layerwatcher";
import tokens from "../config/tokens.json"; import tokens from "../config/tokens.json";
import isEqual from "lodash.isequal"; import isEqual from "lodash.isequal";
import { type MapOptions } from "maplibre-gl"; import { type MapOptions } from "maplibre-gl";
@@ -48,19 +48,19 @@ function setFetchAccessToken(url: string, mapStyle: StyleSpecification) {
const matchesThunderforest = url.match(/\.thunderforest\.com/); const matchesThunderforest = url.match(/\.thunderforest\.com/);
const matchesLocationIQ = url.match(/\.locationiq\.com/); const matchesLocationIQ = url.match(/\.locationiq\.com/);
if (matchesTilehosting || matchesMaptiler) { if (matchesTilehosting || matchesMaptiler) {
const accessToken = style.getAccessToken("openmaptiles", mapStyle, {allowFallback: true}); const accessToken = getAccessToken("openmaptiles", mapStyle, {allowFallback: true});
if (accessToken) { if (accessToken) {
return url.replace("{key}", accessToken); return url.replace("{key}", accessToken);
} }
} }
else if (matchesThunderforest) { else if (matchesThunderforest) {
const accessToken = style.getAccessToken("thunderforest", mapStyle, {allowFallback: true}); const accessToken = getAccessToken("thunderforest", mapStyle, {allowFallback: true});
if (accessToken) { if (accessToken) {
return url.replace("{key}", accessToken); return url.replace("{key}", accessToken);
} }
} }
else if (matchesLocationIQ) { else if (matchesLocationIQ) {
const accessToken = style.getAccessToken("locationiq", mapStyle, {allowFallback: true}); const accessToken = getAccessToken("locationiq", mapStyle, {allowFallback: true});
if (accessToken) { if (accessToken) {
return url.replace("{key}", accessToken); return url.replace("{key}", accessToken);
} }
@@ -123,7 +123,7 @@ type AppState = {
fileHandle: FileSystemFileHandle | null fileHandle: FileSystemFileHandle | null
}; };
export default class App extends React.Component<any, AppState> { export class App extends React.Component<any, AppState> {
revisionStore: RevisionStore; revisionStore: RevisionStore;
styleStore: IStyleStore | null = null; styleStore: IStyleStore | null = null;
layerWatcher: LayerWatcher; layerWatcher: LayerWatcher;
@@ -137,7 +137,7 @@ export default class App extends React.Component<any, AppState> {
this.state = { this.state = {
errors: [], errors: [],
infos: [], infos: [],
mapStyle: style.emptyStyle, mapStyle: emptyStyle,
selectedLayerIndex: 0, selectedLayerIndex: 0,
sources: {}, sources: {},
vectorLayers: {}, vectorLayers: {},
@@ -698,7 +698,7 @@ export default class App extends React.Component<any, AppState> {
mapStyle: (dirtyMapStyle || mapStyle), mapStyle: (dirtyMapStyle || mapStyle),
mapView: this.state.mapView, mapView: this.state.mapView,
replaceAccessTokens: (mapStyle: StyleSpecification) => { replaceAccessTokens: (mapStyle: StyleSpecification) => {
return style.replaceAccessTokens(mapStyle, { return replaceAccessTokens(mapStyle, {
allowFallback: true allowFallback: true
}); });
}, },
+2 -3
View File
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import ScrollContainer from "./ScrollContainer"; import { ScrollContainer } from "./ScrollContainer";
import { type WithTranslation, withTranslation } from "react-i18next"; import { type WithTranslation, withTranslation } from "react-i18next";
import { IconContext } from "react-icons"; import { IconContext } from "react-icons";
@@ -50,5 +50,4 @@ class AppLayoutInternal extends React.Component<AppLayoutInternalProps> {
} }
} }
const AppLayout = withTranslation()(AppLayoutInternal); export const AppLayout = withTranslation()(AppLayoutInternal);
export default AppLayout;
+1 -2
View File
@@ -61,5 +61,4 @@ class AppMessagePanelInternal extends React.Component<AppMessagePanelInternalPro
} }
} }
const AppMessagePanel = withTranslation()(AppMessagePanelInternal); export const AppMessagePanel = withTranslation()(AppMessagePanelInternal);
export default AppMessagePanel;
+1 -2
View File
@@ -306,5 +306,4 @@ class AppToolbarInternal extends React.Component<AppToolbarInternalProps> {
} }
} }
const AppToolbar = withTranslation()(AppToolbarInternal); export const AppToolbar = withTranslation()(AppToolbarInternal);
export default AppToolbar;
+3 -3
View File
@@ -1,7 +1,7 @@
import React, {type CSSProperties, type PropsWithChildren, type SyntheticEvent} from "react"; import React, {type CSSProperties, type PropsWithChildren, type SyntheticEvent} from "react";
import classnames from "classnames"; import classnames from "classnames";
import FieldDocLabel from "./FieldDocLabel"; import { FieldDocLabel } from "./FieldDocLabel";
import Doc from "./Doc"; import { Doc } from "./Doc";
export type BlockProps = PropsWithChildren & { export type BlockProps = PropsWithChildren & {
"data-wd-key"?: string "data-wd-key"?: string
@@ -19,7 +19,7 @@ type BlockState = {
}; };
/** Wrap a component with a label */ /** Wrap a component with a label */
export default class Block extends React.Component<BlockProps, BlockState> { export class Block extends React.Component<BlockProps, BlockState> {
_blockEl: HTMLDivElement | null = null; _blockEl: HTMLDivElement | null = null;
constructor (props: BlockProps) { constructor (props: BlockProps) {
+2 -4
View File
@@ -1,4 +1,4 @@
import InputJson from "./InputJson"; import { InputJson } from "./InputJson";
import React from "react"; import React from "react";
import { withTranslation, type WithTranslation } from "react-i18next"; import { withTranslation, type WithTranslation } from "react-i18next";
import { type StyleSpecification } from "maplibre-gl"; import { type StyleSpecification } from "maplibre-gl";
@@ -24,6 +24,4 @@ const CodeEditorInternal: React.FC<CodeEditorProps> = (props) => {
</>; </>;
}; };
const CodeEditor = withTranslation()(CodeEditorInternal); export const CodeEditor = withTranslation()(CodeEditorInternal);
export default CodeEditor;
+1 -1
View File
@@ -9,7 +9,7 @@ type CollapseProps = {
}; };
export default class Collapse extends React.Component<CollapseProps> { export class Collapse extends React.Component<CollapseProps> {
static defaultProps = { static defaultProps = {
isActive: true isActive: true
}; };
+1 -1
View File
@@ -6,7 +6,7 @@ type CollapserProps = {
style?: object style?: object
}; };
export default class Collapser extends React.Component<CollapserProps> { export class Collapser extends React.Component<CollapserProps> {
render() { render() {
const iconStyle = { const iconStyle = {
width: 20, width: 20,
@@ -3,19 +3,19 @@ import {PiListPlusBold} from "react-icons/pi";
import {TbMathFunction} from "react-icons/tb"; import {TbMathFunction} from "react-icons/tb";
import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json"; import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json";
import InputButton from "./InputButton"; import { InputButton } from "./InputButton";
import InputSpec from "./InputSpec"; import { InputSpec } from "./InputSpec";
import InputNumber from "./InputNumber"; import { InputNumber } from "./InputNumber";
import InputString from "./InputString"; import { InputString } from "./InputString";
import InputSelect from "./InputSelect"; import { InputSelect } from "./InputSelect";
import Block from "./Block"; import { Block } from "./Block";
import docUid from "../libs/document-uid"; import { generateUniqueId as docUid } from "../libs/document-uid";
import sortNumerically from "../libs/sort-numerically"; import { sortNumerically } from "../libs/sort-numerically";
import {findDefaultFromSpec} from "../libs/spec-helper"; import {findDefaultFromSpec} from "../libs/spec-helper";
import { type WithTranslation, withTranslation } from "react-i18next"; import { type WithTranslation, withTranslation } from "react-i18next";
import labelFromFieldName from "../libs/label-from-field-name"; import { labelFromFieldName } from "../libs/label-from-field-name";
import DeleteStopButton from "./_DeleteStopButton"; import { DeleteStopButton } from "./DeleteStopButton";
import { type MappedLayerErrors } from "../libs/definitions"; import { type MappedLayerErrors } from "../libs/definitions";
@@ -289,6 +289,7 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
<Block <Block
label={t("Function")} label={t("Function")}
key="function" key="function"
data-wd-key="function-type"
> >
<div className="maputnik-data-spec-property-input"> <div className="maputnik-data-spec-property-input">
<InputSelect <InputSelect
@@ -303,6 +304,7 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
<Block <Block
label={t("Base")} label={t("Base")}
key="base" key="base"
data-wd-key="function-base"
> >
<div className="maputnik-data-spec-property-input"> <div className="maputnik-data-spec-property-input">
<InputSpec <InputSpec
@@ -317,6 +319,7 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
<Block <Block
label={"Property"} label={"Property"}
key="property" key="property"
data-wd-key="function-property"
> >
<div className="maputnik-data-spec-property-input"> <div className="maputnik-data-spec-property-input">
<InputString <InputString
@@ -330,6 +333,7 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
<Block <Block
label={t("Default")} label={t("Default")}
key="default" key="default"
data-wd-key="function-default"
> >
<InputSpec <InputSpec
fieldName={this.props.fieldName} fieldName={this.props.fieldName}
@@ -368,6 +372,7 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
} }
<InputButton <InputButton
className="maputnik-add-stop" className="maputnik-add-stop"
data-wd-key="convert-to-expression"
onClick={this.props.onExpressionClick?.bind(this)} onClick={this.props.onExpressionClick?.bind(this)}
> >
<TbMathFunction style={{ verticalAlign: "text-bottom" }} /> <TbMathFunction style={{ verticalAlign: "text-bottom" }} />
@@ -380,5 +385,4 @@ class DataPropertyInternal extends React.Component<DataPropertyInternalProps, Da
} }
} }
const DataProperty = withTranslation()(DataPropertyInternal); export const DataProperty = withTranslation()(DataPropertyInternal);
export default DataProperty;
@@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import InputButton from "./InputButton"; import { InputButton } from "./InputButton";
import {MdDelete} from "react-icons/md"; import {MdDelete} from "react-icons/md";
import { type WithTranslation, withTranslation } from "react-i18next"; import { type WithTranslation, withTranslation } from "react-i18next";
@@ -23,5 +23,4 @@ class DeleteStopButtonInternal extends React.Component<DeleteStopButtonInternalP
} }
} }
const DeleteStopButton = withTranslation()(DeleteStopButtonInternal); export const DeleteStopButton = withTranslation()(DeleteStopButtonInternal);
export default DeleteStopButton;
+1 -1
View File
@@ -23,7 +23,7 @@ type DocProps = {
} }
}; };
export default class Doc extends React.Component<DocProps> { export class Doc extends React.Component<DocProps> {
render () { render () {
const {fieldSpec} = this.props; const {fieldSpec} = this.props;
@@ -2,10 +2,10 @@ import React from "react";
import {MdDelete, MdUndo} from "react-icons/md"; import {MdDelete, MdUndo} from "react-icons/md";
import { type WithTranslation, withTranslation } from "react-i18next"; import { type WithTranslation, withTranslation } from "react-i18next";
import Block from "./Block"; import { Block } from "./Block";
import InputButton from "./InputButton"; import { InputButton } from "./InputButton";
import labelFromFieldName from "../libs/label-from-field-name"; import { labelFromFieldName } from "../libs/label-from-field-name";
import FieldJson from "./FieldJson"; import { FieldJson } from "./FieldJson";
import type { StylePropertySpecification } from "maplibre-gl"; import type { StylePropertySpecification } from "maplibre-gl";
import { type MappedLayerErrors } from "../libs/definitions"; import { type MappedLayerErrors } from "../libs/definitions";
@@ -50,6 +50,7 @@ class ExpressionPropertyInternal extends React.Component<ExpressionPropertyInter
onClick={this.props.onUndo} onClick={this.props.onUndo}
disabled={undoDisabled} disabled={undoDisabled}
className="maputnik-delete-stop" className="maputnik-delete-stop"
data-wd-key="undo-expression"
title={t("Revert from expression")} title={t("Revert from expression")}
> >
<MdUndo /> <MdUndo />
@@ -59,6 +60,7 @@ class ExpressionPropertyInternal extends React.Component<ExpressionPropertyInter
key="delete_action" key="delete_action"
onClick={this.props.onDelete} onClick={this.props.onDelete}
className="maputnik-delete-stop" className="maputnik-delete-stop"
data-wd-key="delete-expression"
title={t("Delete expression")} title={t("Delete expression")}
> >
<MdDelete /> <MdDelete />
@@ -90,5 +92,4 @@ class ExpressionPropertyInternal extends React.Component<ExpressionPropertyInter
} }
} }
const ExpressionProperty = withTranslation()(ExpressionPropertyInternal); export const ExpressionProperty = withTranslation()(ExpressionPropertyInternal);
export default ExpressionProperty;
+3 -5
View File
@@ -1,5 +1,5 @@
import InputArray, { type InputArrayProps } from "./InputArray"; import { InputArray, type InputArrayProps } from "./InputArray";
import Fieldset from "./Fieldset"; import { Fieldset } from "./Fieldset";
type FieldArrayProps = InputArrayProps & { type FieldArrayProps = InputArrayProps & {
name?: string name?: string
@@ -8,12 +8,10 @@ type FieldArrayProps = InputArrayProps & {
} }
}; };
const FieldArray: React.FC<FieldArrayProps> = (props) => { export const FieldArray: React.FC<FieldArrayProps> = (props) => {
return ( return (
<Fieldset label={props.label} fieldSpec={props.fieldSpec}> <Fieldset label={props.label} fieldSpec={props.fieldSpec}>
<InputArray {...props} /> <InputArray {...props} />
</Fieldset> </Fieldset>
); );
}; };
export default FieldArray;
+3 -5
View File
@@ -1,5 +1,5 @@
import Block from "./Block"; import { Block } from "./Block";
import InputAutocomplete, { type InputAutocompleteProps } from "./InputAutocomplete"; import { InputAutocomplete, type InputAutocompleteProps } from "./InputAutocomplete";
type FieldAutocompleteProps = InputAutocompleteProps & { type FieldAutocompleteProps = InputAutocompleteProps & {
@@ -7,12 +7,10 @@ type FieldAutocompleteProps = InputAutocompleteProps & {
}; };
const FieldAutocomplete: React.FC<FieldAutocompleteProps> = (props) => { export const FieldAutocomplete: React.FC<FieldAutocompleteProps> = (props) => {
return ( return (
<Block label={props.label}> <Block label={props.label}>
<InputAutocomplete {...props} /> <InputAutocomplete {...props} />
</Block> </Block>
); );
}; };
export default FieldAutocomplete;
+3 -5
View File
@@ -1,5 +1,5 @@
import Block from "./Block"; import { Block } from "./Block";
import InputCheckbox, {type InputCheckboxProps} from "./InputCheckbox"; import { InputCheckbox, type InputCheckboxProps } from "./InputCheckbox";
type FieldCheckboxProps = InputCheckboxProps & { type FieldCheckboxProps = InputCheckboxProps & {
@@ -7,12 +7,10 @@ type FieldCheckboxProps = InputCheckboxProps & {
}; };
const FieldCheckbox: React.FC<FieldCheckboxProps> = (props) => { export const FieldCheckbox: React.FC<FieldCheckboxProps> = (props) => {
return ( return (
<Block label={props.label}> <Block label={props.label}>
<InputCheckbox {...props} /> <InputCheckbox {...props} />
</Block> </Block>
); );
}; };
export default FieldCheckbox;
+3 -5
View File
@@ -1,5 +1,5 @@
import Block from "./Block"; import { Block } from "./Block";
import InputColor, {type InputColorProps} from "./InputColor"; import { InputColor, type InputColorProps } from "./InputColor";
type FieldColorProps = InputColorProps & { type FieldColorProps = InputColorProps & {
@@ -10,12 +10,10 @@ type FieldColorProps = InputColorProps & {
}; };
const FieldColor: React.FC<FieldColorProps> = (props) => { export const FieldColor: React.FC<FieldColorProps> = (props) => {
return ( return (
<Block label={props.label} fieldSpec={props.fieldSpec}> <Block label={props.label} fieldSpec={props.fieldSpec}>
<InputColor {...props} /> <InputColor {...props} />
</Block> </Block>
); );
}; };
export default FieldColor;
+3 -4
View File
@@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import Block from "./Block"; import { Block } from "./Block";
import InputString from "./InputString"; import { InputString } from "./InputString";
import { type WithTranslation, withTranslation } from "react-i18next"; import { type WithTranslation, withTranslation } from "react-i18next";
type FieldCommentInternalProps = { type FieldCommentInternalProps = {
@@ -36,5 +36,4 @@ const FieldCommentInternal: React.FC<FieldCommentInternalProps> = (props) => {
); );
}; };
const FieldComment = withTranslation()(FieldCommentInternal); export const FieldComment = withTranslation()(FieldCommentInternal);
export default FieldComment;
+1 -3
View File
@@ -10,7 +10,7 @@ type FieldDocLabelProps = {
}; };
const FieldDocLabel: React.FC<FieldDocLabelProps> = (props) => { export const FieldDocLabel: React.FC<FieldDocLabelProps> = (props) => {
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const onToggleDoc = (state: boolean) => { const onToggleDoc = (state: boolean) => {
@@ -49,5 +49,3 @@ const FieldDocLabel: React.FC<FieldDocLabelProps> = (props) => {
} }
return <div />; return <div />;
}; };
export default FieldDocLabel;
+7 -6
View File
@@ -1,16 +1,17 @@
import InputDynamicArray, {type InputDynamicArrayProps} from "./InputDynamicArray"; import { InputDynamicArray, type InputDynamicArrayProps } from "./InputDynamicArray";
import Fieldset from "./Fieldset"; import { Fieldset } from "./Fieldset";
type FieldDynamicArrayProps = InputDynamicArrayProps & { type FieldDynamicArrayProps = InputDynamicArrayProps & {
name?: string name?: string;
fieldSpec?: {
value?: string;
}
}; };
const FieldDynamicArray: React.FC<FieldDynamicArrayProps> = (props) => { export const FieldDynamicArray: React.FC<FieldDynamicArrayProps> = (props) => {
return ( return (
<Fieldset label={props.label}> <Fieldset label={props.label}>
<InputDynamicArray {...props} /> <InputDynamicArray {...props} />
</Fieldset> </Fieldset>
); );
}; };
export default FieldDynamicArray;
+3 -5
View File
@@ -1,5 +1,5 @@
import InputEnum, {type InputEnumProps} from "./InputEnum"; import { InputEnum, type InputEnumProps } from "./InputEnum";
import Fieldset from "./Fieldset"; import { Fieldset } from "./Fieldset";
type FieldEnumProps = InputEnumProps & { type FieldEnumProps = InputEnumProps & {
@@ -10,12 +10,10 @@ type FieldEnumProps = InputEnumProps & {
}; };
const FieldEnum: React.FC<FieldEnumProps> = (props) => { export const FieldEnum: React.FC<FieldEnumProps> = (props) => {
return ( return (
<Fieldset label={props.label} fieldSpec={props.fieldSpec}> <Fieldset label={props.label} fieldSpec={props.fieldSpec}>
<InputEnum {...props} /> <InputEnum {...props} />
</Fieldset> </Fieldset>
); );
}; };
export default FieldEnum;
+5 -7
View File
@@ -1,9 +1,9 @@
import React from "react"; import React from "react";
import SpecProperty from "./_SpecProperty"; import { SpecProperty } from "./SpecProperty";
import DataProperty, { type Stop } from "./_DataProperty"; import { DataProperty, type Stop } from "./DataProperty";
import ZoomProperty from "./_ZoomProperty"; import { ZoomProperty } from "./ZoomProperty";
import ExpressionProperty from "./_ExpressionProperty"; import { ExpressionProperty } from "./ExpressionProperty";
import {function as styleFunction} from "@maplibre/maplibre-gl-style-spec"; import {function as styleFunction} from "@maplibre/maplibre-gl-style-spec";
import {findDefaultFromSpec} from "../libs/spec-helper"; import {findDefaultFromSpec} from "../libs/spec-helper";
import { type MappedLayerErrors } from "../libs/definitions"; import { type MappedLayerErrors } from "../libs/definitions";
@@ -128,7 +128,7 @@ type FieldFunctionProps = {
/** Supports displaying spec field for zoom function objects /** Supports displaying spec field for zoom function objects
* https://www.mapbox.com/mapbox-gl-style-spec/#types-function-zoom-property * https://www.mapbox.com/mapbox-gl-style-spec/#types-function-zoom-property
*/ */
const FieldFunction: React.FC<FieldFunctionProps> = (props) => { export const FieldFunction: React.FC<FieldFunctionProps> = (props) => {
const [dataType, setDataType] = React.useState( const [dataType, setDataType] = React.useState(
getDataType(props.value, props.fieldSpec) getDataType(props.value, props.fieldSpec)
); );
@@ -402,5 +402,3 @@ const FieldFunction: React.FC<FieldFunctionProps> = (props) => {
</div> </div>
); );
}; };
export default FieldFunction;
+3 -5
View File
@@ -1,7 +1,7 @@
import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json"; import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json";
import Block from "./Block"; import { Block } from "./Block";
import InputString from "./InputString"; import { InputString } from "./InputString";
type FieldIdProps = { type FieldIdProps = {
value: string value: string
@@ -10,7 +10,7 @@ type FieldIdProps = {
error?: {message: string} error?: {message: string}
}; };
const FieldId: React.FC<FieldIdProps> = (props) => { export const FieldId: React.FC<FieldIdProps> = (props) => {
return ( return (
<Block label="ID" fieldSpec={latest.layer.id} <Block label="ID" fieldSpec={latest.layer.id}
data-wd-key={props.wdKey} data-wd-key={props.wdKey}
@@ -24,5 +24,3 @@ const FieldId: React.FC<FieldIdProps> = (props) => {
</Block> </Block>
); );
}; };
export default FieldId;
+2 -4
View File
@@ -1,11 +1,9 @@
import InputJson, {type InputJsonProps} from "./InputJson"; import { InputJson, type InputJsonProps } from "./InputJson";
type FieldJsonProps = InputJsonProps & {}; type FieldJsonProps = InputJsonProps & {};
const FieldJson: React.FC<FieldJsonProps> = (props) => { export const FieldJson: React.FC<FieldJsonProps> = (props) => {
return <InputJson {...props} />; return <InputJson {...props} />;
}; };
export default FieldJson;
+3 -4
View File
@@ -1,8 +1,8 @@
import React from "react"; import React from "react";
import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json"; import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json";
import Block from "./Block"; import { Block } from "./Block";
import InputNumber from "./InputNumber"; import { InputNumber } from "./InputNumber";
import { type WithTranslation, withTranslation } from "react-i18next"; import { type WithTranslation, withTranslation } from "react-i18next";
type FieldMaxZoomInternalProps = { type FieldMaxZoomInternalProps = {
@@ -31,5 +31,4 @@ const FieldMaxZoomInternal: React.FC<FieldMaxZoomInternalProps> = (props) => {
); );
}; };
const FieldMaxZoom = withTranslation()(FieldMaxZoomInternal); export const FieldMaxZoom = withTranslation()(FieldMaxZoomInternal);
export default FieldMaxZoom;
+3 -4
View File
@@ -1,8 +1,8 @@
import React from "react"; import React from "react";
import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json"; import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json";
import Block from "./Block"; import { Block } from "./Block";
import InputNumber from "./InputNumber"; import { InputNumber } from "./InputNumber";
import { type WithTranslation, withTranslation } from "react-i18next"; import { type WithTranslation, withTranslation } from "react-i18next";
type FieldMinZoomInternalProps = { type FieldMinZoomInternalProps = {
@@ -31,5 +31,4 @@ const FieldMinZoomInternal: React.FC<FieldMinZoomInternalProps> = (props) => {
); );
}; };
const FieldMinZoom = withTranslation()(FieldMinZoomInternal); export const FieldMinZoom = withTranslation()(FieldMinZoomInternal);
export default FieldMinZoom;
+3 -5
View File
@@ -1,5 +1,5 @@
import InputMultiInput, {type InputMultiInputProps} from "./InputMultiInput"; import { InputMultiInput, type InputMultiInputProps } from "./InputMultiInput";
import Fieldset from "./Fieldset"; import { Fieldset } from "./Fieldset";
type FieldMultiInputProps = InputMultiInputProps & { type FieldMultiInputProps = InputMultiInputProps & {
@@ -7,12 +7,10 @@ type FieldMultiInputProps = InputMultiInputProps & {
}; };
const FieldMultiInput: React.FC<FieldMultiInputProps> = (props) => { export const FieldMultiInput: React.FC<FieldMultiInputProps> = (props) => {
return ( return (
<Fieldset label={props.label}> <Fieldset label={props.label}>
<InputMultiInput {...props} /> <InputMultiInput {...props} />
</Fieldset> </Fieldset>
); );
}; };
export default FieldMultiInput;
+3 -5
View File
@@ -1,5 +1,5 @@
import InputNumber, {type InputNumberProps} from "./InputNumber"; import { InputNumber, type InputNumberProps } from "./InputNumber";
import Block from "./Block"; import { Block } from "./Block";
type FieldNumberProps = InputNumberProps & { type FieldNumberProps = InputNumberProps & {
@@ -10,12 +10,10 @@ type FieldNumberProps = InputNumberProps & {
}; };
const FieldNumber: React.FC<FieldNumberProps> = (props) => { export const FieldNumber: React.FC<FieldNumberProps> = (props) => {
return ( return (
<Block label={props.label} fieldSpec={props.fieldSpec}> <Block label={props.label} fieldSpec={props.fieldSpec}>
<InputNumber {...props} /> <InputNumber {...props} />
</Block> </Block>
); );
}; };
export default FieldNumber;
+3 -5
View File
@@ -1,5 +1,5 @@
import Block from "./Block"; import { Block } from "./Block";
import InputSelect, {type InputSelectProps} from "./InputSelect"; import { InputSelect, type InputSelectProps } from "./InputSelect";
type FieldSelectProps = InputSelectProps & { type FieldSelectProps = InputSelectProps & {
@@ -10,12 +10,10 @@ type FieldSelectProps = InputSelectProps & {
}; };
const FieldSelect: React.FC<FieldSelectProps> = (props) => { export const FieldSelect: React.FC<FieldSelectProps> = (props) => {
return ( return (
<Block label={props.label} fieldSpec={props.fieldSpec}> <Block label={props.label} fieldSpec={props.fieldSpec}>
<InputSelect {...props} /> <InputSelect {...props} />
</Block> </Block>
); );
}; };
export default FieldSelect;
+3 -4
View File
@@ -1,8 +1,8 @@
import React from "react"; import React from "react";
import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json"; import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json";
import Block from "./Block"; import { Block } from "./Block";
import InputAutocomplete from "./InputAutocomplete"; import { InputAutocomplete } from "./InputAutocomplete";
import { type WithTranslation, withTranslation } from "react-i18next"; import { type WithTranslation, withTranslation } from "react-i18next";
type FieldSourceInternalProps = { type FieldSourceInternalProps = {
@@ -38,5 +38,4 @@ const FieldSourceInternal: React.FC<FieldSourceInternalProps> = ({
}; };
const FieldSource = withTranslation()(FieldSourceInternal); export const FieldSource = withTranslation()(FieldSourceInternal);
export default FieldSource;
+3 -4
View File
@@ -1,8 +1,8 @@
import React from "react"; import React from "react";
import {latest} from "@maplibre/maplibre-gl-style-spec"; import {latest} from "@maplibre/maplibre-gl-style-spec";
import Block from "./Block"; import { Block } from "./Block";
import InputAutocomplete from "./InputAutocomplete"; import { InputAutocomplete } from "./InputAutocomplete";
import { type WithTranslation, withTranslation } from "react-i18next"; import { type WithTranslation, withTranslation } from "react-i18next";
type FieldSourceLayerInternalProps = { type FieldSourceLayerInternalProps = {
@@ -35,5 +35,4 @@ const FieldSourceLayerInternal: React.FC<FieldSourceLayerInternalProps> = ({
); );
}; };
const FieldSourceLayer = withTranslation()(FieldSourceLayerInternal); export const FieldSourceLayer = withTranslation()(FieldSourceLayerInternal);
export default FieldSourceLayer;
+4 -6
View File
@@ -1,6 +1,6 @@
import Block, { type BlockProps } from "./Block"; import { Block, type BlockProps } from "./Block";
import InputSpec, { type FieldSpecType, type InputSpecProps } from "./InputSpec"; import { InputSpec, type FieldSpecType, type InputSpecProps } from "./InputSpec";
import Fieldset, { type FieldsetProps } from "./Fieldset"; import { Fieldset, type FieldsetProps } from "./Fieldset";
function getElementFromType(fieldSpec: { type?: FieldSpecType, values?: unknown[] }): typeof Fieldset | typeof Block { function getElementFromType(fieldSpec: { type?: FieldSpecType, values?: unknown[] }): typeof Fieldset | typeof Block {
switch(fieldSpec.type) { switch(fieldSpec.type) {
@@ -36,7 +36,7 @@ function getElementFromType(fieldSpec: { type?: FieldSpecType, values?: unknown[
export type FieldSpecProps = InputSpecProps & BlockProps & FieldsetProps; export type FieldSpecProps = InputSpecProps & BlockProps & FieldsetProps;
const FieldSpec: React.FC<FieldSpecProps> = (props) => { export const FieldSpec: React.FC<FieldSpecProps> = (props) => {
const TypeBlock = getElementFromType(props.fieldSpec!); const TypeBlock = getElementFromType(props.fieldSpec!);
return ( return (
@@ -45,5 +45,3 @@ const FieldSpec: React.FC<FieldSpecProps> = (props) => {
</TypeBlock> </TypeBlock>
); );
}; };
export default FieldSpec;
+3 -5
View File
@@ -1,5 +1,5 @@
import Block from "./Block"; import { Block } from "./Block";
import InputString, {type InputStringProps} from "./InputString"; import { InputString, type InputStringProps } from "./InputString";
type FieldStringProps = InputStringProps & { type FieldStringProps = InputStringProps & {
name?: string name?: string
@@ -9,12 +9,10 @@ type FieldStringProps = InputStringProps & {
} }
}; };
const FieldString: React.FC<FieldStringProps> = (props) => { export const FieldString: React.FC<FieldStringProps> = (props) => {
return ( return (
<Block label={props.label} fieldSpec={props.fieldSpec}> <Block label={props.label} fieldSpec={props.fieldSpec}>
<InputString {...props} /> <InputString {...props} />
</Block> </Block>
); );
}; };
export default FieldString;
+4 -5
View File
@@ -1,8 +1,8 @@
import React from "react"; import React from "react";
import {v8} from "@maplibre/maplibre-gl-style-spec"; import {v8} from "@maplibre/maplibre-gl-style-spec";
import Block from "./Block"; import { Block } from "./Block";
import InputSelect from "./InputSelect"; import { InputSelect } from "./InputSelect";
import InputString from "./InputString"; import { InputString } from "./InputString";
import { type WithTranslation, withTranslation } from "react-i18next"; import { type WithTranslation, withTranslation } from "react-i18next";
import { startCase } from "lodash"; import { startCase } from "lodash";
@@ -43,5 +43,4 @@ const FieldTypeInternal: React.FC<FieldTypeInternalProps> = ({
); );
}; };
const FieldType = withTranslation()(FieldTypeInternal); export const FieldType = withTranslation()(FieldTypeInternal);
export default FieldType;
+3 -5
View File
@@ -1,5 +1,5 @@
import InputUrl, {type FieldUrlProps as InputUrlProps} from "./InputUrl"; import { InputUrl, type FieldUrlProps as InputUrlProps } from "./InputUrl";
import Block from "./Block"; import { Block } from "./Block";
type FieldUrlProps = InputUrlProps & { type FieldUrlProps = InputUrlProps & {
@@ -10,12 +10,10 @@ type FieldUrlProps = InputUrlProps & {
}; };
const FieldUrl: React.FC<FieldUrlProps> = (props) => { export const FieldUrl: React.FC<FieldUrlProps> = (props) => {
return ( return (
<Block label={props.label} fieldSpec={props.fieldSpec}> <Block label={props.label} fieldSpec={props.fieldSpec}>
<InputUrl {...props} /> <InputUrl {...props} />
</Block> </Block>
); );
}; };
export default FieldUrl;
+4 -6
View File
@@ -1,8 +1,8 @@
import React, { type PropsWithChildren, type ReactElement } from "react"; import React, { type PropsWithChildren, type ReactElement } from "react";
import classnames from "classnames"; import classnames from "classnames";
import FieldDocLabel from "./FieldDocLabel"; import { FieldDocLabel } from "./FieldDocLabel";
import Doc from "./Doc"; import { Doc } from "./Doc";
import generateUniqueId from "../libs/document-uid"; import { generateUniqueId } from "../libs/document-uid";
export type FieldsetProps = PropsWithChildren & { export type FieldsetProps = PropsWithChildren & {
label?: string, label?: string,
@@ -12,7 +12,7 @@ export type FieldsetProps = PropsWithChildren & {
}; };
const Fieldset: React.FC<FieldsetProps> = (props) => { export const Fieldset: React.FC<FieldsetProps> = (props) => {
const [showDoc, setShowDoc] = React.useState(false); const [showDoc, setShowDoc] = React.useState(false);
const labelId = React.useRef(generateUniqueId("fieldset_label_")); const labelId = React.useRef(generateUniqueId("fieldset_label_"));
@@ -49,5 +49,3 @@ const Fieldset: React.FC<FieldsetProps> = (props) => {
</div> </div>
); );
}; };
export default Fieldset;
+10 -9
View File
@@ -7,13 +7,13 @@ import {migrate, convertFilter} from "@maplibre/maplibre-gl-style-spec";
import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json"; import latest from "@maplibre/maplibre-gl-style-spec/dist/latest.json";
import {combiningFilterOps} from "../libs/filterops"; import {combiningFilterOps} from "../libs/filterops";
import InputSelect from "./InputSelect"; import { InputSelect } from "./InputSelect";
import Block from "./Block"; import { Block } from "./Block";
import SingleFilterEditor from "./SingleFilterEditor"; import { SingleFilterEditor } from "./SingleFilterEditor";
import FilterEditorBlock from "./FilterEditorBlock"; import { FilterEditorBlock } from "./FilterEditorBlock";
import InputButton from "./InputButton"; import { InputButton } from "./InputButton";
import Doc from "./Doc"; import { Doc } from "./Doc";
import ExpressionProperty from "./_ExpressionProperty"; import { ExpressionProperty } from "./ExpressionProperty";
import { type WithTranslation, withTranslation } from "react-i18next"; import { type WithTranslation, withTranslation } from "react-i18next";
import type { MappedLayerErrors, StyleSpecificationWithId } from "../libs/definitions"; import type { MappedLayerErrors, StyleSpecificationWithId } from "../libs/definitions";
@@ -215,6 +215,7 @@ class FilterEditorInternal extends React.Component<FilterEditorInternalProps, Fi
onClick={this.makeExpression} onClick={this.makeExpression}
title={t("Convert to expression")} title={t("Convert to expression")}
className="maputnik-make-zoom-function" className="maputnik-make-zoom-function"
data-wd-key="filter-convert-to-expression"
> >
<TbMathFunction /> <TbMathFunction />
</InputButton> </InputButton>
@@ -248,6 +249,7 @@ class FilterEditorInternal extends React.Component<FilterEditorInternalProps, Fi
fieldSpec={fieldSpec} fieldSpec={fieldSpec}
label={t("Filter")} label={t("Filter")}
action={actions} action={actions}
data-wd-key="filter-combining-operator"
> >
<InputSelect <InputSelect
value={combiningOp} value={combiningOp}
@@ -316,5 +318,4 @@ class FilterEditorInternal extends React.Component<FilterEditorInternalProps, Fi
} }
} }
const FilterEditor = withTranslation()(FilterEditorInternal); export const FilterEditor = withTranslation()(FilterEditorInternal);
export default FilterEditor;
+2 -3
View File
@@ -1,5 +1,5 @@
import React, { type PropsWithChildren } from "react"; import React, { type PropsWithChildren } from "react";
import InputButton from "./InputButton"; import { InputButton } from "./InputButton";
import {MdDelete} from "react-icons/md"; import {MdDelete} from "react-icons/md";
import { type WithTranslation, withTranslation } from "react-i18next"; import { type WithTranslation, withTranslation } from "react-i18next";
@@ -27,5 +27,4 @@ class FilterEditorBlockInternal extends React.Component<FilterEditorBlockInterna
} }
} }
const FilterEditorBlock = withTranslation()(FilterEditorBlockInternal); export const FilterEditorBlock = withTranslation()(FilterEditorBlockInternal);
export default FilterEditorBlock;
@@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import InputButton from "./InputButton"; import { InputButton } from "./InputButton";
import {MdFunctions, MdInsertChart} from "react-icons/md"; import {MdFunctions, MdInsertChart} from "react-icons/md";
import { TbMathFunction } from "react-icons/tb"; import { TbMathFunction } from "react-icons/tb";
import { type WithTranslation, withTranslation } from "react-i18next"; import { type WithTranslation, withTranslation } from "react-i18next";
@@ -67,5 +67,4 @@ class FunctionInputButtonsInternal extends React.Component<FunctionInputButtonsI
} }
} }
const FunctionInputButtons = withTranslation()(FunctionInputButtonsInternal); export const FunctionInputButtons = withTranslation()(FunctionInputButtonsInternal);
export default FunctionInputButtons;
+1 -3
View File
@@ -13,7 +13,7 @@ type IconLayerProps = {
className?: string className?: string
}; };
const IconLayer: React.FC<IconLayerProps> = (props) => { export const IconLayer: React.FC<IconLayerProps> = (props) => {
const iconProps = { style: props.style }; const iconProps = { style: props.style };
switch(props.type) { switch(props.type) {
case "fill-extrusion": return <IoMdCube {...iconProps} />; case "fill-extrusion": return <IoMdCube {...iconProps} />;
@@ -29,5 +29,3 @@ const IconLayer: React.FC<IconLayerProps> = (props) => {
default: return <MdPriorityHigh {...iconProps} />; default: return <MdPriorityHigh {...iconProps} />;
} }
}; };
export default IconLayer;
+3 -3
View File
@@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import InputString from "./InputString"; import { InputString } from "./InputString";
import InputNumber from "./InputNumber"; import { InputNumber } from "./InputNumber";
export type InputArrayProps = { export type InputArrayProps = {
value: (string | number | undefined)[] value: (string | number | undefined)[]
@@ -17,7 +17,7 @@ type InputArrayState = {
initialPropsValue: unknown[] initialPropsValue: unknown[]
}; };
export default class InputArray extends React.Component<InputArrayProps, InputArrayState> { export class InputArray extends React.Component<InputArrayProps, InputArrayState> {
static defaultProps = { static defaultProps = {
value: [], value: [],
default: [], default: [],
@@ -1,18 +0,0 @@
import InputAutocomplete from "./InputAutocomplete";
import { mount } from "cypress/react";
const fruits = ["apple", "banana", "cherry"];
describe("<InputAutocomplete />", () => {
it("filters options when typing", () => {
mount(
<InputAutocomplete aria-label="Fruit" options={fruits.map(f => [f, f])} />
);
cy.get("input").focus();
cy.get(".maputnik-autocomplete-menu-item").should("have.length", 3);
cy.get("input").type("ch");
cy.get(".maputnik-autocomplete-menu-item").should("have.length", 1).and("contain", "cherry");
cy.get(".maputnik-autocomplete-menu-item").click();
cy.get("input").should("have.value", "cherry");
});
});
+1 -1
View File
@@ -11,7 +11,7 @@ export type InputAutocompleteProps = {
"aria-label"?: string "aria-label"?: string
}; };
export default function InputAutocomplete({ export function InputAutocomplete({
value, value,
options = [], options = [],
onChange = () => {}, onChange = () => {},
+1 -1
View File
@@ -14,7 +14,7 @@ type InputButtonProps = {
title?: string title?: string
}; };
export default class InputButton extends React.Component<InputButtonProps> { export class InputButton extends React.Component<InputButtonProps> {
render() { render() {
return <button return <button
id={this.props.id} id={this.props.id}
+1 -1
View File
@@ -6,7 +6,7 @@ export type InputCheckboxProps = {
onChange(...args: unknown[]): unknown onChange(...args: unknown[]): unknown
}; };
export default class InputCheckbox extends React.Component<InputCheckboxProps> { export class InputCheckbox extends React.Component<InputCheckboxProps> {
static defaultProps = { static defaultProps = {
value: false, value: false,
}; };
+1 -1
View File
@@ -20,7 +20,7 @@ export type InputColorProps = {
}; };
/*** Number fields with support for min, max and units and documentation*/ /*** Number fields with support for min, max and units and documentation*/
export default class InputColor extends React.Component<InputColorProps> { export class InputColor extends React.Component<InputColorProps> {
state = { state = {
pickerOpened: false pickerOpened: false
}; };
+8 -10
View File
@@ -3,13 +3,13 @@ import capitalize from "lodash.capitalize";
import {MdDelete} from "react-icons/md"; import {MdDelete} from "react-icons/md";
import { type WithTranslation, withTranslation } from "react-i18next"; import { type WithTranslation, withTranslation } from "react-i18next";
import InputString from "./InputString"; import { InputString } from "./InputString";
import InputNumber from "./InputNumber"; import { InputNumber } from "./InputNumber";
import InputButton from "./InputButton"; import { InputButton } from "./InputButton";
import FieldDocLabel from "./FieldDocLabel"; import { FieldDocLabel } from "./FieldDocLabel";
import InputEnum from "./InputEnum"; import { InputEnum } from "./InputEnum";
import InputUrl from "./InputUrl"; import { InputUrl } from "./InputUrl";
import InputColor from "./InputColor"; import { InputColor } from "./InputColor";
export type InputDynamicArrayProps = { export type InputDynamicArrayProps = {
@@ -141,9 +141,7 @@ class InputDynamicArrayInternal extends React.Component<InputDynamicArrayInterna
} }
} }
const InputDynamicArray = withTranslation()(InputDynamicArrayInternal); export const InputDynamicArray = withTranslation()(InputDynamicArrayInternal);
export default InputDynamicArray;
type DeleteValueInputButtonProps = { type DeleteValueInputButtonProps = {
onClick?(...args: unknown[]): unknown onClick?(...args: unknown[]): unknown
} & WithTranslation; } & WithTranslation;
+3 -3
View File
@@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import InputSelect from "./InputSelect"; import { InputSelect } from "./InputSelect";
import InputMultiInput from "./InputMultiInput"; import { InputMultiInput } from "./InputMultiInput";
function optionsLabelLength(options: any[]) { function optionsLabelLength(options: any[]) {
@@ -25,7 +25,7 @@ export type InputEnumProps = {
}; };
export default class InputEnum extends React.Component<InputEnumProps> { export class InputEnum extends React.Component<InputEnumProps> {
render() { render() {
const {options, value, onChange, name, label} = this.props; const {options, value, onChange, name, label} = this.props;
+2 -2
View File
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import InputAutocomplete from "./InputAutocomplete"; import { InputAutocomplete } from "./InputAutocomplete";
export type InputFontProps = { export type InputFontProps = {
name: string name: string
@@ -11,7 +11,7 @@ export type InputFontProps = {
"aria-label"?: string "aria-label"?: string
}; };
export default class InputFont extends React.Component<InputFontProps> { export class InputFont extends React.Component<InputFontProps> {
static defaultProps = { static defaultProps = {
fonts: [] fonts: []
}; };
+1 -2
View File
@@ -135,5 +135,4 @@ class InputJsonInternal extends React.Component<InputJsonInternalProps, InputJso
} }
} }
const InputJson = withTranslation()(InputJsonInternal); export const InputJson = withTranslation()(InputJsonInternal);
export default InputJson;
+1 -1
View File
@@ -9,7 +9,7 @@ export type InputMultiInputProps = {
"aria-label"?: string "aria-label"?: string
}; };
export default class InputMultiInput extends React.Component<InputMultiInputProps> { export class InputMultiInput extends React.Component<InputMultiInputProps> {
render() { render() {
let options = this.props.options; let options = this.props.options;
if(options.length > 0 && !Array.isArray(options[0])) { if(options.length > 0 && !Array.isArray(options[0])) {
+2 -2
View File
@@ -1,5 +1,5 @@
import React, { type BaseSyntheticEvent } from "react"; import React, { type BaseSyntheticEvent } from "react";
import generateUniqueId from "../libs/document-uid"; import { generateUniqueId } from "../libs/document-uid";
export type InputNumberProps = { export type InputNumberProps = {
value?: number value?: number
@@ -25,7 +25,7 @@ type InputNumberState = {
dirtyValue?: number | string | undefined dirtyValue?: number | string | undefined
}; };
export default class InputNumber extends React.Component<InputNumberProps, InputNumberState> { export class InputNumber extends React.Component<InputNumberProps, InputNumberState> {
static defaultProps = { static defaultProps = {
rangeStep: 1 rangeStep: 1
}; };
+1 -1
View File
@@ -10,7 +10,7 @@ export type InputSelectProps = {
"aria-label"?: string "aria-label"?: string
}; };
export default class InputSelect extends React.Component<InputSelectProps> { export class InputSelect extends React.Component<InputSelectProps> {
render() { render() {
let options = this.props.options; let options = this.props.options;
if(options.length > 0 && !Array.isArray(options[0])) { if(options.length > 0 && !Array.isArray(options[0])) {
+10 -10
View File
@@ -1,14 +1,14 @@
import React, { type ReactElement } from "react"; import React, { type ReactElement } from "react";
import InputColor, { type InputColorProps } from "./InputColor"; import { InputColor, type InputColorProps } from "./InputColor";
import InputNumber, { type InputNumberProps } from "./InputNumber"; import { InputNumber, type InputNumberProps } from "./InputNumber";
import InputCheckbox, { type InputCheckboxProps } from "./InputCheckbox"; import { InputCheckbox, type InputCheckboxProps } from "./InputCheckbox";
import InputString, { type InputStringProps } from "./InputString"; import { InputString, type InputStringProps } from "./InputString";
import InputArray, { type InputArrayProps } from "./InputArray"; import { InputArray, type InputArrayProps } from "./InputArray";
import InputDynamicArray, { type InputDynamicArrayProps } from "./InputDynamicArray"; import { InputDynamicArray, type InputDynamicArrayProps } from "./InputDynamicArray";
import InputFont, { type InputFontProps } from "./InputFont"; import { InputFont, type InputFontProps } from "./InputFont";
import InputAutocomplete, { type InputAutocompleteProps } from "./InputAutocomplete"; import { InputAutocomplete, type InputAutocompleteProps } from "./InputAutocomplete";
import InputEnum, { type InputEnumProps } from "./InputEnum"; import { InputEnum, type InputEnumProps } from "./InputEnum";
import capitalize from "lodash.capitalize"; import capitalize from "lodash.capitalize";
const iconProperties = ["background-pattern", "fill-pattern", "line-pattern", "fill-extrusion-pattern", "icon-image"]; const iconProperties = ["background-pattern", "fill-pattern", "line-pattern", "fill-extrusion-pattern", "icon-image"];
@@ -38,7 +38,7 @@ export type InputSpecProps = {
/** Display any field from the Maplibre GL style spec and /** Display any field from the Maplibre GL style spec and
* choose the correct field component based on the @{fieldSpec} * choose the correct field component based on the @{fieldSpec}
* to display @{value}. */ * to display @{value}. */
export default class InputSpec extends React.Component<InputSpecProps> { export class InputSpec extends React.Component<InputSpecProps> {
childNodes() { childNodes() {
const commonProps = { const commonProps = {
+1 -1
View File
@@ -20,7 +20,7 @@ type InputStringState = {
value?: string value?: string
}; };
export default class InputString extends React.Component<InputStringProps, InputStringState> { export class InputString extends React.Component<InputStringProps, InputStringState> {
static defaultProps = { static defaultProps = {
onInput: () => {}, onInput: () => {},
}; };

Some files were not shown because too many files have changed in this diff Show More