diff --git a/.ahoy.yml b/.ahoy.yml
index facfca892..8638f2e2e 100644
--- a/.ahoy.yml
+++ b/.ahoy.yml
@@ -278,11 +278,16 @@ commands:
cmd: ahoy cli vendor/bin/phpunit --testsuite=functional "$@"
test-functional-javascript:
- aliases: [test-js]
usage: Run PHPUnit functional JavaScript tests.
cmd: ahoy cli vendor/bin/phpunit --testsuite=functional-javascript "$@"
#;> TOOL_PHPUNIT
+ #;< TOOL_JEST
+ test-js:
+ usage: Run Jest JavaScript unit tests.
+ cmd: ahoy cli "yarn test"
+ #;> TOOL_JEST
+
#;< TOOL_BEHAT
test-bdd:
usage: Run BDD tests.
diff --git a/.circleci/config.yml b/.circleci/config.yml
index f61d3dfb5..534026ed3 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -363,6 +363,9 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \
if [ -n \"${PACKAGE_TOKEN:-}\" ]; then export COMPOSER_AUTH='{\"github-oauth\": {\"github.com\": \"${PACKAGE_TOKEN-}\"}}'; fi && \
COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist"
+ #;< TOOL_JEST
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
+ #;> TOOL_JEST
- run:
name: Provision site
@@ -380,6 +383,12 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./scripts/vortex/provision.sh
no_output_timeout: 30m
+ #;< TOOL_JEST
+ - run:
+ name: Test with Jest
+ command: docker compose exec -T cli bash -c "yarn test" || [ "${VORTEX_CI_JEST_IGNORE_FAILURE:-0}" -eq 1 ]
+ #;> TOOL_JEST
+
#;< TOOL_PHPUNIT
- run:
name: Test with PHPUnit
diff --git a/.circleci/vortex-test-common.yml b/.circleci/vortex-test-common.yml
index 3a83e2063..cbdb17bc9 100644
--- a/.circleci/vortex-test-common.yml
+++ b/.circleci/vortex-test-common.yml
@@ -229,6 +229,9 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \
if [ -n \"${PACKAGE_TOKEN:-}\" ]; then export COMPOSER_AUTH='{\"github-oauth\": {\"github.com\": \"${PACKAGE_TOKEN-}\"}}'; fi && \
COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist"
+ #;< TOOL_JEST
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
+ #;> TOOL_JEST
- run:
name: Provision site
@@ -246,6 +249,12 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./scripts/vortex/provision.sh
no_output_timeout: 30m
+ #;< TOOL_JEST
+ - run:
+ name: Test with Jest
+ command: docker compose exec -T cli bash -c "yarn test" || [ "${VORTEX_CI_JEST_IGNORE_FAILURE:-0}" -eq 1 ]
+ #;> TOOL_JEST
+
#;< TOOL_PHPUNIT
- run:
name: Test with PHPUnit
diff --git a/.dockerignore b/.dockerignore
index b02049969..22f2224fb 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -41,6 +41,7 @@ drush/contrib/
!composer.lock
!gherkinlint.json
!package-lock.json
+!jest.config.js
!package.json
!patches
!phpcs.xml
diff --git a/.eslintrc.json b/.eslintrc.json
index ef68a6a93..091de0679 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -61,6 +61,17 @@
"operator-linebreak": ["error", "after", { "overrides": { "?": "ignore", ":": "ignore" } }],
"yml/indent": ["error", 2]
},
+ "overrides": [
+ {
+ "files": ["*.test.js"],
+ "env": { "jest": true },
+ "rules": {
+ "no-eval": "off",
+ "max-nested-callbacks": ["warn", 5],
+ "jsdoc/check-tag-names": "off"
+ }
+ }
+ ],
"settings": {
"jsdoc": {
"tagNamePreference": {
diff --git a/.github/workflows/build-test-deploy.yml b/.github/workflows/build-test-deploy.yml
index e4cf98eb8..e4350b788 100644
--- a/.github/workflows/build-test-deploy.yml
+++ b/.github/workflows/build-test-deploy.yml
@@ -407,6 +407,9 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \
if [ -n \"${PACKAGE_TOKEN:-}\" ]; then export COMPOSER_AUTH='{\"github-oauth\": {\"github.com\": \"${PACKAGE_TOKEN-}\"}}'; fi && \
COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist"
+ #;< TOOL_JEST
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
+ #;> TOOL_JEST
- name: Provision site
run: |
@@ -423,6 +426,13 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./scripts/vortex/provision.sh
timeout-minutes: 30
+ #;< TOOL_JEST
+ - name: Test with Jest
+ if: ${{ matrix.instance == 0 || strategy.job-total == 1 }}
+ run: docker compose exec -T cli bash -c "yarn test"
+ continue-on-error: ${{ vars.VORTEX_CI_JEST_IGNORE_FAILURE == '1' }}
+ #;> TOOL_JEST
+
#;< TOOL_PHPUNIT
- name: Test with PHPUnit
if: ${{ matrix.instance == 0 || strategy.job-total == 1 }}
diff --git a/.vortex/CLAUDE.md b/.vortex/CLAUDE.md
index bd0505caf..9e13066eb 100644
--- a/.vortex/CLAUDE.md
+++ b/.vortex/CLAUDE.md
@@ -88,8 +88,6 @@ When updating template scripts:
**NEVER run without explicit user permission**:
- `ahoy update-snapshots`
-- `UPDATE_SNAPSHOTS=1 ./vendor/bin/phpunit`
-- Any `UPDATE_SNAPSHOTS=1` command
These modify many files and take 10-15 minutes.
diff --git a/.vortex/docs/.utils/variables/extra/ci.variables.sh b/.vortex/docs/.utils/variables/extra/ci.variables.sh
index c2fe6bae2..bdee75805 100755
--- a/.vortex/docs/.utils/variables/extra/ci.variables.sh
+++ b/.vortex/docs/.utils/variables/extra/ci.variables.sh
@@ -69,6 +69,9 @@ VORTEX_CI_BEHAT_IGNORE_FAILURE=0
# Test Behat profile to use in CI. If not set, the `default` profile will be used.
VORTEX_CI_BEHAT_PROFILE=
+# Ignore Jest test failures.
+VORTEX_CI_JEST_IGNORE_FAILURE=0
+
# Directory to store test results in CI.
VORTEX_CI_TEST_RESULTS=/tmp/tests
diff --git a/.vortex/docs/content/development/jest.mdx b/.vortex/docs/content/development/jest.mdx
new file mode 100644
index 000000000..caf990892
--- /dev/null
+++ b/.vortex/docs/content/development/jest.mdx
@@ -0,0 +1,135 @@
+---
+sidebar_label: Jest
+sidebar_position: 6
+---
+
+# Jest
+
+**Vortex** uses [Jest](https://jestjs.io/) as a framework for JavaScript unit
+testing. Jest tests verify that JavaScript behaviors in custom Drupal modules
+work correctly in isolation, without requiring a browser or Drupal bootstrap.
+
+For running tests, configuration, and CI settings, see the
+[Jest tool reference](/docs/tools/jest).
+
+## Test file structure
+
+Test files are co-located with source files in the `js/` directory of each
+custom module:
+
+```text
+web/modules/custom/my_module/
+└── js/
+ ├── my_module.js # Source file (Drupal behavior)
+ └── my_module.test.js # Jest test file
+```
+
+Jest automatically discovers `*.test.js` files in `web/modules/custom/*/js/`
+directories. Adding a new module with tests requires no configuration changes.
+
+## Writing tests
+
+Drupal JavaScript uses the IIFE pattern `((Drupal) => { ... })(Drupal)` where
+`Drupal` is a global object. Tests load these source files using `eval()` after
+setting up the required globals.
+
+### Test template
+
+```javascript
+/**
+ * @jest-environment jsdom
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+describe('Drupal.behaviors.myModule', () => {
+ beforeEach(() => {
+ localStorage.clear();
+ global.Drupal = { behaviors: {} };
+
+ const filePath = path.resolve(__dirname, 'my_module.js');
+ const code = fs.readFileSync(filePath, 'utf8');
+ eval(code);
+ });
+
+ afterEach(() => {
+ delete global.Drupal;
+ });
+
+ it('should attach behavior to the context', () => {
+ document.body.innerHTML = '
';
+ Drupal.behaviors.myModule.attach(document);
+
+ const el = document.querySelector('[data-my-module]');
+ expect(el.classList.contains('processed')).toBe(true);
+ });
+});
+```
+
+### Loading Drupal behaviors
+
+The `eval(fs.readFileSync(...))` pattern executes the source file's IIFE, which
+receives `global.Drupal` as its `Drupal` parameter and registers the behavior.
+After `eval()`, the behavior is accessible via `Drupal.behaviors.myModule`.
+
+### Mocking globals
+
+Set globals in `beforeEach` and clean them up in `afterEach`:
+
+| Global | Setup | When needed |
+|--------|-------|-------------|
+| `Drupal` | `global.Drupal = { behaviors: {} }` | Always — required by all Drupal behaviors |
+| `jQuery` | `global.jQuery = require('jquery')` or a mock | When the source file uses `jQuery` or `$` |
+| `drupalSettings` | `global.drupalSettings = { path: { baseUrl: '/' } }` | When the source file reads `drupalSettings` |
+| `localStorage` | Provided by jsdom; call `localStorage.clear()` | When the source file uses `localStorage` |
+
+### Testing DOM interactions
+
+The `jsdom` environment provides `document` and `window`. Set up HTML before
+each test:
+
+```javascript
+document.body.innerHTML = `
+
+ Save
+
+
+`;
+
+Drupal.behaviors.myModule.attach(document);
+document.querySelector('[data-action="save"]').click();
+
+expect(document.querySelector('[data-status]').textContent).toBe('Saved');
+```
+
+### Testing timed behavior
+
+Use Jest fake timers for `setTimeout` and `setInterval`:
+
+```javascript
+jest.useFakeTimers();
+
+Drupal.behaviors.myModule.startPolling();
+jest.advanceTimersByTime(5000);
+
+expect(fetchSpy).toHaveBeenCalledTimes(5);
+
+jest.useRealTimers();
+```
+
+### ESLint compatibility
+
+The `.eslintrc.json` includes an override for `*.test.js` files that enables
+the `jest` environment and allows `eval()`. No additional ESLint configuration
+is needed for test files.
+
+## Boilerplate
+
+**Vortex** provides a Jest test boilerplate for the [demo module](https://github.com/drevops/vortex/blob/main/web/modules/custom/ys_demo/js/ys_demo.test.js)
+that demonstrates testing a counter block with DOM manipulation, localStorage
+interaction, and event handling.
+
+This boilerplate test runs in continuous integration pipeline when you install
+**Vortex** and can be used as a starting point for writing your own JavaScript
+tests.
diff --git a/.vortex/docs/content/development/variables.mdx b/.vortex/docs/content/development/variables.mdx
index 49b07f9e0..6355eba1d 100644
--- a/.vortex/docs/content/development/variables.mdx
+++ b/.vortex/docs/content/development/variables.mdx
@@ -168,6 +168,7 @@ The list below is automatically generated with [Shellvar](https://github.com/ale
| `VORTEX_CI_DCLINT_IGNORE_FAILURE` | Ignore DCLint failures. | `UNDEFINED` | `CI config` |
| `VORTEX_CI_GHERKIN_LINT_IGNORE_FAILURE` | Ignore Gherkin Lint failures. | `UNDEFINED` | `CI config` |
| `VORTEX_CI_HADOLINT_IGNORE_FAILURE` | Ignore Hadolint failures. | `UNDEFINED` | `CI config` |
+| `VORTEX_CI_JEST_IGNORE_FAILURE` | Ignore Jest test failures. | `UNDEFINED` | `CI config` |
| `VORTEX_CI_NODEJS_LINT_IGNORE_FAILURE` | Ignore NodeJS linters failures. | `UNDEFINED` | `CI config` |
| `VORTEX_CI_PHPCS_IGNORE_FAILURE` | Ignore PHPCS failures. | `UNDEFINED` | `CI config` |
| `VORTEX_CI_PHPMD_IGNORE_FAILURE` | Ignore PHPMD failures. | `UNDEFINED` | `CI config` |
diff --git a/.vortex/docs/content/tools/jest.mdx b/.vortex/docs/content/tools/jest.mdx
new file mode 100644
index 000000000..d47a0c290
--- /dev/null
+++ b/.vortex/docs/content/tools/jest.mdx
@@ -0,0 +1,171 @@
+---
+sidebar_label: Jest
+---
+
+# Jest – JavaScript Testing Framework
+
+https://jestjs.io/
+
+> Jest is a delightful JavaScript testing framework with a focus on simplicity.
+
+**Vortex** comes with [pre-configured Jest setup](https://github.com/drevops/vortex/blob/main/jest.config.js) for testing JavaScript in custom Drupal modules.
+
+For writing tests, mocking Drupal globals, and test templates, see the
+[Jest development guide](/docs/development/jest).
+
+## Usage
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+
+
+ ```shell
+ ahoy test-js # Run all Jest tests.
+ ```
+
+
+ ```shell
+ docker compose exec cli bash -c "yarn test" # Run all Jest tests.
+ ```
+
+
+
+### Running tests matching a pattern
+
+
+
+ ```shell
+ ahoy test-js -- --testPathPattern=ys_demo
+ ```
+
+
+ ```shell
+ docker compose exec cli bash -c "yarn test --testPathPattern=ys_demo"
+ ```
+
+
+
+### Running a specific test by name
+
+
+
+ ```shell
+ ahoy test-js -- -t "should increment the value"
+ ```
+
+
+ ```shell
+ docker compose exec cli bash -c "yarn test -t 'should increment the value'"
+ ```
+
+
+
+## Configuration
+
+See [Jest configuration reference](https://jestjs.io/docs/configuration).
+
+All global configuration takes place in the [`jest.config.js`](https://github.com/drevops/vortex/blob/main/jest.config.js) file.
+
+By default, Jest will discover and run test files in `web/modules/custom/*/js/`
+directories. Test files must use the `.test.js` extension and be co-located
+alongside the source files they test.
+
+The configuration uses the `jsdom` test environment to provide browser globals
+like `document`, `window`, and `localStorage`.
+
+### Test discovery
+
+The `jest.config.js` file dynamically scans `web/modules/custom/` for modules
+that contain a `js/` directory, and adds each as a root for test discovery. This
+means adding a new custom module with JavaScript tests requires no configuration
+changes.
+
+## Writing tests
+
+Test files are placed next to the source file they test:
+
+```text
+web/modules/custom/my_module/
+└── js/
+ ├── my_module.js # Source file
+ └── my_module.test.js # Test file
+```
+
+### Loading Drupal behaviors
+
+Drupal JavaScript uses the IIFE pattern with `Drupal` as a global. Tests load
+the source file using `eval()` after setting up the global:
+
+```javascript
+/**
+ * @jest-environment jsdom
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+describe('Drupal.behaviors.myModule', () => {
+ beforeEach(() => {
+ localStorage.clear();
+ global.Drupal = { behaviors: {} };
+
+ const filePath = path.resolve(__dirname, 'my_module.js');
+ const code = fs.readFileSync(filePath, 'utf8');
+ eval(code);
+ });
+
+ afterEach(() => {
+ delete global.Drupal;
+ });
+
+ it('should attach behavior', () => {
+ document.body.innerHTML = '
';
+ Drupal.behaviors.myModule.attach(document);
+
+ const el = document.querySelector('[data-my-module]');
+ expect(el.classList.contains('processed')).toBe(true);
+ });
+});
+```
+
+### Key patterns
+
+- **`global.Drupal = { behaviors: {} }`** in `beforeEach` provides the Drupal
+ global that the IIFE receives as a parameter.
+- **`eval(fs.readFileSync(...))`** loads and executes the source file, which
+ registers the behavior on `Drupal.behaviors`.
+- **`delete global.Drupal`** in `afterEach` ensures test isolation.
+- **`document.body.innerHTML`** sets up the DOM for each test using jsdom.
+- **`jest.useFakeTimers()`** controls `setTimeout` and `setInterval` for
+ testing timed behavior.
+
+### ESLint configuration
+
+The `.eslintrc.json` file includes an override for `*.test.js` files that
+enables the `jest` environment and disables `no-eval` to allow the source
+loading pattern:
+
+```json
+{
+ "overrides": [
+ {
+ "files": ["*.test.js"],
+ "env": { "jest": true },
+ "rules": {
+ "no-eval": "off",
+ "max-nested-callbacks": ["warn", 5],
+ "jsdoc/check-tag-names": "off"
+ }
+ }
+ ]
+}
+```
+
+## Ignoring fail in continuous integration pipeline
+
+This tool runs in continuous integration pipeline by default and fails the build
+if there are any violations.
+
+Set `VORTEX_CI_JEST_IGNORE_FAILURE` environment variable to `1` to ignore
+failures. The tool will still run and report violations, if any.
diff --git a/.vortex/installer/src/Prompts/Handlers/Tools.php b/.vortex/installer/src/Prompts/Handlers/Tools.php
index 5bafdfcc0..68a384520 100644
--- a/.vortex/installer/src/Prompts/Handlers/Tools.php
+++ b/.vortex/installer/src/Prompts/Handlers/Tools.php
@@ -30,6 +30,8 @@ class Tools extends AbstractHandler {
const BEHAT = 'behat';
+ const JEST = 'jest';
+
/**
* {@inheritdoc}
*/
@@ -62,6 +64,7 @@ public function default(array $responses): null|string|bool|array {
return [
self::BEHAT,
self::ESLINT,
+ self::JEST,
self::PHPCS,
self::PHPMD,
self::PHPSTAN,
@@ -111,10 +114,10 @@ public function process(): void {
$this->processGroup($name);
}
- // Remove fei: command and its call when both FE lint tools and custom
+ // Remove fei: command and its call when all FE tools and custom
// theme are absent, as there are no front-end dependencies to install.
- $fe_group = $groups['frontend_linting'] ?? NULL;
- if ($fe_group && isset($fe_group['tools']) && !array_intersect($fe_group['tools'], $selected_tools)) {
+ $fe_all_group = $groups['frontend_all'] ?? NULL;
+ if ($fe_all_group && isset($fe_all_group['tools']) && !array_intersect($fe_all_group['tools'], $selected_tools)) {
$theme = $this->responses[Theme::id()] ?? NULL;
if (in_array($theme, [Theme::OLIVERO, Theme::CLARO, Theme::STARK])) {
File::replaceContentInFile($this->tmpDir . '/.ahoy.yml', Replacement::create('ahoy_fei', function (string $content): string {
@@ -353,6 +356,27 @@ public static function getToolDefinitions(string $filter = 'all'): array {
'files' => ['.stylelintrc.js'],
],
+ self::JEST => [
+ 'title' => 'Jest',
+ 'present' => fn(): mixed => File::contains($this->dstDir . '/package.json', '"jest":') ||
+ File::exists($this->dstDir . '/jest.config.js'),
+ 'package.json' => function (JsonManipulator $pj): void {
+ $pj->removeSubNode('devDependencies', 'jest');
+ $pj->removeSubNode('devDependencies', 'jest-environment-jsdom');
+ $pj->removeSubNode('scripts', 'test');
+ },
+ 'files' => fn(): array => [
+ $this->tmpDir . '/jest.config.js',
+ glob($this->tmpDir . '/' . $this->webroot . '/modules/custom/*/js/*.test.js'),
+ ],
+ 'lines' => [
+ 'AGENTS.md' => [
+ '# Jest testing',
+ 'ahoy test-js',
+ ],
+ ],
+ ],
+
self::PHPUNIT => [
'title' => 'PHPUnit',
'present' => fn(): mixed => File::contains($this->dstDir . '/composer.json', 'phpunit/phpunit') ||
@@ -458,14 +482,20 @@ public static function getToolDefinitions(string $filter = 'all'): array {
],
'frontend_linting' => [
'tools' => [self::ESLINT, self::STYLELINT],
- 'files' => ['package.json', 'yarn.lock'],
'ahoy' => [
- '/^\h*ahoy cli "yarn install --frozen-lockfile"\h*\n?/m',
'/^\h*ahoy cli "yarn run lint"\h*\n?/m',
'/^\h*ahoy cli "yarn run lint-fix"\h*\n?/m',
],
'token' => 'TOOL_ESLINT_STYLELINT',
],
+ 'frontend_testing' => [
+ 'tools' => [self::JEST],
+ 'token' => 'TOOL_JEST',
+ ],
+ 'frontend_all' => [
+ 'tools' => [self::ESLINT, self::STYLELINT, self::JEST],
+ 'files' => ['package.json', 'yarn.lock'],
+ ],
];
if ($filter === 'tools') {
diff --git a/.vortex/installer/tests/Fixtures/handler_process/_baseline/.ahoy.yml b/.vortex/installer/tests/Fixtures/handler_process/_baseline/.ahoy.yml
index c5bb74fc7..86af0b06e 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/_baseline/.ahoy.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/_baseline/.ahoy.yml
@@ -236,10 +236,13 @@ commands:
cmd: ahoy cli vendor/bin/phpunit --testsuite=functional "$@"
test-functional-javascript:
- aliases: [test-js]
usage: Run PHPUnit functional JavaScript tests.
cmd: ahoy cli vendor/bin/phpunit --testsuite=functional-javascript "$@"
+ test-js:
+ usage: Run Jest JavaScript unit tests.
+ cmd: ahoy cli "yarn test"
+
test-bdd:
usage: Run BDD tests.
aliases: [test-behat]
diff --git a/.vortex/installer/tests/Fixtures/handler_process/_baseline/.dockerignore b/.vortex/installer/tests/Fixtures/handler_process/_baseline/.dockerignore
index fcbd7c174..ca2193891 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/_baseline/.dockerignore
+++ b/.vortex/installer/tests/Fixtures/handler_process/_baseline/.dockerignore
@@ -40,6 +40,7 @@ drush/contrib/
!composer.lock
!gherkinlint.json
!package-lock.json
+!jest.config.js
!package.json
!patches
!phpcs.xml
diff --git a/.vortex/installer/tests/Fixtures/handler_process/_baseline/.eslintrc.json b/.vortex/installer/tests/Fixtures/handler_process/_baseline/.eslintrc.json
index ef68a6a93..091de0679 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/_baseline/.eslintrc.json
+++ b/.vortex/installer/tests/Fixtures/handler_process/_baseline/.eslintrc.json
@@ -61,6 +61,17 @@
"operator-linebreak": ["error", "after", { "overrides": { "?": "ignore", ":": "ignore" } }],
"yml/indent": ["error", 2]
},
+ "overrides": [
+ {
+ "files": ["*.test.js"],
+ "env": { "jest": true },
+ "rules": {
+ "no-eval": "off",
+ "max-nested-callbacks": ["warn", 5],
+ "jsdoc/check-tag-names": "off"
+ }
+ }
+ ],
"settings": {
"jsdoc": {
"tagNamePreference": {
diff --git a/.vortex/installer/tests/Fixtures/handler_process/_baseline/.github/workflows/build-test-deploy.yml b/.vortex/installer/tests/Fixtures/handler_process/_baseline/.github/workflows/build-test-deploy.yml
index ac5a691f1..d6b75b1cb 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/_baseline/.github/workflows/build-test-deploy.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/_baseline/.github/workflows/build-test-deploy.yml
@@ -366,6 +366,7 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \
if [ -n \"${PACKAGE_TOKEN:-}\" ]; then export COMPOSER_AUTH='{\"github-oauth\": {\"github.com\": \"${PACKAGE_TOKEN-}\"}}'; fi && \
COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist"
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
- name: Provision site
run: |
@@ -376,6 +377,11 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./scripts/vortex/provision.sh
timeout-minutes: 30
+ - name: Test with Jest
+ if: ${{ matrix.instance == 0 || strategy.job-total == 1 }}
+ run: docker compose exec -T cli bash -c "yarn test"
+ continue-on-error: ${{ vars.VORTEX_CI_JEST_IGNORE_FAILURE == '1' }}
+
- name: Test with PHPUnit
if: ${{ matrix.instance == 0 || strategy.job-total == 1 }}
run: docker compose exec -T cli vendor/bin/phpunit
diff --git a/.vortex/installer/tests/Fixtures/handler_process/_baseline/AGENTS.md b/.vortex/installer/tests/Fixtures/handler_process/_baseline/AGENTS.md
index 53b4ab0a3..b32df8396 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/_baseline/AGENTS.md
+++ b/.vortex/installer/tests/Fixtures/handler_process/_baseline/AGENTS.md
@@ -59,6 +59,9 @@ ahoy test-kernel # Run PHPUnit Kernel tests
ahoy test-functional # Run PHPUnit Functional tests
ahoy test -- --filter=TestClassName # Run specific PHPUnit test class
+# Jest testing
+ahoy test-js # Run Jest JavaScript unit tests
+
# Behat testing
ahoy test-bdd # Run Behat tests
ahoy test-bdd -- --tags=@tagname # Run Behat tests with specific tag
diff --git a/.vortex/installer/tests/Fixtures/handler_process/_baseline/jest.config.js b/.vortex/installer/tests/Fixtures/handler_process/_baseline/jest.config.js
new file mode 100644
index 000000000..0ac9851fc
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/_baseline/jest.config.js
@@ -0,0 +1,32 @@
+const fs = require('fs');
+const path = require('path');
+
+// Discover js/ directories in custom modules, resolving symlinks to real
+// paths. Jest resolves symlinks internally, so roots must use real paths
+// for test files to be matched.
+const dirs = ['web/modules/custom'];
+const roots = [];
+
+dirs.forEach((dir) => {
+ if (fs.existsSync(dir)) {
+ fs.readdirSync(dir).forEach((name) => {
+ const jsDir = path.resolve(dir, name, 'js');
+ if (fs.existsSync(jsDir)) {
+ roots.push(jsDir);
+ }
+ });
+ }
+});
+
+module.exports = {
+ testEnvironment: 'jest-environment-jsdom',
+ roots: roots.length > 0 ? roots : [path.resolve('web/modules/custom')],
+ testRegex: 'web/modules/custom/.+\\.test\\.js$',
+ testPathIgnorePatterns: ['/node_modules/', '/vendor/'],
+ modulePathIgnorePatterns: [
+ '/web/core/',
+ '/web/modules/contrib/',
+ '/web/themes/',
+ '/.vortex/',
+ ],
+};
diff --git a/.vortex/installer/tests/Fixtures/handler_process/_baseline/package.json b/.vortex/installer/tests/Fixtures/handler_process/_baseline/package.json
index e778d6707..62eac946c 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/_baseline/package.json
+++ b/.vortex/installer/tests/Fixtures/handler_process/_baseline/package.json
@@ -13,10 +13,13 @@
"lint": "yarn run lint-js && yarn run lint-css",
"lint-fix-js": "eslint web/modules/custom --ext .js --no-error-on-unmatched-pattern --fix",
"lint-fix-css": "stylelint --allow-empty-input \"web/modules/custom/**/*.css\" --fix",
- "lint-fix": "yarn run lint-fix-js && yarn run lint-fix-css"
+ "lint-fix": "yarn run lint-fix-js && yarn run lint-fix-css",
+ "test": "jest"
},
"devDependencies": {
"@homer0/prettier-plugin-jsdoc": "__VERSION__",
+ "jest": "__VERSION__",
+ "jest-environment-jsdom": "__VERSION__",
"eslint": "__VERSION__",
"eslint-config-airbnb-base": "__VERSION__",
"eslint-config-prettier": "__VERSION__",
diff --git a/.vortex/installer/tests/Fixtures/handler_process/_baseline/web/modules/custom/sw_demo/js/sw_demo.js b/.vortex/installer/tests/Fixtures/handler_process/_baseline/web/modules/custom/sw_demo/js/sw_demo.js
index 3282266ce..886028723 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/_baseline/web/modules/custom/sw_demo/js/sw_demo.js
+++ b/.vortex/installer/tests/Fixtures/handler_process/_baseline/web/modules/custom/sw_demo/js/sw_demo.js
@@ -4,6 +4,8 @@
((Drupal) => {
Drupal.behaviors.ysDemo = {
+ storageKey: 'ys_counter_value',
+
attach(context) {
this.initCounterBlock(context);
},
@@ -17,7 +19,7 @@
initCounterBlock(context) {
const counterBlocks = context.querySelectorAll('[data-sw-demo-counter]');
- counterBlocks.forEach(function processBlock(block) {
+ counterBlocks.forEach((block) => {
// Skip if already processed.
if (block.classList.contains('sw-demo-counter-processed')) {
return;
@@ -29,39 +31,24 @@
const buttons = block.querySelectorAll('[data-counter-action]');
// Load saved value from localStorage.
- const storageKey = 'ys_counter_value';
- let currentValue = parseInt(localStorage.getItem(storageKey), 10) || 0;
+ let currentValue = this.getCounterValue();
valueElement.textContent = currentValue;
// Add event listeners to buttons.
- buttons.forEach(function processButton(button) {
- button.addEventListener('click', function handleClick() {
- const action = this.getAttribute('data-counter-action');
-
- switch (action) {
- case 'increment':
- currentValue += 1;
- break;
- case 'decrement':
- currentValue -= 1;
- break;
- case 'reset':
- currentValue = 0;
- break;
- default:
- // No action for unknown action types.
- break;
- }
+ buttons.forEach((button) => {
+ button.addEventListener('click', () => {
+ const action = button.getAttribute('data-counter-action');
+ currentValue = this.applyAction(currentValue, action);
- // Update display
+ // Update display.
valueElement.textContent = currentValue;
// Save to localStorage.
- localStorage.setItem(storageKey, currentValue.toString());
+ localStorage.setItem(this.storageKey, currentValue.toString());
// Add visual feedback.
valueElement.classList.add('updated');
- Drupal.behaviors.ysDemo.removeUpdatedClassAfterDelay(valueElement);
+ this.removeUpdatedClassAfterDelay(valueElement);
// Log action for debugging.
// eslint-disable-next-line no-console
@@ -71,6 +58,26 @@
});
},
+ /**
+ * Apply a counter action and return the new value.
+ *
+ * @param {number} value The current counter value.
+ * @param {string} action The action to apply.
+ * @return {number} The new counter value.
+ */
+ applyAction(value, action) {
+ switch (action) {
+ case 'increment':
+ return value + 1;
+ case 'decrement':
+ return value - 1;
+ case 'reset':
+ return 0;
+ default:
+ return value;
+ }
+ },
+
/**
* Remove updated class after a delay for visual feedback.
*
@@ -88,8 +95,7 @@
* @return {number} The current counter value, or 0 if not set.
*/
getCounterValue() {
- const storageKey = 'ys_counter_value';
- return parseInt(localStorage.getItem(storageKey), 10) || 0;
+ return parseInt(localStorage.getItem(this.storageKey), 10) || 0;
},
};
})(Drupal);
diff --git a/.vortex/installer/tests/Fixtures/handler_process/_baseline/web/modules/custom/sw_demo/js/sw_demo.test.js b/.vortex/installer/tests/Fixtures/handler_process/_baseline/web/modules/custom/sw_demo/js/sw_demo.test.js
new file mode 100644
index 000000000..a423fda42
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/_baseline/web/modules/custom/sw_demo/js/sw_demo.test.js
@@ -0,0 +1,274 @@
+/**
+ * @jest-environment jsdom
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+describe('Drupal.behaviors.ysDemo', () => {
+ beforeEach(() => {
+ localStorage.clear();
+ global.Drupal = { behaviors: {} };
+
+ const filePath = path.resolve(__dirname, 'sw_demo.js');
+ const code = fs.readFileSync(filePath, 'utf8');
+ eval(code);
+ });
+
+ afterEach(() => {
+ delete global.Drupal;
+ });
+
+ function createCounterBlockHtml() {
+ return `
+
+ 0
+ +
+ -
+ Reset
+
+ `;
+ }
+
+ describe('storageKey', () => {
+ it('should have the expected storage key', () => {
+ expect(Drupal.behaviors.ysDemo.storageKey).toBe('ys_counter_value');
+ });
+ });
+
+ describe('getCounterValue', () => {
+ it('should return 0 when localStorage is empty', () => {
+ expect(Drupal.behaviors.ysDemo.getCounterValue()).toBe(0);
+ });
+
+ it('should return the stored value from localStorage', () => {
+ localStorage.setItem('ys_counter_value', '42');
+ expect(Drupal.behaviors.ysDemo.getCounterValue()).toBe(42);
+ });
+
+ it('should return 0 for non-numeric localStorage values', () => {
+ localStorage.setItem('ys_counter_value', 'invalid');
+ expect(Drupal.behaviors.ysDemo.getCounterValue()).toBe(0);
+ });
+
+ it('should return negative values correctly', () => {
+ localStorage.setItem('ys_counter_value', '-5');
+ expect(Drupal.behaviors.ysDemo.getCounterValue()).toBe(-5);
+ });
+ });
+
+ describe('applyAction', () => {
+ it('should increment the value', () => {
+ expect(Drupal.behaviors.ysDemo.applyAction(0, 'increment')).toBe(1);
+ expect(Drupal.behaviors.ysDemo.applyAction(10, 'increment')).toBe(11);
+ });
+
+ it('should decrement the value', () => {
+ expect(Drupal.behaviors.ysDemo.applyAction(0, 'decrement')).toBe(-1);
+ expect(Drupal.behaviors.ysDemo.applyAction(5, 'decrement')).toBe(4);
+ });
+
+ it('should reset the value to zero', () => {
+ expect(Drupal.behaviors.ysDemo.applyAction(42, 'reset')).toBe(0);
+ expect(Drupal.behaviors.ysDemo.applyAction(-5, 'reset')).toBe(0);
+ });
+
+ it('should return the same value for unknown actions', () => {
+ expect(Drupal.behaviors.ysDemo.applyAction(7, 'unknown')).toBe(7);
+ });
+ });
+
+ describe('removeUpdatedClassAfterDelay', () => {
+ beforeEach(() => {
+ jest.useFakeTimers();
+ });
+
+ afterEach(() => {
+ jest.useRealTimers();
+ });
+
+ it('should remove the updated class after 300ms', () => {
+ document.body.innerHTML = ' ';
+ const element = document.querySelector('span');
+
+ Drupal.behaviors.ysDemo.removeUpdatedClassAfterDelay(element);
+
+ expect(element.classList.contains('updated')).toBe(true);
+ jest.advanceTimersByTime(300);
+ expect(element.classList.contains('updated')).toBe(false);
+ });
+
+ it('should not remove the class before 300ms', () => {
+ document.body.innerHTML = ' ';
+ const element = document.querySelector('span');
+
+ Drupal.behaviors.ysDemo.removeUpdatedClassAfterDelay(element);
+
+ jest.advanceTimersByTime(299);
+ expect(element.classList.contains('updated')).toBe(true);
+ });
+ });
+
+ describe('initCounterBlock', () => {
+ it('should initialize the counter display with 0', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('0');
+ });
+
+ it('should initialize with saved localStorage value', () => {
+ localStorage.setItem('ys_counter_value', '15');
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('15');
+ });
+
+ it('should mark the block as processed', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const block = document.querySelector('[data-sw-demo-counter]');
+ expect(block.classList.contains('sw-demo-counter-processed')).toBe(true);
+ });
+
+ it('should not re-process already processed blocks', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ const block = document.querySelector('[data-sw-demo-counter]');
+ block.classList.add('sw-demo-counter-processed');
+
+ const value = document.querySelector('[data-counter-value]');
+ value.textContent = 'original';
+
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ expect(value.textContent).toBe('original');
+ });
+
+ it('should increment counter on button click', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('1');
+ });
+
+ it('should decrement counter on button click', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const decrementBtn = document.querySelector(
+ '[data-counter-action="decrement"]',
+ );
+ decrementBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('-1');
+ });
+
+ it('should reset counter on button click', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+ incrementBtn.click();
+ incrementBtn.click();
+
+ const resetBtn = document.querySelector('[data-counter-action="reset"]');
+ resetBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('0');
+ });
+
+ it('should save counter value to localStorage on click', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+ incrementBtn.click();
+
+ expect(localStorage.getItem('ys_counter_value')).toBe('2');
+ });
+
+ it('should add updated class on click for visual feedback', () => {
+ jest.useFakeTimers();
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.classList.contains('updated')).toBe(true);
+
+ jest.advanceTimersByTime(300);
+ expect(value.classList.contains('updated')).toBe(false);
+ jest.useRealTimers();
+ });
+
+ it('should handle multiple counter blocks', () => {
+ document.body.innerHTML = `
+
+ 0
+ +
+
+
+ 0
+ +
+
+ `;
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const blocks = document.querySelectorAll('[data-sw-demo-counter]');
+ expect(blocks[0].classList.contains('sw-demo-counter-processed')).toBe(
+ true,
+ );
+ expect(blocks[1].classList.contains('sw-demo-counter-processed')).toBe(
+ true,
+ );
+ });
+ });
+
+ describe('attach', () => {
+ it('should call initCounterBlock with the context', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ const spy = jest.spyOn(Drupal.behaviors.ysDemo, 'initCounterBlock');
+
+ Drupal.behaviors.ysDemo.attach(document);
+
+ expect(spy).toHaveBeenCalledWith(document);
+ spy.mockRestore();
+ });
+
+ it('should fully initialize when called as a behavior', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.attach(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('1');
+ expect(localStorage.getItem('ys_counter_value')).toBe('1');
+ });
+ });
+});
diff --git a/.vortex/installer/tests/Fixtures/handler_process/ciprovider_circleci/.circleci/config.yml b/.vortex/installer/tests/Fixtures/handler_process/ciprovider_circleci/.circleci/config.yml
index d6fa3a565..2845230c7 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/ciprovider_circleci/.circleci/config.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/ciprovider_circleci/.circleci/config.yml
@@ -325,6 +325,7 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \
if [ -n \"${PACKAGE_TOKEN:-}\" ]; then export COMPOSER_AUTH='{\"github-oauth\": {\"github.com\": \"${PACKAGE_TOKEN-}\"}}'; fi && \
COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist"
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
- run:
name: Provision site
@@ -336,6 +337,10 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./scripts/vortex/provision.sh
no_output_timeout: 30m
+ - run:
+ name: Test with Jest
+ command: docker compose exec -T cli bash -c "yarn test" || [ "${VORTEX_CI_JEST_IGNORE_FAILURE:-0}" -eq 1 ]
+
- run:
name: Test with PHPUnit
command: |
diff --git a/.vortex/installer/tests/Fixtures/handler_process/custom_modules_no_demo/web/modules/custom/sw_demo/js/-sw_demo.test.js b/.vortex/installer/tests/Fixtures/handler_process/custom_modules_no_demo/web/modules/custom/sw_demo/js/-sw_demo.test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/.vortex/installer/tests/Fixtures/handler_process/custom_modules_none/web/modules/custom/sw_demo/js/-sw_demo.test.js b/.vortex/installer/tests/Fixtures/handler_process/custom_modules_none/web/modules/custom/sw_demo/js/-sw_demo.test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/.vortex/installer/tests/Fixtures/handler_process/deploy_types_all_circleci/.circleci/config.yml b/.vortex/installer/tests/Fixtures/handler_process/deploy_types_all_circleci/.circleci/config.yml
index d6fa3a565..2845230c7 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/deploy_types_all_circleci/.circleci/config.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/deploy_types_all_circleci/.circleci/config.yml
@@ -325,6 +325,7 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \
if [ -n \"${PACKAGE_TOKEN:-}\" ]; then export COMPOSER_AUTH='{\"github-oauth\": {\"github.com\": \"${PACKAGE_TOKEN-}\"}}'; fi && \
COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist"
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
- run:
name: Provision site
@@ -336,6 +337,10 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./scripts/vortex/provision.sh
no_output_timeout: 30m
+ - run:
+ name: Test with Jest
+ command: docker compose exec -T cli bash -c "yarn test" || [ "${VORTEX_CI_JEST_IGNORE_FAILURE:-0}" -eq 1 ]
+
- run:
name: Test with PHPUnit
command: |
diff --git a/.vortex/installer/tests/Fixtures/handler_process/deploy_types_none_circleci/.circleci/config.yml b/.vortex/installer/tests/Fixtures/handler_process/deploy_types_none_circleci/.circleci/config.yml
index 924d47e70..ca771ee23 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/deploy_types_none_circleci/.circleci/config.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/deploy_types_none_circleci/.circleci/config.yml
@@ -325,6 +325,7 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \
if [ -n \"${PACKAGE_TOKEN:-}\" ]; then export COMPOSER_AUTH='{\"github-oauth\": {\"github.com\": \"${PACKAGE_TOKEN-}\"}}'; fi && \
COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist"
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
- run:
name: Provision site
@@ -336,6 +337,10 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./scripts/vortex/provision.sh
no_output_timeout: 30m
+ - run:
+ name: Test with Jest
+ command: docker compose exec -T cli bash -c "yarn test" || [ "${VORTEX_CI_JEST_IGNORE_FAILURE:-0}" -eq 1 ]
+
- run:
name: Test with PHPUnit
command: |
diff --git a/.vortex/installer/tests/Fixtures/handler_process/deploy_types_none_gha/.github/workflows/build-test-deploy.yml b/.vortex/installer/tests/Fixtures/handler_process/deploy_types_none_gha/.github/workflows/build-test-deploy.yml
index 50fe45ac2..363683454 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/deploy_types_none_gha/.github/workflows/build-test-deploy.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/deploy_types_none_gha/.github/workflows/build-test-deploy.yml
@@ -1,4 +1,4 @@
-@@ -491,95 +491,3 @@
+@@ -497,95 +497,3 @@
timeout-minutes: 120 # Cancel the action after 15 minutes, regardless of whether a connection has been established.
with:
detached: true
diff --git a/.vortex/installer/tests/Fixtures/handler_process/deps_updates_provider_ci_circleci/.circleci/config.yml b/.vortex/installer/tests/Fixtures/handler_process/deps_updates_provider_ci_circleci/.circleci/config.yml
index d6fa3a565..2845230c7 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/deps_updates_provider_ci_circleci/.circleci/config.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/deps_updates_provider_ci_circleci/.circleci/config.yml
@@ -325,6 +325,7 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \
if [ -n \"${PACKAGE_TOKEN:-}\" ]; then export COMPOSER_AUTH='{\"github-oauth\": {\"github.com\": \"${PACKAGE_TOKEN-}\"}}'; fi && \
COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist"
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
- run:
name: Provision site
@@ -336,6 +337,10 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./scripts/vortex/provision.sh
no_output_timeout: 30m
+ - run:
+ name: Test with Jest
+ command: docker compose exec -T cli bash -c "yarn test" || [ "${VORTEX_CI_JEST_IGNORE_FAILURE:-0}" -eq 1 ]
+
- run:
name: Test with PHPUnit
command: |
diff --git a/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/.dockerignore b/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/.dockerignore
index 32eb6f635..4aab2eb79 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/.dockerignore
+++ b/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/.dockerignore
@@ -22,7 +22,7 @@
!drush
drush/contrib/
-@@ -51,3 +51,5 @@
+@@ -52,3 +52,5 @@
!scripts
!tests
!yarn.lock
diff --git a/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/AGENTS.md b/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/AGENTS.md
index 144ce178e..bf8f6765d 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/AGENTS.md
+++ b/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/AGENTS.md
@@ -1,4 +1,4 @@
-@@ -79,8 +79,8 @@
+@@ -82,8 +82,8 @@
## Key Directories
diff --git a/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/docroot/modules/custom/sw_demo/js/sw_demo.js b/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/docroot/modules/custom/sw_demo/js/sw_demo.js
index 3282266ce..886028723 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/docroot/modules/custom/sw_demo/js/sw_demo.js
+++ b/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/docroot/modules/custom/sw_demo/js/sw_demo.js
@@ -4,6 +4,8 @@
((Drupal) => {
Drupal.behaviors.ysDemo = {
+ storageKey: 'ys_counter_value',
+
attach(context) {
this.initCounterBlock(context);
},
@@ -17,7 +19,7 @@
initCounterBlock(context) {
const counterBlocks = context.querySelectorAll('[data-sw-demo-counter]');
- counterBlocks.forEach(function processBlock(block) {
+ counterBlocks.forEach((block) => {
// Skip if already processed.
if (block.classList.contains('sw-demo-counter-processed')) {
return;
@@ -29,39 +31,24 @@
const buttons = block.querySelectorAll('[data-counter-action]');
// Load saved value from localStorage.
- const storageKey = 'ys_counter_value';
- let currentValue = parseInt(localStorage.getItem(storageKey), 10) || 0;
+ let currentValue = this.getCounterValue();
valueElement.textContent = currentValue;
// Add event listeners to buttons.
- buttons.forEach(function processButton(button) {
- button.addEventListener('click', function handleClick() {
- const action = this.getAttribute('data-counter-action');
-
- switch (action) {
- case 'increment':
- currentValue += 1;
- break;
- case 'decrement':
- currentValue -= 1;
- break;
- case 'reset':
- currentValue = 0;
- break;
- default:
- // No action for unknown action types.
- break;
- }
+ buttons.forEach((button) => {
+ button.addEventListener('click', () => {
+ const action = button.getAttribute('data-counter-action');
+ currentValue = this.applyAction(currentValue, action);
- // Update display
+ // Update display.
valueElement.textContent = currentValue;
// Save to localStorage.
- localStorage.setItem(storageKey, currentValue.toString());
+ localStorage.setItem(this.storageKey, currentValue.toString());
// Add visual feedback.
valueElement.classList.add('updated');
- Drupal.behaviors.ysDemo.removeUpdatedClassAfterDelay(valueElement);
+ this.removeUpdatedClassAfterDelay(valueElement);
// Log action for debugging.
// eslint-disable-next-line no-console
@@ -71,6 +58,26 @@
});
},
+ /**
+ * Apply a counter action and return the new value.
+ *
+ * @param {number} value The current counter value.
+ * @param {string} action The action to apply.
+ * @return {number} The new counter value.
+ */
+ applyAction(value, action) {
+ switch (action) {
+ case 'increment':
+ return value + 1;
+ case 'decrement':
+ return value - 1;
+ case 'reset':
+ return 0;
+ default:
+ return value;
+ }
+ },
+
/**
* Remove updated class after a delay for visual feedback.
*
@@ -88,8 +95,7 @@
* @return {number} The current counter value, or 0 if not set.
*/
getCounterValue() {
- const storageKey = 'ys_counter_value';
- return parseInt(localStorage.getItem(storageKey), 10) || 0;
+ return parseInt(localStorage.getItem(this.storageKey), 10) || 0;
},
};
})(Drupal);
diff --git a/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/docroot/modules/custom/sw_demo/js/sw_demo.test.js b/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/docroot/modules/custom/sw_demo/js/sw_demo.test.js
new file mode 100644
index 000000000..a423fda42
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/docroot/modules/custom/sw_demo/js/sw_demo.test.js
@@ -0,0 +1,274 @@
+/**
+ * @jest-environment jsdom
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+describe('Drupal.behaviors.ysDemo', () => {
+ beforeEach(() => {
+ localStorage.clear();
+ global.Drupal = { behaviors: {} };
+
+ const filePath = path.resolve(__dirname, 'sw_demo.js');
+ const code = fs.readFileSync(filePath, 'utf8');
+ eval(code);
+ });
+
+ afterEach(() => {
+ delete global.Drupal;
+ });
+
+ function createCounterBlockHtml() {
+ return `
+
+ 0
+ +
+ -
+ Reset
+
+ `;
+ }
+
+ describe('storageKey', () => {
+ it('should have the expected storage key', () => {
+ expect(Drupal.behaviors.ysDemo.storageKey).toBe('ys_counter_value');
+ });
+ });
+
+ describe('getCounterValue', () => {
+ it('should return 0 when localStorage is empty', () => {
+ expect(Drupal.behaviors.ysDemo.getCounterValue()).toBe(0);
+ });
+
+ it('should return the stored value from localStorage', () => {
+ localStorage.setItem('ys_counter_value', '42');
+ expect(Drupal.behaviors.ysDemo.getCounterValue()).toBe(42);
+ });
+
+ it('should return 0 for non-numeric localStorage values', () => {
+ localStorage.setItem('ys_counter_value', 'invalid');
+ expect(Drupal.behaviors.ysDemo.getCounterValue()).toBe(0);
+ });
+
+ it('should return negative values correctly', () => {
+ localStorage.setItem('ys_counter_value', '-5');
+ expect(Drupal.behaviors.ysDemo.getCounterValue()).toBe(-5);
+ });
+ });
+
+ describe('applyAction', () => {
+ it('should increment the value', () => {
+ expect(Drupal.behaviors.ysDemo.applyAction(0, 'increment')).toBe(1);
+ expect(Drupal.behaviors.ysDemo.applyAction(10, 'increment')).toBe(11);
+ });
+
+ it('should decrement the value', () => {
+ expect(Drupal.behaviors.ysDemo.applyAction(0, 'decrement')).toBe(-1);
+ expect(Drupal.behaviors.ysDemo.applyAction(5, 'decrement')).toBe(4);
+ });
+
+ it('should reset the value to zero', () => {
+ expect(Drupal.behaviors.ysDemo.applyAction(42, 'reset')).toBe(0);
+ expect(Drupal.behaviors.ysDemo.applyAction(-5, 'reset')).toBe(0);
+ });
+
+ it('should return the same value for unknown actions', () => {
+ expect(Drupal.behaviors.ysDemo.applyAction(7, 'unknown')).toBe(7);
+ });
+ });
+
+ describe('removeUpdatedClassAfterDelay', () => {
+ beforeEach(() => {
+ jest.useFakeTimers();
+ });
+
+ afterEach(() => {
+ jest.useRealTimers();
+ });
+
+ it('should remove the updated class after 300ms', () => {
+ document.body.innerHTML = ' ';
+ const element = document.querySelector('span');
+
+ Drupal.behaviors.ysDemo.removeUpdatedClassAfterDelay(element);
+
+ expect(element.classList.contains('updated')).toBe(true);
+ jest.advanceTimersByTime(300);
+ expect(element.classList.contains('updated')).toBe(false);
+ });
+
+ it('should not remove the class before 300ms', () => {
+ document.body.innerHTML = ' ';
+ const element = document.querySelector('span');
+
+ Drupal.behaviors.ysDemo.removeUpdatedClassAfterDelay(element);
+
+ jest.advanceTimersByTime(299);
+ expect(element.classList.contains('updated')).toBe(true);
+ });
+ });
+
+ describe('initCounterBlock', () => {
+ it('should initialize the counter display with 0', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('0');
+ });
+
+ it('should initialize with saved localStorage value', () => {
+ localStorage.setItem('ys_counter_value', '15');
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('15');
+ });
+
+ it('should mark the block as processed', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const block = document.querySelector('[data-sw-demo-counter]');
+ expect(block.classList.contains('sw-demo-counter-processed')).toBe(true);
+ });
+
+ it('should not re-process already processed blocks', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ const block = document.querySelector('[data-sw-demo-counter]');
+ block.classList.add('sw-demo-counter-processed');
+
+ const value = document.querySelector('[data-counter-value]');
+ value.textContent = 'original';
+
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ expect(value.textContent).toBe('original');
+ });
+
+ it('should increment counter on button click', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('1');
+ });
+
+ it('should decrement counter on button click', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const decrementBtn = document.querySelector(
+ '[data-counter-action="decrement"]',
+ );
+ decrementBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('-1');
+ });
+
+ it('should reset counter on button click', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+ incrementBtn.click();
+ incrementBtn.click();
+
+ const resetBtn = document.querySelector('[data-counter-action="reset"]');
+ resetBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('0');
+ });
+
+ it('should save counter value to localStorage on click', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+ incrementBtn.click();
+
+ expect(localStorage.getItem('ys_counter_value')).toBe('2');
+ });
+
+ it('should add updated class on click for visual feedback', () => {
+ jest.useFakeTimers();
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.classList.contains('updated')).toBe(true);
+
+ jest.advanceTimersByTime(300);
+ expect(value.classList.contains('updated')).toBe(false);
+ jest.useRealTimers();
+ });
+
+ it('should handle multiple counter blocks', () => {
+ document.body.innerHTML = `
+
+ 0
+ +
+
+
+ 0
+ +
+
+ `;
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const blocks = document.querySelectorAll('[data-sw-demo-counter]');
+ expect(blocks[0].classList.contains('sw-demo-counter-processed')).toBe(
+ true,
+ );
+ expect(blocks[1].classList.contains('sw-demo-counter-processed')).toBe(
+ true,
+ );
+ });
+ });
+
+ describe('attach', () => {
+ it('should call initCounterBlock with the context', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ const spy = jest.spyOn(Drupal.behaviors.ysDemo, 'initCounterBlock');
+
+ Drupal.behaviors.ysDemo.attach(document);
+
+ expect(spy).toHaveBeenCalledWith(document);
+ spy.mockRestore();
+ });
+
+ it('should fully initialize when called as a behavior', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.attach(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('1');
+ expect(localStorage.getItem('ys_counter_value')).toBe('1');
+ });
+ });
+});
diff --git a/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/jest.config.js b/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/jest.config.js
new file mode 100644
index 000000000..bf6ae3613
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/jest.config.js
@@ -0,0 +1,28 @@
+@@ -4,7 +4,7 @@
+ // Discover js/ directories in custom modules, resolving symlinks to real
+ // paths. Jest resolves symlinks internally, so roots must use real paths
+ // for test files to be matched.
+-const dirs = ['web/modules/custom'];
++const dirs = ['docroot/modules/custom'];
+ const roots = [];
+
+ dirs.forEach((dir) => {
+@@ -20,13 +20,13 @@
+
+ module.exports = {
+ testEnvironment: 'jest-environment-jsdom',
+- roots: roots.length > 0 ? roots : [path.resolve('web/modules/custom')],
+- testRegex: 'web/modules/custom/.+\\.test\\.js$',
++ roots: roots.length > 0 ? roots : [path.resolve('docroot/modules/custom')],
++ testRegex: 'docroot/modules/custom/.+\\.test\\.js$',
+ testPathIgnorePatterns: ['/node_modules/', '/vendor/'],
+ modulePathIgnorePatterns: [
+- '/web/core/',
+- '/web/modules/contrib/',
+- '/web/themes/',
++ '/docroot/core/',
++ '/docroot/modules/contrib/',
++ '/docroot/themes/',
+ '/.vortex/',
+ ],
+ };
diff --git a/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/package.json b/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/package.json
index a31d3de73..40c40de58 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/package.json
+++ b/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/package.json
@@ -11,6 +11,6 @@
- "lint-fix-css": "stylelint --allow-empty-input \"web/modules/custom/**/*.css\" --fix",
+ "lint-fix-js": "eslint docroot/modules/custom --ext .js --no-error-on-unmatched-pattern --fix",
+ "lint-fix-css": "stylelint --allow-empty-input \"docroot/modules/custom/**/*.css\" --fix",
- "lint-fix": "yarn run lint-fix-js && yarn run lint-fix-css"
+ "lint-fix": "yarn run lint-fix-js && yarn run lint-fix-css",
+ "test": "jest"
},
- "devDependencies": {
diff --git a/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/web/modules/custom/sw_demo/js/-sw_demo.test.js b/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/web/modules/custom/sw_demo/js/-sw_demo.test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/.vortex/installer/tests/Fixtures/handler_process/hosting_lagoon/.dockerignore b/.vortex/installer/tests/Fixtures/handler_process/hosting_lagoon/.dockerignore
index 2ad15805e..eb16fbd6f 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/hosting_lagoon/.dockerignore
+++ b/.vortex/installer/tests/Fixtures/handler_process/hosting_lagoon/.dockerignore
@@ -1,4 +1,4 @@
-@@ -51,3 +51,5 @@
+@@ -52,3 +52,5 @@
!scripts
!tests
!yarn.lock
diff --git a/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/.dockerignore b/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/.dockerignore
index 32eb6f635..4aab2eb79 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/.dockerignore
+++ b/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/.dockerignore
@@ -22,7 +22,7 @@
!drush
drush/contrib/
-@@ -51,3 +51,5 @@
+@@ -52,3 +52,5 @@
!scripts
!tests
!yarn.lock
diff --git a/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/AGENTS.md b/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/AGENTS.md
index 144ce178e..bf8f6765d 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/AGENTS.md
+++ b/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/AGENTS.md
@@ -1,4 +1,4 @@
-@@ -79,8 +79,8 @@
+@@ -82,8 +82,8 @@
## Key Directories
diff --git a/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/docroot/modules/custom/sw_demo/js/sw_demo.js b/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/docroot/modules/custom/sw_demo/js/sw_demo.js
index 3282266ce..886028723 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/docroot/modules/custom/sw_demo/js/sw_demo.js
+++ b/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/docroot/modules/custom/sw_demo/js/sw_demo.js
@@ -4,6 +4,8 @@
((Drupal) => {
Drupal.behaviors.ysDemo = {
+ storageKey: 'ys_counter_value',
+
attach(context) {
this.initCounterBlock(context);
},
@@ -17,7 +19,7 @@
initCounterBlock(context) {
const counterBlocks = context.querySelectorAll('[data-sw-demo-counter]');
- counterBlocks.forEach(function processBlock(block) {
+ counterBlocks.forEach((block) => {
// Skip if already processed.
if (block.classList.contains('sw-demo-counter-processed')) {
return;
@@ -29,39 +31,24 @@
const buttons = block.querySelectorAll('[data-counter-action]');
// Load saved value from localStorage.
- const storageKey = 'ys_counter_value';
- let currentValue = parseInt(localStorage.getItem(storageKey), 10) || 0;
+ let currentValue = this.getCounterValue();
valueElement.textContent = currentValue;
// Add event listeners to buttons.
- buttons.forEach(function processButton(button) {
- button.addEventListener('click', function handleClick() {
- const action = this.getAttribute('data-counter-action');
-
- switch (action) {
- case 'increment':
- currentValue += 1;
- break;
- case 'decrement':
- currentValue -= 1;
- break;
- case 'reset':
- currentValue = 0;
- break;
- default:
- // No action for unknown action types.
- break;
- }
+ buttons.forEach((button) => {
+ button.addEventListener('click', () => {
+ const action = button.getAttribute('data-counter-action');
+ currentValue = this.applyAction(currentValue, action);
- // Update display
+ // Update display.
valueElement.textContent = currentValue;
// Save to localStorage.
- localStorage.setItem(storageKey, currentValue.toString());
+ localStorage.setItem(this.storageKey, currentValue.toString());
// Add visual feedback.
valueElement.classList.add('updated');
- Drupal.behaviors.ysDemo.removeUpdatedClassAfterDelay(valueElement);
+ this.removeUpdatedClassAfterDelay(valueElement);
// Log action for debugging.
// eslint-disable-next-line no-console
@@ -71,6 +58,26 @@
});
},
+ /**
+ * Apply a counter action and return the new value.
+ *
+ * @param {number} value The current counter value.
+ * @param {string} action The action to apply.
+ * @return {number} The new counter value.
+ */
+ applyAction(value, action) {
+ switch (action) {
+ case 'increment':
+ return value + 1;
+ case 'decrement':
+ return value - 1;
+ case 'reset':
+ return 0;
+ default:
+ return value;
+ }
+ },
+
/**
* Remove updated class after a delay for visual feedback.
*
@@ -88,8 +95,7 @@
* @return {number} The current counter value, or 0 if not set.
*/
getCounterValue() {
- const storageKey = 'ys_counter_value';
- return parseInt(localStorage.getItem(storageKey), 10) || 0;
+ return parseInt(localStorage.getItem(this.storageKey), 10) || 0;
},
};
})(Drupal);
diff --git a/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/docroot/modules/custom/sw_demo/js/sw_demo.test.js b/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/docroot/modules/custom/sw_demo/js/sw_demo.test.js
new file mode 100644
index 000000000..a423fda42
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/docroot/modules/custom/sw_demo/js/sw_demo.test.js
@@ -0,0 +1,274 @@
+/**
+ * @jest-environment jsdom
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+describe('Drupal.behaviors.ysDemo', () => {
+ beforeEach(() => {
+ localStorage.clear();
+ global.Drupal = { behaviors: {} };
+
+ const filePath = path.resolve(__dirname, 'sw_demo.js');
+ const code = fs.readFileSync(filePath, 'utf8');
+ eval(code);
+ });
+
+ afterEach(() => {
+ delete global.Drupal;
+ });
+
+ function createCounterBlockHtml() {
+ return `
+
+ 0
+ +
+ -
+ Reset
+
+ `;
+ }
+
+ describe('storageKey', () => {
+ it('should have the expected storage key', () => {
+ expect(Drupal.behaviors.ysDemo.storageKey).toBe('ys_counter_value');
+ });
+ });
+
+ describe('getCounterValue', () => {
+ it('should return 0 when localStorage is empty', () => {
+ expect(Drupal.behaviors.ysDemo.getCounterValue()).toBe(0);
+ });
+
+ it('should return the stored value from localStorage', () => {
+ localStorage.setItem('ys_counter_value', '42');
+ expect(Drupal.behaviors.ysDemo.getCounterValue()).toBe(42);
+ });
+
+ it('should return 0 for non-numeric localStorage values', () => {
+ localStorage.setItem('ys_counter_value', 'invalid');
+ expect(Drupal.behaviors.ysDemo.getCounterValue()).toBe(0);
+ });
+
+ it('should return negative values correctly', () => {
+ localStorage.setItem('ys_counter_value', '-5');
+ expect(Drupal.behaviors.ysDemo.getCounterValue()).toBe(-5);
+ });
+ });
+
+ describe('applyAction', () => {
+ it('should increment the value', () => {
+ expect(Drupal.behaviors.ysDemo.applyAction(0, 'increment')).toBe(1);
+ expect(Drupal.behaviors.ysDemo.applyAction(10, 'increment')).toBe(11);
+ });
+
+ it('should decrement the value', () => {
+ expect(Drupal.behaviors.ysDemo.applyAction(0, 'decrement')).toBe(-1);
+ expect(Drupal.behaviors.ysDemo.applyAction(5, 'decrement')).toBe(4);
+ });
+
+ it('should reset the value to zero', () => {
+ expect(Drupal.behaviors.ysDemo.applyAction(42, 'reset')).toBe(0);
+ expect(Drupal.behaviors.ysDemo.applyAction(-5, 'reset')).toBe(0);
+ });
+
+ it('should return the same value for unknown actions', () => {
+ expect(Drupal.behaviors.ysDemo.applyAction(7, 'unknown')).toBe(7);
+ });
+ });
+
+ describe('removeUpdatedClassAfterDelay', () => {
+ beforeEach(() => {
+ jest.useFakeTimers();
+ });
+
+ afterEach(() => {
+ jest.useRealTimers();
+ });
+
+ it('should remove the updated class after 300ms', () => {
+ document.body.innerHTML = ' ';
+ const element = document.querySelector('span');
+
+ Drupal.behaviors.ysDemo.removeUpdatedClassAfterDelay(element);
+
+ expect(element.classList.contains('updated')).toBe(true);
+ jest.advanceTimersByTime(300);
+ expect(element.classList.contains('updated')).toBe(false);
+ });
+
+ it('should not remove the class before 300ms', () => {
+ document.body.innerHTML = ' ';
+ const element = document.querySelector('span');
+
+ Drupal.behaviors.ysDemo.removeUpdatedClassAfterDelay(element);
+
+ jest.advanceTimersByTime(299);
+ expect(element.classList.contains('updated')).toBe(true);
+ });
+ });
+
+ describe('initCounterBlock', () => {
+ it('should initialize the counter display with 0', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('0');
+ });
+
+ it('should initialize with saved localStorage value', () => {
+ localStorage.setItem('ys_counter_value', '15');
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('15');
+ });
+
+ it('should mark the block as processed', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const block = document.querySelector('[data-sw-demo-counter]');
+ expect(block.classList.contains('sw-demo-counter-processed')).toBe(true);
+ });
+
+ it('should not re-process already processed blocks', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ const block = document.querySelector('[data-sw-demo-counter]');
+ block.classList.add('sw-demo-counter-processed');
+
+ const value = document.querySelector('[data-counter-value]');
+ value.textContent = 'original';
+
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ expect(value.textContent).toBe('original');
+ });
+
+ it('should increment counter on button click', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('1');
+ });
+
+ it('should decrement counter on button click', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const decrementBtn = document.querySelector(
+ '[data-counter-action="decrement"]',
+ );
+ decrementBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('-1');
+ });
+
+ it('should reset counter on button click', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+ incrementBtn.click();
+ incrementBtn.click();
+
+ const resetBtn = document.querySelector('[data-counter-action="reset"]');
+ resetBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('0');
+ });
+
+ it('should save counter value to localStorage on click', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+ incrementBtn.click();
+
+ expect(localStorage.getItem('ys_counter_value')).toBe('2');
+ });
+
+ it('should add updated class on click for visual feedback', () => {
+ jest.useFakeTimers();
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.classList.contains('updated')).toBe(true);
+
+ jest.advanceTimersByTime(300);
+ expect(value.classList.contains('updated')).toBe(false);
+ jest.useRealTimers();
+ });
+
+ it('should handle multiple counter blocks', () => {
+ document.body.innerHTML = `
+
+ 0
+ +
+
+
+ 0
+ +
+
+ `;
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const blocks = document.querySelectorAll('[data-sw-demo-counter]');
+ expect(blocks[0].classList.contains('sw-demo-counter-processed')).toBe(
+ true,
+ );
+ expect(blocks[1].classList.contains('sw-demo-counter-processed')).toBe(
+ true,
+ );
+ });
+ });
+
+ describe('attach', () => {
+ it('should call initCounterBlock with the context', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ const spy = jest.spyOn(Drupal.behaviors.ysDemo, 'initCounterBlock');
+
+ Drupal.behaviors.ysDemo.attach(document);
+
+ expect(spy).toHaveBeenCalledWith(document);
+ spy.mockRestore();
+ });
+
+ it('should fully initialize when called as a behavior', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.attach(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('1');
+ expect(localStorage.getItem('ys_counter_value')).toBe('1');
+ });
+ });
+});
diff --git a/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/jest.config.js b/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/jest.config.js
new file mode 100644
index 000000000..bf6ae3613
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/jest.config.js
@@ -0,0 +1,28 @@
+@@ -4,7 +4,7 @@
+ // Discover js/ directories in custom modules, resolving symlinks to real
+ // paths. Jest resolves symlinks internally, so roots must use real paths
+ // for test files to be matched.
+-const dirs = ['web/modules/custom'];
++const dirs = ['docroot/modules/custom'];
+ const roots = [];
+
+ dirs.forEach((dir) => {
+@@ -20,13 +20,13 @@
+
+ module.exports = {
+ testEnvironment: 'jest-environment-jsdom',
+- roots: roots.length > 0 ? roots : [path.resolve('web/modules/custom')],
+- testRegex: 'web/modules/custom/.+\\.test\\.js$',
++ roots: roots.length > 0 ? roots : [path.resolve('docroot/modules/custom')],
++ testRegex: 'docroot/modules/custom/.+\\.test\\.js$',
+ testPathIgnorePatterns: ['/node_modules/', '/vendor/'],
+ modulePathIgnorePatterns: [
+- '/web/core/',
+- '/web/modules/contrib/',
+- '/web/themes/',
++ '/docroot/core/',
++ '/docroot/modules/contrib/',
++ '/docroot/themes/',
+ '/.vortex/',
+ ],
+ };
diff --git a/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/package.json b/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/package.json
index a31d3de73..40c40de58 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/package.json
+++ b/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/package.json
@@ -11,6 +11,6 @@
- "lint-fix-css": "stylelint --allow-empty-input \"web/modules/custom/**/*.css\" --fix",
+ "lint-fix-js": "eslint docroot/modules/custom --ext .js --no-error-on-unmatched-pattern --fix",
+ "lint-fix-css": "stylelint --allow-empty-input \"docroot/modules/custom/**/*.css\" --fix",
- "lint-fix": "yarn run lint-fix-js && yarn run lint-fix-css"
+ "lint-fix": "yarn run lint-fix-js && yarn run lint-fix-css",
+ "test": "jest"
},
- "devDependencies": {
diff --git a/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/web/modules/custom/sw_demo/js/-sw_demo.test.js b/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/web/modules/custom/sw_demo/js/-sw_demo.test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___lagoon/.dockerignore b/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___lagoon/.dockerignore
index 2ad15805e..eb16fbd6f 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___lagoon/.dockerignore
+++ b/.vortex/installer/tests/Fixtures/handler_process/hosting_project_name___lagoon/.dockerignore
@@ -1,4 +1,4 @@
-@@ -51,3 +51,5 @@
+@@ -52,3 +52,5 @@
!scripts
!tests
!yarn.lock
diff --git a/.vortex/installer/tests/Fixtures/handler_process/migration_disabled_circleci/.circleci/config.yml b/.vortex/installer/tests/Fixtures/handler_process/migration_disabled_circleci/.circleci/config.yml
index d6fa3a565..2845230c7 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/migration_disabled_circleci/.circleci/config.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/migration_disabled_circleci/.circleci/config.yml
@@ -325,6 +325,7 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \
if [ -n \"${PACKAGE_TOKEN:-}\" ]; then export COMPOSER_AUTH='{\"github-oauth\": {\"github.com\": \"${PACKAGE_TOKEN-}\"}}'; fi && \
COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist"
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
- run:
name: Provision site
@@ -336,6 +337,10 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./scripts/vortex/provision.sh
no_output_timeout: 30m
+ - run:
+ name: Test with Jest
+ command: docker compose exec -T cli bash -c "yarn test" || [ "${VORTEX_CI_JEST_IGNORE_FAILURE:-0}" -eq 1 ]
+
- run:
name: Test with PHPUnit
command: |
diff --git a/.vortex/installer/tests/Fixtures/handler_process/migration_disabled_lagoon/.dockerignore b/.vortex/installer/tests/Fixtures/handler_process/migration_disabled_lagoon/.dockerignore
index 2ad15805e..eb16fbd6f 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/migration_disabled_lagoon/.dockerignore
+++ b/.vortex/installer/tests/Fixtures/handler_process/migration_disabled_lagoon/.dockerignore
@@ -1,4 +1,4 @@
-@@ -51,3 +51,5 @@
+@@ -52,3 +52,5 @@
!scripts
!tests
!yarn.lock
diff --git a/.vortex/installer/tests/Fixtures/handler_process/migration_download_source_acquia/.github/workflows/build-test-deploy.yml b/.vortex/installer/tests/Fixtures/handler_process/migration_download_source_acquia/.github/workflows/build-test-deploy.yml
index ef6849b63..37d96c1a1 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/migration_download_source_acquia/.github/workflows/build-test-deploy.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/migration_download_source_acquia/.github/workflows/build-test-deploy.yml
@@ -8,7 +8,7 @@
- name: Export DB
run: |
if [ ! -f /tmp/download-db-success ]; then echo "==> Database download semaphore file is missing. DB export will not proceed."; exit 0; fi
-@@ -372,6 +375,10 @@
+@@ -373,6 +376,10 @@
if [ -f .data/db.sql ]; then
docker compose exec cli mkdir -p .data
docker compose cp -L .data/db.sql cli:/app/.data/db.sql
diff --git a/.vortex/installer/tests/Fixtures/handler_process/migration_download_source_ftp/.github/workflows/build-test-deploy.yml b/.vortex/installer/tests/Fixtures/handler_process/migration_download_source_ftp/.github/workflows/build-test-deploy.yml
index ef6849b63..37d96c1a1 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/migration_download_source_ftp/.github/workflows/build-test-deploy.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/migration_download_source_ftp/.github/workflows/build-test-deploy.yml
@@ -8,7 +8,7 @@
- name: Export DB
run: |
if [ ! -f /tmp/download-db-success ]; then echo "==> Database download semaphore file is missing. DB export will not proceed."; exit 0; fi
-@@ -372,6 +375,10 @@
+@@ -373,6 +376,10 @@
if [ -f .data/db.sql ]; then
docker compose exec cli mkdir -p .data
docker compose cp -L .data/db.sql cli:/app/.data/db.sql
diff --git a/.vortex/installer/tests/Fixtures/handler_process/migration_download_source_lagoon/.github/workflows/build-test-deploy.yml b/.vortex/installer/tests/Fixtures/handler_process/migration_download_source_lagoon/.github/workflows/build-test-deploy.yml
index ef6849b63..37d96c1a1 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/migration_download_source_lagoon/.github/workflows/build-test-deploy.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/migration_download_source_lagoon/.github/workflows/build-test-deploy.yml
@@ -8,7 +8,7 @@
- name: Export DB
run: |
if [ ! -f /tmp/download-db-success ]; then echo "==> Database download semaphore file is missing. DB export will not proceed."; exit 0; fi
-@@ -372,6 +375,10 @@
+@@ -373,6 +376,10 @@
if [ -f .data/db.sql ]; then
docker compose exec cli mkdir -p .data
docker compose cp -L .data/db.sql cli:/app/.data/db.sql
diff --git a/.vortex/installer/tests/Fixtures/handler_process/migration_download_source_s3/.github/workflows/build-test-deploy.yml b/.vortex/installer/tests/Fixtures/handler_process/migration_download_source_s3/.github/workflows/build-test-deploy.yml
index ef6849b63..37d96c1a1 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/migration_download_source_s3/.github/workflows/build-test-deploy.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/migration_download_source_s3/.github/workflows/build-test-deploy.yml
@@ -8,7 +8,7 @@
- name: Export DB
run: |
if [ ! -f /tmp/download-db-success ]; then echo "==> Database download semaphore file is missing. DB export will not proceed."; exit 0; fi
-@@ -372,6 +375,10 @@
+@@ -373,6 +376,10 @@
if [ -f .data/db.sql ]; then
docker compose exec cli mkdir -p .data
docker compose cp -L .data/db.sql cli:/app/.data/db.sql
diff --git a/.vortex/installer/tests/Fixtures/handler_process/migration_download_source_url/.github/workflows/build-test-deploy.yml b/.vortex/installer/tests/Fixtures/handler_process/migration_download_source_url/.github/workflows/build-test-deploy.yml
index ef6849b63..37d96c1a1 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/migration_download_source_url/.github/workflows/build-test-deploy.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/migration_download_source_url/.github/workflows/build-test-deploy.yml
@@ -8,7 +8,7 @@
- name: Export DB
run: |
if [ ! -f /tmp/download-db-success ]; then echo "==> Database download semaphore file is missing. DB export will not proceed."; exit 0; fi
-@@ -372,6 +375,10 @@
+@@ -373,6 +376,10 @@
if [ -f .data/db.sql ]; then
docker compose exec cli mkdir -p .data
docker compose cp -L .data/db.sql cli:/app/.data/db.sql
diff --git a/.vortex/installer/tests/Fixtures/handler_process/migration_enabled/.github/workflows/build-test-deploy.yml b/.vortex/installer/tests/Fixtures/handler_process/migration_enabled/.github/workflows/build-test-deploy.yml
index ef6849b63..37d96c1a1 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/migration_enabled/.github/workflows/build-test-deploy.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/migration_enabled/.github/workflows/build-test-deploy.yml
@@ -8,7 +8,7 @@
- name: Export DB
run: |
if [ ! -f /tmp/download-db-success ]; then echo "==> Database download semaphore file is missing. DB export will not proceed."; exit 0; fi
-@@ -372,6 +375,10 @@
+@@ -373,6 +376,10 @@
if [ -f .data/db.sql ]; then
docker compose exec cli mkdir -p .data
docker compose cp -L .data/db.sql cli:/app/.data/db.sql
diff --git a/.vortex/installer/tests/Fixtures/handler_process/migration_enabled_circleci/.circleci/config.yml b/.vortex/installer/tests/Fixtures/handler_process/migration_enabled_circleci/.circleci/config.yml
index 93f6a46bf..f4111360d 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/migration_enabled_circleci/.circleci/config.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/migration_enabled_circleci/.circleci/config.yml
@@ -330,6 +330,7 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \
if [ -n \"${PACKAGE_TOKEN:-}\" ]; then export COMPOSER_AUTH='{\"github-oauth\": {\"github.com\": \"${PACKAGE_TOKEN-}\"}}'; fi && \
COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist"
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
- run:
name: Provision site
@@ -345,6 +346,10 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./scripts/vortex/provision.sh
no_output_timeout: 30m
+ - run:
+ name: Test with Jest
+ command: docker compose exec -T cli bash -c "yarn test" || [ "${VORTEX_CI_JEST_IGNORE_FAILURE:-0}" -eq 1 ]
+
- run:
name: Test with PHPUnit
command: |
diff --git a/.vortex/installer/tests/Fixtures/handler_process/migration_enabled_lagoon/.dockerignore b/.vortex/installer/tests/Fixtures/handler_process/migration_enabled_lagoon/.dockerignore
index 2ad15805e..eb16fbd6f 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/migration_enabled_lagoon/.dockerignore
+++ b/.vortex/installer/tests/Fixtures/handler_process/migration_enabled_lagoon/.dockerignore
@@ -1,4 +1,4 @@
-@@ -51,3 +51,5 @@
+@@ -52,3 +52,5 @@
!scripts
!tests
!yarn.lock
diff --git a/.vortex/installer/tests/Fixtures/handler_process/migration_enabled_lagoon/.github/workflows/build-test-deploy.yml b/.vortex/installer/tests/Fixtures/handler_process/migration_enabled_lagoon/.github/workflows/build-test-deploy.yml
index ef6849b63..37d96c1a1 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/migration_enabled_lagoon/.github/workflows/build-test-deploy.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/migration_enabled_lagoon/.github/workflows/build-test-deploy.yml
@@ -8,7 +8,7 @@
- name: Export DB
run: |
if [ ! -f /tmp/download-db-success ]; then echo "==> Database download semaphore file is missing. DB export will not proceed."; exit 0; fi
-@@ -372,6 +375,10 @@
+@@ -373,6 +376,10 @@
if [ -f .data/db.sql ]; then
docker compose exec cli mkdir -p .data
docker compose cp -L .data/db.sql cli:/app/.data/db.sql
diff --git a/.vortex/installer/tests/Fixtures/handler_process/names/web/modules/custom/sw_demo/js/-sw_demo.test.js b/.vortex/installer/tests/Fixtures/handler_process/names/web/modules/custom/sw_demo/js/-sw_demo.test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/.vortex/installer/tests/Fixtures/handler_process/names/web/modules/custom/the_force_demo/js/the_force_demo.js b/.vortex/installer/tests/Fixtures/handler_process/names/web/modules/custom/the_force_demo/js/the_force_demo.js
index e8ec713c4..e17060cb4 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/names/web/modules/custom/the_force_demo/js/the_force_demo.js
+++ b/.vortex/installer/tests/Fixtures/handler_process/names/web/modules/custom/the_force_demo/js/the_force_demo.js
@@ -4,6 +4,8 @@
((Drupal) => {
Drupal.behaviors.ysDemo = {
+ storageKey: 'ys_counter_value',
+
attach(context) {
this.initCounterBlock(context);
},
@@ -17,7 +19,7 @@
initCounterBlock(context) {
const counterBlocks = context.querySelectorAll('[data-the-force-demo-counter]');
- counterBlocks.forEach(function processBlock(block) {
+ counterBlocks.forEach((block) => {
// Skip if already processed.
if (block.classList.contains('the-force-demo-counter-processed')) {
return;
@@ -29,39 +31,24 @@
const buttons = block.querySelectorAll('[data-counter-action]');
// Load saved value from localStorage.
- const storageKey = 'ys_counter_value';
- let currentValue = parseInt(localStorage.getItem(storageKey), 10) || 0;
+ let currentValue = this.getCounterValue();
valueElement.textContent = currentValue;
// Add event listeners to buttons.
- buttons.forEach(function processButton(button) {
- button.addEventListener('click', function handleClick() {
- const action = this.getAttribute('data-counter-action');
-
- switch (action) {
- case 'increment':
- currentValue += 1;
- break;
- case 'decrement':
- currentValue -= 1;
- break;
- case 'reset':
- currentValue = 0;
- break;
- default:
- // No action for unknown action types.
- break;
- }
+ buttons.forEach((button) => {
+ button.addEventListener('click', () => {
+ const action = button.getAttribute('data-counter-action');
+ currentValue = this.applyAction(currentValue, action);
- // Update display
+ // Update display.
valueElement.textContent = currentValue;
// Save to localStorage.
- localStorage.setItem(storageKey, currentValue.toString());
+ localStorage.setItem(this.storageKey, currentValue.toString());
// Add visual feedback.
valueElement.classList.add('updated');
- Drupal.behaviors.ysDemo.removeUpdatedClassAfterDelay(valueElement);
+ this.removeUpdatedClassAfterDelay(valueElement);
// Log action for debugging.
// eslint-disable-next-line no-console
@@ -71,6 +58,26 @@
});
},
+ /**
+ * Apply a counter action and return the new value.
+ *
+ * @param {number} value The current counter value.
+ * @param {string} action The action to apply.
+ * @return {number} The new counter value.
+ */
+ applyAction(value, action) {
+ switch (action) {
+ case 'increment':
+ return value + 1;
+ case 'decrement':
+ return value - 1;
+ case 'reset':
+ return 0;
+ default:
+ return value;
+ }
+ },
+
/**
* Remove updated class after a delay for visual feedback.
*
@@ -88,8 +95,7 @@
* @return {number} The current counter value, or 0 if not set.
*/
getCounterValue() {
- const storageKey = 'ys_counter_value';
- return parseInt(localStorage.getItem(storageKey), 10) || 0;
+ return parseInt(localStorage.getItem(this.storageKey), 10) || 0;
},
};
})(Drupal);
diff --git a/.vortex/installer/tests/Fixtures/handler_process/names/web/modules/custom/the_force_demo/js/the_force_demo.test.js b/.vortex/installer/tests/Fixtures/handler_process/names/web/modules/custom/the_force_demo/js/the_force_demo.test.js
new file mode 100644
index 000000000..5f3df0f6a
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/names/web/modules/custom/the_force_demo/js/the_force_demo.test.js
@@ -0,0 +1,274 @@
+/**
+ * @jest-environment jsdom
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+describe('Drupal.behaviors.ysDemo', () => {
+ beforeEach(() => {
+ localStorage.clear();
+ global.Drupal = { behaviors: {} };
+
+ const filePath = path.resolve(__dirname, 'the_force_demo.js');
+ const code = fs.readFileSync(filePath, 'utf8');
+ eval(code);
+ });
+
+ afterEach(() => {
+ delete global.Drupal;
+ });
+
+ function createCounterBlockHtml() {
+ return `
+
+ 0
+ +
+ -
+ Reset
+
+ `;
+ }
+
+ describe('storageKey', () => {
+ it('should have the expected storage key', () => {
+ expect(Drupal.behaviors.ysDemo.storageKey).toBe('ys_counter_value');
+ });
+ });
+
+ describe('getCounterValue', () => {
+ it('should return 0 when localStorage is empty', () => {
+ expect(Drupal.behaviors.ysDemo.getCounterValue()).toBe(0);
+ });
+
+ it('should return the stored value from localStorage', () => {
+ localStorage.setItem('ys_counter_value', '42');
+ expect(Drupal.behaviors.ysDemo.getCounterValue()).toBe(42);
+ });
+
+ it('should return 0 for non-numeric localStorage values', () => {
+ localStorage.setItem('ys_counter_value', 'invalid');
+ expect(Drupal.behaviors.ysDemo.getCounterValue()).toBe(0);
+ });
+
+ it('should return negative values correctly', () => {
+ localStorage.setItem('ys_counter_value', '-5');
+ expect(Drupal.behaviors.ysDemo.getCounterValue()).toBe(-5);
+ });
+ });
+
+ describe('applyAction', () => {
+ it('should increment the value', () => {
+ expect(Drupal.behaviors.ysDemo.applyAction(0, 'increment')).toBe(1);
+ expect(Drupal.behaviors.ysDemo.applyAction(10, 'increment')).toBe(11);
+ });
+
+ it('should decrement the value', () => {
+ expect(Drupal.behaviors.ysDemo.applyAction(0, 'decrement')).toBe(-1);
+ expect(Drupal.behaviors.ysDemo.applyAction(5, 'decrement')).toBe(4);
+ });
+
+ it('should reset the value to zero', () => {
+ expect(Drupal.behaviors.ysDemo.applyAction(42, 'reset')).toBe(0);
+ expect(Drupal.behaviors.ysDemo.applyAction(-5, 'reset')).toBe(0);
+ });
+
+ it('should return the same value for unknown actions', () => {
+ expect(Drupal.behaviors.ysDemo.applyAction(7, 'unknown')).toBe(7);
+ });
+ });
+
+ describe('removeUpdatedClassAfterDelay', () => {
+ beforeEach(() => {
+ jest.useFakeTimers();
+ });
+
+ afterEach(() => {
+ jest.useRealTimers();
+ });
+
+ it('should remove the updated class after 300ms', () => {
+ document.body.innerHTML = ' ';
+ const element = document.querySelector('span');
+
+ Drupal.behaviors.ysDemo.removeUpdatedClassAfterDelay(element);
+
+ expect(element.classList.contains('updated')).toBe(true);
+ jest.advanceTimersByTime(300);
+ expect(element.classList.contains('updated')).toBe(false);
+ });
+
+ it('should not remove the class before 300ms', () => {
+ document.body.innerHTML = ' ';
+ const element = document.querySelector('span');
+
+ Drupal.behaviors.ysDemo.removeUpdatedClassAfterDelay(element);
+
+ jest.advanceTimersByTime(299);
+ expect(element.classList.contains('updated')).toBe(true);
+ });
+ });
+
+ describe('initCounterBlock', () => {
+ it('should initialize the counter display with 0', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('0');
+ });
+
+ it('should initialize with saved localStorage value', () => {
+ localStorage.setItem('ys_counter_value', '15');
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('15');
+ });
+
+ it('should mark the block as processed', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const block = document.querySelector('[data-the-force-demo-counter]');
+ expect(block.classList.contains('the-force-demo-counter-processed')).toBe(true);
+ });
+
+ it('should not re-process already processed blocks', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ const block = document.querySelector('[data-the-force-demo-counter]');
+ block.classList.add('the-force-demo-counter-processed');
+
+ const value = document.querySelector('[data-counter-value]');
+ value.textContent = 'original';
+
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ expect(value.textContent).toBe('original');
+ });
+
+ it('should increment counter on button click', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('1');
+ });
+
+ it('should decrement counter on button click', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const decrementBtn = document.querySelector(
+ '[data-counter-action="decrement"]',
+ );
+ decrementBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('-1');
+ });
+
+ it('should reset counter on button click', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+ incrementBtn.click();
+ incrementBtn.click();
+
+ const resetBtn = document.querySelector('[data-counter-action="reset"]');
+ resetBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('0');
+ });
+
+ it('should save counter value to localStorage on click', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+ incrementBtn.click();
+
+ expect(localStorage.getItem('ys_counter_value')).toBe('2');
+ });
+
+ it('should add updated class on click for visual feedback', () => {
+ jest.useFakeTimers();
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.classList.contains('updated')).toBe(true);
+
+ jest.advanceTimersByTime(300);
+ expect(value.classList.contains('updated')).toBe(false);
+ jest.useRealTimers();
+ });
+
+ it('should handle multiple counter blocks', () => {
+ document.body.innerHTML = `
+
+ 0
+ +
+
+
+ 0
+ +
+
+ `;
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const blocks = document.querySelectorAll('[data-the-force-demo-counter]');
+ expect(blocks[0].classList.contains('the-force-demo-counter-processed')).toBe(
+ true,
+ );
+ expect(blocks[1].classList.contains('the-force-demo-counter-processed')).toBe(
+ true,
+ );
+ });
+ });
+
+ describe('attach', () => {
+ it('should call initCounterBlock with the context', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ const spy = jest.spyOn(Drupal.behaviors.ysDemo, 'initCounterBlock');
+
+ Drupal.behaviors.ysDemo.attach(document);
+
+ expect(spy).toHaveBeenCalledWith(document);
+ spy.mockRestore();
+ });
+
+ it('should fully initialize when called as a behavior', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.attach(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('1');
+ expect(localStorage.getItem('ys_counter_value')).toBe('1');
+ });
+ });
+});
diff --git a/.vortex/installer/tests/Fixtures/handler_process/provision_database_lagoon/.dockerignore b/.vortex/installer/tests/Fixtures/handler_process/provision_database_lagoon/.dockerignore
index 2ad15805e..eb16fbd6f 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/provision_database_lagoon/.dockerignore
+++ b/.vortex/installer/tests/Fixtures/handler_process/provision_database_lagoon/.dockerignore
@@ -1,4 +1,4 @@
-@@ -51,3 +51,5 @@
+@@ -52,3 +52,5 @@
!scripts
!tests
!yarn.lock
diff --git a/.vortex/installer/tests/Fixtures/handler_process/provision_profile/.github/workflows/build-test-deploy.yml b/.vortex/installer/tests/Fixtures/handler_process/provision_profile/.github/workflows/build-test-deploy.yml
index e734f63c1..64a248e90 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/provision_profile/.github/workflows/build-test-deploy.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/provision_profile/.github/workflows/build-test-deploy.yml
@@ -175,7 +175,7 @@
- name: Login to container registry
run: ./scripts/vortex/login-container-registry.sh
-@@ -495,7 +355,6 @@
+@@ -501,7 +361,6 @@
deploy:
runs-on: ubuntu-latest
needs: [build, lint]
diff --git a/.vortex/installer/tests/Fixtures/handler_process/timezone_circleci/.circleci/config.yml b/.vortex/installer/tests/Fixtures/handler_process/timezone_circleci/.circleci/config.yml
index d6fa3a565..2845230c7 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/timezone_circleci/.circleci/config.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/timezone_circleci/.circleci/config.yml
@@ -325,6 +325,7 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \
if [ -n \"${PACKAGE_TOKEN:-}\" ]; then export COMPOSER_AUTH='{\"github-oauth\": {\"github.com\": \"${PACKAGE_TOKEN-}\"}}'; fi && \
COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist"
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
- run:
name: Provision site
@@ -336,6 +337,10 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./scripts/vortex/provision.sh
no_output_timeout: 30m
+ - run:
+ name: Test with Jest
+ command: docker compose exec -T cli bash -c "yarn test" || [ "${VORTEX_CI_JEST_IGNORE_FAILURE:-0}" -eq 1 ]
+
- run:
name: Test with PHPUnit
command: |
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_lint/.dockerignore b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_lint/.dockerignore
index f0f4da85f..87d269cd9 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_lint/.dockerignore
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_lint/.dockerignore
@@ -1,5 +1,5 @@
-@@ -42,12 +42,8 @@
- !package-lock.json
+@@ -43,12 +43,8 @@
+ !jest.config.js
!package.json
!patches
-!phpcs.xml
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_lint_circleci/.circleci/config.yml b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_lint_circleci/.circleci/config.yml
index 61e18627c..3246c1ca1 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_lint_circleci/.circleci/config.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_lint_circleci/.circleci/config.yml
@@ -309,6 +309,7 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \
if [ -n \"${PACKAGE_TOKEN:-}\" ]; then export COMPOSER_AUTH='{\"github-oauth\": {\"github.com\": \"${PACKAGE_TOKEN-}\"}}'; fi && \
COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist"
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
- run:
name: Provision site
@@ -320,6 +321,10 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./scripts/vortex/provision.sh
no_output_timeout: 30m
+ - run:
+ name: Test with Jest
+ command: docker compose exec -T cli bash -c "yarn test" || [ "${VORTEX_CI_JEST_IGNORE_FAILURE:-0}" -eq 1 ]
+
- run:
name: Test with PHPUnit
command: |
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_lint_circleci/.dockerignore b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_lint_circleci/.dockerignore
index f0f4da85f..87d269cd9 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_lint_circleci/.dockerignore
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_lint_circleci/.dockerignore
@@ -1,5 +1,5 @@
-@@ -42,12 +42,8 @@
- !package-lock.json
+@@ -43,12 +43,8 @@
+ !jest.config.js
!package.json
!patches
-!phpcs.xml
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests/.ahoy.yml b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests/.ahoy.yml
index 1b83b43e5..a3a7ea510 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests/.ahoy.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests/.ahoy.yml
@@ -18,11 +18,10 @@
lint-fix:
usage: Fix lint issues of back-end and front-end code.
cmd: |
-@@ -218,32 +212,6 @@
- ahoy cli vendor/bin/twig-cs-fixer lint --fix
+@@ -219,34 +213,9 @@
ahoy cli "yarn run lint-fix"
ahoy cli "yarn run --cwd=\${WEBROOT}/themes/custom/\${DRUPAL_THEME} lint-fix"
--
+
- test:
- usage: Run all PHPUnit tests.
- cmd: ahoy cli vendor/bin/phpunit "$@"
@@ -40,9 +39,12 @@
- cmd: ahoy cli vendor/bin/phpunit --testsuite=functional "$@"
-
- test-functional-javascript:
-- aliases: [test-js]
- usage: Run PHPUnit functional JavaScript tests.
- cmd: ahoy cli vendor/bin/phpunit --testsuite=functional-javascript "$@"
+-
+ test-js:
+ usage: Run Jest JavaScript unit tests.
+ cmd: ahoy cli "yarn test"
-
- test-bdd:
- usage: Run BDD tests.
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests/.dockerignore b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests/.dockerignore
index 7caffb41e..f1daa63b0 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests/.dockerignore
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests/.dockerignore
@@ -7,9 +7,9 @@
!composer.lock
-!gherkinlint.json
!package-lock.json
+ !jest.config.js
!package.json
- !patches
-@@ -45,7 +43,6 @@
+@@ -46,7 +44,6 @@
!phpcs.xml
!phpmd.xml
!phpstan.neon
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests/.github/workflows/build-test-deploy.yml b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests/.github/workflows/build-test-deploy.yml
index 45fba9470..8900db508 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests/.github/workflows/build-test-deploy.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests/.github/workflows/build-test-deploy.yml
@@ -9,11 +9,10 @@
- name: Lint module code with NodeJS linters
run: docker compose exec -T cli bash -c "yarn run lint"
continue-on-error: ${{ vars.VORTEX_CI_NODEJS_LINT_IGNORE_FAILURE == '1' }}
-@@ -374,88 +370,6 @@
- docker compose cp -L .data/db.sql cli:/app/.data/db.sql
- fi
- docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./scripts/vortex/provision.sh
-- timeout-minutes: 30
+@@ -381,88 +377,6 @@
+ if: ${{ matrix.instance == 0 || strategy.job-total == 1 }}
+ run: docker compose exec -T cli bash -c "yarn test"
+ continue-on-error: ${{ vars.VORTEX_CI_JEST_IGNORE_FAILURE == '1' }}
-
- - name: Test with PHPUnit
- if: ${{ matrix.instance == 0 || strategy.job-total == 1 }}
@@ -95,6 +94,7 @@
- env:
- VORTEX_CI_BEHAT_PROFILE: ${{ vars.VORTEX_CI_BEHAT_PROFILE }}
- continue-on-error: ${{ vars.VORTEX_CI_BEHAT_IGNORE_FAILURE == '1' }}
- timeout-minutes: 30
+- timeout-minutes: 30
- name: Process test logs and artifacts
+ if: always()
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests/AGENTS.md b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests/AGENTS.md
index 4a1a607fa..39a88b8ea 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests/AGENTS.md
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests/AGENTS.md
@@ -1,4 +1,4 @@
-@@ -52,16 +52,6 @@
+@@ -52,19 +52,9 @@
ahoy lint # Check code style
ahoy lint-fix # Auto-fix code style
@@ -9,6 +9,9 @@
-ahoy test-functional # Run PHPUnit Functional tests
-ahoy test -- --filter=TestClassName # Run specific PHPUnit test class
-
+ # Jest testing
+ ahoy test-js # Run Jest JavaScript unit tests
+
-# Behat testing
-ahoy test-bdd # Run Behat tests
-ahoy test-bdd -- --tags=@tagname # Run Behat tests with specific tag
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests_circleci/.ahoy.yml b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests_circleci/.ahoy.yml
index 1b83b43e5..a3a7ea510 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests_circleci/.ahoy.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests_circleci/.ahoy.yml
@@ -18,11 +18,10 @@
lint-fix:
usage: Fix lint issues of back-end and front-end code.
cmd: |
-@@ -218,32 +212,6 @@
- ahoy cli vendor/bin/twig-cs-fixer lint --fix
+@@ -219,34 +213,9 @@
ahoy cli "yarn run lint-fix"
ahoy cli "yarn run --cwd=\${WEBROOT}/themes/custom/\${DRUPAL_THEME} lint-fix"
--
+
- test:
- usage: Run all PHPUnit tests.
- cmd: ahoy cli vendor/bin/phpunit "$@"
@@ -40,9 +39,12 @@
- cmd: ahoy cli vendor/bin/phpunit --testsuite=functional "$@"
-
- test-functional-javascript:
-- aliases: [test-js]
- usage: Run PHPUnit functional JavaScript tests.
- cmd: ahoy cli vendor/bin/phpunit --testsuite=functional-javascript "$@"
+-
+ test-js:
+ usage: Run Jest JavaScript unit tests.
+ cmd: ahoy cli "yarn test"
-
- test-bdd:
- usage: Run BDD tests.
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests_circleci/.circleci/config.yml b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests_circleci/.circleci/config.yml
index dc61bdb31..0c6fe1dc3 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests_circleci/.circleci/config.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests_circleci/.circleci/config.yml
@@ -321,6 +321,7 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \
if [ -n \"${PACKAGE_TOKEN:-}\" ]; then export COMPOSER_AUTH='{\"github-oauth\": {\"github.com\": \"${PACKAGE_TOKEN-}\"}}'; fi && \
COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist"
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
- run:
name: Provision site
@@ -332,6 +333,10 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./scripts/vortex/provision.sh
no_output_timeout: 30m
+ - run:
+ name: Test with Jest
+ command: docker compose exec -T cli bash -c "yarn test" || [ "${VORTEX_CI_JEST_IGNORE_FAILURE:-0}" -eq 1 ]
+
- run:
name: Process test logs and artifacts
command: |
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests_circleci/.dockerignore b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests_circleci/.dockerignore
index 7caffb41e..f1daa63b0 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests_circleci/.dockerignore
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests_circleci/.dockerignore
@@ -7,9 +7,9 @@
!composer.lock
-!gherkinlint.json
!package-lock.json
+ !jest.config.js
!package.json
- !patches
-@@ -45,7 +43,6 @@
+@@ -46,7 +44,6 @@
!phpcs.xml
!phpmd.xml
!phpstan.neon
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests_circleci/AGENTS.md b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests_circleci/AGENTS.md
index 4a1a607fa..39a88b8ea 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests_circleci/AGENTS.md
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests_circleci/AGENTS.md
@@ -1,4 +1,4 @@
-@@ -52,16 +52,6 @@
+@@ -52,19 +52,9 @@
ahoy lint # Check code style
ahoy lint-fix # Auto-fix code style
@@ -9,6 +9,9 @@
-ahoy test-functional # Run PHPUnit Functional tests
-ahoy test -- --filter=TestClassName # Run specific PHPUnit test class
-
+ # Jest testing
+ ahoy test-js # Run Jest JavaScript unit tests
+
-# Behat testing
-ahoy test-bdd # Run Behat tests
-ahoy test-bdd -- --tags=@tagname # Run Behat tests with specific tag
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint/-jest.config.js b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint/-jest.config.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint/.ahoy.yml b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint/.ahoy.yml
index 77491026e..ff8f10d38 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint/.ahoy.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint/.ahoy.yml
@@ -1,12 +1,4 @@
-@@ -156,7 +156,6 @@
- fei:
- usage: Install front-end assets.
- cmd: |
-- ahoy cli "yarn install --frozen-lockfile"
- ahoy cli "yarn --cwd=${WEBROOT}/themes/custom/${DRUPAL_THEME} install --frozen-lockfile"
-
- fe:
-@@ -192,7 +191,6 @@
+@@ -192,7 +192,6 @@
usage: Lint front-end code.
cmd: |
ahoy cli vendor/bin/twig-cs-fixer lint
@@ -14,7 +6,7 @@
ahoy cli "yarn run --cwd=\${WEBROOT}/themes/custom/\${DRUPAL_THEME} lint"
lint-tests:
-@@ -216,7 +214,6 @@
+@@ -216,7 +215,6 @@
usage: Fix lint issues of front-end code.
cmd: |
ahoy cli vendor/bin/twig-cs-fixer lint --fix
@@ -22,3 +14,14 @@
ahoy cli "yarn run --cwd=\${WEBROOT}/themes/custom/\${DRUPAL_THEME} lint-fix"
test:
+@@ -238,10 +236,6 @@
+ test-functional-javascript:
+ usage: Run PHPUnit functional JavaScript tests.
+ cmd: ahoy cli vendor/bin/phpunit --testsuite=functional-javascript "$@"
+-
+- test-js:
+- usage: Run Jest JavaScript unit tests.
+- cmd: ahoy cli "yarn test"
+
+ test-bdd:
+ usage: Run BDD tests.
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint/.github/workflows/build-test-deploy.yml b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint/.github/workflows/build-test-deploy.yml
index abf9480cf..cd6c04bf2 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint/.github/workflows/build-test-deploy.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint/.github/workflows/build-test-deploy.yml
@@ -6,14 +6,34 @@
- name: Audit Composer packages
run: docker compose exec -T cli composer audit
-@@ -157,10 +156,6 @@
- - name: Lint code with Gherkin Lint
+@@ -158,10 +157,6 @@
run: docker compose exec -T cli vendor/bin/gherkinlint lint tests/behat/features
continue-on-error: ${{ vars.VORTEX_CI_GHERKIN_LINT_IGNORE_FAILURE == '1' }}
--
+
- - name: Lint module code with NodeJS linters
- run: docker compose exec -T cli bash -c "yarn run lint"
- continue-on-error: ${{ vars.VORTEX_CI_NODEJS_LINT_IGNORE_FAILURE == '1' }}
-
+-
- name: Lint theme code with NodeJS linters
if: ${{ vars.VORTEX_FRONTEND_BUILD_SKIP != '1' }}
+ run: docker compose exec -T cli bash -c "yarn --cwd=\${WEBROOT}/themes/custom/\${DRUPAL_THEME} run lint"
+@@ -366,7 +361,6 @@
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \
+ if [ -n \"${PACKAGE_TOKEN:-}\" ]; then export COMPOSER_AUTH='{\"github-oauth\": {\"github.com\": \"${PACKAGE_TOKEN-}\"}}'; fi && \
+ COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist"
+- docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
+
+ - name: Provision site
+ run: |
+@@ -376,11 +370,6 @@
+ fi
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./scripts/vortex/provision.sh
+ timeout-minutes: 30
+-
+- - name: Test with Jest
+- if: ${{ matrix.instance == 0 || strategy.job-total == 1 }}
+- run: docker compose exec -T cli bash -c "yarn test"
+- continue-on-error: ${{ vars.VORTEX_CI_JEST_IGNORE_FAILURE == '1' }}
+
+ - name: Test with PHPUnit
+ if: ${{ matrix.instance == 0 || strategy.job-total == 1 }}
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint/AGENTS.md b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint/AGENTS.md
new file mode 100644
index 000000000..3bd67d24d
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint/AGENTS.md
@@ -0,0 +1,10 @@
+@@ -59,9 +59,6 @@
+ ahoy test-functional # Run PHPUnit Functional tests
+ ahoy test -- --filter=TestClassName # Run specific PHPUnit test class
+
+-# Jest testing
+-ahoy test-js # Run Jest JavaScript unit tests
+-
+ # Behat testing
+ ahoy test-bdd # Run Behat tests
+ ahoy test-bdd -- --tags=@tagname # Run Behat tests with specific tag
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint/web/modules/custom/sw_demo/js/-sw_demo.test.js b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint/web/modules/custom/sw_demo/js/-sw_demo.test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_circleci/-jest.config.js b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_circleci/-jest.config.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_circleci/.ahoy.yml b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_circleci/.ahoy.yml
index 77491026e..ff8f10d38 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_circleci/.ahoy.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_circleci/.ahoy.yml
@@ -1,12 +1,4 @@
-@@ -156,7 +156,6 @@
- fei:
- usage: Install front-end assets.
- cmd: |
-- ahoy cli "yarn install --frozen-lockfile"
- ahoy cli "yarn --cwd=${WEBROOT}/themes/custom/${DRUPAL_THEME} install --frozen-lockfile"
-
- fe:
-@@ -192,7 +191,6 @@
+@@ -192,7 +192,6 @@
usage: Lint front-end code.
cmd: |
ahoy cli vendor/bin/twig-cs-fixer lint
@@ -14,7 +6,7 @@
ahoy cli "yarn run --cwd=\${WEBROOT}/themes/custom/\${DRUPAL_THEME} lint"
lint-tests:
-@@ -216,7 +214,6 @@
+@@ -216,7 +215,6 @@
usage: Fix lint issues of front-end code.
cmd: |
ahoy cli vendor/bin/twig-cs-fixer lint --fix
@@ -22,3 +14,14 @@
ahoy cli "yarn run --cwd=\${WEBROOT}/themes/custom/\${DRUPAL_THEME} lint-fix"
test:
+@@ -238,10 +236,6 @@
+ test-functional-javascript:
+ usage: Run PHPUnit functional JavaScript tests.
+ cmd: ahoy cli vendor/bin/phpunit --testsuite=functional-javascript "$@"
+-
+- test-js:
+- usage: Run Jest JavaScript unit tests.
+- cmd: ahoy cli "yarn test"
+
+ test-bdd:
+ usage: Run BDD tests.
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_circleci/AGENTS.md b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_circleci/AGENTS.md
new file mode 100644
index 000000000..3bd67d24d
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_circleci/AGENTS.md
@@ -0,0 +1,10 @@
+@@ -59,9 +59,6 @@
+ ahoy test-functional # Run PHPUnit Functional tests
+ ahoy test -- --filter=TestClassName # Run specific PHPUnit test class
+
+-# Jest testing
+-ahoy test-js # Run Jest JavaScript unit tests
+-
+ # Behat testing
+ ahoy test-bdd # Run Behat tests
+ ahoy test-bdd -- --tags=@tagname # Run Behat tests with specific tag
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_circleci/web/modules/custom/sw_demo/js/-sw_demo.test.js b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_circleci/web/modules/custom/sw_demo/js/-sw_demo.test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme/-jest.config.js b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme/-jest.config.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme/.ahoy.yml b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme/.ahoy.yml
index 552725dd2..6b6e52b20 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme/.ahoy.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme/.ahoy.yml
@@ -52,3 +52,14 @@
test:
usage: Run all PHPUnit tests.
+@@ -238,10 +212,6 @@
+ test-functional-javascript:
+ usage: Run PHPUnit functional JavaScript tests.
+ cmd: ahoy cli vendor/bin/phpunit --testsuite=functional-javascript "$@"
+-
+- test-js:
+- usage: Run Jest JavaScript unit tests.
+- cmd: ahoy cli "yarn test"
+
+ test-bdd:
+ usage: Run BDD tests.
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme/.github/workflows/build-test-deploy.yml b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme/.github/workflows/build-test-deploy.yml
index 7629caf10..fd0b226e7 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme/.github/workflows/build-test-deploy.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme/.github/workflows/build-test-deploy.yml
@@ -6,11 +6,10 @@
- name: Audit Composer packages
run: docker compose exec -T cli composer audit
-@@ -157,15 +156,6 @@
- - name: Lint code with Gherkin Lint
+@@ -158,15 +157,6 @@
run: docker compose exec -T cli vendor/bin/gherkinlint lint tests/behat/features
continue-on-error: ${{ vars.VORTEX_CI_GHERKIN_LINT_IGNORE_FAILURE == '1' }}
--
+
- - name: Lint module code with NodeJS linters
- run: docker compose exec -T cli bash -c "yarn run lint"
- continue-on-error: ${{ vars.VORTEX_CI_NODEJS_LINT_IGNORE_FAILURE == '1' }}
@@ -19,6 +18,27 @@
- if: ${{ vars.VORTEX_FRONTEND_BUILD_SKIP != '1' }}
- run: docker compose exec -T cli bash -c "yarn --cwd=\${WEBROOT}/themes/custom/\${DRUPAL_THEME} run lint"
- continue-on-error: ${{ vars.VORTEX_CI_NODEJS_LINT_IGNORE_FAILURE == '1' }}
-
+-
database:
runs-on: ubuntu-latest
+ if: ${{ !inputs.deploy_target && (github.event_name == 'push' || !startsWith(github.head_ref, 'project/')) }}
+@@ -366,7 +356,6 @@
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \
+ if [ -n \"${PACKAGE_TOKEN:-}\" ]; then export COMPOSER_AUTH='{\"github-oauth\": {\"github.com\": \"${PACKAGE_TOKEN-}\"}}'; fi && \
+ COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist"
+- docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
+
+ - name: Provision site
+ run: |
+@@ -376,11 +365,6 @@
+ fi
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./scripts/vortex/provision.sh
+ timeout-minutes: 30
+-
+- - name: Test with Jest
+- if: ${{ matrix.instance == 0 || strategy.job-total == 1 }}
+- run: docker compose exec -T cli bash -c "yarn test"
+- continue-on-error: ${{ vars.VORTEX_CI_JEST_IGNORE_FAILURE == '1' }}
+
+ - name: Test with PHPUnit
+ if: ${{ matrix.instance == 0 || strategy.job-total == 1 }}
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme/AGENTS.md b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme/AGENTS.md
new file mode 100644
index 000000000..3bd67d24d
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme/AGENTS.md
@@ -0,0 +1,10 @@
+@@ -59,9 +59,6 @@
+ ahoy test-functional # Run PHPUnit Functional tests
+ ahoy test -- --filter=TestClassName # Run specific PHPUnit test class
+
+-# Jest testing
+-ahoy test-js # Run Jest JavaScript unit tests
+-
+ # Behat testing
+ ahoy test-bdd # Run Behat tests
+ ahoy test-bdd -- --tags=@tagname # Run Behat tests with specific tag
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme/web/modules/custom/sw_demo/js/-sw_demo.test.js b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme/web/modules/custom/sw_demo/js/-sw_demo.test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme_circleci/-jest.config.js b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme_circleci/-jest.config.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme_circleci/.ahoy.yml b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme_circleci/.ahoy.yml
index 552725dd2..6b6e52b20 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme_circleci/.ahoy.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme_circleci/.ahoy.yml
@@ -52,3 +52,14 @@
test:
usage: Run all PHPUnit tests.
+@@ -238,10 +212,6 @@
+ test-functional-javascript:
+ usage: Run PHPUnit functional JavaScript tests.
+ cmd: ahoy cli vendor/bin/phpunit --testsuite=functional-javascript "$@"
+-
+- test-js:
+- usage: Run Jest JavaScript unit tests.
+- cmd: ahoy cli "yarn test"
+
+ test-bdd:
+ usage: Run BDD tests.
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme_circleci/AGENTS.md b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme_circleci/AGENTS.md
new file mode 100644
index 000000000..3bd67d24d
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme_circleci/AGENTS.md
@@ -0,0 +1,10 @@
+@@ -59,9 +59,6 @@
+ ahoy test-functional # Run PHPUnit Functional tests
+ ahoy test -- --filter=TestClassName # Run specific PHPUnit test class
+
+-# Jest testing
+-ahoy test-js # Run Jest JavaScript unit tests
+-
+ # Behat testing
+ ahoy test-bdd # Run Behat tests
+ ahoy test-bdd -- --tags=@tagname # Run Behat tests with specific tag
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme_circleci/web/modules/custom/sw_demo/js/-sw_demo.test.js b/.vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme_circleci/web/modules/custom/sw_demo/js/-sw_demo.test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat/.ahoy.yml b/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat/.ahoy.yml
index 95a936442..eef81b19f 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat/.ahoy.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat/.ahoy.yml
@@ -15,10 +15,10 @@
lint-fix:
usage: Fix lint issues of back-end and front-end code.
cmd: |
-@@ -239,11 +236,6 @@
- aliases: [test-js]
- usage: Run PHPUnit functional JavaScript tests.
- cmd: ahoy cli vendor/bin/phpunit --testsuite=functional-javascript "$@"
+@@ -242,11 +239,6 @@
+ test-js:
+ usage: Run Jest JavaScript unit tests.
+ cmd: ahoy cli "yarn test"
-
- test-bdd:
- usage: Run BDD tests.
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat/.dockerignore b/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat/.dockerignore
index 9fcb635dc..a2fab9006 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat/.dockerignore
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat/.dockerignore
@@ -7,5 +7,5 @@
!composer.lock
-!gherkinlint.json
!package-lock.json
+ !jest.config.js
!package.json
- !patches
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat/.github/workflows/build-test-deploy.yml b/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat/.github/workflows/build-test-deploy.yml
index caba2ca13..b1df6e3d3 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat/.github/workflows/build-test-deploy.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat/.github/workflows/build-test-deploy.yml
@@ -9,7 +9,7 @@
- name: Lint module code with NodeJS linters
run: docker compose exec -T cli bash -c "yarn run lint"
continue-on-error: ${{ vars.VORTEX_CI_NODEJS_LINT_IGNORE_FAILURE == '1' }}
-@@ -445,18 +441,6 @@
+@@ -451,18 +447,6 @@
fi
env:
VORTEX_CI_CODE_COVERAGE_THRESHOLD: ${{ vars.VORTEX_CI_CODE_COVERAGE_THRESHOLD || '90' }}
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat/AGENTS.md b/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat/AGENTS.md
index 0006a1bc5..79ac0ed1a 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat/AGENTS.md
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat/AGENTS.md
@@ -1,6 +1,6 @@
-@@ -59,9 +59,6 @@
- ahoy test-functional # Run PHPUnit Functional tests
- ahoy test -- --filter=TestClassName # Run specific PHPUnit test class
+@@ -62,9 +62,6 @@
+ # Jest testing
+ ahoy test-js # Run Jest JavaScript unit tests
-# Behat testing
-ahoy test-bdd # Run Behat tests
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat_circleci/.ahoy.yml b/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat_circleci/.ahoy.yml
index 95a936442..eef81b19f 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat_circleci/.ahoy.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat_circleci/.ahoy.yml
@@ -15,10 +15,10 @@
lint-fix:
usage: Fix lint issues of back-end and front-end code.
cmd: |
-@@ -239,11 +236,6 @@
- aliases: [test-js]
- usage: Run PHPUnit functional JavaScript tests.
- cmd: ahoy cli vendor/bin/phpunit --testsuite=functional-javascript "$@"
+@@ -242,11 +239,6 @@
+ test-js:
+ usage: Run Jest JavaScript unit tests.
+ cmd: ahoy cli "yarn test"
-
- test-bdd:
- usage: Run BDD tests.
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat_circleci/.circleci/config.yml b/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat_circleci/.circleci/config.yml
index c15652b01..3fc5671a8 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat_circleci/.circleci/config.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat_circleci/.circleci/config.yml
@@ -321,6 +321,7 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \
if [ -n \"${PACKAGE_TOKEN:-}\" ]; then export COMPOSER_AUTH='{\"github-oauth\": {\"github.com\": \"${PACKAGE_TOKEN-}\"}}'; fi && \
COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist"
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
- run:
name: Provision site
@@ -332,6 +333,10 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./scripts/vortex/provision.sh
no_output_timeout: 30m
+ - run:
+ name: Test with Jest
+ command: docker compose exec -T cli bash -c "yarn test" || [ "${VORTEX_CI_JEST_IGNORE_FAILURE:-0}" -eq 1 ]
+
- run:
name: Test with PHPUnit
command: |
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat_circleci/.dockerignore b/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat_circleci/.dockerignore
index 9fcb635dc..a2fab9006 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat_circleci/.dockerignore
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat_circleci/.dockerignore
@@ -7,5 +7,5 @@
!composer.lock
-!gherkinlint.json
!package-lock.json
+ !jest.config.js
!package.json
- !patches
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat_circleci/AGENTS.md b/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat_circleci/AGENTS.md
index 0006a1bc5..79ac0ed1a 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat_circleci/AGENTS.md
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_behat_circleci/AGENTS.md
@@ -1,6 +1,6 @@
-@@ -59,9 +59,6 @@
- ahoy test-functional # Run PHPUnit Functional tests
- ahoy test -- --filter=TestClassName # Run specific PHPUnit test class
+@@ -62,9 +62,6 @@
+ # Jest testing
+ ahoy test-js # Run Jest JavaScript unit tests
-# Behat testing
-ahoy test-bdd # Run Behat tests
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_eslint/package.json b/.vortex/installer/tests/Fixtures/handler_process/tools_no_eslint/package.json
index 752712570..718afcd48 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_no_eslint/package.json
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_eslint/package.json
@@ -1,4 +1,4 @@
-@@ -8,24 +8,12 @@
+@@ -8,27 +8,15 @@
"node": ">= __VERSION__"
},
"scripts": {
@@ -8,11 +8,14 @@
- "lint-fix-js": "eslint web/modules/custom --ext .js --no-error-on-unmatched-pattern --fix",
+ "lint": "yarn run lint-css",
"lint-fix-css": "stylelint --allow-empty-input \"web/modules/custom/**/*.css\" --fix",
-- "lint-fix": "yarn run lint-fix-js && yarn run lint-fix-css"
-+ "lint-fix": "yarn run lint-fix-css"
+- "lint-fix": "yarn run lint-fix-js && yarn run lint-fix-css",
++ "lint-fix": "yarn run lint-fix-css",
+ "test": "jest"
},
"devDependencies": {
- "@homer0/prettier-plugin-jsdoc": "__VERSION__",
+ "jest": "__VERSION__",
+ "jest-environment-jsdom": "__VERSION__",
- "eslint": "__VERSION__",
- "eslint-config-airbnb-base": "__VERSION__",
- "eslint-config-prettier": "__VERSION__",
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_circleci/.circleci/config.yml b/.vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_circleci/.circleci/config.yml
index 1d1757698..a8e17f5b9 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_circleci/.circleci/config.yml
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_circleci/.circleci/config.yml
@@ -320,6 +320,7 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \
if [ -n \"${PACKAGE_TOKEN:-}\" ]; then export COMPOSER_AUTH='{\"github-oauth\": {\"github.com\": \"${PACKAGE_TOKEN-}\"}}'; fi && \
COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist"
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
- run:
name: Provision site
@@ -331,6 +332,10 @@ jobs:
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./scripts/vortex/provision.sh
no_output_timeout: 30m
+ - run:
+ name: Test with Jest
+ command: docker compose exec -T cli bash -c "yarn test" || [ "${VORTEX_CI_JEST_IGNORE_FAILURE:-0}" -eq 1 ]
+
- run:
name: Test with PHPUnit
command: |
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_circleci/package.json b/.vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_circleci/package.json
index 752712570..718afcd48 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_circleci/package.json
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_circleci/package.json
@@ -1,4 +1,4 @@
-@@ -8,24 +8,12 @@
+@@ -8,27 +8,15 @@
"node": ">= __VERSION__"
},
"scripts": {
@@ -8,11 +8,14 @@
- "lint-fix-js": "eslint web/modules/custom --ext .js --no-error-on-unmatched-pattern --fix",
+ "lint": "yarn run lint-css",
"lint-fix-css": "stylelint --allow-empty-input \"web/modules/custom/**/*.css\" --fix",
-- "lint-fix": "yarn run lint-fix-js && yarn run lint-fix-css"
-+ "lint-fix": "yarn run lint-fix-css"
+- "lint-fix": "yarn run lint-fix-js && yarn run lint-fix-css",
++ "lint-fix": "yarn run lint-fix-css",
+ "test": "jest"
},
"devDependencies": {
- "@homer0/prettier-plugin-jsdoc": "__VERSION__",
+ "jest": "__VERSION__",
+ "jest-environment-jsdom": "__VERSION__",
- "eslint": "__VERSION__",
- "eslint-config-airbnb-base": "__VERSION__",
- "eslint-config-prettier": "__VERSION__",
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_no_theme/package.json b/.vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_no_theme/package.json
index 752712570..718afcd48 100644
--- a/.vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_no_theme/package.json
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_no_theme/package.json
@@ -1,4 +1,4 @@
-@@ -8,24 +8,12 @@
+@@ -8,27 +8,15 @@
"node": ">= __VERSION__"
},
"scripts": {
@@ -8,11 +8,14 @@
- "lint-fix-js": "eslint web/modules/custom --ext .js --no-error-on-unmatched-pattern --fix",
+ "lint": "yarn run lint-css",
"lint-fix-css": "stylelint --allow-empty-input \"web/modules/custom/**/*.css\" --fix",
-- "lint-fix": "yarn run lint-fix-js && yarn run lint-fix-css"
-+ "lint-fix": "yarn run lint-fix-css"
+- "lint-fix": "yarn run lint-fix-js && yarn run lint-fix-css",
++ "lint-fix": "yarn run lint-fix-css",
+ "test": "jest"
},
"devDependencies": {
- "@homer0/prettier-plugin-jsdoc": "__VERSION__",
+ "jest": "__VERSION__",
+ "jest-environment-jsdom": "__VERSION__",
- "eslint": "__VERSION__",
- "eslint-config-airbnb-base": "__VERSION__",
- "eslint-config-prettier": "__VERSION__",
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest/-jest.config.js b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest/-jest.config.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest/.ahoy.yml b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest/.ahoy.yml
new file mode 100644
index 000000000..f31e3bcab
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest/.ahoy.yml
@@ -0,0 +1,11 @@
+@@ -239,10 +239,6 @@
+ usage: Run PHPUnit functional JavaScript tests.
+ cmd: ahoy cli vendor/bin/phpunit --testsuite=functional-javascript "$@"
+
+- test-js:
+- usage: Run Jest JavaScript unit tests.
+- cmd: ahoy cli "yarn test"
+-
+ test-bdd:
+ usage: Run BDD tests.
+ aliases: [test-behat]
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest/.github/workflows/build-test-deploy.yml b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest/.github/workflows/build-test-deploy.yml
new file mode 100644
index 000000000..b77d72e2d
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest/.github/workflows/build-test-deploy.yml
@@ -0,0 +1,20 @@
+@@ -366,7 +366,6 @@
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \
+ if [ -n \"${PACKAGE_TOKEN:-}\" ]; then export COMPOSER_AUTH='{\"github-oauth\": {\"github.com\": \"${PACKAGE_TOKEN-}\"}}'; fi && \
+ COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist"
+- docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
+
+ - name: Provision site
+ run: |
+@@ -376,11 +375,6 @@
+ fi
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./scripts/vortex/provision.sh
+ timeout-minutes: 30
+-
+- - name: Test with Jest
+- if: ${{ matrix.instance == 0 || strategy.job-total == 1 }}
+- run: docker compose exec -T cli bash -c "yarn test"
+- continue-on-error: ${{ vars.VORTEX_CI_JEST_IGNORE_FAILURE == '1' }}
+
+ - name: Test with PHPUnit
+ if: ${{ matrix.instance == 0 || strategy.job-total == 1 }}
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest/AGENTS.md b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest/AGENTS.md
new file mode 100644
index 000000000..3bd67d24d
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest/AGENTS.md
@@ -0,0 +1,10 @@
+@@ -59,9 +59,6 @@
+ ahoy test-functional # Run PHPUnit Functional tests
+ ahoy test -- --filter=TestClassName # Run specific PHPUnit test class
+
+-# Jest testing
+-ahoy test-js # Run Jest JavaScript unit tests
+-
+ # Behat testing
+ ahoy test-bdd # Run Behat tests
+ ahoy test-bdd -- --tags=@tagname # Run Behat tests with specific tag
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest/package.json b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest/package.json
new file mode 100644
index 000000000..cc07aebbd
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest/package.json
@@ -0,0 +1,15 @@
+@@ -13,13 +13,10 @@
+ "lint": "yarn run lint-js && yarn run lint-css",
+ "lint-fix-js": "eslint web/modules/custom --ext .js --no-error-on-unmatched-pattern --fix",
+ "lint-fix-css": "stylelint --allow-empty-input \"web/modules/custom/**/*.css\" --fix",
+- "lint-fix": "yarn run lint-fix-js && yarn run lint-fix-css",
+- "test": "jest"
++ "lint-fix": "yarn run lint-fix-js && yarn run lint-fix-css"
+ },
+ "devDependencies": {
+ "@homer0/prettier-plugin-jsdoc": "__VERSION__",
+- "jest": "__VERSION__",
+- "jest-environment-jsdom": "__VERSION__",
+ "eslint": "__VERSION__",
+ "eslint-config-airbnb-base": "__VERSION__",
+ "eslint-config-prettier": "__VERSION__",
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest/web/modules/custom/sw_demo/js/-sw_demo.test.js b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest/web/modules/custom/sw_demo/js/-sw_demo.test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/-jest.config.js b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/-jest.config.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/.ahoy.yml b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/.ahoy.yml
new file mode 100644
index 000000000..f31e3bcab
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/.ahoy.yml
@@ -0,0 +1,11 @@
+@@ -239,10 +239,6 @@
+ usage: Run PHPUnit functional JavaScript tests.
+ cmd: ahoy cli vendor/bin/phpunit --testsuite=functional-javascript "$@"
+
+- test-js:
+- usage: Run Jest JavaScript unit tests.
+- cmd: ahoy cli "yarn test"
+-
+ test-bdd:
+ usage: Run BDD tests.
+ aliases: [test-behat]
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/.circleci/config.yml b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/.circleci/config.yml
new file mode 100644
index 000000000..d6fa3a565
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/.circleci/config.yml
@@ -0,0 +1,542 @@
+# CircleCI configuration file for building, testing, and deploying.
+#
+# This configuration file uses the "docker" executor to run the Docker stack.
+#
+# A "runner" container, created from a specified container image, is used to
+# checkout source code and run commands defined in this file. Application Docker
+# containers defined in `docker-compose.yml` run on a *remote* Docker server
+# controlled by CircleCI.
+# The "runner" container uses Docker client to control the remote Docker server.
+version: '2.1'
+
+aliases:
+ # SSH key fingerprint to download the database.
+ # Replace this key fingerprint with your own and remove this comment.
+ - &db_ssh_fingerprint "SHA256:6d+U5QubT0eAWz+4N2wt+WM2qx6o4cvyvQ6xILETJ84"
+
+ # SSH key fingerprint to deploy code.
+ # Replace this key fingerprint with your own and remove this comment.
+ - &deploy_ssh_fingerprint "SHA256:6d+U5QubT0eAWz+4N2wt+WM2qx6o4cvyvQ6xILETJ84"
+
+ # Schedule to run nightly database build (to cache the database for the next day).
+ - &nightly_db_schedule "0 18 * * *"
+
+ # Shared runner container configuration applied to each job.
+ - &runner_config
+ working_directory: &working_directory ~/project
+ environment:
+ VORTEX_DOWNLOAD_DB_SSH_FINGERPRINT: *db_ssh_fingerprint
+ VORTEX_DEPLOY_SSH_FINGERPRINT: *deploy_ssh_fingerprint
+ docker:
+ # Using the 'runner' container where each job will be executed.
+ # This container has all the necessary tools to run a dockerized environment.
+ # https://github.com/drevops/ci-runner
+ # https://hub.docker.com/repository/docker/drevops/ci-runner/tags
+ - image: drevops/ci-runner:__VERSION__
+ auth:
+ username: ${VORTEX_CONTAINER_REGISTRY_USER}
+ password: ${VORTEX_CONTAINER_REGISTRY_PASS}
+ environment:
+ # Set runner timezone via UI to ensure that executed operations use correct timestamps.
+ # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
+ TZ: UTC
+ # Set runner terminal capabilities.
+ TERM: xterm-256color
+ # Disable strict host key checking for SSH connections.
+ VORTEX_SSH_DISABLE_STRICT_HOST_KEY_CHECKING: "1"
+ # Remove all SSH keys from the runner container.
+ VORTEX_SSH_REMOVE_ALL_KEYS: "1"
+ # How often to refresh the cache of the DB dump. Refer to `date` command.
+ VORTEX_CI_DB_CACHE_TIMESTAMP: +%Y%m%d
+ # Use previous database caches on this branch as a fallback if the above cache
+ # does not match (for example, the cache is available only from the previous
+ # day). If "no" is set, the cache will be rebuilt from scratch.
+ VORTEX_CI_DB_CACHE_FALLBACK: "yes"
+ # Which branch to use as a source of DB caches.
+ VORTEX_CI_DB_CACHE_BRANCH: "develop"
+ # Directory to store test results.
+ VORTEX_CI_TEST_RESULTS: &test_results /tmp/tests
+ # Directory to store test artifacts.
+ VORTEX_CI_ARTIFACTS: &artifacts /tmp/artifacts
+ # Directory to use for artifact deployments.
+ VORTEX_DEPLOY_ARTIFACT_SRC: /tmp/workspace/code
+ # Source code location for artifact deployments.
+ VORTEX_DEPLOY_ARTIFACT_ROOT: *working_directory
+ # Report file location for artifact deployments.
+ VORTEX_DEPLOY_ARTIFACT_LOG: /tmp/artifacts/deployment_log.txt
+ # Check only minimal stack requirements.
+ VORTEX_DOCTOR_CHECK_MINIMAL: 1
+ # CI runner resource class.
+ # https://circleci.com/docs/2.0/configuration-reference/#resource_class
+ # Change to 'large' for faster builds.
+ resource_class: medium
+
+ - &step_setup_remote_docker
+ setup_remote_docker:
+ # Docker Layer Caching allows to significantly speed up builds by caching
+ # images built during previous runs.
+ # https://circleci.com/docs/2.0/docker-layer-caching/
+ docker_layer_caching: false
+ version: default
+
+ - &step_process_codebase_for_ci
+ run:
+ name: Process codebase to run in CI
+ command: |
+ find . -name "docker-compose.yml" -print0 | xargs -0 -I {} sh -c "sed -i -e ''/###/d'' {} && sed -i -e ''s/##//'' {}"
+ mkdir -p /tmp/workspace/code
+
+ - &load_variables_from_dotenv
+ run:
+ name: Load environment variables from .env file
+ # Load variables from .env file, respecting existing values, and make them available for the next steps.
+ command: t=$(mktemp) && export -p >"${t}" && set -a && . ./.env && set +a && . "${t}" && export -p >> "$BASH_ENV"
+
+################################################################################
+# JOBS
+################################################################################
+
+jobs:
+
+ # Lint job runs in parallel with database and build jobs.
+ lint:
+ <<: *runner_config
+ steps:
+ - checkout
+ - *step_process_codebase_for_ci
+ - *load_variables_from_dotenv
+
+ - run:
+ name: Validate Composer configuration
+ command: composer validate --strict || [ "${VORTEX_CI_COMPOSER_VALIDATE_IGNORE_FAILURE:-0}" -eq 1 ]
+
+ - *step_setup_remote_docker
+
+ - run:
+ name: Login to container registry
+ command: ./scripts/vortex/login-container-registry.sh
+
+ - run:
+ name: Lint Dockerfiles with Hadolint
+ command: |
+ for file in $(find .docker -name 'Dockerfile' -o -name '*.dockerfile'); do
+ echo "Linting ${file}" && cat "${file}" | docker run --rm -i hadolint/hadolint || [ "${VORTEX_CI_HADOLINT_IGNORE_FAILURE:-0}" -eq 1 ]
+ done
+
+ - run:
+ name: Lint Docker Compose files with DCLint
+ command: docker run --rm -v "${PWD}":/app zavoloklom/dclint:__VERSION__ . || [ "${VORTEX_CI_DCLINT_IGNORE_FAILURE:-0}" -eq 1 ]
+
+ - run:
+ name: Build stack
+ command: docker compose up --no-deps --detach cli
+
+ - run:
+ name: Install development dependencies
+ command: |
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \
+ if [ -n \"${PACKAGE_TOKEN:-}\" ]; then export COMPOSER_AUTH='{\"github-oauth\": {\"github.com\": \"${PACKAGE_TOKEN-}\"}}'; fi && \
+ COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist"
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
+
+ - run:
+ name: Audit Composer packages
+ command: docker compose exec -T cli composer audit || [ "${VORTEX_CI_COMPOSER_AUDIT_IGNORE_FAILURE:-0}" -eq 1 ]
+
+ - run:
+ name: Validate Composer configuration is normalized
+ command: docker compose exec -T cli composer normalize --dry-run || [ "${VORTEX_CI_COMPOSER_NORMALIZE_IGNORE_FAILURE:-0}" -eq 1 ]
+
+ - run:
+ name: Lint code with PHPCS
+ command: docker compose exec -T cli vendor/bin/phpcs || [ "${VORTEX_CI_PHPCS_IGNORE_FAILURE:-0}" -eq 1 ]
+
+ - run:
+ name: Lint code with PHPStan
+ command: docker compose exec -T cli vendor/bin/phpstan || [ "${VORTEX_CI_PHPSTAN_IGNORE_FAILURE:-0}" -eq 1 ]
+
+ - run:
+ name: Lint code with Rector
+ command: docker compose exec -T cli vendor/bin/rector --dry-run || [ "${VORTEX_CI_RECTOR_IGNORE_FAILURE:-0}" -eq 1 ]
+
+ - run:
+ name: Lint code with PHPMD
+ command: docker compose exec -T cli vendor/bin/phpmd . text phpmd.xml || [ "${VORTEX_CI_PHPMD_IGNORE_FAILURE:-0}" -eq 1 ]
+
+ - run:
+ name: Lint code with Twig CS Fixer
+ command: docker compose exec -T cli vendor/bin/twig-cs-fixer || [ "${VORTEX_CI_TWIG_CS_FIXER_IGNORE_FAILURE:-0}" -eq 1 ]
+
+ - run:
+ name: Lint code with Gherkin Lint
+ command: docker compose exec -T cli vendor/bin/gherkinlint lint tests/behat/features || [ "${VORTEX_CI_GHERKIN_LINT_IGNORE_FAILURE:-0}" -eq 1 ]
+
+ - run:
+ name: Lint module code with NodeJS linters
+ command: docker compose exec -T cli bash -c "yarn run lint" || [ "${VORTEX_CI_NODEJS_LINT_IGNORE_FAILURE:-0}" -eq 1 ]
+
+ - run:
+ name: Lint theme code with NodeJS linters
+ command: |
+ [ "${VORTEX_FRONTEND_BUILD_SKIP:-0}" -eq 1 ] && exit 0
+ docker compose exec -T cli bash -c "yarn --cwd=\${WEBROOT}/themes/custom/\${DRUPAL_THEME} run lint" || [ "${VORTEX_CI_NODEJS_LINT_IGNORE_FAILURE:-0}" -eq 1 ]
+
+ # Database handling is a first step of the build.
+ # - $VORTEX_CI_DB_CACHE_TIMESTAMP is used to determine if a fresh DB dump
+ # should be downloaded for the current build. Usually, a daily database dump
+ # is sufficient for development activities.
+ # - $VORTEX_CI_DB_CACHE_FALLBACK is used if the cache did not match $VORTEX_CI_DB_CACHE_TIMESTAMP.
+ # This allows to rely on the cache from the previous days within the same branch.
+ database: &job-database
+ <<: *runner_config
+ steps:
+ - attach_workspace:
+ at: /tmp/workspace
+
+ - add_ssh_keys:
+ fingerprints:
+ - *db_ssh_fingerprint
+
+ - checkout
+ - *step_process_codebase_for_ci
+ - *load_variables_from_dotenv
+ - *step_setup_remote_docker
+
+ - run:
+ name: Create cache keys for database caching as files
+ command: |
+ echo "${VORTEX_CI_DB_CACHE_BRANCH}" | tee /tmp/db_cache_branch
+ echo "${VORTEX_CI_DB_CACHE_FALLBACK/no/${CIRCLE_BUILD_NUM}}" | tee /tmp/db_cache_fallback
+ date "${VORTEX_CI_DB_CACHE_TIMESTAMP}" | tee /tmp/db_cache_timestamp
+ echo "yes" | tee /tmp/db_cache_fallback_yes
+
+ - restore_cache:
+ keys:
+ # Restore DB cache based on the cache strategy set by the cache keys below.
+ # https://circleci.com/docs/2.0/caching/#restoring-cache
+ # Change 'v1' to 'v2', 'v3' etc., commit and push to force cache reset.
+ # Lookup cache based on the default branch and a timestamp. Allows
+ # to use cache from the very first build on the day (sanitized database dump, for example).
+ - __VERSION__{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback" }}-{{ checksum "/tmp/db_cache_timestamp" }}
+ # Fallback to caching by default branch name only. Allows to use
+ # cache from the branch build on the previous day.
+ - __VERSION__{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback" }}-
+
+ - run:
+ name: Download DB
+ command: VORTEX_DOWNLOAD_DB_SEMAPHORE=/tmp/download-db-success ./scripts/vortex/download-db.sh
+ no_output_timeout: 30m
+
+ # Execute commands after database download script finished: if the
+ # DB dump was downloaded - build the site (to ensure that the DB dump
+ # is valid) and export the DB using selected method (to support
+ # "file-to-image" or "image-to-file" conversions).
+ # Note that configuration changes and the DB updates are not applied, so
+ # the database will be cached in the same state as downloaded.
+ - run:
+ name: Export DB after download
+ command: |
+ [ ! -f /tmp/download-db-success ] && echo "==> Database download semaphore file is missing. DB export will not proceed." && exit 0
+ ./scripts/vortex/login-container-registry.sh
+ docker compose up --detach && sleep 15
+ docker compose exec cli mkdir -p .data && docker compose cp -L .data/db.sql cli:/app/.data/db.sql || true
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "VORTEX_PROVISION_POST_OPERATIONS_SKIP=1 ./scripts/vortex/provision.sh"
+ grep -q ^VORTEX_DB_IMAGE .env && rm .data/db.sql || true
+ ./scripts/vortex/export-db.sh db.sql
+ no_output_timeout: 30m
+
+ - save_cache:
+ # Save cache per default branch and the timestamp.
+ # The cache will not be saved if it already exists.
+ # Note that the cache fallback flag is enabled for this case in order
+ # to save cache even if the fallback is not used when restoring it.
+ key: __VERSION__{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback_yes" }}-{{ checksum "/tmp/db_cache_timestamp" }}
+ paths:
+ - /root/project/.data
+
+ # Nightly database job. Same as above, but with additional variables set.
+ # Triggered by the "nightly-db" schedule configured in CircleCI UI.
+ database-nightly:
+ <<: *job-database
+ environment:
+ VORTEX_DOWNLOAD_DB_SSH_FINGERPRINT: *db_ssh_fingerprint
+ VORTEX_DEPLOY_SSH_FINGERPRINT: *deploy_ssh_fingerprint
+ # Enforce fresh DB build (do not rely on fallback caches).
+ VORTEX_CI_DB_CACHE_FALLBACK: 'no'
+ # Always use fresh base image for the database (if database-in-image storage is used).
+ VORTEX_DB_IMAGE_BASE: drevops/mariadb-drupal-data:__VERSION__
+ # Deploy container image (if database-in-image storage is used).
+ VORTEX_EXPORT_DB_CONTAINER_REGISTRY_DEPLOY_PROCEED: 1
+ # Do not build the Drupal front-end.
+ VORTEX_FRONTEND_BUILD_SKIP: 1
+
+ # Build and test is a second step of the build. The testing is performed
+ # within the same job to save time on provisioning during the job.
+ build: &job_build
+ <<: *runner_config
+ parallelism: 2
+ steps:
+ - attach_workspace:
+ at: /tmp/workspace
+
+ - checkout
+ - *step_process_codebase_for_ci
+ - *load_variables_from_dotenv
+
+ - run:
+ name: Validate Composer configuration
+ command: composer validate --strict || [ "${VORTEX_CI_COMPOSER_VALIDATE_IGNORE_FAILURE:-0}" -eq 1 ]
+
+ - run:
+ name: Set cache keys for database caching
+ command: |
+ echo "${VORTEX_CI_DB_CACHE_BRANCH}" | tee /tmp/db_cache_branch
+ echo "yes" | tee /tmp/db_cache_fallback_yes
+ echo "$(date ${VORTEX_CI_DB_CACHE_TIMESTAMP})" | tee /tmp/db_cache_timestamp
+
+ - restore_cache:
+ keys:
+ # Use cached artifacts from previous builds of this branch.
+ # https://circleci.com/docs/2.0/caching/#restoring-cache
+ - __VERSION__{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback_yes" }}-{{ checksum "/tmp/db_cache_timestamp" }}
+ - __VERSION__{{ checksum "/tmp/db_cache_branch" }}-{{ checksum "/tmp/db_cache_fallback_yes" }}-
+
+ - *step_setup_remote_docker
+
+ - run:
+ name: Login to container registry
+ command: ./scripts/vortex/login-container-registry.sh
+
+ - run:
+ name: Build stack
+ command: docker compose up --detach && docker builder prune --all --force
+
+ - run:
+ name: Export built codebase
+ command: |
+ echo "${VORTEX_DEPLOY_TYPES:-}" | grep -vq "artifact" && exit 0 || true
+ mkdir -p "/tmp/workspace/code"
+ docker compose cp -L cli:"/app/." "/tmp/workspace/code"
+ du -sh "/tmp/workspace/code"
+
+ - run:
+ name: Install development dependencies
+ command: |
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c " \
+ if [ -n \"${PACKAGE_TOKEN:-}\" ]; then export COMPOSER_AUTH='{\"github-oauth\": {\"github.com\": \"${PACKAGE_TOKEN-}\"}}'; fi && \
+ COMPOSER_MEMORY_LIMIT=-1 composer --ansi install --prefer-dist"
+
+ - run:
+ name: Provision site
+ command: |
+ if [ -f .data/db.sql ]; then
+ docker compose exec cli mkdir -p .data
+ docker compose cp -L .data/db.sql cli:/app/.data/db.sql
+ fi
+ docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli ./scripts/vortex/provision.sh
+ no_output_timeout: 30m
+
+ - run:
+ name: Test with PHPUnit
+ command: |
+ [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0
+ docker compose exec -T cli vendor/bin/phpunit || [ "${VORTEX_CI_PHPUNIT_IGNORE_FAILURE:-0}" -eq 1 ]
+
+ - run:
+ name: Process PHPUnit logs and coverage
+ command: |
+ [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0
+ mkdir -p "${VORTEX_CI_ARTIFACTS}"
+ if docker compose ps --services --filter "status=running" | grep -q cli && docker compose exec cli test -d /app/.logs; then
+ docker compose cp cli:/app/.logs/. "${VORTEX_CI_ARTIFACTS}/"
+ fi
+
+ - run:
+ name: Extract code coverage
+ command: |
+ [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0
+ RATE=$(grep -om1 'line-rate="[0-9.]*"' /tmp/artifacts/coverage/phpunit/cobertura.xml | tr -cd '0-9.')
+ PERCENT=$(awk "BEGIN {printf \"%.2f\", $RATE*100}")
+ echo "Coverage: $PERCENT% (threshold: ${VORTEX_CI_CODE_COVERAGE_THRESHOLD:-90}%)"
+ echo "export COVERAGE_PERCENT=${PERCENT}" >> "${BASH_ENV}"
+
+ - run:
+ name: Post coverage summary as PR comment
+ command: |
+ [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0
+ [ "${VORTEX_CI_CODE_COVERAGE_PR_COMMENT_SKIP:-0}" = "1" ] && exit 0
+ .circleci/post-coverage-comment.sh /tmp/artifacts/coverage/phpunit/coverage.txt
+
+ - run:
+ name: Upload code coverage reports to Codecov
+ command: |
+ [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0
+ if [ -n "${CODECOV_TOKEN}" ] && [ -d /tmp/artifacts/coverage ] && ! echo "${CIRCLE_BRANCH}" | grep -q '^deps/'; then
+ codecov -Z -s /tmp/artifacts/coverage;
+ fi
+
+ - run:
+ name: Check code coverage threshold
+ command: |
+ [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0
+ if [ "${COVERAGE_PERCENT//.}" -lt "$((${VORTEX_CI_CODE_COVERAGE_THRESHOLD:-90}*100))" ]; then
+ echo "FAIL: coverage ${COVERAGE_PERCENT}% is below threshold ${VORTEX_CI_CODE_COVERAGE_THRESHOLD:-90}%"
+ exit 1
+ fi
+
+ - run:
+ name: Test with Behat
+ command: |
+ if [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ]; then export VORTEX_CI_BEHAT_PROFILE="${VORTEX_CI_BEHAT_PROFILE:-p${CIRCLE_NODE_INDEX}}"; fi
+ echo "Running with ${VORTEX_CI_BEHAT_PROFILE:-default} profile"
+ docker compose exec -T cli php -d memory_limit=-1 vendor/bin/behat --colors --strict --profile="${VORTEX_CI_BEHAT_PROFILE:-default}" || \
+ docker compose exec -T cli php -d memory_limit=-1 vendor/bin/behat --colors --strict --rerun --profile="${VORTEX_CI_BEHAT_PROFILE:-default}" || \
+ [ "${VORTEX_CI_BEHAT_IGNORE_FAILURE:-0}" -eq 1 ]
+ no_output_timeout: 30m
+
+ - run:
+ name: Process test logs and artifacts
+ command: |
+ mkdir -p "${VORTEX_CI_TEST_RESULTS}" "${VORTEX_CI_ARTIFACTS}"
+ if docker compose ps --services --filter "status=running" | grep -q cli && docker compose exec cli test -d /app/.logs; then
+ docker compose cp cli:/app/.logs/. "${VORTEX_CI_ARTIFACTS}/"
+ if docker compose exec -T cli sh -c '[ -d /app/.logs/test_results/ ]'; then
+ docker compose cp cli:/app/.logs/test_results/. "${VORTEX_CI_TEST_RESULTS}/"
+ fi
+ fi
+ when: always
+
+ - store_test_results:
+ path: *test_results
+
+ - store_artifacts:
+ path: *artifacts
+
+ - persist_to_workspace:
+ root: /tmp/workspace
+ paths:
+ - code
+
+ # Deploy primary branches.
+ deploy: &job_deploy
+ <<: *runner_config
+ steps:
+ - attach_workspace:
+ at: /tmp/workspace
+
+ - add_ssh_keys:
+ fingerprints:
+ - *deploy_ssh_fingerprint
+
+ - checkout
+ - *step_process_codebase_for_ci
+ - *load_variables_from_dotenv
+
+ - run:
+ name: Check if deployment should be skipped
+ command: |
+ if [ "${CIRCLE_PULL_REQUEST}" != "" ] && echo "${CIRCLE_BRANCH}" | grep -q "^project/"; then
+ echo "Skipping deployment - PR from project/* branch"
+ circleci-agent step halt
+ fi
+
+ - run:
+ name: Deploy
+ command: |
+ VORTEX_DEPLOY_BRANCH="${CIRCLE_BRANCH}" \
+ VORTEX_DEPLOY_PR="$(echo ${CIRCLE_PULL_REQUEST} | cut -d'/' -f 7)" \
+ VORTEX_DEPLOY_PR_HEAD=${CIRCLE_SHA1} \
+ ./scripts/vortex/deploy.sh
+ no_output_timeout: 30m
+
+ - store_artifacts:
+ path: *artifacts
+
+ # Deploy tags.
+ deploy-tags: &job-deploy-tags
+ <<: *runner_config
+ steps:
+ - attach_workspace:
+ at: /tmp/workspace
+
+ - add_ssh_keys:
+ fingerprints:
+ - *deploy_ssh_fingerprint
+
+ - checkout
+ - *step_process_codebase_for_ci
+ - *load_variables_from_dotenv
+
+ - run:
+ name: Deploy
+ command: VORTEX_DEPLOY_MODE="tag" ./scripts/vortex/deploy.sh
+ no_output_timeout: 30m
+
+ - store_artifacts:
+ path: *artifacts
+
+################################################################################
+# WORKFLOWS
+################################################################################
+
+workflows:
+ # Commit workflow. Runs for every commit push to the remote repository.
+ commit:
+ jobs:
+ - database:
+ filters:
+ tags:
+ only: /.*/
+ - lint:
+ filters:
+ tags:
+ only: /.*/
+ - build:
+ requires:
+ - database
+ filters:
+ tags:
+ only: /.*/
+ - deploy:
+ requires:
+ - build
+ - lint
+ filters:
+ branches:
+ # Allowed branches:
+ # - production, main, master, develop, ci, cisomething
+ # - project/description
+ # - deps/*
+ # - feature/description, feature/123-description
+ # - bugfix/description, bugfix/123-description
+ # - release/__VERSION__, release/__VERSION__ (per https://semver.org/)
+ # - release/2023-04-17, release/2023-04-17.123 (date-based)
+ # - hotfix/__VERSION__, hotfix/__VERSION__ (per https://semver.org/)
+ # - hotfix/2023-04-17, hotfix/2023-04-17.123 (date-based)
+ only: /^(production|main|master|develop)$|^project\/[a-zA-z0-9\-\.]+|^(feature|bugfix)\/[a-zA-Z0-9\-\.\,_]+$|^ci.*|^(release|hotfix)\/[0-9]+(\.[0-9]+){2}(-rc\.[0-9]+)?$|^(release|hotfix)\/[0-9]{4}-[0-9]{2}-[0-9]{2}(\.[0-9]+)?$/
+ tags:
+ ignore: /.*/
+ - deploy-tags:
+ requires:
+ - build
+ - lint
+ filters:
+ branches:
+ ignore: /.*/
+ tags:
+ # Allowed tags:
+ # - __VERSION__, __VERSION__ (per https://semver.org/)
+ # - 2023-04-17, 2023-04-17.123 (date-based)
+ only: /^[0-9]+(\.[0-9]+){2}(-rc\.[0-9]+)?$|^[0-9]{4}-[0-9]{2}-[0-9]{2}(\.[0-9]+)?$/
+
+ # Nightly database workflow runs overnight to capture fresh database and cache it.
+ nightly-db:
+ triggers:
+ - schedule:
+ cron: *nightly_db_schedule
+ filters:
+ branches:
+ only:
+ - develop
+ jobs:
+ - database-nightly
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/.circleci/post-coverage-comment.sh b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/.circleci/post-coverage-comment.sh
new file mode 100755
index 000000000..d9e737409
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/.circleci/post-coverage-comment.sh
@@ -0,0 +1,79 @@
+#!/usr/bin/env bash
+##
+## Post code coverage summary as a PR comment on GitHub.
+##
+## Minimizes previous coverage comments before posting a new one.
+##
+## Environment variables:
+## CIRCLE_PULL_REQUEST - CircleCI PR URL.
+## GITHUB_TOKEN - GitHub token for API access.
+## CIRCLE_PROJECT_USERNAME - GitHub org/user.
+## CIRCLE_PROJECT_REPONAME - GitHub repo name.
+## VORTEX_CI_CODE_COVERAGE_THRESHOLD - Coverage threshold percentage (default: 90).
+##
+## Usage:
+## .circleci/post-coverage-comment.sh /path/to/coverage.txt
+
+set -euo pipefail
+
+COVERAGE_FILE="${1:-}"
+
+if [ -z "${COVERAGE_FILE}" ] || [ ! -f "${COVERAGE_FILE}" ]; then
+ echo "ERROR: Coverage file not found: ${COVERAGE_FILE}" >&2
+ exit 1
+fi
+
+if [ -z "${CIRCLE_PULL_REQUEST:-}" ]; then
+ echo "Not a pull request. Skipping."
+ exit 0
+fi
+
+if [ -z "${GITHUB_TOKEN:-}" ]; then
+ echo "GITHUB_TOKEN is not set. Skipping."
+ exit 0
+fi
+
+COVERAGE_SUMMARY=$(awk '/^ *Summary:/{f=1;next} f && /^$/{exit} f' "${COVERAGE_FILE}")
+COVERAGE_DETAILS=$(awk 'BEGIN{s=0} /^ *Summary:/{s=1} s==1 && /^$/{s=2;next} s==2' "${COVERAGE_FILE}")
+PR_NUMBER=$(echo "${CIRCLE_PULL_REQUEST}" | cut -d'/' -f 7)
+REPO_SLUG="${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}"
+THRESHOLD="${VORTEX_CI_CODE_COVERAGE_THRESHOLD:-90}"
+
+MARKER=""
+
+BODY=$(jq -n --arg body "**Code coverage** (threshold: ${THRESHOLD}%)
+\`\`\`
+${COVERAGE_SUMMARY}
+\`\`\`
+
+Per-class coverage
+
+\`\`\`
+${COVERAGE_DETAILS}
+\`\`\`
+
+${MARKER}" '{body: $body}')
+
+# Minimize previous coverage comments.
+COMMENTS_JSON=$(curl -s \
+ -H "Authorization: token ${GITHUB_TOKEN}" \
+ -H "Accept: application/vnd.github.v3+json" \
+ "https://api.github.com/repos/${REPO_SLUG}/issues/${PR_NUMBER}/comments?per_page=100")
+
+EXISTING_IDS=$(echo "${COMMENTS_JSON}" | jq -r '.[] | select(.body | contains("")) | .node_id')
+
+for NODE_ID in ${EXISTING_IDS}; do
+ GRAPHQL_BODY=$(jq -n --arg id "${NODE_ID}" '{query: "mutation($id:ID!){minimizeComment(input:{subjectId:$id,classifier:OUTDATED}){minimizedComment{isMinimized}}}", variables: {id: $id}}')
+ curl -s -X POST \
+ -H "Authorization: bearer ${GITHUB_TOKEN}" \
+ -H "Content-Type: application/json" \
+ "https://api.github.com/graphql" \
+ -d "${GRAPHQL_BODY}"
+done
+
+# Post new coverage comment.
+curl -s -X POST \
+ -H "Authorization: token ${GITHUB_TOKEN}" \
+ -H "Accept: application/vnd.github.v3+json" \
+ "https://api.github.com/repos/${REPO_SLUG}/issues/${PR_NUMBER}/comments" \
+ -d "${BODY}"
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/.github/workflows/-build-test-deploy.yml b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/.github/workflows/-build-test-deploy.yml
new file mode 100644
index 000000000..e69de29bb
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/AGENTS.md b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/AGENTS.md
new file mode 100644
index 000000000..3bd67d24d
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/AGENTS.md
@@ -0,0 +1,10 @@
+@@ -59,9 +59,6 @@
+ ahoy test-functional # Run PHPUnit Functional tests
+ ahoy test -- --filter=TestClassName # Run specific PHPUnit test class
+
+-# Jest testing
+-ahoy test-js # Run Jest JavaScript unit tests
+-
+ # Behat testing
+ ahoy test-bdd # Run Behat tests
+ ahoy test-bdd -- --tags=@tagname # Run Behat tests with specific tag
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/README.md b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/README.md
new file mode 100644
index 000000000..7738f8316
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/README.md
@@ -0,0 +1,9 @@
+@@ -9,7 +9,7 @@
+
+ Drupal 11 implementation of star wars for star wars Org
+
+-[](https://github.com/star_wars_org/star_wars/actions/workflows/build-test-deploy.yml)
++[](https://circleci.com/gh/star_wars_org/star_wars)
+
+ 
+ [](https://codecov.io/gh/star_wars_org/star_wars)
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/docs/ci.md b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/docs/ci.md
new file mode 100644
index 000000000..542f58060
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/docs/ci.md
@@ -0,0 +1,12 @@
+@@ -5,9 +5,9 @@
+
+ ## CI provider
+
+-This project uses [GitHub Actions](https://github.com/features/actions).
++This project uses [CircleCI](https://circleci.com/).
+
+-See [GitHub Actions documentation](https://www.vortextemplate.com/docs/continuous-integration/github-actions)
++See [CircleCI documentation](https://www.vortextemplate.com/docs/continuous-integration/circleci)
+ for setup and configuration details.
+
+ ## Project-specific configuration
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/package.json b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/package.json
new file mode 100644
index 000000000..cc07aebbd
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/package.json
@@ -0,0 +1,15 @@
+@@ -13,13 +13,10 @@
+ "lint": "yarn run lint-js && yarn run lint-css",
+ "lint-fix-js": "eslint web/modules/custom --ext .js --no-error-on-unmatched-pattern --fix",
+ "lint-fix-css": "stylelint --allow-empty-input \"web/modules/custom/**/*.css\" --fix",
+- "lint-fix": "yarn run lint-fix-js && yarn run lint-fix-css",
+- "test": "jest"
++ "lint-fix": "yarn run lint-fix-js && yarn run lint-fix-css"
+ },
+ "devDependencies": {
+ "@homer0/prettier-plugin-jsdoc": "__VERSION__",
+- "jest": "__VERSION__",
+- "jest-environment-jsdom": "__VERSION__",
+ "eslint": "__VERSION__",
+ "eslint-config-airbnb-base": "__VERSION__",
+ "eslint-config-prettier": "__VERSION__",
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/tests/phpunit/CircleCiConfigTest.php b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/tests/phpunit/CircleCiConfigTest.php
new file mode 100644
index 000000000..416620280
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/tests/phpunit/CircleCiConfigTest.php
@@ -0,0 +1,257 @@
+config = Yaml::decode($file);
+ }
+
+ /**
+ * Tests for deploy branch regex.
+ *
+ * @see https://semver.org/
+ */
+ #[DataProvider('dataProviderDeployBranchRegex')]
+ public function testDeployBranchRegex(string $branch, bool $expected = TRUE): void {
+ $this->assertEquals($expected, preg_match($this->config['workflows']['commit']['jobs'][3]['deploy']['filters']['branches']['only'], $branch));
+ }
+
+ /**
+ * Data provider for testDeployBranchRegex().
+ */
+ public static function dataProviderDeployBranchRegex(): \Iterator {
+ // Positive branches.
+ yield ['production'];
+ yield ['main'];
+ yield ['master'];
+ yield ['develop'];
+
+ yield ['ci'];
+ yield ['cisomething'];
+
+ yield ['release/__VERSION__'];
+ yield ['release/__VERSION__'];
+ yield ['hotfix/__VERSION__'];
+ yield ['hotfix/__VERSION__'];
+
+ yield ['release/2023-04-17'];
+ yield ['release/2023-04-17.1'];
+ yield ['hotfix/2023-04-17'];
+ yield ['hotfix/2023-04-17.1'];
+
+ yield ['feature/description'];
+ yield ['feature/Description'];
+ yield ['feature/Description-With-Hyphens'];
+ yield ['feature/Description-With_Underscores'];
+ yield ['feature/123-description'];
+ yield ['feature/123-Description'];
+ yield ['feature/UNDERSCORES_UNDERSCORES'];
+ yield ['feature/123-Description-With_UNDERSCORES'];
+ yield ['feature/1.x'];
+ yield ['feature/0.x'];
+ yield ['feature/0.1.x'];
+ yield ['feature/__VERSION__.x'];
+ yield ['feature/1.x-description'];
+ yield ['feature/0.x-description'];
+ yield ['feature/0.1.x-description'];
+ yield ['feature/__VERSION__.x-description'];
+
+ yield ['bugfix/description'];
+ yield ['bugfix/Description'];
+ yield ['bugfix/Description-With-Hyphens'];
+ yield ['bugfix/Description-With_Underscores'];
+ yield ['bugfix/123-description'];
+ yield ['bugfix/123-Description'];
+ yield ['bugfix/UNDERSCORES_UNDERSCORES'];
+ yield ['bugfix/123-Description-With_UNDERSCORES'];
+ yield ['bugfix/1.x'];
+ yield ['bugfix/0.x'];
+ yield ['bugfix/0.1.x'];
+ yield ['bugfix/__VERSION__.x'];
+ yield ['bugfix/1.x-description'];
+ yield ['bugfix/0.x-description'];
+ yield ['bugfix/0.1.x-description'];
+ yield ['bugfix/__VERSION__.x-description'];
+
+ yield ['project/description'];
+ yield ['project/Description'];
+ yield ['project/Description-With-Hyphens'];
+ yield ['project/123-description'];
+ yield ['project/123-Description'];
+ yield ['project/1.x'];
+ yield ['project/0.x'];
+ yield ['project/0.1.x'];
+ yield ['project/__VERSION__.x'];
+ yield ['project/1.x-description'];
+ yield ['project/0.x-description'];
+ yield ['project/0.1.x-description'];
+ yield ['project/__VERSION__.x-description'];
+
+ // Negative branches.
+ yield ['something', FALSE];
+ yield ['premain', FALSE];
+ yield ['premaster', FALSE];
+ yield ['predevelop', FALSE];
+ yield ['mainpost', FALSE];
+ yield ['masterpost', FALSE];
+ yield ['developpost', FALSE];
+ yield ['premainpost', FALSE];
+ yield ['premasterpost', FALSE];
+ yield ['predeveloppost', FALSE];
+
+ yield ['preci', FALSE];
+ yield ['precipost', FALSE];
+
+ yield ['deps/something', FALSE];
+ yield ['deps', FALSE];
+ yield ['predeps', FALSE];
+ yield ['depspost', FALSE];
+ yield ['predepspost', FALSE];
+
+ yield ['feature', FALSE];
+ yield ['release', FALSE];
+ yield ['hotfix', FALSE];
+ yield ['prefeature', FALSE];
+ yield ['prerelease', FALSE];
+ yield ['prehotfix', FALSE];
+ yield ['featurepost', FALSE];
+ yield ['releasepost', FALSE];
+ yield ['hotfixpost', FALSE];
+ yield ['prefeaturepost', FALSE];
+ yield ['prereleasepost', FALSE];
+ yield ['prehotfixpost', FALSE];
+
+ yield ['release/123', FALSE];
+ yield ['release/123.456', FALSE];
+ yield ['hotfix/123', FALSE];
+ yield ['hotfix/123.456', FALSE];
+
+ yield ['release/202-04-17', FALSE];
+ yield ['release/2023-4-17', FALSE];
+ yield ['release/2023-04-1', FALSE];
+ yield ['release/pre2023-04-17', FALSE];
+ yield ['release/2023-04-17post', FALSE];
+ yield ['release/pre2023-04-17post', FALSE];
+
+ yield ['hotfix/202-04-17', FALSE];
+ yield ['hotfix/2023-4-17', FALSE];
+ yield ['hotfix/2023-04-1', FALSE];
+ yield ['hotfix/pre2023-04-17', FALSE];
+ yield ['hotfix/2023-04-17post', FALSE];
+ yield ['hotfix/pre2023-04-17post', FALSE];
+
+ yield ['release/__VERSION__', FALSE];
+ yield ['release/__VERSION__', FALSE];
+ yield ['release/__VERSION__', FALSE];
+ yield ['release/__VERSION__', FALSE];
+ yield ['release/__VERSION__', FALSE];
+ yield ['release/__VERSION__', FALSE];
+ yield ['release/__VERSION__', FALSE];
+ yield ['release/__VERSION__', FALSE];
+ yield ['release/__VERSION__', FALSE];
+ yield ['release/__VERSION__', FALSE];
+
+ yield ['hotfix/__VERSION__', FALSE];
+ yield ['hotfix/__VERSION__', FALSE];
+ yield ['hotfix/__VERSION__', FALSE];
+ yield ['hotfix/__VERSION__', FALSE];
+ yield ['hotfix/__VERSION__', FALSE];
+ yield ['hotfix/__VERSION__', FALSE];
+ yield ['hotfix/__VERSION__', FALSE];
+ yield ['hotfix/__VERSION__', FALSE];
+ yield ['hotfix/__VERSION__', FALSE];
+ yield ['hotfix/__VERSION__', FALSE];
+
+ yield ['prefeature/something', FALSE];
+ yield ['prefbugfix/something', FALSE];
+ yield ['prerelease/something', FALSE];
+ yield ['prehotfix/something', FALSE];
+ yield ['featurepost/something', FALSE];
+ yield ['bugfixpost/something', FALSE];
+ yield ['releasepost/something', FALSE];
+ yield ['hotfixpost/something', FALSE];
+ yield ['prefeaturepost/something', FALSE];
+ yield ['prebugfixpost/something', FALSE];
+ yield ['prereleasepost/something', FALSE];
+ yield ['prehotfixpost/something', FALSE];
+ yield ['preproject/something', FALSE];
+ yield ['projectpost/something', FALSE];
+ }
+
+ /**
+ * Tests for deploy tag regex.
+ *
+ * @see https://semver.org/
+ */
+ #[DataProvider('dataProviderDeployTagRegex')]
+ public function testDeployTagRegex(string $branch, bool $expected = TRUE): void {
+ $this->assertEquals($expected, preg_match($this->config['workflows']['commit']['jobs'][4]['deploy-tags']['filters']['tags']['only'], $branch));
+ }
+
+ /**
+ * Data provider for testDeployTagRegex().
+ */
+ public static function dataProviderDeployTagRegex(): \Iterator {
+ // Positive tags.
+ yield ['__VERSION__'];
+ yield ['__VERSION__'];
+ yield ['2023-04-17'];
+ yield ['2023-04-17.123'];
+
+ // Negative tags.
+ yield ['123', FALSE];
+ yield ['123.456', FALSE];
+ yield ['__VERSION__', FALSE];
+ yield ['__VERSION__', FALSE];
+ yield ['__VERSION__', FALSE];
+ yield ['__VERSION__', FALSE];
+ yield ['__VERSION__', FALSE];
+
+ yield ['202-04-17', FALSE];
+ yield ['2023-0-17', FALSE];
+ yield ['2023-04-1', FALSE];
+ yield ['pre2023-04-17', FALSE];
+ yield ['2023-04-17post', FALSE];
+ yield ['pre2023-04-17post', FALSE];
+ yield ['2023-04-17.123.', FALSE];
+ yield ['2023-04-17.pre123', FALSE];
+ yield ['2023-04-17.pre123post', FALSE];
+ yield ['2023-04-17.123post', FALSE];
+ }
+
+}
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/tests/phpunit/Drupal/EnvironmentSettingsTest.php b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/tests/phpunit/Drupal/EnvironmentSettingsTest.php
new file mode 100644
index 000000000..aa1deaed6
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/tests/phpunit/Drupal/EnvironmentSettingsTest.php
@@ -0,0 +1,12 @@
+@@ -280,9 +280,9 @@
+ }
+
+ /**
+- * Test per-environment settings for GitHub Actions.
++ * Test per-environment settings for CircleCI.
+ */
+- public function testEnvironmentGha(): void {
++ public function testEnvironmentCircleCi(): void {
+ $this->setEnvVars([
+ 'CI' => TRUE,
+ ]);
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/web/modules/custom/sw_demo/js/-sw_demo.test.js b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/web/modules/custom/sw_demo/js/-sw_demo.test.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/web/sites/default/includes/providers/-settings.gha.php b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/web/sites/default/includes/providers/-settings.gha.php
new file mode 100644
index 000000000..e69de29bb
diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/web/sites/default/includes/providers/settings.circleci.php b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/web/sites/default/includes/providers/settings.circleci.php
new file mode 100644
index 000000000..7cad35cf8
--- /dev/null
+++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/web/sites/default/includes/providers/settings.circleci.php
@@ -0,0 +1,17 @@
+assertFileDoesNotExist(static::$sut . '/.prettierrc.json');
$test->assertFileDoesNotExist(static::$sut . '/.prettierignore');
$test->assertFileDoesNotExist(static::$sut . '/.stylelintrc.js');
+ $test->assertFileDoesNotExist(static::$sut . '/jest.config.js');
$test->assertSutContains([
'/\blint-fe:/',
@@ -335,10 +336,40 @@ public static function dataProviderHandlerProcess(): \Iterator {
'rector/rector',
])),
];
+ yield 'tools_no_jest' => [
+ static::cw(function (): void {
+ $tools = array_keys(Tools::getToolDefinitions('tools'));
+ Env::put(Tools::envName(), Converter::toList(array_diff($tools, [Tools::JEST])));
+ Env::put(CiProvider::envName(), CiProvider::GITHUB_ACTIONS);
+ }),
+ static::cw(function (FunctionalTestCase $test): void {
+ $pj = static::$sut . '/package.json';
+ $test->assertFileNotContainsString($pj, '"jest":');
+ $test->assertFileNotContainsString($pj, '"jest-environment-jsdom":');
+ $test->assertFileDoesNotExist(static::$sut . '/jest.config.js');
+ $test->assertFileContainsString($pj, '"eslint":');
+ $test->assertFileContainsString($pj, '"stylelint":');
+ }),
+ ];
+ yield 'tools_no_jest_circleci' => [
+ static::cw(function (): void {
+ $tools = array_keys(Tools::getToolDefinitions('tools'));
+ Env::put(Tools::envName(), Converter::toList(array_diff($tools, [Tools::JEST])));
+ Env::put(CiProvider::envName(), CiProvider::CIRCLECI);
+ }),
+ static::cw(function (FunctionalTestCase $test): void {
+ $pj = static::$sut . '/package.json';
+ $test->assertFileNotContainsString($pj, '"jest":');
+ $test->assertFileNotContainsString($pj, '"jest-environment-jsdom":');
+ $test->assertFileDoesNotExist(static::$sut . '/jest.config.js');
+ $test->assertFileContainsString($pj, '"eslint":');
+ $test->assertFileContainsString($pj, '"stylelint":');
+ }),
+ ];
yield 'tools_groups_no_fe_lint' => [
static::cw(function (): void {
$tools = array_keys(Tools::getToolDefinitions('tools'));
- Env::put(Tools::envName(), Converter::toList(array_diff($tools, [Tools::ESLINT, Tools::STYLELINT])));
+ Env::put(Tools::envName(), Converter::toList(array_diff($tools, [Tools::ESLINT, Tools::STYLELINT, Tools::JEST])));
Env::put(CiProvider::envName(), CiProvider::GITHUB_ACTIONS);
}),
static::cw(function (FunctionalTestCase $test): void {
@@ -349,12 +380,13 @@ public static function dataProviderHandlerProcess(): \Iterator {
$test->assertFileDoesNotExist(static::$sut . '/.prettierrc.json');
$test->assertFileDoesNotExist(static::$sut . '/.prettierignore');
$test->assertFileDoesNotExist(static::$sut . '/.stylelintrc.js');
+ $test->assertFileDoesNotExist(static::$sut . '/jest.config.js');
}),
];
yield 'tools_groups_no_fe_lint_circleci' => [
static::cw(function (): void {
$tools = array_keys(Tools::getToolDefinitions('tools'));
- Env::put(Tools::envName(), Converter::toList(array_diff($tools, [Tools::ESLINT, Tools::STYLELINT])));
+ Env::put(Tools::envName(), Converter::toList(array_diff($tools, [Tools::ESLINT, Tools::STYLELINT, Tools::JEST])));
Env::put(CiProvider::envName(), CiProvider::CIRCLECI);
}),
static::cw(function (FunctionalTestCase $test): void {
@@ -365,6 +397,7 @@ public static function dataProviderHandlerProcess(): \Iterator {
$test->assertFileDoesNotExist(static::$sut . '/.prettierrc.json');
$test->assertFileDoesNotExist(static::$sut . '/.prettierignore');
$test->assertFileDoesNotExist(static::$sut . '/.stylelintrc.js');
+ $test->assertFileDoesNotExist(static::$sut . '/jest.config.js');
}),
];
yield 'tools_groups_no_be_tests' => [
@@ -410,7 +443,7 @@ public static function dataProviderHandlerProcess(): \Iterator {
yield 'tools_groups_no_fe_lint_no_theme' => [
static::cw(function (): void {
$tools = array_keys(Tools::getToolDefinitions('tools'));
- Env::put(Tools::envName(), Converter::toList(array_diff($tools, [Tools::ESLINT, Tools::STYLELINT])));
+ Env::put(Tools::envName(), Converter::toList(array_diff($tools, [Tools::ESLINT, Tools::STYLELINT, Tools::JEST])));
Env::put(CiProvider::envName(), CiProvider::GITHUB_ACTIONS);
Env::put(Theme::envName(), Theme::OLIVERO);
}),
@@ -422,6 +455,7 @@ public static function dataProviderHandlerProcess(): \Iterator {
$test->assertFileDoesNotExist(static::$sut . '/.prettierrc.json');
$test->assertFileDoesNotExist(static::$sut . '/.prettierignore');
$test->assertFileDoesNotExist(static::$sut . '/.stylelintrc.js');
+ $test->assertFileDoesNotExist(static::$sut . '/jest.config.js');
$test->assertSutNotContains([
'yarn install',
'yarn run lint',
@@ -433,7 +467,7 @@ public static function dataProviderHandlerProcess(): \Iterator {
yield 'tools_groups_no_fe_lint_no_theme_circleci' => [
static::cw(function (): void {
$tools = array_keys(Tools::getToolDefinitions('tools'));
- Env::put(Tools::envName(), Converter::toList(array_diff($tools, [Tools::ESLINT, Tools::STYLELINT])));
+ Env::put(Tools::envName(), Converter::toList(array_diff($tools, [Tools::ESLINT, Tools::STYLELINT, Tools::JEST])));
Env::put(CiProvider::envName(), CiProvider::CIRCLECI);
Env::put(Theme::envName(), Theme::OLIVERO);
}),
@@ -445,6 +479,7 @@ public static function dataProviderHandlerProcess(): \Iterator {
$test->assertFileDoesNotExist(static::$sut . '/.prettierrc.json');
$test->assertFileDoesNotExist(static::$sut . '/.prettierignore');
$test->assertFileDoesNotExist(static::$sut . '/.stylelintrc.js');
+ $test->assertFileDoesNotExist(static::$sut . '/jest.config.js');
$test->assertSutNotContains([
'yarn install',
'yarn run lint',
diff --git a/.vortex/installer/tests/Unit/Handlers/AbstractHandlerDiscoveryTestCase.php b/.vortex/installer/tests/Unit/Handlers/AbstractHandlerDiscoveryTestCase.php
index 8ffff020e..8d4cd0e4c 100644
--- a/.vortex/installer/tests/Unit/Handlers/AbstractHandlerDiscoveryTestCase.php
+++ b/.vortex/installer/tests/Unit/Handlers/AbstractHandlerDiscoveryTestCase.php
@@ -139,7 +139,7 @@ protected static function getExpectedDefaults(): array {
VersionScheme::id() => VersionScheme::CALVER,
Timezone::id() => 'UTC',
Services::id() => [Services::CLAMAV, Services::REDIS, Services::SOLR],
- Tools::id() => [Tools::BEHAT, Tools::ESLINT, Tools::PHPCS, Tools::PHPMD, Tools::PHPSTAN, Tools::PHPUNIT, Tools::RECTOR, Tools::STYLELINT],
+ Tools::id() => [Tools::BEHAT, Tools::ESLINT, Tools::JEST, Tools::PHPCS, Tools::PHPMD, Tools::PHPSTAN, Tools::PHPUNIT, Tools::RECTOR, Tools::STYLELINT],
HostingProvider::id() => HostingProvider::NONE,
HostingProjectName::id() => NULL,
Webroot::id() => Webroot::WEB,
diff --git a/.vortex/installer/tests/Unit/Handlers/ToolsHandlerDiscoveryTest.php b/.vortex/installer/tests/Unit/Handlers/ToolsHandlerDiscoveryTest.php
index 61fd64b53..f450ba358 100644
--- a/.vortex/installer/tests/Unit/Handlers/ToolsHandlerDiscoveryTest.php
+++ b/.vortex/installer/tests/Unit/Handlers/ToolsHandlerDiscoveryTest.php
@@ -18,11 +18,11 @@ public static function dataProviderRunPrompts(): \Iterator {
$expected_installed = static::getExpectedInstalled();
yield 'tools - prompt - defaults' => [
[Tools::id() => Key::ENTER],
- [Tools::id() => [Tools::BEHAT, Tools::ESLINT, Tools::PHPCS, Tools::PHPMD, Tools::PHPSTAN, Tools::PHPUNIT, Tools::RECTOR, Tools::STYLELINT]] + $expected_defaults,
+ [Tools::id() => [Tools::BEHAT, Tools::ESLINT, Tools::JEST, Tools::PHPCS, Tools::PHPMD, Tools::PHPSTAN, Tools::PHPUNIT, Tools::RECTOR, Tools::STYLELINT]] + $expected_defaults,
];
yield 'tools - discovery - all tools' => [
[],
- [Tools::id() => [Tools::BEHAT, Tools::ESLINT, Tools::PHPCS, Tools::PHPMD, Tools::PHPSTAN, Tools::PHPUNIT, Tools::RECTOR, Tools::STYLELINT]] + $expected_installed,
+ [Tools::id() => [Tools::BEHAT, Tools::ESLINT, Tools::JEST, Tools::PHPCS, Tools::PHPMD, Tools::PHPSTAN, Tools::PHPUNIT, Tools::RECTOR, Tools::STYLELINT]] + $expected_installed,
function (AbstractHandlerDiscoveryTestCase $test, Config $config): void {
$test->stubVortexProject($config);
$dependencies = [
@@ -34,7 +34,7 @@ function (AbstractHandlerDiscoveryTestCase $test, Config $config): void {
'behat/behat' => '*',
];
$test->stubComposerJsonDependencies($dependencies, TRUE);
- file_put_contents(static::$sut . '/package.json', json_encode(['devDependencies' => ['eslint' => '*', 'stylelint' => '*']], JSON_PRETTY_PRINT));
+ file_put_contents(static::$sut . '/package.json', json_encode(['devDependencies' => ['eslint' => '*', 'jest' => '*', 'stylelint' => '*']], JSON_PRETTY_PRINT));
},
];
yield 'tools - discovery - none' => [
@@ -180,6 +180,22 @@ function (AbstractHandlerDiscoveryTestCase $test, Config $config): void {
File::dump(static::$sut . '/behat.yml');
},
];
+ yield 'tools - discovery - jest' => [
+ [],
+ [Tools::id() => [Tools::JEST]] + $expected_installed,
+ function (AbstractHandlerDiscoveryTestCase $test, Config $config): void {
+ $test->stubVortexProject($config);
+ file_put_contents(static::$sut . '/package.json', json_encode(['devDependencies' => ['jest' => '*']], JSON_PRETTY_PRINT));
+ },
+ ];
+ yield 'tools - discovery - jest, alt' => [
+ [],
+ [Tools::id() => [Tools::JEST]] + $expected_installed,
+ function (AbstractHandlerDiscoveryTestCase $test, Config $config): void {
+ $test->stubVortexProject($config);
+ File::dump(static::$sut . '/jest.config.js');
+ },
+ ];
yield 'tools - discovery - eslint' => [
[],
[Tools::id() => [Tools::ESLINT]] + $expected_installed,
diff --git a/.vortex/tests/phpunit/Functional/AhoyWorkflowTest.php b/.vortex/tests/phpunit/Functional/AhoyWorkflowTest.php
index 26d79756a..52c549103 100644
--- a/.vortex/tests/phpunit/Functional/AhoyWorkflowTest.php
+++ b/.vortex/tests/phpunit/Functional/AhoyWorkflowTest.php
@@ -102,6 +102,8 @@ public function testAhoyWorkflowStateful(): void {
$this->subtestAhoyTestFunctionalJavascript();
+ $this->subtestAhoyTestJs();
+
$this->subtestAhoyTestBdd();
// Run this test as a last one to make sure that there is no concurrency
diff --git a/.vortex/tests/phpunit/Traits/Subtests/SubtestAhoyTrait.php b/.vortex/tests/phpunit/Traits/Subtests/SubtestAhoyTrait.php
index d8885e28d..d4c12f015 100644
--- a/.vortex/tests/phpunit/Traits/Subtests/SubtestAhoyTrait.php
+++ b/.vortex/tests/phpunit/Traits/Subtests/SubtestAhoyTrait.php
@@ -533,6 +533,33 @@ protected function runAhoyTestPhpunit(string $type, string $file): void {
$this->removePathHostAndContainer('.logs');
}
+ protected function subtestAhoyTestJs(string $webroot = 'web'): void {
+ $this->logStepStart();
+
+ $file = $webroot . '/modules/custom/sw_demo/js/sw_demo.test.js';
+ $this->assertFileExists($file);
+
+ $this->logSubstep('Run all Jest tests');
+ $this->cmd('ahoy test-js', 'Tests:');
+
+ $this->logSubstep('Run Jest tests matching a path pattern');
+ $this->cmd('ahoy test-js -- --testPathPattern=sw_demo', 'Tests:');
+
+ $this->logSubstep('Run a specific Jest test by name');
+ $this->cmd('ahoy test-js -- -t "should have the expected storage key"', 'Tests:');
+
+ $this->logSubstep('Assert that Jest test failure works');
+ $this->fileBackup($file);
+ File::replaceContentInFile($file, 'toBe(true)', 'toBe(false)');
+ $this->syncToContainer($file);
+ $this->cmdFail('ahoy test-js');
+
+ $this->fileRestore($file);
+ $this->syncToContainer($file);
+
+ $this->logStepFinish();
+ }
+
protected function subtestAhoyTestBdd(string $webroot = 'web'): void {
$this->logStepStart();
diff --git a/AGENTS.md b/AGENTS.md
index 29c26d3f4..80d3ac594 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -69,6 +69,9 @@ ahoy test-kernel # Run PHPUnit Kernel tests
ahoy test-functional # Run PHPUnit Functional tests
ahoy test -- --filter=TestClassName # Run specific PHPUnit test class
+# Jest testing
+ahoy test-js # Run Jest JavaScript unit tests
+
# Behat testing
ahoy test-bdd # Run Behat tests
ahoy test-bdd -- --tags=@tagname # Run Behat tests with specific tag
diff --git a/jest.config.js b/jest.config.js
new file mode 100644
index 000000000..0ac9851fc
--- /dev/null
+++ b/jest.config.js
@@ -0,0 +1,32 @@
+const fs = require('fs');
+const path = require('path');
+
+// Discover js/ directories in custom modules, resolving symlinks to real
+// paths. Jest resolves symlinks internally, so roots must use real paths
+// for test files to be matched.
+const dirs = ['web/modules/custom'];
+const roots = [];
+
+dirs.forEach((dir) => {
+ if (fs.existsSync(dir)) {
+ fs.readdirSync(dir).forEach((name) => {
+ const jsDir = path.resolve(dir, name, 'js');
+ if (fs.existsSync(jsDir)) {
+ roots.push(jsDir);
+ }
+ });
+ }
+});
+
+module.exports = {
+ testEnvironment: 'jest-environment-jsdom',
+ roots: roots.length > 0 ? roots : [path.resolve('web/modules/custom')],
+ testRegex: 'web/modules/custom/.+\\.test\\.js$',
+ testPathIgnorePatterns: ['/node_modules/', '/vendor/'],
+ modulePathIgnorePatterns: [
+ '/web/core/',
+ '/web/modules/contrib/',
+ '/web/themes/',
+ '/.vortex/',
+ ],
+};
diff --git a/package.json b/package.json
index c2363d2a5..0fbe69c09 100644
--- a/package.json
+++ b/package.json
@@ -13,10 +13,13 @@
"lint": "yarn run lint-js && yarn run lint-css",
"lint-fix-js": "eslint web/modules/custom --ext .js --no-error-on-unmatched-pattern --fix",
"lint-fix-css": "stylelint --allow-empty-input \"web/modules/custom/**/*.css\" --fix",
- "lint-fix": "yarn run lint-fix-js && yarn run lint-fix-css"
+ "lint-fix": "yarn run lint-fix-js && yarn run lint-fix-css",
+ "test": "jest"
},
"devDependencies": {
"@homer0/prettier-plugin-jsdoc": "^11.0.1",
+ "jest": "^29.7.0",
+ "jest-environment-jsdom": "^29.7.0",
"eslint": "^8.57.1",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^10.1.8",
diff --git a/web/modules/custom/ys_demo/js/ys_demo.js b/web/modules/custom/ys_demo/js/ys_demo.js
index 5f917a4b9..3b027b983 100644
--- a/web/modules/custom/ys_demo/js/ys_demo.js
+++ b/web/modules/custom/ys_demo/js/ys_demo.js
@@ -4,6 +4,8 @@
((Drupal) => {
Drupal.behaviors.ysDemo = {
+ storageKey: 'ys_counter_value',
+
attach(context) {
this.initCounterBlock(context);
},
@@ -17,7 +19,7 @@
initCounterBlock(context) {
const counterBlocks = context.querySelectorAll('[data-ys-demo-counter]');
- counterBlocks.forEach(function processBlock(block) {
+ counterBlocks.forEach((block) => {
// Skip if already processed.
if (block.classList.contains('ys-demo-counter-processed')) {
return;
@@ -29,39 +31,24 @@
const buttons = block.querySelectorAll('[data-counter-action]');
// Load saved value from localStorage.
- const storageKey = 'ys_counter_value';
- let currentValue = parseInt(localStorage.getItem(storageKey), 10) || 0;
+ let currentValue = this.getCounterValue();
valueElement.textContent = currentValue;
// Add event listeners to buttons.
- buttons.forEach(function processButton(button) {
- button.addEventListener('click', function handleClick() {
- const action = this.getAttribute('data-counter-action');
-
- switch (action) {
- case 'increment':
- currentValue += 1;
- break;
- case 'decrement':
- currentValue -= 1;
- break;
- case 'reset':
- currentValue = 0;
- break;
- default:
- // No action for unknown action types.
- break;
- }
+ buttons.forEach((button) => {
+ button.addEventListener('click', () => {
+ const action = button.getAttribute('data-counter-action');
+ currentValue = this.applyAction(currentValue, action);
- // Update display
+ // Update display.
valueElement.textContent = currentValue;
// Save to localStorage.
- localStorage.setItem(storageKey, currentValue.toString());
+ localStorage.setItem(this.storageKey, currentValue.toString());
// Add visual feedback.
valueElement.classList.add('updated');
- Drupal.behaviors.ysDemo.removeUpdatedClassAfterDelay(valueElement);
+ this.removeUpdatedClassAfterDelay(valueElement);
// Log action for debugging.
// eslint-disable-next-line no-console
@@ -71,6 +58,26 @@
});
},
+ /**
+ * Apply a counter action and return the new value.
+ *
+ * @param {number} value The current counter value.
+ * @param {string} action The action to apply.
+ * @return {number} The new counter value.
+ */
+ applyAction(value, action) {
+ switch (action) {
+ case 'increment':
+ return value + 1;
+ case 'decrement':
+ return value - 1;
+ case 'reset':
+ return 0;
+ default:
+ return value;
+ }
+ },
+
/**
* Remove updated class after a delay for visual feedback.
*
@@ -88,8 +95,7 @@
* @return {number} The current counter value, or 0 if not set.
*/
getCounterValue() {
- const storageKey = 'ys_counter_value';
- return parseInt(localStorage.getItem(storageKey), 10) || 0;
+ return parseInt(localStorage.getItem(this.storageKey), 10) || 0;
},
};
})(Drupal);
diff --git a/web/modules/custom/ys_demo/js/ys_demo.test.js b/web/modules/custom/ys_demo/js/ys_demo.test.js
new file mode 100644
index 000000000..03c8f24d5
--- /dev/null
+++ b/web/modules/custom/ys_demo/js/ys_demo.test.js
@@ -0,0 +1,274 @@
+/**
+ * @jest-environment jsdom
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+describe('Drupal.behaviors.ysDemo', () => {
+ beforeEach(() => {
+ localStorage.clear();
+ global.Drupal = { behaviors: {} };
+
+ const filePath = path.resolve(__dirname, 'ys_demo.js');
+ const code = fs.readFileSync(filePath, 'utf8');
+ eval(code);
+ });
+
+ afterEach(() => {
+ delete global.Drupal;
+ });
+
+ function createCounterBlockHtml() {
+ return `
+
+ 0
+ +
+ -
+ Reset
+
+ `;
+ }
+
+ describe('storageKey', () => {
+ it('should have the expected storage key', () => {
+ expect(Drupal.behaviors.ysDemo.storageKey).toBe('ys_counter_value');
+ });
+ });
+
+ describe('getCounterValue', () => {
+ it('should return 0 when localStorage is empty', () => {
+ expect(Drupal.behaviors.ysDemo.getCounterValue()).toBe(0);
+ });
+
+ it('should return the stored value from localStorage', () => {
+ localStorage.setItem('ys_counter_value', '42');
+ expect(Drupal.behaviors.ysDemo.getCounterValue()).toBe(42);
+ });
+
+ it('should return 0 for non-numeric localStorage values', () => {
+ localStorage.setItem('ys_counter_value', 'invalid');
+ expect(Drupal.behaviors.ysDemo.getCounterValue()).toBe(0);
+ });
+
+ it('should return negative values correctly', () => {
+ localStorage.setItem('ys_counter_value', '-5');
+ expect(Drupal.behaviors.ysDemo.getCounterValue()).toBe(-5);
+ });
+ });
+
+ describe('applyAction', () => {
+ it('should increment the value', () => {
+ expect(Drupal.behaviors.ysDemo.applyAction(0, 'increment')).toBe(1);
+ expect(Drupal.behaviors.ysDemo.applyAction(10, 'increment')).toBe(11);
+ });
+
+ it('should decrement the value', () => {
+ expect(Drupal.behaviors.ysDemo.applyAction(0, 'decrement')).toBe(-1);
+ expect(Drupal.behaviors.ysDemo.applyAction(5, 'decrement')).toBe(4);
+ });
+
+ it('should reset the value to zero', () => {
+ expect(Drupal.behaviors.ysDemo.applyAction(42, 'reset')).toBe(0);
+ expect(Drupal.behaviors.ysDemo.applyAction(-5, 'reset')).toBe(0);
+ });
+
+ it('should return the same value for unknown actions', () => {
+ expect(Drupal.behaviors.ysDemo.applyAction(7, 'unknown')).toBe(7);
+ });
+ });
+
+ describe('removeUpdatedClassAfterDelay', () => {
+ beforeEach(() => {
+ jest.useFakeTimers();
+ });
+
+ afterEach(() => {
+ jest.useRealTimers();
+ });
+
+ it('should remove the updated class after 300ms', () => {
+ document.body.innerHTML = ' ';
+ const element = document.querySelector('span');
+
+ Drupal.behaviors.ysDemo.removeUpdatedClassAfterDelay(element);
+
+ expect(element.classList.contains('updated')).toBe(true);
+ jest.advanceTimersByTime(300);
+ expect(element.classList.contains('updated')).toBe(false);
+ });
+
+ it('should not remove the class before 300ms', () => {
+ document.body.innerHTML = ' ';
+ const element = document.querySelector('span');
+
+ Drupal.behaviors.ysDemo.removeUpdatedClassAfterDelay(element);
+
+ jest.advanceTimersByTime(299);
+ expect(element.classList.contains('updated')).toBe(true);
+ });
+ });
+
+ describe('initCounterBlock', () => {
+ it('should initialize the counter display with 0', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('0');
+ });
+
+ it('should initialize with saved localStorage value', () => {
+ localStorage.setItem('ys_counter_value', '15');
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('15');
+ });
+
+ it('should mark the block as processed', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const block = document.querySelector('[data-ys-demo-counter]');
+ expect(block.classList.contains('ys-demo-counter-processed')).toBe(true);
+ });
+
+ it('should not re-process already processed blocks', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ const block = document.querySelector('[data-ys-demo-counter]');
+ block.classList.add('ys-demo-counter-processed');
+
+ const value = document.querySelector('[data-counter-value]');
+ value.textContent = 'original';
+
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ expect(value.textContent).toBe('original');
+ });
+
+ it('should increment counter on button click', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('1');
+ });
+
+ it('should decrement counter on button click', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const decrementBtn = document.querySelector(
+ '[data-counter-action="decrement"]',
+ );
+ decrementBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('-1');
+ });
+
+ it('should reset counter on button click', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+ incrementBtn.click();
+ incrementBtn.click();
+
+ const resetBtn = document.querySelector('[data-counter-action="reset"]');
+ resetBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('0');
+ });
+
+ it('should save counter value to localStorage on click', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+ incrementBtn.click();
+
+ expect(localStorage.getItem('ys_counter_value')).toBe('2');
+ });
+
+ it('should add updated class on click for visual feedback', () => {
+ jest.useFakeTimers();
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.classList.contains('updated')).toBe(true);
+
+ jest.advanceTimersByTime(300);
+ expect(value.classList.contains('updated')).toBe(false);
+ jest.useRealTimers();
+ });
+
+ it('should handle multiple counter blocks', () => {
+ document.body.innerHTML = `
+
+ 0
+ +
+
+
+ 0
+ +
+
+ `;
+ Drupal.behaviors.ysDemo.initCounterBlock(document);
+
+ const blocks = document.querySelectorAll('[data-ys-demo-counter]');
+ expect(blocks[0].classList.contains('ys-demo-counter-processed')).toBe(
+ true,
+ );
+ expect(blocks[1].classList.contains('ys-demo-counter-processed')).toBe(
+ true,
+ );
+ });
+ });
+
+ describe('attach', () => {
+ it('should call initCounterBlock with the context', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ const spy = jest.spyOn(Drupal.behaviors.ysDemo, 'initCounterBlock');
+
+ Drupal.behaviors.ysDemo.attach(document);
+
+ expect(spy).toHaveBeenCalledWith(document);
+ spy.mockRestore();
+ });
+
+ it('should fully initialize when called as a behavior', () => {
+ document.body.innerHTML = createCounterBlockHtml();
+ Drupal.behaviors.ysDemo.attach(document);
+
+ const incrementBtn = document.querySelector(
+ '[data-counter-action="increment"]',
+ );
+ incrementBtn.click();
+
+ const value = document.querySelector('[data-counter-value]');
+ expect(value.textContent).toBe('1');
+ expect(localStorage.getItem('ys_counter_value')).toBe('1');
+ });
+ });
+});
diff --git a/yarn.lock b/yarn.lock
index f3ed4f408..6fdcef7b7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,7 +2,7 @@
# yarn lockfile v1
-"@babel/code-frame@^7.0.0":
+"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.28.6", "@babel/code-frame@^7.29.0":
version "7.29.0"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.0.tgz#7cd7a59f15b3cc0dcd803038f7792712a7d0b15c"
integrity sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==
@@ -11,11 +11,265 @@
js-tokens "^4.0.0"
picocolors "^1.1.1"
+"@babel/compat-data@^7.28.6":
+ version "7.29.0"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.0.tgz#00d03e8c0ac24dd9be942c5370990cbe1f17d88d"
+ integrity sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==
+
+"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9":
+ version "7.29.0"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.0.tgz#5286ad785df7f79d656e88ce86e650d16ca5f322"
+ integrity sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==
+ dependencies:
+ "@babel/code-frame" "^7.29.0"
+ "@babel/generator" "^7.29.0"
+ "@babel/helper-compilation-targets" "^7.28.6"
+ "@babel/helper-module-transforms" "^7.28.6"
+ "@babel/helpers" "^7.28.6"
+ "@babel/parser" "^7.29.0"
+ "@babel/template" "^7.28.6"
+ "@babel/traverse" "^7.29.0"
+ "@babel/types" "^7.29.0"
+ "@jridgewell/remapping" "^2.3.5"
+ convert-source-map "^2.0.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.2"
+ json5 "^2.2.3"
+ semver "^6.3.1"
+
+"@babel/generator@^7.29.0", "@babel/generator@^7.7.2":
+ version "7.29.1"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.1.tgz#d09876290111abbb00ef962a7b83a5307fba0d50"
+ integrity sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==
+ dependencies:
+ "@babel/parser" "^7.29.0"
+ "@babel/types" "^7.29.0"
+ "@jridgewell/gen-mapping" "^0.3.12"
+ "@jridgewell/trace-mapping" "^0.3.28"
+ jsesc "^3.0.2"
+
+"@babel/helper-compilation-targets@^7.28.6":
+ version "7.28.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz#32c4a3f41f12ed1532179b108a4d746e105c2b25"
+ integrity sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==
+ dependencies:
+ "@babel/compat-data" "^7.28.6"
+ "@babel/helper-validator-option" "^7.27.1"
+ browserslist "^4.24.0"
+ lru-cache "^5.1.1"
+ semver "^6.3.1"
+
+"@babel/helper-globals@^7.28.0":
+ version "7.28.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674"
+ integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==
+
+"@babel/helper-module-imports@^7.28.6":
+ version "7.28.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz#60632cbd6ffb70b22823187201116762a03e2d5c"
+ integrity sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==
+ dependencies:
+ "@babel/traverse" "^7.28.6"
+ "@babel/types" "^7.28.6"
+
+"@babel/helper-module-transforms@^7.28.6":
+ version "7.28.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz#9312d9d9e56edc35aeb6e95c25d4106b50b9eb1e"
+ integrity sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==
+ dependencies:
+ "@babel/helper-module-imports" "^7.28.6"
+ "@babel/helper-validator-identifier" "^7.28.5"
+ "@babel/traverse" "^7.28.6"
+
+"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.28.6", "@babel/helper-plugin-utils@^7.8.0":
+ version "7.28.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz#6f13ea251b68c8532e985fd532f28741a8af9ac8"
+ integrity sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==
+
+"@babel/helper-string-parser@^7.27.1":
+ version "7.27.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687"
+ integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==
+
"@babel/helper-validator-identifier@^7.28.5":
version "7.28.5"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4"
integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==
+"@babel/helper-validator-option@^7.27.1":
+ version "7.27.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f"
+ integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==
+
+"@babel/helpers@^7.28.6":
+ version "7.29.2"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.29.2.tgz#9cfbccb02b8e229892c0b07038052cc1a8709c49"
+ integrity sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==
+ dependencies:
+ "@babel/template" "^7.28.6"
+ "@babel/types" "^7.29.0"
+
+"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.28.6", "@babel/parser@^7.29.0":
+ version "7.29.2"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.2.tgz#58bd50b9a7951d134988a1ae177a35ef9a703ba1"
+ integrity sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==
+ dependencies:
+ "@babel/types" "^7.29.0"
+
+"@babel/plugin-syntax-async-generators@^7.8.4":
+ version "7.8.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d"
+ integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-bigint@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea"
+ integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-class-properties@^7.12.13":
+ version "7.12.13"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10"
+ integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.12.13"
+
+"@babel/plugin-syntax-class-static-block@^7.14.5":
+ version "7.14.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406"
+ integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.14.5"
+
+"@babel/plugin-syntax-import-attributes@^7.24.7":
+ version "7.28.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz#b71d5914665f60124e133696f17cd7669062c503"
+ integrity sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.28.6"
+
+"@babel/plugin-syntax-import-meta@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51"
+ integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-syntax-json-strings@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a"
+ integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-jsx@^7.7.2":
+ version "7.28.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz#f8ca28bbd84883b5fea0e447c635b81ba73997ee"
+ integrity sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.28.6"
+
+"@babel/plugin-syntax-logical-assignment-operators@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699"
+ integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9"
+ integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-numeric-separator@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97"
+ integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-syntax-object-rest-spread@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871"
+ integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-optional-catch-binding@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1"
+ integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-optional-chaining@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a"
+ integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-private-property-in-object@^7.14.5":
+ version "7.14.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad"
+ integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.14.5"
+
+"@babel/plugin-syntax-top-level-await@^7.14.5":
+ version "7.14.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c"
+ integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.14.5"
+
+"@babel/plugin-syntax-typescript@^7.7.2":
+ version "7.28.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz#c7b2ddf1d0a811145b1de800d1abd146af92e3a2"
+ integrity sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.28.6"
+
+"@babel/template@^7.28.6", "@babel/template@^7.3.3":
+ version "7.28.6"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.28.6.tgz#0e7e56ecedb78aeef66ce7972b082fce76a23e57"
+ integrity sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==
+ dependencies:
+ "@babel/code-frame" "^7.28.6"
+ "@babel/parser" "^7.28.6"
+ "@babel/types" "^7.28.6"
+
+"@babel/traverse@^7.28.6", "@babel/traverse@^7.29.0":
+ version "7.29.0"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.0.tgz#f323d05001440253eead3c9c858adbe00b90310a"
+ integrity sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==
+ dependencies:
+ "@babel/code-frame" "^7.29.0"
+ "@babel/generator" "^7.29.0"
+ "@babel/helper-globals" "^7.28.0"
+ "@babel/parser" "^7.29.0"
+ "@babel/template" "^7.28.6"
+ "@babel/types" "^7.29.0"
+ debug "^4.3.1"
+
+"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.28.2", "@babel/types@^7.28.6", "@babel/types@^7.29.0", "@babel/types@^7.3.3":
+ version "7.29.0"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.0.tgz#9f5b1e838c446e72cf3cd4b918152b8c605e37c7"
+ integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==
+ dependencies:
+ "@babel/helper-string-parser" "^7.27.1"
+ "@babel/helper-validator-identifier" "^7.28.5"
+
+"@bcoe/v8-coverage@^0.2.3":
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
+ integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
+
"@cacheable/memory@^2.0.7":
version "2.0.7"
resolved "https://registry.yarnpkg.com/@cacheable/memory/-/memory-2.0.7.tgz#1e066dc543b7c6797d0d230ce23d90898aca14c7"
@@ -139,6 +393,248 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
+"@istanbuljs/load-nyc-config@^1.0.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
+ integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==
+ dependencies:
+ camelcase "^5.3.1"
+ find-up "^4.1.0"
+ get-package-type "^0.1.0"
+ js-yaml "^3.13.1"
+ resolve-from "^5.0.0"
+
+"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3":
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98"
+ integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==
+
+"@jest/console@^29.7.0":
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc"
+ integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==
+ dependencies:
+ "@jest/types" "^29.6.3"
+ "@types/node" "*"
+ chalk "^4.0.0"
+ jest-message-util "^29.7.0"
+ jest-util "^29.7.0"
+ slash "^3.0.0"
+
+"@jest/core@^29.7.0":
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f"
+ integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==
+ dependencies:
+ "@jest/console" "^29.7.0"
+ "@jest/reporters" "^29.7.0"
+ "@jest/test-result" "^29.7.0"
+ "@jest/transform" "^29.7.0"
+ "@jest/types" "^29.6.3"
+ "@types/node" "*"
+ ansi-escapes "^4.2.1"
+ chalk "^4.0.0"
+ ci-info "^3.2.0"
+ exit "^0.1.2"
+ graceful-fs "^4.2.9"
+ jest-changed-files "^29.7.0"
+ jest-config "^29.7.0"
+ jest-haste-map "^29.7.0"
+ jest-message-util "^29.7.0"
+ jest-regex-util "^29.6.3"
+ jest-resolve "^29.7.0"
+ jest-resolve-dependencies "^29.7.0"
+ jest-runner "^29.7.0"
+ jest-runtime "^29.7.0"
+ jest-snapshot "^29.7.0"
+ jest-util "^29.7.0"
+ jest-validate "^29.7.0"
+ jest-watcher "^29.7.0"
+ micromatch "^4.0.4"
+ pretty-format "^29.7.0"
+ slash "^3.0.0"
+ strip-ansi "^6.0.0"
+
+"@jest/environment@^29.7.0":
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7"
+ integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==
+ dependencies:
+ "@jest/fake-timers" "^29.7.0"
+ "@jest/types" "^29.6.3"
+ "@types/node" "*"
+ jest-mock "^29.7.0"
+
+"@jest/expect-utils@^29.7.0":
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6"
+ integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==
+ dependencies:
+ jest-get-type "^29.6.3"
+
+"@jest/expect@^29.7.0":
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2"
+ integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==
+ dependencies:
+ expect "^29.7.0"
+ jest-snapshot "^29.7.0"
+
+"@jest/fake-timers@^29.7.0":
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565"
+ integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==
+ dependencies:
+ "@jest/types" "^29.6.3"
+ "@sinonjs/fake-timers" "^10.0.2"
+ "@types/node" "*"
+ jest-message-util "^29.7.0"
+ jest-mock "^29.7.0"
+ jest-util "^29.7.0"
+
+"@jest/globals@^29.7.0":
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d"
+ integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==
+ dependencies:
+ "@jest/environment" "^29.7.0"
+ "@jest/expect" "^29.7.0"
+ "@jest/types" "^29.6.3"
+ jest-mock "^29.7.0"
+
+"@jest/reporters@^29.7.0":
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7"
+ integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==
+ dependencies:
+ "@bcoe/v8-coverage" "^0.2.3"
+ "@jest/console" "^29.7.0"
+ "@jest/test-result" "^29.7.0"
+ "@jest/transform" "^29.7.0"
+ "@jest/types" "^29.6.3"
+ "@jridgewell/trace-mapping" "^0.3.18"
+ "@types/node" "*"
+ chalk "^4.0.0"
+ collect-v8-coverage "^1.0.0"
+ exit "^0.1.2"
+ glob "^7.1.3"
+ graceful-fs "^4.2.9"
+ istanbul-lib-coverage "^3.0.0"
+ istanbul-lib-instrument "^6.0.0"
+ istanbul-lib-report "^3.0.0"
+ istanbul-lib-source-maps "^4.0.0"
+ istanbul-reports "^3.1.3"
+ jest-message-util "^29.7.0"
+ jest-util "^29.7.0"
+ jest-worker "^29.7.0"
+ slash "^3.0.0"
+ string-length "^4.0.1"
+ strip-ansi "^6.0.0"
+ v8-to-istanbul "^9.0.1"
+
+"@jest/schemas@^29.6.3":
+ version "29.6.3"
+ resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03"
+ integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==
+ dependencies:
+ "@sinclair/typebox" "^0.27.8"
+
+"@jest/source-map@^29.6.3":
+ version "29.6.3"
+ resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4"
+ integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==
+ dependencies:
+ "@jridgewell/trace-mapping" "^0.3.18"
+ callsites "^3.0.0"
+ graceful-fs "^4.2.9"
+
+"@jest/test-result@^29.7.0":
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c"
+ integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==
+ dependencies:
+ "@jest/console" "^29.7.0"
+ "@jest/types" "^29.6.3"
+ "@types/istanbul-lib-coverage" "^2.0.0"
+ collect-v8-coverage "^1.0.0"
+
+"@jest/test-sequencer@^29.7.0":
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce"
+ integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==
+ dependencies:
+ "@jest/test-result" "^29.7.0"
+ graceful-fs "^4.2.9"
+ jest-haste-map "^29.7.0"
+ slash "^3.0.0"
+
+"@jest/transform@^29.7.0":
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c"
+ integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==
+ dependencies:
+ "@babel/core" "^7.11.6"
+ "@jest/types" "^29.6.3"
+ "@jridgewell/trace-mapping" "^0.3.18"
+ babel-plugin-istanbul "^6.1.1"
+ chalk "^4.0.0"
+ convert-source-map "^2.0.0"
+ fast-json-stable-stringify "^2.1.0"
+ graceful-fs "^4.2.9"
+ jest-haste-map "^29.7.0"
+ jest-regex-util "^29.6.3"
+ jest-util "^29.7.0"
+ micromatch "^4.0.4"
+ pirates "^4.0.4"
+ slash "^3.0.0"
+ write-file-atomic "^4.0.2"
+
+"@jest/types@^29.6.3":
+ version "29.6.3"
+ resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59"
+ integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==
+ dependencies:
+ "@jest/schemas" "^29.6.3"
+ "@types/istanbul-lib-coverage" "^2.0.0"
+ "@types/istanbul-reports" "^3.0.0"
+ "@types/node" "*"
+ "@types/yargs" "^17.0.8"
+ chalk "^4.0.0"
+
+"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5":
+ version "0.3.13"
+ resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f"
+ integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==
+ dependencies:
+ "@jridgewell/sourcemap-codec" "^1.5.0"
+ "@jridgewell/trace-mapping" "^0.3.24"
+
+"@jridgewell/remapping@^2.3.5":
+ version "2.3.5"
+ resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1"
+ integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.5"
+ "@jridgewell/trace-mapping" "^0.3.24"
+
+"@jridgewell/resolve-uri@^3.1.0":
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
+ integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
+
+"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0":
+ version "1.5.5"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba"
+ integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
+
+"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28":
+ version "0.3.31"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0"
+ integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==
+ dependencies:
+ "@jridgewell/resolve-uri" "^3.1.0"
+ "@jridgewell/sourcemap-codec" "^1.4.14"
+
"@keyv/bigmap@^1.3.0":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@keyv/bigmap/-/bigmap-1.3.1.tgz#fc82fa83947e7ff68c6798d08907db842771ef2c"
@@ -183,21 +679,142 @@
resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8"
integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==
+"@sinclair/typebox@^0.27.8":
+ version "0.27.10"
+ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.10.tgz#beefe675f1853f73676aecc915b2bd2ac98c4fc6"
+ integrity sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==
+
"@sindresorhus/base62@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/base62/-/base62-1.0.0.tgz#c47c42410e5212e4fa4657670e118ddfba39acd6"
integrity sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA==
+"@sinonjs/commons@^3.0.0":
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd"
+ integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==
+ dependencies:
+ type-detect "4.0.8"
+
+"@sinonjs/fake-timers@^10.0.2":
+ version "10.3.0"
+ resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66"
+ integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==
+ dependencies:
+ "@sinonjs/commons" "^3.0.0"
+
+"@tootallnate/once@2":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
+ integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==
+
+"@types/babel__core@^7.1.14":
+ version "7.20.5"
+ resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017"
+ integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==
+ dependencies:
+ "@babel/parser" "^7.20.7"
+ "@babel/types" "^7.20.7"
+ "@types/babel__generator" "*"
+ "@types/babel__template" "*"
+ "@types/babel__traverse" "*"
+
+"@types/babel__generator@*":
+ version "7.27.0"
+ resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9"
+ integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==
+ dependencies:
+ "@babel/types" "^7.0.0"
+
+"@types/babel__template@*":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f"
+ integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==
+ dependencies:
+ "@babel/parser" "^7.1.0"
+ "@babel/types" "^7.0.0"
+
+"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6":
+ version "7.28.0"
+ resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74"
+ integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==
+ dependencies:
+ "@babel/types" "^7.28.2"
+
"@types/estree@^1.0.8":
version "1.0.8"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
+"@types/graceful-fs@^4.1.3":
+ version "4.1.9"
+ resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4"
+ integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==
+ dependencies:
+ "@types/node" "*"
+
+"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7"
+ integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==
+
+"@types/istanbul-lib-report@*":
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf"
+ integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==
+ dependencies:
+ "@types/istanbul-lib-coverage" "*"
+
+"@types/istanbul-reports@^3.0.0":
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54"
+ integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==
+ dependencies:
+ "@types/istanbul-lib-report" "*"
+
+"@types/jsdom@^20.0.0":
+ version "20.0.1"
+ resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-20.0.1.tgz#07c14bc19bd2f918c1929541cdaacae894744808"
+ integrity sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==
+ dependencies:
+ "@types/node" "*"
+ "@types/tough-cookie" "*"
+ parse5 "^7.0.0"
+
"@types/json5@^0.0.29":
version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
+"@types/node@*":
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-25.5.0.tgz#5c99f37c443d9ccc4985866913f1ed364217da31"
+ integrity sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==
+ dependencies:
+ undici-types "~7.18.0"
+
+"@types/stack-utils@^2.0.0":
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8"
+ integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==
+
+"@types/tough-cookie@*":
+ version "4.0.5"
+ resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304"
+ integrity sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==
+
+"@types/yargs-parser@*":
+ version "21.0.3"
+ resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15"
+ integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==
+
+"@types/yargs@^17.0.8":
+ version "17.0.35"
+ resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.35.tgz#07013e46aa4d7d7d50a49e15604c1c5340d4eb24"
+ integrity sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==
+ dependencies:
+ "@types/yargs-parser" "*"
+
"@typescript-eslint/types@^8.46.4":
version "8.56.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.56.0.tgz#a2444011b9a98ca13d70411d2cbfed5443b3526a"
@@ -208,16 +825,43 @@
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8"
integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==
+abab@^2.0.6:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
+ integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==
+
+acorn-globals@^7.0.0:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-7.0.1.tgz#0dbf05c44fa7c94332914c02066d5beff62c40c3"
+ integrity sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==
+ dependencies:
+ acorn "^8.1.0"
+ acorn-walk "^8.0.2"
+
acorn-jsx@^5.3.2:
version "5.3.2"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
-acorn@^8.15.0, acorn@^8.9.0:
+acorn-walk@^8.0.2:
+ version "8.3.5"
+ resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.5.tgz#8a6b8ca8fc5b34685af15dabb44118663c296496"
+ integrity sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==
+ dependencies:
+ acorn "^8.11.0"
+
+acorn@^8.1.0, acorn@^8.11.0, acorn@^8.15.0, acorn@^8.8.1, acorn@^8.9.0:
version "8.16.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a"
integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==
+agent-base@6:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
+ integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
+ dependencies:
+ debug "4"
+
ajv@^6.12.4:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
@@ -238,6 +882,13 @@ ajv@^8.0.1:
json-schema-traverse "^1.0.0"
require-from-string "^2.0.2"
+ansi-escapes@^4.2.1:
+ version "4.3.2"
+ resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
+ integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==
+ dependencies:
+ type-fest "^0.21.3"
+
ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
@@ -250,11 +901,31 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
dependencies:
color-convert "^2.0.1"
+ansi-styles@^5.0.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
+ integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
+
+anymatch@^3.0.3:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
+ integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
+ dependencies:
+ normalize-path "^3.0.0"
+ picomatch "^2.0.4"
+
are-docs-informative@^0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/are-docs-informative/-/are-docs-informative-0.0.2.tgz#387f0e93f5d45280373d387a59d34c96db321963"
integrity sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==
+argparse@^1.0.7:
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
+ integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
+ dependencies:
+ sprintf-js "~1.0.2"
+
argparse@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
@@ -343,6 +1014,11 @@ async-function@^1.0.0:
resolved "https://registry.yarnpkg.com/async-function/-/async-function-1.0.0.tgz#509c9fca60eaf85034c6829838188e4e4c8ffb2b"
integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==
+asynckit@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
+ integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
+
available-typed-arrays@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846"
@@ -350,6 +1026,69 @@ available-typed-arrays@^1.0.7:
dependencies:
possible-typed-array-names "^1.0.0"
+babel-jest@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5"
+ integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==
+ dependencies:
+ "@jest/transform" "^29.7.0"
+ "@types/babel__core" "^7.1.14"
+ babel-plugin-istanbul "^6.1.1"
+ babel-preset-jest "^29.6.3"
+ chalk "^4.0.0"
+ graceful-fs "^4.2.9"
+ slash "^3.0.0"
+
+babel-plugin-istanbul@^6.1.1:
+ version "6.1.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73"
+ integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@istanbuljs/load-nyc-config" "^1.0.0"
+ "@istanbuljs/schema" "^0.1.2"
+ istanbul-lib-instrument "^5.0.4"
+ test-exclude "^6.0.0"
+
+babel-plugin-jest-hoist@^29.6.3:
+ version "29.6.3"
+ resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626"
+ integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==
+ dependencies:
+ "@babel/template" "^7.3.3"
+ "@babel/types" "^7.3.3"
+ "@types/babel__core" "^7.1.14"
+ "@types/babel__traverse" "^7.0.6"
+
+babel-preset-current-node-syntax@^1.0.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz#20730d6cdc7dda5d89401cab10ac6a32067acde6"
+ integrity sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==
+ dependencies:
+ "@babel/plugin-syntax-async-generators" "^7.8.4"
+ "@babel/plugin-syntax-bigint" "^7.8.3"
+ "@babel/plugin-syntax-class-properties" "^7.12.13"
+ "@babel/plugin-syntax-class-static-block" "^7.14.5"
+ "@babel/plugin-syntax-import-attributes" "^7.24.7"
+ "@babel/plugin-syntax-import-meta" "^7.10.4"
+ "@babel/plugin-syntax-json-strings" "^7.8.3"
+ "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
+ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
+ "@babel/plugin-syntax-numeric-separator" "^7.10.4"
+ "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
+ "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
+ "@babel/plugin-syntax-optional-chaining" "^7.8.3"
+ "@babel/plugin-syntax-private-property-in-object" "^7.14.5"
+ "@babel/plugin-syntax-top-level-await" "^7.14.5"
+
+babel-preset-jest@^29.6.3:
+ version "29.6.3"
+ resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c"
+ integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==
+ dependencies:
+ babel-plugin-jest-hoist "^29.6.3"
+ babel-preset-current-node-syntax "^1.0.0"
+
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
@@ -360,6 +1099,11 @@ balanced-match@^2.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-2.0.0.tgz#dc70f920d78db8b858535795867bf48f820633d9"
integrity sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==
+baseline-browser-mapping@^2.9.0:
+ version "2.10.10"
+ resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.10.tgz#e74bd066724c1d8d7d8ea75fc3be25389a7a5c56"
+ integrity sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==
+
brace-expansion@^1.1.7:
version "1.1.12"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843"
@@ -375,6 +1119,29 @@ braces@^3.0.3:
dependencies:
fill-range "^7.1.1"
+browserslist@^4.24.0:
+ version "4.28.1"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95"
+ integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==
+ dependencies:
+ baseline-browser-mapping "^2.9.0"
+ caniuse-lite "^1.0.30001759"
+ electron-to-chromium "^1.5.263"
+ node-releases "^2.0.27"
+ update-browserslist-db "^1.2.0"
+
+bser@2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05"
+ integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==
+ dependencies:
+ node-int64 "^0.4.0"
+
+buffer-from@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
+ integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
+
cacheable@^2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/cacheable/-/cacheable-2.3.2.tgz#89800b4864c3ab8b23dae6dca8b6232cbf49b299"
@@ -417,6 +1184,21 @@ callsites@^3.0.0:
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
+camelcase@^5.3.1:
+ version "5.3.1"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
+ integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
+
+camelcase@^6.2.0:
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
+ integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
+
+caniuse-lite@^1.0.30001759:
+ version "1.0.30001781"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz#344b47c03eb8168b79c3c158b872bcfbdd02a400"
+ integrity sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==
+
chalk@^4.0.0:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
@@ -425,6 +1207,40 @@ chalk@^4.0.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
+char-regex@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
+ integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==
+
+ci-info@^3.2.0:
+ version "3.9.0"
+ resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4"
+ integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
+
+cjs-module-lexer@^1.0.0:
+ version "1.4.3"
+ resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d"
+ integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==
+
+cliui@^8.0.1:
+ version "8.0.1"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa"
+ integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==
+ dependencies:
+ string-width "^4.2.0"
+ strip-ansi "^6.0.1"
+ wrap-ansi "^7.0.0"
+
+co@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
+ integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==
+
+collect-v8-coverage@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz#cc1f01eb8d02298cbc9a437c74c70ab4e5210b80"
+ integrity sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==
+
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
@@ -442,6 +1258,13 @@ colord@^2.9.3:
resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43"
integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==
+combined-stream@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
+ integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
+ dependencies:
+ delayed-stream "~1.0.0"
+
comment-parser@1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.4.1.tgz#bdafead37961ac079be11eb7ec65c4d021eaf9cc"
@@ -462,6 +1285,11 @@ confusing-browser-globals@^1.0.10:
resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81"
integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==
+convert-source-map@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
+ integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
+
cosmiconfig@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-9.0.0.tgz#34c3fc58287b915f3ae905ab6dc3de258b55ad9d"
@@ -472,7 +1300,20 @@ cosmiconfig@^9.0.0:
js-yaml "^4.1.0"
parse-json "^5.2.0"
-cross-spawn@^7.0.2:
+create-jest@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320"
+ integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==
+ dependencies:
+ "@jest/types" "^29.6.3"
+ chalk "^4.0.0"
+ exit "^0.1.2"
+ graceful-fs "^4.2.9"
+ jest-config "^29.7.0"
+ jest-util "^29.7.0"
+ prompts "^2.0.1"
+
+cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.6"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
@@ -499,6 +1340,32 @@ cssesc@^3.0.0:
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
+cssom@^0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36"
+ integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==
+
+cssom@~0.3.6:
+ version "0.3.8"
+ resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a"
+ integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==
+
+cssstyle@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852"
+ integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==
+ dependencies:
+ cssom "~0.3.6"
+
+data-urls@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143"
+ integrity sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==
+ dependencies:
+ abab "^2.0.6"
+ whatwg-mimetype "^3.0.0"
+ whatwg-url "^11.0.0"
+
data-view-buffer@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz#211a03ba95ecaf7798a8c7198d79536211f88570"
@@ -526,6 +1393,13 @@ data-view-byte-offset@^1.0.1:
es-errors "^1.3.0"
is-data-view "^1.0.1"
+debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.4.3:
+ version "4.4.3"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
+ integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
+ dependencies:
+ ms "^2.1.3"
+
debug@^3.2.7:
version "3.2.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
@@ -533,18 +1407,26 @@ debug@^3.2.7:
dependencies:
ms "^2.1.1"
-debug@^4.3.1, debug@^4.3.2, debug@^4.4.3:
- version "4.4.3"
- resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
- integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
- dependencies:
- ms "^2.1.3"
+decimal.js@^10.4.2:
+ version "10.6.0"
+ resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.6.0.tgz#e649a43e3ab953a72192ff5983865e509f37ed9a"
+ integrity sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==
+
+dedent@^1.0.0:
+ version "1.7.2"
+ resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.7.2.tgz#34e2264ab538301e27cf7b07bf2369c19baa8dd9"
+ integrity sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==
deep-is@^0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
+deepmerge@^4.2.2:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a"
+ integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==
+
define-data-property@^1.0.1, define-data-property@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e"
@@ -563,11 +1445,26 @@ define-properties@^1.2.1:
has-property-descriptors "^1.0.0"
object-keys "^1.1.1"
+delayed-stream@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+ integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
+
+detect-newline@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
+ integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==
+
diff-sequences@^27.5.1:
version "27.5.1"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327"
integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==
+diff-sequences@^29.6.3:
+ version "29.6.3"
+ resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921"
+ integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==
+
dir-glob@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
@@ -589,6 +1486,13 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
+domexception@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673"
+ integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==
+ dependencies:
+ webidl-conversions "^7.0.0"
+
dunder-proto@^1.0.0, dunder-proto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a"
@@ -598,11 +1502,26 @@ dunder-proto@^1.0.0, dunder-proto@^1.0.1:
es-errors "^1.3.0"
gopd "^1.2.0"
+electron-to-chromium@^1.5.263:
+ version "1.5.322"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.322.tgz#9c24e49f7098ca19bc87c0e9c7e0ad6ffe4fddca"
+ integrity sha512-vFU34OcrvMcH66T+dYC3G4nURmgfDVewMIu6Q2urXpumAPSMmzvcn04KVVV8Opikq8Vs5nUbO/8laNhNRqSzYw==
+
+emittery@^0.13.1:
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad"
+ integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==
+
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+entities@^6.0.0:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/entities/-/entities-6.0.1.tgz#c28c34a43379ca7f61d074130b2f5f7020a30694"
+ integrity sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==
+
env-paths@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
@@ -718,11 +1637,32 @@ es-to-primitive@^1.3.0:
is-date-object "^1.0.5"
is-symbol "^1.0.4"
+escalade@^3.1.1, escalade@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5"
+ integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
+
escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+escape-string-regexp@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
+ integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
+
+escodegen@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17"
+ integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==
+ dependencies:
+ esprima "^4.0.1"
+ estraverse "^5.2.0"
+ esutils "^2.0.2"
+ optionalDependencies:
+ source-map "~0.6.1"
+
eslint-compat-utils@^0.6.0:
version "0.6.5"
resolved "https://registry.yarnpkg.com/eslint-compat-utils/-/eslint-compat-utils-0.6.5.tgz#6b06350a1c947c4514cfa64a170a6bfdbadc7ec2"
@@ -911,6 +1851,11 @@ espree@^9.6.0, espree@^9.6.1:
acorn-jsx "^5.3.2"
eslint-visitor-keys "^3.4.1"
+esprima@^4.0.0, esprima@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
+ integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
+
esquery@^1.4.2, esquery@^1.6.0, esquery@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d"
@@ -935,6 +1880,37 @@ esutils@^2.0.2:
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
+execa@^5.0.0:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
+ integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==
+ dependencies:
+ cross-spawn "^7.0.3"
+ get-stream "^6.0.0"
+ human-signals "^2.1.0"
+ is-stream "^2.0.0"
+ merge-stream "^2.0.0"
+ npm-run-path "^4.0.1"
+ onetime "^5.1.2"
+ signal-exit "^3.0.3"
+ strip-final-newline "^2.0.0"
+
+exit@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
+ integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==
+
+expect@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc"
+ integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==
+ dependencies:
+ "@jest/expect-utils" "^29.7.0"
+ jest-get-type "^29.6.3"
+ jest-matcher-utils "^29.7.0"
+ jest-message-util "^29.7.0"
+ jest-util "^29.7.0"
+
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@@ -956,7 +1932,7 @@ fast-glob@^3.2.9, fast-glob@^3.3.3:
merge2 "^1.3.0"
micromatch "^4.0.8"
-fast-json-stable-stringify@^2.0.0:
+fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
@@ -983,6 +1959,13 @@ fastq@^1.6.0:
dependencies:
reusify "^1.0.4"
+fb-watchman@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c"
+ integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==
+ dependencies:
+ bser "2.1.1"
+
file-entry-cache@^11.1.1:
version "11.1.2"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-11.1.2.tgz#5b2014aac2259b5591ae6fd7f6d1ed2153545abe"
@@ -1004,6 +1987,14 @@ fill-range@^7.1.1:
dependencies:
to-regex-range "^5.0.1"
+find-up@^4.0.0, find-up@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
+ integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
+ dependencies:
+ locate-path "^5.0.0"
+ path-exists "^4.0.0"
+
find-up@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
@@ -1042,11 +2033,27 @@ for-each@^0.3.3, for-each@^0.3.5:
dependencies:
is-callable "^1.2.7"
+form-data@^4.0.0:
+ version "4.0.5"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.5.tgz#b49e48858045ff4cbf6b03e1805cebcad3679053"
+ integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.8"
+ es-set-tostringtag "^2.1.0"
+ hasown "^2.0.2"
+ mime-types "^2.1.12"
+
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
+fsevents@^2.3.2:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
+ integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
+
function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
@@ -1074,6 +2081,16 @@ generator-function@^2.0.0:
resolved "https://registry.yarnpkg.com/generator-function/-/generator-function-2.0.1.tgz#0e75dd410d1243687a0ba2e951b94eedb8f737a2"
integrity sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==
+gensync@^1.0.0-beta.2:
+ version "1.0.0-beta.2"
+ resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
+ integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
+
+get-caller-file@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
+ integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
+
get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
@@ -1090,6 +2107,11 @@ get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@
hasown "^2.0.2"
math-intrinsics "^1.1.0"
+get-package-type@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
+ integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==
+
get-proto@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1"
@@ -1098,6 +2120,11 @@ get-proto@^1.0.1:
dunder-proto "^1.0.1"
es-object-atoms "^1.0.0"
+get-stream@^6.0.0:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
+ integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
+
get-symbol-description@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee"
@@ -1121,7 +2148,7 @@ glob-parent@^6.0.2:
dependencies:
is-glob "^4.0.3"
-glob@^7.1.3:
+glob@^7.1.3, glob@^7.1.4:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
@@ -1186,6 +2213,11 @@ gopd@^1.0.1, gopd@^1.2.0:
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
+graceful-fs@^4.2.9:
+ version "4.2.11"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
+ integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
+
graphemer@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
@@ -1246,16 +2278,57 @@ hookified@^1.14.0, hookified@^1.15.0:
resolved "https://registry.yarnpkg.com/hookified/-/hookified-1.15.1.tgz#b1fafeaa5489cdc29cb85546a8f837ed4ffbbcb6"
integrity sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==
+html-encoding-sniffer@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9"
+ integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==
+ dependencies:
+ whatwg-encoding "^2.0.0"
+
html-entities@^2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.6.0.tgz#7c64f1ea3b36818ccae3d3fb48b6974208e984f8"
integrity sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==
+html-escaper@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
+ integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
+
html-tags@^3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce"
integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==
+http-proxy-agent@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43"
+ integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==
+ dependencies:
+ "@tootallnate/once" "2"
+ agent-base "6"
+ debug "4"
+
+https-proxy-agent@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6"
+ integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==
+ dependencies:
+ agent-base "6"
+ debug "4"
+
+human-signals@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
+ integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
+
+iconv-lite@0.6.3:
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
+ integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3.0.0"
+
ignore@^5.2.0:
version "5.3.2"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
@@ -1274,6 +2347,14 @@ import-fresh@^3.2.1, import-fresh@^3.3.0:
parent-module "^1.0.0"
resolve-from "^4.0.0"
+import-local@^3.0.2:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260"
+ integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==
+ dependencies:
+ pkg-dir "^4.2.0"
+ resolve-cwd "^3.0.0"
+
imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
@@ -1392,6 +2473,11 @@ is-fullwidth-code-point@^3.0.0:
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+is-generator-fn@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118"
+ integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==
+
is-generator-function@^1.0.10:
version "1.1.2"
resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.2.tgz#ae3b61e3d5ea4e4839b90bad22b02335051a17d5"
@@ -1443,6 +2529,11 @@ is-plain-object@^5.0.0:
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
+is-potential-custom-element-name@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5"
+ integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==
+
is-regex@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22"
@@ -1465,6 +2556,11 @@ is-shared-array-buffer@^1.0.4:
dependencies:
call-bound "^1.0.3"
+is-stream@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
+ integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
+
is-string@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.1.tgz#92ea3f3d5c5b6e039ca8677e5ac8d07ea773cbb9"
@@ -1519,11 +2615,444 @@ isexe@^2.0.0:
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
+istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0:
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756"
+ integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==
+
+istanbul-lib-instrument@^5.0.4:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d"
+ integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==
+ dependencies:
+ "@babel/core" "^7.12.3"
+ "@babel/parser" "^7.14.7"
+ "@istanbuljs/schema" "^0.1.2"
+ istanbul-lib-coverage "^3.2.0"
+ semver "^6.3.0"
+
+istanbul-lib-instrument@^6.0.0:
+ version "6.0.3"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765"
+ integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==
+ dependencies:
+ "@babel/core" "^7.23.9"
+ "@babel/parser" "^7.23.9"
+ "@istanbuljs/schema" "^0.1.3"
+ istanbul-lib-coverage "^3.2.0"
+ semver "^7.5.4"
+
+istanbul-lib-report@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d"
+ integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==
+ dependencies:
+ istanbul-lib-coverage "^3.0.0"
+ make-dir "^4.0.0"
+ supports-color "^7.1.0"
+
+istanbul-lib-source-maps@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551"
+ integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==
+ dependencies:
+ debug "^4.1.1"
+ istanbul-lib-coverage "^3.0.0"
+ source-map "^0.6.1"
+
+istanbul-reports@^3.1.3:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz#cb4535162b5784aa623cee21a7252cf2c807ac93"
+ integrity sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==
+ dependencies:
+ html-escaper "^2.0.0"
+ istanbul-lib-report "^3.0.0"
+
+jest-changed-files@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a"
+ integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==
+ dependencies:
+ execa "^5.0.0"
+ jest-util "^29.7.0"
+ p-limit "^3.1.0"
+
+jest-circus@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a"
+ integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==
+ dependencies:
+ "@jest/environment" "^29.7.0"
+ "@jest/expect" "^29.7.0"
+ "@jest/test-result" "^29.7.0"
+ "@jest/types" "^29.6.3"
+ "@types/node" "*"
+ chalk "^4.0.0"
+ co "^4.6.0"
+ dedent "^1.0.0"
+ is-generator-fn "^2.0.0"
+ jest-each "^29.7.0"
+ jest-matcher-utils "^29.7.0"
+ jest-message-util "^29.7.0"
+ jest-runtime "^29.7.0"
+ jest-snapshot "^29.7.0"
+ jest-util "^29.7.0"
+ p-limit "^3.1.0"
+ pretty-format "^29.7.0"
+ pure-rand "^6.0.0"
+ slash "^3.0.0"
+ stack-utils "^2.0.3"
+
+jest-cli@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995"
+ integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==
+ dependencies:
+ "@jest/core" "^29.7.0"
+ "@jest/test-result" "^29.7.0"
+ "@jest/types" "^29.6.3"
+ chalk "^4.0.0"
+ create-jest "^29.7.0"
+ exit "^0.1.2"
+ import-local "^3.0.2"
+ jest-config "^29.7.0"
+ jest-util "^29.7.0"
+ jest-validate "^29.7.0"
+ yargs "^17.3.1"
+
+jest-config@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f"
+ integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==
+ dependencies:
+ "@babel/core" "^7.11.6"
+ "@jest/test-sequencer" "^29.7.0"
+ "@jest/types" "^29.6.3"
+ babel-jest "^29.7.0"
+ chalk "^4.0.0"
+ ci-info "^3.2.0"
+ deepmerge "^4.2.2"
+ glob "^7.1.3"
+ graceful-fs "^4.2.9"
+ jest-circus "^29.7.0"
+ jest-environment-node "^29.7.0"
+ jest-get-type "^29.6.3"
+ jest-regex-util "^29.6.3"
+ jest-resolve "^29.7.0"
+ jest-runner "^29.7.0"
+ jest-util "^29.7.0"
+ jest-validate "^29.7.0"
+ micromatch "^4.0.4"
+ parse-json "^5.2.0"
+ pretty-format "^29.7.0"
+ slash "^3.0.0"
+ strip-json-comments "^3.1.1"
+
+jest-diff@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a"
+ integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==
+ dependencies:
+ chalk "^4.0.0"
+ diff-sequences "^29.6.3"
+ jest-get-type "^29.6.3"
+ pretty-format "^29.7.0"
+
+jest-docblock@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a"
+ integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==
+ dependencies:
+ detect-newline "^3.0.0"
+
+jest-each@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1"
+ integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==
+ dependencies:
+ "@jest/types" "^29.6.3"
+ chalk "^4.0.0"
+ jest-get-type "^29.6.3"
+ jest-util "^29.7.0"
+ pretty-format "^29.7.0"
+
+jest-environment-jsdom@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz#d206fa3551933c3fd519e5dfdb58a0f5139a837f"
+ integrity sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==
+ dependencies:
+ "@jest/environment" "^29.7.0"
+ "@jest/fake-timers" "^29.7.0"
+ "@jest/types" "^29.6.3"
+ "@types/jsdom" "^20.0.0"
+ "@types/node" "*"
+ jest-mock "^29.7.0"
+ jest-util "^29.7.0"
+ jsdom "^20.0.0"
+
+jest-environment-node@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376"
+ integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==
+ dependencies:
+ "@jest/environment" "^29.7.0"
+ "@jest/fake-timers" "^29.7.0"
+ "@jest/types" "^29.6.3"
+ "@types/node" "*"
+ jest-mock "^29.7.0"
+ jest-util "^29.7.0"
+
+jest-get-type@^29.6.3:
+ version "29.6.3"
+ resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1"
+ integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==
+
+jest-haste-map@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104"
+ integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==
+ dependencies:
+ "@jest/types" "^29.6.3"
+ "@types/graceful-fs" "^4.1.3"
+ "@types/node" "*"
+ anymatch "^3.0.3"
+ fb-watchman "^2.0.0"
+ graceful-fs "^4.2.9"
+ jest-regex-util "^29.6.3"
+ jest-util "^29.7.0"
+ jest-worker "^29.7.0"
+ micromatch "^4.0.4"
+ walker "^1.0.8"
+ optionalDependencies:
+ fsevents "^2.3.2"
+
+jest-leak-detector@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728"
+ integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==
+ dependencies:
+ jest-get-type "^29.6.3"
+ pretty-format "^29.7.0"
+
+jest-matcher-utils@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12"
+ integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==
+ dependencies:
+ chalk "^4.0.0"
+ jest-diff "^29.7.0"
+ jest-get-type "^29.6.3"
+ pretty-format "^29.7.0"
+
+jest-message-util@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3"
+ integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==
+ dependencies:
+ "@babel/code-frame" "^7.12.13"
+ "@jest/types" "^29.6.3"
+ "@types/stack-utils" "^2.0.0"
+ chalk "^4.0.0"
+ graceful-fs "^4.2.9"
+ micromatch "^4.0.4"
+ pretty-format "^29.7.0"
+ slash "^3.0.0"
+ stack-utils "^2.0.3"
+
+jest-mock@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347"
+ integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==
+ dependencies:
+ "@jest/types" "^29.6.3"
+ "@types/node" "*"
+ jest-util "^29.7.0"
+
+jest-pnp-resolver@^1.2.2:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e"
+ integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==
+
+jest-regex-util@^29.6.3:
+ version "29.6.3"
+ resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52"
+ integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==
+
+jest-resolve-dependencies@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428"
+ integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==
+ dependencies:
+ jest-regex-util "^29.6.3"
+ jest-snapshot "^29.7.0"
+
+jest-resolve@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30"
+ integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==
+ dependencies:
+ chalk "^4.0.0"
+ graceful-fs "^4.2.9"
+ jest-haste-map "^29.7.0"
+ jest-pnp-resolver "^1.2.2"
+ jest-util "^29.7.0"
+ jest-validate "^29.7.0"
+ resolve "^1.20.0"
+ resolve.exports "^2.0.0"
+ slash "^3.0.0"
+
+jest-runner@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e"
+ integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==
+ dependencies:
+ "@jest/console" "^29.7.0"
+ "@jest/environment" "^29.7.0"
+ "@jest/test-result" "^29.7.0"
+ "@jest/transform" "^29.7.0"
+ "@jest/types" "^29.6.3"
+ "@types/node" "*"
+ chalk "^4.0.0"
+ emittery "^0.13.1"
+ graceful-fs "^4.2.9"
+ jest-docblock "^29.7.0"
+ jest-environment-node "^29.7.0"
+ jest-haste-map "^29.7.0"
+ jest-leak-detector "^29.7.0"
+ jest-message-util "^29.7.0"
+ jest-resolve "^29.7.0"
+ jest-runtime "^29.7.0"
+ jest-util "^29.7.0"
+ jest-watcher "^29.7.0"
+ jest-worker "^29.7.0"
+ p-limit "^3.1.0"
+ source-map-support "0.5.13"
+
+jest-runtime@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817"
+ integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==
+ dependencies:
+ "@jest/environment" "^29.7.0"
+ "@jest/fake-timers" "^29.7.0"
+ "@jest/globals" "^29.7.0"
+ "@jest/source-map" "^29.6.3"
+ "@jest/test-result" "^29.7.0"
+ "@jest/transform" "^29.7.0"
+ "@jest/types" "^29.6.3"
+ "@types/node" "*"
+ chalk "^4.0.0"
+ cjs-module-lexer "^1.0.0"
+ collect-v8-coverage "^1.0.0"
+ glob "^7.1.3"
+ graceful-fs "^4.2.9"
+ jest-haste-map "^29.7.0"
+ jest-message-util "^29.7.0"
+ jest-mock "^29.7.0"
+ jest-regex-util "^29.6.3"
+ jest-resolve "^29.7.0"
+ jest-snapshot "^29.7.0"
+ jest-util "^29.7.0"
+ slash "^3.0.0"
+ strip-bom "^4.0.0"
+
+jest-snapshot@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5"
+ integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==
+ dependencies:
+ "@babel/core" "^7.11.6"
+ "@babel/generator" "^7.7.2"
+ "@babel/plugin-syntax-jsx" "^7.7.2"
+ "@babel/plugin-syntax-typescript" "^7.7.2"
+ "@babel/types" "^7.3.3"
+ "@jest/expect-utils" "^29.7.0"
+ "@jest/transform" "^29.7.0"
+ "@jest/types" "^29.6.3"
+ babel-preset-current-node-syntax "^1.0.0"
+ chalk "^4.0.0"
+ expect "^29.7.0"
+ graceful-fs "^4.2.9"
+ jest-diff "^29.7.0"
+ jest-get-type "^29.6.3"
+ jest-matcher-utils "^29.7.0"
+ jest-message-util "^29.7.0"
+ jest-util "^29.7.0"
+ natural-compare "^1.4.0"
+ pretty-format "^29.7.0"
+ semver "^7.5.3"
+
+jest-util@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc"
+ integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==
+ dependencies:
+ "@jest/types" "^29.6.3"
+ "@types/node" "*"
+ chalk "^4.0.0"
+ ci-info "^3.2.0"
+ graceful-fs "^4.2.9"
+ picomatch "^2.2.3"
+
+jest-validate@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c"
+ integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==
+ dependencies:
+ "@jest/types" "^29.6.3"
+ camelcase "^6.2.0"
+ chalk "^4.0.0"
+ jest-get-type "^29.6.3"
+ leven "^3.1.0"
+ pretty-format "^29.7.0"
+
+jest-watcher@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2"
+ integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==
+ dependencies:
+ "@jest/test-result" "^29.7.0"
+ "@jest/types" "^29.6.3"
+ "@types/node" "*"
+ ansi-escapes "^4.2.1"
+ chalk "^4.0.0"
+ emittery "^0.13.1"
+ jest-util "^29.7.0"
+ string-length "^4.0.1"
+
+jest-worker@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a"
+ integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==
+ dependencies:
+ "@types/node" "*"
+ jest-util "^29.7.0"
+ merge-stream "^2.0.0"
+ supports-color "^8.0.0"
+
+jest@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613"
+ integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==
+ dependencies:
+ "@jest/core" "^29.7.0"
+ "@jest/types" "^29.6.3"
+ import-local "^3.0.2"
+ jest-cli "^29.7.0"
+
js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+js-yaml@^3.13.1:
+ version "3.14.2"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.2.tgz#77485ce1dd7f33c061fd1b16ecea23b55fcb04b0"
+ integrity sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==
+ dependencies:
+ argparse "^1.0.7"
+ esprima "^4.0.0"
+
js-yaml@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b"
@@ -1536,6 +3065,43 @@ jsdoc-type-pratt-parser@~7.0.0:
resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-7.0.0.tgz#b0381514e79aa6326185078059ca5915bb8ede3b"
integrity sha512-c7YbokssPOSHmqTbSAmTtnVgAVa/7lumWNYqomgd5KOMyPrRve2anx6lonfOsXEQacqF9FKVUj7bLg4vRSvdYA==
+jsdom@^20.0.0:
+ version "20.0.3"
+ resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-20.0.3.tgz#886a41ba1d4726f67a8858028c99489fed6ad4db"
+ integrity sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==
+ dependencies:
+ abab "^2.0.6"
+ acorn "^8.8.1"
+ acorn-globals "^7.0.0"
+ cssom "^0.5.0"
+ cssstyle "^2.3.0"
+ data-urls "^3.0.2"
+ decimal.js "^10.4.2"
+ domexception "^4.0.0"
+ escodegen "^2.0.0"
+ form-data "^4.0.0"
+ html-encoding-sniffer "^3.0.0"
+ http-proxy-agent "^5.0.0"
+ https-proxy-agent "^5.0.1"
+ is-potential-custom-element-name "^1.0.1"
+ nwsapi "^2.2.2"
+ parse5 "^7.1.1"
+ saxes "^6.0.0"
+ symbol-tree "^3.2.4"
+ tough-cookie "^4.1.2"
+ w3c-xmlserializer "^4.0.0"
+ webidl-conversions "^7.0.0"
+ whatwg-encoding "^2.0.0"
+ whatwg-mimetype "^3.0.0"
+ whatwg-url "^11.0.0"
+ ws "^8.11.0"
+ xml-name-validator "^4.0.0"
+
+jsesc@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d"
+ integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==
+
json-buffer@3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
@@ -1568,6 +3134,11 @@ json5@^1.0.2:
dependencies:
minimist "^1.2.0"
+json5@^2.2.3:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
+ integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
+
keyv@^4.5.3:
version "4.5.4"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
@@ -1587,11 +3158,21 @@ kind-of@^6.0.2:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
+kleur@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
+ integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
+
known-css-properties@^0.37.0:
version "0.37.0"
resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.37.0.tgz#10ebe49b9dbb6638860ff8a002fb65a053f4aec5"
integrity sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==
+leven@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
+ integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==
+
levn@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
@@ -1605,6 +3186,13 @@ lines-and-columns@^1.1.6:
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
+locate-path@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
+ integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
+ dependencies:
+ p-locate "^4.1.0"
+
locate-path@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
@@ -1622,6 +3210,27 @@ lodash.truncate@^4.4.2:
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==
+lru-cache@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
+ integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
+ dependencies:
+ yallist "^3.0.2"
+
+make-dir@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e"
+ integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==
+ dependencies:
+ semver "^7.5.3"
+
+makeerror@1.0.12:
+ version "1.0.12"
+ resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a"
+ integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==
+ dependencies:
+ tmpl "1.0.5"
+
math-intrinsics@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
@@ -1642,12 +3251,17 @@ meow@^13.2.0:
resolved "https://registry.yarnpkg.com/meow/-/meow-13.2.0.tgz#6b7d63f913f984063b3cc261b6e8800c4cd3474f"
integrity sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==
+merge-stream@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
+ integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
+
merge2@^1.3.0, merge2@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
-micromatch@^4.0.8:
+micromatch@^4.0.4, micromatch@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
@@ -1655,6 +3269,30 @@ micromatch@^4.0.8:
braces "^3.0.3"
picomatch "^2.3.1"
+mime-db@1.52.0:
+ version "1.52.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
+ integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
+mime-types@^2.1.12:
+ version "2.1.35"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
+ integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+ dependencies:
+ mime-db "1.52.0"
+
+mimic-fn@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
+ integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
+
+minimatch@^3.0.4:
+ version "3.1.5"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.5.tgz#580c88f8d5445f2bd6aa8f3cadefa0de79fbd69e"
+ integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==
+ dependencies:
+ brace-expansion "^1.1.7"
+
minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
@@ -1682,11 +3320,33 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
+node-int64@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
+ integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==
+
+node-releases@^2.0.27:
+ version "2.0.36"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.36.tgz#99fd6552aaeda9e17c4713b57a63964a2e325e9d"
+ integrity sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==
+
normalize-path@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+npm-run-path@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
+ integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
+ dependencies:
+ path-key "^3.0.0"
+
+nwsapi@^2.2.2:
+ version "2.2.23"
+ resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.23.tgz#59712c3a88e6de2bb0b6ccc1070397267019cf6c"
+ integrity sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==
+
object-deep-merge@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/object-deep-merge/-/object-deep-merge-2.0.0.tgz#94d24cf713d4a7a143653500ff4488a2d494681f"
@@ -1760,6 +3420,13 @@ once@^1.3.0:
dependencies:
wrappy "1"
+onetime@^5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
+ integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
+ dependencies:
+ mimic-fn "^2.1.0"
+
optionator@^0.9.3:
version "0.9.4"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734"
@@ -1781,13 +3448,27 @@ own-keys@^1.0.1:
object-keys "^1.1.1"
safe-push-apply "^1.0.0"
-p-limit@^3.0.2:
+p-limit@^2.2.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
+ integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
+ dependencies:
+ p-try "^2.0.0"
+
+p-limit@^3.0.2, p-limit@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
dependencies:
yocto-queue "^0.1.0"
+p-locate@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
+ integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
+ dependencies:
+ p-limit "^2.2.0"
+
p-locate@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
@@ -1795,6 +3476,11 @@ p-locate@^5.0.0:
dependencies:
p-limit "^3.0.2"
+p-try@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
+ integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
+
parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
@@ -1824,6 +3510,13 @@ parse-statements@1.0.11:
resolved "https://registry.yarnpkg.com/parse-statements/-/parse-statements-1.0.11.tgz#8787c5d383ae5746568571614be72b0689584344"
integrity sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==
+parse5@^7.0.0, parse5@^7.1.1:
+ version "7.3.0"
+ resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.3.0.tgz#d7e224fa72399c7a175099f45fc2ad024b05ec05"
+ integrity sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==
+ dependencies:
+ entities "^6.0.0"
+
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
@@ -1834,7 +3527,7 @@ path-is-absolute@^1.0.0:
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
-path-key@^3.1.0:
+path-key@^3.0.0, path-key@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
@@ -1854,11 +3547,28 @@ picocolors@^1.1.1:
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
+picomatch@^2.0.4, picomatch@^2.2.3:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.2.tgz#5a942915e26b372dc0f0e6753149a16e6b1c5601"
+ integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==
+
picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+pirates@^4.0.4:
+ version "4.0.7"
+ resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.7.tgz#643b4a18c4257c8a65104b73f3049ce9a0a15e22"
+ integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==
+
+pkg-dir@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
+ integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==
+ dependencies:
+ find-up "^4.0.0"
+
possible-typed-array-names@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae"
@@ -1918,11 +3628,40 @@ prettier@^3.7.4:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.1.tgz#edf48977cf991558f4fcbd8a3ba6015ba2a3a173"
integrity sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==
-punycode@^2.1.0:
+pretty-format@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812"
+ integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==
+ dependencies:
+ "@jest/schemas" "^29.6.3"
+ ansi-styles "^5.0.0"
+ react-is "^18.0.0"
+
+prompts@^2.0.1:
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"
+ integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==
+ dependencies:
+ kleur "^3.0.3"
+ sisteransi "^1.0.5"
+
+psl@^1.1.33:
+ version "1.15.0"
+ resolved "https://registry.yarnpkg.com/psl/-/psl-1.15.0.tgz#bdace31896f1d97cec6a79e8224898ce93d974c6"
+ integrity sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==
+ dependencies:
+ punycode "^2.3.1"
+
+punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
+pure-rand@^6.0.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2"
+ integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==
+
qified@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/qified/-/qified-0.6.0.tgz#a9c33f51fa37d03003065638bb7dff45d7a81297"
@@ -1930,6 +3669,11 @@ qified@^0.6.0:
dependencies:
hookified "^1.14.0"
+querystringify@^2.1.1:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
+ integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
+
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
@@ -1940,6 +3684,11 @@ ramda@0.32.0:
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.32.0.tgz#b2116807b59b6b177af7a2ad19b14a3653570e96"
integrity sha512-GQWAHhxhxWBWA8oIBr1XahFVjQ9Fic6MK9ikijfd4TZHfE2+urfk+irVlR5VOn48uwMgM+loRRBJd6Yjsbc0zQ==
+react-is@^18.0.0:
+ version "18.3.1"
+ resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
+ integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
+
reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9:
version "1.0.10"
resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9"
@@ -1966,16 +3715,33 @@ regexp.prototype.flags@^1.5.4:
gopd "^1.2.0"
set-function-name "^2.0.2"
+require-directory@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
+ integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
+
require-from-string@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
+requires-port@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
+ integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
+
reserved-identifiers@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/reserved-identifiers/-/reserved-identifiers-1.2.0.tgz#d2982cd698e317dd3dced1ee1c52412dbd64fc64"
integrity sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==
+resolve-cwd@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
+ integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==
+ dependencies:
+ resolve-from "^5.0.0"
+
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
@@ -1986,7 +3752,12 @@ resolve-from@^5.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
-resolve@^1.22.4:
+resolve.exports@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f"
+ integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==
+
+resolve@^1.20.0, resolve@^1.22.4:
version "1.22.11"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262"
integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==
@@ -2042,12 +3813,24 @@ safe-regex-test@^1.1.0:
es-errors "^1.3.0"
is-regex "^1.2.1"
+"safer-buffer@>= 2.1.2 < 3.0.0":
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
+ integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+
+saxes@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5"
+ integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==
+ dependencies:
+ xmlchars "^2.2.0"
+
semver@^6.3.0, semver@^6.3.1:
version "6.3.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
-semver@^7.5.4, semver@^7.7.3:
+semver@^7.5.3, semver@^7.5.4, semver@^7.7.3:
version "7.7.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a"
integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==
@@ -2135,11 +3918,21 @@ side-channel@^1.1.0:
side-channel-map "^1.0.1"
side-channel-weakmap "^1.0.2"
+signal-exit@^3.0.3, signal-exit@^3.0.7:
+ version "3.0.7"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
+ integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
+
signal-exit@^4.0.1:
version "4.1.0"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
+sisteransi@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
+ integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
+
slash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
@@ -2159,6 +3952,19 @@ source-map-js@^1.0.1, source-map-js@^1.2.1:
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
+source-map-support@0.5.13:
+ version "0.5.13"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932"
+ integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==
+ dependencies:
+ buffer-from "^1.0.0"
+ source-map "^0.6.0"
+
+source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
+ integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+
spdx-exceptions@^2.1.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz#5d607d27fc806f66d7b64a766650fa890f04ed66"
@@ -2177,6 +3983,18 @@ spdx-license-ids@^3.0.0:
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz#abf5a08a6f5d7279559b669f47f0a43e8f3464ef"
integrity sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==
+sprintf-js@~1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
+ integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
+
+stack-utils@^2.0.3:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f"
+ integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==
+ dependencies:
+ escape-string-regexp "^2.0.0"
+
stop-iteration-iterator@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz#f481ff70a548f6124d0312c3aa14cbfa7aa542ad"
@@ -2185,7 +4003,15 @@ stop-iteration-iterator@^1.1.0:
es-errors "^1.3.0"
internal-slot "^1.1.0"
-string-width@^4.2.3:
+string-length@^4.0.1:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a"
+ integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==
+ dependencies:
+ char-regex "^1.0.2"
+ strip-ansi "^6.0.0"
+
+string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -2226,7 +4052,7 @@ string.prototype.trimstart@^1.0.8:
define-properties "^1.2.1"
es-object-atoms "^1.0.0"
-strip-ansi@^6.0.1:
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -2238,6 +4064,16 @@ strip-bom@^3.0.0:
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==
+strip-bom@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878"
+ integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==
+
+strip-final-newline@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
+ integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
+
strip-json-comments@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
@@ -2315,6 +4151,13 @@ supports-color@^7.0.0, supports-color@^7.1.0:
dependencies:
has-flag "^4.0.0"
+supports-color@^8.0.0:
+ version "8.1.1"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
+ integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
+ dependencies:
+ has-flag "^4.0.0"
+
supports-hyperlinks@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz#b8e485b179681dea496a1e7abdf8985bd3145461"
@@ -2333,6 +4176,11 @@ svg-tags@^1.0.0:
resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
integrity sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==
+symbol-tree@^3.2.4:
+ version "3.2.4"
+ resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
+ integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
+
synckit@^0.11.12:
version "0.11.12"
resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.12.tgz#abe74124264fbc00a48011b0d98bdc1cffb64a7b"
@@ -2351,11 +4199,25 @@ table@^6.9.0:
string-width "^4.2.3"
strip-ansi "^6.0.1"
+test-exclude@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e"
+ integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==
+ dependencies:
+ "@istanbuljs/schema" "^0.1.2"
+ glob "^7.1.4"
+ minimatch "^3.0.4"
+
text-table@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
+tmpl@1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
+ integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==
+
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
@@ -2371,6 +4233,23 @@ to-valid-identifier@^1.0.0:
"@sindresorhus/base62" "^1.0.0"
reserved-identifiers "^1.0.0"
+tough-cookie@^4.1.2:
+ version "4.1.4"
+ resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.4.tgz#945f1461b45b5a8c76821c33ea49c3ac192c1b36"
+ integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==
+ dependencies:
+ psl "^1.1.33"
+ punycode "^2.1.1"
+ universalify "^0.2.0"
+ url-parse "^1.5.3"
+
+tr46@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9"
+ integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==
+ dependencies:
+ punycode "^2.1.1"
+
tsconfig-paths@^3.15.0:
version "3.15.0"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4"
@@ -2388,11 +4267,21 @@ type-check@^0.4.0, type-check@~0.4.0:
dependencies:
prelude-ls "^1.2.1"
+type-detect@4.0.8:
+ version "4.0.8"
+ resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
+ integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
+
type-fest@^0.20.2:
version "0.20.2"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
+type-fest@^0.21.3:
+ version "0.21.3"
+ resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
+ integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
+
typed-array-buffer@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536"
@@ -2448,6 +4337,24 @@ unbox-primitive@^1.1.0:
has-symbols "^1.1.0"
which-boxed-primitive "^1.1.1"
+undici-types@~7.18.0:
+ version "7.18.2"
+ resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.18.2.tgz#29357a89e7b7ca4aef3bf0fd3fd0cd73884229e9"
+ integrity sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==
+
+universalify@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
+ integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==
+
+update-browserslist-db@^1.2.0:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d"
+ integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==
+ dependencies:
+ escalade "^3.2.0"
+ picocolors "^1.1.1"
+
uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
@@ -2455,11 +4362,67 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"
+url-parse@^1.5.3:
+ version "1.5.10"
+ resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
+ integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
+ dependencies:
+ querystringify "^2.1.1"
+ requires-port "^1.0.0"
+
util-deprecate@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
+v8-to-istanbul@^9.0.1:
+ version "9.3.0"
+ resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175"
+ integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==
+ dependencies:
+ "@jridgewell/trace-mapping" "^0.3.12"
+ "@types/istanbul-lib-coverage" "^2.0.1"
+ convert-source-map "^2.0.0"
+
+w3c-xmlserializer@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073"
+ integrity sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==
+ dependencies:
+ xml-name-validator "^4.0.0"
+
+walker@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f"
+ integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==
+ dependencies:
+ makeerror "1.0.12"
+
+webidl-conversions@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"
+ integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==
+
+whatwg-encoding@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53"
+ integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==
+ dependencies:
+ iconv-lite "0.6.3"
+
+whatwg-mimetype@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7"
+ integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==
+
+whatwg-url@^11.0.0:
+ version "11.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018"
+ integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==
+ dependencies:
+ tr46 "^3.0.0"
+ webidl-conversions "^7.0.0"
+
which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz#d76ec27df7fa165f18d5808374a5fe23c29b176e"
@@ -2532,11 +4495,28 @@ word-wrap@^1.2.5:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
+write-file-atomic@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd"
+ integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==
+ dependencies:
+ imurmurhash "^0.1.4"
+ signal-exit "^3.0.7"
+
write-file-atomic@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-5.0.1.tgz#68df4717c55c6fa4281a7860b4c2ba0a6d2b11e7"
@@ -2545,6 +4525,31 @@ write-file-atomic@^5.0.1:
imurmurhash "^0.1.4"
signal-exit "^4.0.1"
+ws@^8.11.0:
+ version "8.20.0"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-8.20.0.tgz#4cd9532358eba60bc863aad1623dfb045a4d4af8"
+ integrity sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==
+
+xml-name-validator@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835"
+ integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==
+
+xmlchars@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
+ integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
+
+y18n@^5.0.5:
+ version "5.0.8"
+ resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
+ integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
+
+yallist@^3.0.2:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
+ integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
+
yaml-eslint-parser@^1.2.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/yaml-eslint-parser/-/yaml-eslint-parser-1.3.2.tgz#f62010fe8293d39930422d70e46f23b075bd13b5"
@@ -2558,6 +4563,24 @@ yaml@^2.0.0:
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.2.tgz#5694f25eca0ce9c3e7a9d9e00ce0ddabbd9e35c5"
integrity sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==
+yargs-parser@^21.1.1:
+ version "21.1.1"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
+ integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
+
+yargs@^17.3.1:
+ version "17.7.2"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
+ integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
+ dependencies:
+ cliui "^8.0.1"
+ escalade "^3.1.1"
+ get-caller-file "^2.0.5"
+ require-directory "^2.1.1"
+ string-width "^4.2.3"
+ y18n "^5.0.5"
+ yargs-parser "^21.1.1"
+
yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"