Skip to content

[#2394] Added Jest for JavaScript unit testing.#2418

Merged
AlexSkrypnyk merged 36 commits intomainfrom
feature/2394-jest-js-testing
Mar 25, 2026
Merged

[#2394] Added Jest for JavaScript unit testing.#2418
AlexSkrypnyk merged 36 commits intomainfrom
feature/2394-jest-js-testing

Conversation

@AlexSkrypnyk
Copy link
Copy Markdown
Member

@AlexSkrypnyk AlexSkrypnyk commented Mar 24, 2026

Closes #2394

Summary

Added Jest as a first-class JavaScript unit testing tool in Vortex. This includes a jest.config.js that auto-discovers test files in custom module js/ directories, jest and jest-environment-jsdom dependencies in package.json, and a new test-js ahoy command. CI pipelines for both GitHub Actions and CircleCI were updated to run Jest tests as a separate step, controlled by a VORTEX_CI_NODEJS_TEST_IGNORE_FAILURE flag.

The ys_demo module's JavaScript was refactored to extract logic into testable methods (applyAction, getCounterValue) and a comprehensive test suite (ys_demo.test.js) was added to demonstrate and validate the pattern.

The installer's Tools handler was extended to support Jest as a selectable tool with proper discovery, removal logic, and group membership. The frontend_linting group was split into frontend_linting (ESLint/Stylelint only) and a new frontend_all group (ESLint + Stylelint + Jest) that controls shared yarn install and package.json/yarn.lock file removal.

Changes

Jest tooling

  • jest.config.js — new config that discovers js/ directories inside web/modules/custom/*/ as Jest roots, using jsdom environment and matching *.test.js files
  • package.json — added jest and jest-environment-jsdom devDependencies; added "test": "jest" script
  • yarn.lock — updated with new Jest dependency tree

Ahoy command

  • .ahoy.yml — removed test-js alias from test-functional-javascript; added new test-js command under TOOL_JEST token that runs yarn test

CI pipelines

  • .circleci/config.yml — added yarn install step under TOOL_JEST token in install phase; added "Test module code with Jest" run step
  • .github/workflows/build-test-deploy.yml — same additions for GitHub Actions; Jest step uses continue-on-error controlled by VORTEX_CI_NODEJS_TEST_IGNORE_FAILURE

ESLint config

  • .eslintrc.json — added overrides block for *.test.js files: enables jest env globals, disables no-eval, relaxes max-nested-callbacks

Demo module refactor

  • web/modules/custom/ys_demo/js/ys_demo.js — extracted inline switch into applyAction() method; extracted storageKey to a behaviour property; refactored arrow functions for testability
  • web/modules/custom/ys_demo/js/ys_demo.test.js — new 270-line test suite covering storageKey, getCounterValue, applyAction, and full DOM interaction tests for counter blocks

Installer

  • .vortex/installer/src/Prompts/Handlers/Tools.php — added JEST constant; registered Jest in tool definitions with present detection, package.json removal callbacks, file list, and AGENTS.md lines; split frontend_linting group into separate frontend_linting, frontend_testing, and frontend_all groups; updated frontend_all check to control yarn install removal
  • .vortex/installer/tests/Functional/Handlers/ToolsHandlerProcessTest.php — added tools_no_jest and tools_no_jest_circleci test cases; updated existing no-FE-lint cases to also exclude Jest
  • .vortex/installer/tests/Unit/Handlers/ToolsHandlerDiscoveryTest.php — added Jest to expected default/installed tool lists; added jest and jest.config.js discovery test cases

Docs

  • AGENTS.md — added # Jest testing section with ahoy test-js command

Before / After

Tool group structure

Before:
  ┌─────────────────────────────────┐
  │ frontend_linting                │
  │   tools: [eslint, stylelint]    │
  │   files: [package.json,         │
  │           yarn.lock]            │
  │   ahoy: [yarn install,          │
  │          yarn lint,             │
  │          yarn lint-fix]         │
  └─────────────────────────────────┘

After:
  ┌─────────────────────────────────┐
  │ frontend_linting                │
  │   tools: [eslint, stylelint]    │
  │   ahoy: [yarn lint,             │
  │          yarn lint-fix]         │
  ├─────────────────────────────────┤
  │ frontend_testing                │
  │   tools: [jest]                 │
  │   token: TOOL_JEST              │
  ├─────────────────────────────────┤
  │ frontend_all  (new)             │
  │   tools: [eslint, stylelint,    │
  │           jest]                 │
  │   files: [package.json,         │
  │           yarn.lock]            │
  │   ahoy: [yarn install]          │
  └─────────────────────────────────┘

ahoy test-js command

Before:
  test-functional-javascript:
    aliases: [test-js]   ← test-js pointed to PHPUnit JS tests

After:
  test-functional-javascript:
    (no alias)

  test-js:               ← new dedicated command
    cmd: ahoy cli "yarn test"   ← runs Jest

Summary by CodeRabbit

  • New Features

    • Added Jest support and an ahoy test-js command that runs the Jest unit test suite; installer can include Jest as an optional tool.
  • Tests

    • Added comprehensive Jest tests for the demo counter behavior and updated installer test expectations.
  • CI

    • Workflows now install Node deps and run Jest (with an optional continue-on-error flag).
  • Refactor

    • Demo counter code refactored for clearer state handling.
  • Lint

    • ESLint enabled for Jest test files.
  • Docs

    • Development guide updated with Jest testing instructions.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Add Jest-based JS unit testing: introduce Jest config and deps, new ahoy test-js command, CI steps to install and run Jest, installer/tooling support for detecting/removing Jest, ESLint test overrides, a refactor to the demo counter for testability, and a comprehensive Jest test suite for the demo module.

Changes

Cohort / File(s) Summary
Ahoy CLI
\.ahoy\.yml
Remove test-js alias from test-functional-javascript; add new test-js command (TOOL_JEST‑guarded) that runs ahoy cli "yarn test" and documents usage.
CI/CD workflows
.circleci/config.yml, .github/workflows/build-test-deploy.yml
Add TOOL_JEST‑guarded yarn install --frozen-lockfile steps and a yarn test (Jest) step with conditional continue-on-error handling.
Jest config & deps
jest.config.js, package.json
Add jest.config.js; add scripts.test: "jest"; add devDependencies jest and jest-environment-jsdom.
ESLint overrides
.eslintrc.json
Add override for *.test.js enabling jest env, disable no-eval, and raise max-nested-callbacks warning threshold to 5 for test files.
Vortex installer tooling
.vortex/installer/src/Prompts/Handlers/Tools.php
Add Tools::JEST constant, include it in defaults/discovery, add frontend_testing and expanded frontend_all grouping, detect Jest via package.json or jest.config.js, and remove Jest artifacts when deselected.
Vortex installer tests
.vortex/installer/tests/.../ToolsHandlerProcessTest.php, .vortex/installer/tests/.../ToolsHandlerDiscoveryTest.php, .vortex/installer/tests/.../AbstractHandlerDiscoveryTestCase.php
Add and update tests to include/exclude Tools::JEST, assert presence/absence of jest.config.js and jest devDeps, and update expected defaults.
Demo module code
web/modules/custom/ys_demo/js/ys_demo.js
Refactor counter behavior: centralized storageKey property, new applyAction() helper, use arrow functions and instance methods for improved testability and persistence.
Demo module tests
web/modules/custom/ys_demo/js/ys_demo.test.js
Add comprehensive Jest suite covering API, DOM interactions, timers, localStorage persistence, multi-block handling, and behavior attach/init flows.
Docs
AGENTS.md
Document new ahoy test-js command for running Jest unit tests.

Sequence Diagram(s)

sequenceDiagram
    participant Dev as Developer
    participant CI as CI (GitHub Actions / CircleCI)
    participant CLI as cli container
    participant Yarn as Yarn/Jest

    Dev->>CI: push / open PR
    CI->>CLI: start job & install deps (composer + yarn) #;< TOOL_JEST
    CLI->>Yarn: yarn install --frozen-lockfile
    CLI->>Yarn: yarn test
    Yarn-->>CI: return test results
    CI-->>Dev: status / artifacts
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰
I hopped through package.json to find jest tucked tight,
I scoped each module root by candlelight.
Buttons click, counters climb, timers softly chime,
Green checks sprinkle carrots — green on every line.
A happy rabbit cheers: “All tests passed just right!”

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding Jest for JavaScript unit testing, matching the primary objective of the PR.
Linked Issues check ✅ Passed All objectives from #2394 are met: Jest is integrated as a supported testing tool, auto-discovery of JS test files in custom modules is implemented via jest.config.js, and the ys_demo module is refactored and tested to validate Jest configuration.
Out of Scope Changes check ✅ Passed All changes are directly related to adding Jest support and its integration: configuration files, tool definitions, CI workflows, refactoring to support testability, and comprehensive test coverage are all within scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/2394-jest-js-testing

Comment @coderabbitai help to get the list of available commands and usage tips.

@AlexSkrypnyk

This comment has been minimized.

@github-actions

This comment has been minimized.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.vortex/installer/src/Prompts/Handlers/Tools.php (1)

214-214: ⚠️ Potential issue | 🔴 Critical

Typo: 'goups' should be 'groups'.

This typo will cause processGroup() to always receive an empty array from getToolDefinitions(), making group processing ineffective.

🐛 Proposed fix
   protected function processGroup(string $name): void {
-    $config = static::getToolDefinitions('goups')[$name];
+    $config = static::getToolDefinitions('groups')[$name];
     $selected_tools = $this->getResponseAsArray();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.vortex/installer/src/Prompts/Handlers/Tools.php at line 214, There's a typo
when fetching tool definitions: change the key passed to
static::getToolDefinitions from 'goups' to 'groups' so processGroup() receives
the correct definitions; locate the call in Tools.php where $config =
static::getToolDefinitions('goups')[$name]; and replace the string with 'groups'
to ensure getToolDefinitions($key) returns the intended group data for
subsequent processing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.circleci/config.yml:
- Around line 154-156: The CI runs the same "yarn install --frozen-lockfile"
command under both TOOL_ESLINT_STYLELINT and TOOL_JEST markers, causing
duplicate installs; update the config so the docker compose exec ... "yarn
install --frozen-lockfile" line is executed only once by either consolidating
the markers into a single combined marker (e.g., TOOL_NODE or
TOOL_ESLINT_STYLELINT_JEST) or by adding conditional logic to run that install
when any of TOOL_ESLINT_STYLELINT or TOOL_JEST is active; modify the marker
names and/or conditional blocks around the existing docker compose exec command
to reference the new combined marker (or an OR-style condition) so the install
step is deduplicated.

In @.eslintrc.json:
- Around line 64-73: The override currently disables "no-eval" globally for test
files; to minimize scope, remove the "no-eval": "off" entry from the overrides
block and instead add an inline eslint disable comment (//
eslint-disable-next-line no-eval) at the specific eval usage in ys_demo.test.js
(around the eval at line 11); if you prefer to keep the blanket rule, leave
"no-eval": "off" inside the overrides (under "files", "rules") as-is — pick one
approach and update either .eslintrc.json or the specific test file accordingly.

In @.github/workflows/build-test-deploy.yml:
- Around line 134-139: The pipeline runs the same "docker compose exec ... yarn
install --frozen-lockfile" command under both TOOL_ESLINT_STYLELINT and
TOOL_JEST tokens causing duplicate installs; update the workflow to perform a
single install guarded by a consolidated token (e.g., use the existing
frontend_all group or combine conditions) and remove the duplicate block so only
one docker compose exec ... "yarn install --frozen-lockfile" is emitted when
either linting or testing tools are enabled; locate the two occurrences of the
docker compose exec line and replace them with one conditional that references
TOOL_ESLINT_STYLELINT || TOOL_JEST or frontend_all as the gate.

In `@jest.config.js`:
- Around line 7-19: The current discovery only scans the dirs array (initialized
as dirs = ['web/modules/custom']) and pushes found js directories into roots;
update the dirs initialization to include your custom theme path(s) (e.g., add
'web/themes/custom' or other theme directories) so the forEach loop that checks
fs.existsSync(dir) and inspects each name will also discover theme/js folders;
ensure any added theme paths follow the same resolution logic used by the
existing roots population so existing code that references roots continues to
work.

In `@web/modules/custom/ys_demo/js/ys_demo.test.js`:
- Around line 1-12: The test currently reads ys_demo.js and executes it via eval
(see ys_demo.test.js where fs.readFileSync + eval is used and global.Drupal is
set), which hampers source maps, strict mode handling, and coverage; change the
test to load the module without eval—either refactor the module to export its
behavior and require/import it in the test, or use Node's vm module to run the
file in a controlled context that preserves source mapping and isolation; ensure
tests still set global.Drupal and call the module initializer (or imported
function) instead of eval-ing the file content so coverage and debugging work
properly.
- Around line 222-242: Test currently only checks processed class for multiple
counters but not that each counter operates independently; update the test in
ys_demo.test.js to clear localStorage at start, query each block via
document.querySelectorAll('[data-ys-demo-counter]'), find its own button
(data-counter-action="increment") and value span (data-counter-value), simulate
clicking each block's increment button separately and assert that only the
corresponding span text increments (e.g., first block's span becomes "1" after
its click while second remains "0", then vice versa); keep using
Drupal.behaviors.ysDemo.initCounterBlock to initialize and reference the
behavior and DOM attribute selectors to locate elements.

---

Outside diff comments:
In @.vortex/installer/src/Prompts/Handlers/Tools.php:
- Line 214: There's a typo when fetching tool definitions: change the key passed
to static::getToolDefinitions from 'goups' to 'groups' so processGroup()
receives the correct definitions; locate the call in Tools.php where $config =
static::getToolDefinitions('goups')[$name]; and replace the string with 'groups'
to ensure getToolDefinitions($key) returns the intended group data for
subsequent processing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 37e8d514-fa13-448c-8859-02d4a6ca955d

📥 Commits

Reviewing files that changed from the base of the PR and between 271488b and 1d73987.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (13)
  • .ahoy.yml
  • .circleci/config.yml
  • .eslintrc.json
  • .github/workflows/build-test-deploy.yml
  • .vortex/installer/src/Prompts/Handlers/Tools.php
  • .vortex/installer/tests/Functional/Handlers/ToolsHandlerProcessTest.php
  • .vortex/installer/tests/Unit/Handlers/AbstractHandlerDiscoveryTestCase.php
  • .vortex/installer/tests/Unit/Handlers/ToolsHandlerDiscoveryTest.php
  • AGENTS.md
  • jest.config.js
  • package.json
  • web/modules/custom/ys_demo/js/ys_demo.js
  • web/modules/custom/ys_demo/js/ys_demo.test.js

Comment on lines +154 to +156
#;< TOOL_JEST
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
#;> TOOL_JEST
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Potential duplicate yarn install when both TOOL_ESLINT_STYLELINT and TOOL_JEST are enabled.

When both tool markers are active, yarn install --frozen-lockfile will execute twice (lines 152 and 155). Since both tools share the same package.json and node_modules, a single install suffices.

Consider consolidating under a single marker (e.g., TOOL_NODE or TOOL_ESLINT_STYLELINT_JEST) or restructuring to avoid redundancy.

♻️ Alternative: use a combined marker or conditional logic

One option is to have a shared frontend/Node marker that covers any Node-based tooling:

-            #;< TOOL_ESLINT_STYLELINT
-            docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
-            #;> TOOL_ESLINT_STYLELINT
-            #;< TOOL_JEST
-            docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
-            #;> TOOL_JEST
+            #;< TOOL_NODE
+            docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
+            #;> TOOL_NODE

This would require corresponding changes in the installer's tool definitions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.circleci/config.yml around lines 154 - 156, The CI runs the same "yarn
install --frozen-lockfile" command under both TOOL_ESLINT_STYLELINT and
TOOL_JEST markers, causing duplicate installs; update the config so the docker
compose exec ... "yarn install --frozen-lockfile" line is executed only once by
either consolidating the markers into a single combined marker (e.g., TOOL_NODE
or TOOL_ESLINT_STYLELINT_JEST) or by adding conditional logic to run that
install when any of TOOL_ESLINT_STYLELINT or TOOL_JEST is active; modify the
marker names and/or conditional blocks around the existing docker compose exec
command to reference the new combined marker (or an OR-style condition) so the
install step is deduplicated.

Comment on lines +134 to +139
#;< TOOL_ESLINT_STYLELINT
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
#;> TOOL_ESLINT_STYLELINT
#;< TOOL_JEST
docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
#;> TOOL_JEST
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Potential duplicate yarn install execution when both ESLint/Stylelint and Jest are enabled.

When both TOOL_ESLINT_STYLELINT and TOOL_JEST tokens are retained, yarn install --frozen-lockfile executes twice. While functionally harmless (second run is a no-op), this adds unnecessary CI time.

Consider consolidating these into a single conditional block or relying on the frontend_all group token from the installer to gate a single install step.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build-test-deploy.yml around lines 134 - 139, The pipeline
runs the same "docker compose exec ... yarn install --frozen-lockfile" command
under both TOOL_ESLINT_STYLELINT and TOOL_JEST tokens causing duplicate
installs; update the workflow to perform a single install guarded by a
consolidated token (e.g., use the existing frontend_all group or combine
conditions) and remove the duplicate block so only one docker compose exec ...
"yarn install --frozen-lockfile" is emitted when either linting or testing tools
are enabled; locate the two occurrences of the docker compose exec line and
replace them with one conditional that references TOOL_ESLINT_STYLELINT ||
TOOL_JEST or frontend_all as the gate.

Comment on lines +1 to +12
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);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Using eval() to load the module is functional but consider alternatives.

The fs.readFileSync + eval approach works but has limitations:

  • No source map support for debugging
  • Potential issues with strict mode
  • Harder to track code coverage

Consider using Jest's vm module or restructuring the module to export for testing. However, given ESLint overrides are configured and this works with the existing IIFE pattern, this is acceptable for the current scope.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/modules/custom/ys_demo/js/ys_demo.test.js` around lines 1 - 12, The test
currently reads ys_demo.js and executes it via eval (see ys_demo.test.js where
fs.readFileSync + eval is used and global.Drupal is set), which hampers source
maps, strict mode handling, and coverage; change the test to load the module
without eval—either refactor the module to export its behavior and
require/import it in the test, or use Node's vm module to run the file in a
controlled context that preserves source mapping and isolation; ensure tests
still set global.Drupal and call the module initializer (or imported function)
instead of eval-ing the file content so coverage and debugging work properly.

Comment on lines +222 to +242
it('should handle multiple counter blocks', () => {
document.body.innerHTML = `
<div data-ys-demo-counter>
<span data-counter-value>0</span>
<button data-counter-action="increment">+</button>
</div>
<div data-ys-demo-counter>
<span data-counter-value>0</span>
<button data-counter-action="increment">+</button>
</div>
`;
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,
);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider testing independent counter operation for multiple blocks.

The current test verifies both blocks are marked as processed, but doesn't verify they operate independently (each incrementing its own display). This might be intentional given the current shared localStorage implementation, but worth noting for future consideration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/modules/custom/ys_demo/js/ys_demo.test.js` around lines 222 - 242, Test
currently only checks processed class for multiple counters but not that each
counter operates independently; update the test in ys_demo.test.js to clear
localStorage at start, query each block via
document.querySelectorAll('[data-ys-demo-counter]'), find its own button
(data-counter-action="increment") and value span (data-counter-value), simulate
clicking each block's increment button separately and assert that only the
corresponding span text increments (e.g., first block's span becomes "1" after
its click while second remains "0", then vice versa); keep using
Drupal.behaviors.ysDemo.initCounterBlock to initialize and reference the
behavior and DOM attribute selectors to locate elements.

@AlexSkrypnyk

This comment has been minimized.

1 similar comment
@AlexSkrypnyk

This comment has been minimized.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.circleci/config.yml (1)

366-390: ⚠️ Potential issue | 🟠 Major

Guard the Jest path with CIRCLE_NODE_INDEX.

This job has parallelism: 2, but Line 367 and Line 389 execute the Jest install/test path on every node. Unlike PHPUnit below, that duplicates the same suite and doubles the Node setup cost.

♻️ Suggested change
       - 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"
             #;< TOOL_JEST
-            docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
+            if [ "${CIRCLE_NODE_TOTAL:-1}" -eq 1 ] || [ "${CIRCLE_NODE_INDEX:-0}" -eq 0 ]; then
+              docker compose exec $(env | cut -f1 -d= | sed 's/^/-e /') -T cli bash -c "yarn install --frozen-lockfile"
+            fi
             #;> TOOL_JEST
@@
       #;< TOOL_JEST
       - run:
           name: Test with Jest
-          command: docker compose exec -T cli bash -c "yarn test" || [ "${VORTEX_CI_NODEJS_TEST_IGNORE_FAILURE:-0}" -eq 1 ]
+          command: |
+            [ "${CIRCLE_NODE_TOTAL:-1}" -gt 1 ] && [ "${CIRCLE_NODE_INDEX:-0}" -ne 0 ] && exit 0
+            docker compose exec -T cli bash -c "yarn test" || [ "${VORTEX_CI_NODEJS_TEST_IGNORE_FAILURE:-0}" -eq 1 ]
       #;> TOOL_JEST
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.circleci/config.yml around lines 366 - 390, The Jest install/test steps
(the TOOL_JEST blocks that run the yarn install and yarn test commands) are
executed on every parallel node; wrap both TOOL_JEST sections with a conditional
that only runs them on a single parallel index (e.g., guard with if [
"${CIRCLE_NODE_INDEX:-0}" -eq "0" ]; then ... fi) so only one parallel worker
performs the Jest setup and tests, leaving other nodes to run their existing
suites (mirror how PHPUnit is handled).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/build-test-deploy.yml:
- Around line 410-412: The install step currently wrapped by the TOOL_JEST
markers (the docker compose exec ... "yarn install --frozen-lockfile" command)
runs on all build matrix jobs; mirror the Jest guard by adding the same matrix
condition (matrix.instance == 0) so only the job that runs Jest performs the
install. Locate the TOOL_JEST block around the docker compose exec command and
wrap or annotate that step with the same conditional used for the Jest run
(matrix.instance == 0) to avoid running yarn install on the second matrix leg.

In @.vortex/installer/src/Prompts/Handlers/Tools.php:
- Around line 368-371: The cleanup currently only glob-matches modules' Jest
tests in the 'files' closure (the array returned by 'files' => fn(): array =>
[...]) and misses theme test files; update that closure to also include a glob
for themes (e.g. $this->tmpDir . '/' . $this->webroot .
'/themes/custom/*/js/*.test.js') alongside the existing
modules/custom/*/js/*.test.js entry so deselecting Jest removes theme tests as
well.

---

Outside diff comments:
In @.circleci/config.yml:
- Around line 366-390: The Jest install/test steps (the TOOL_JEST blocks that
run the yarn install and yarn test commands) are executed on every parallel
node; wrap both TOOL_JEST sections with a conditional that only runs them on a
single parallel index (e.g., guard with if [ "${CIRCLE_NODE_INDEX:-0}" -eq "0"
]; then ... fi) so only one parallel worker performs the Jest setup and tests,
leaving other nodes to run their existing suites (mirror how PHPUnit is
handled).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ca447bf7-92d6-49e8-bf3f-cc01717ff44c

📥 Commits

Reviewing files that changed from the base of the PR and between 1d73987 and 11c5bd1.

⛔ Files ignored due to path filters (8)
  • .vortex/installer/tests/Fixtures/handler_process/_baseline/.ahoy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/_baseline/.eslintrc.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/_baseline/.github/workflows/build-test-deploy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/_baseline/AGENTS.md is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/_baseline/jest.config.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/_baseline/package.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/_baseline/web/modules/custom/sw_demo/js/sw_demo.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/_baseline/web/modules/custom/sw_demo/js/sw_demo.test.js is excluded by !.vortex/installer/tests/Fixtures/**
📒 Files selected for processing (3)
  • .circleci/config.yml
  • .github/workflows/build-test-deploy.yml
  • .vortex/installer/src/Prompts/Handlers/Tools.php

@AlexSkrypnyk

This comment has been minimized.

3 similar comments
@AlexSkrypnyk

This comment has been minimized.

@AlexSkrypnyk

This comment has been minimized.

@AlexSkrypnyk

This comment has been minimized.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@jest.config.js`:
- Around line 21-26: The fallback roots array in module.exports is using a
relative path while discovered roots are resolved to absolute paths; update the
fallback to an absolute path (e.g., use path.resolve('web/modules/custom')) so
the ternary expression roots.length > 0 ? roots : [ ... ] returns absolute paths
consistently, and ensure the file imports/uses path.resolve where roots are
constructed so both branches produce the same absolute-form paths (refer to the
roots variable and the module.exports block).
- Around line 4-15: The comment incorrectly claims symlinks are resolved but the
code uses path.resolve (which does not follow symlinks) when building jsDir;
update the code or comment: either replace path.resolve(dir, name, 'js') with
fs.realpathSync(path.join(dir, name, 'js')) (guarded with exists checks or
try/catch because realpathSync throws) so jsDir uses the real path, or if
symlink resolution is not required, change the comment to remove the symlink
claim and keep path.resolve; locate the logic around dirs/roots, the forEach
that computes jsDir, and adjust accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e77ebe4f-742c-4faa-8d6f-2d2f59e5d329

📥 Commits

Reviewing files that changed from the base of the PR and between 11c5bd1 and 5d2f196.

⛔ Files ignored due to path filters (150)
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/.docker/cli.dockerfile is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/.docker/nginx-drupal.dockerfile is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/.env.local.example is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/phpunit.xml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/-autoload.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/modules/custom/sw_demo/-sw_demo.deploy.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/modules/custom/sw_demo/-sw_demo.info.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/modules/custom/sw_demo/-sw_demo.libraries.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/modules/custom/sw_demo/-sw_demo.module is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/modules/custom/sw_demo/config/install/-views.view.sw_demo_articles.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/modules/custom/sw_demo/css/-sw_demo.css is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/modules/custom/sw_demo/js/-sw_demo.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/modules/custom/sw_demo/src/Plugin/Block/-CounterBlock.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/modules/custom/sw_demo/src/Plugin/GeneratedContent/Node/-Article.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/modules/custom/sw_demo/src/Plugin/GeneratedContent/Taxonomy/-Tags.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/modules/custom/sw_demo/templates/-sw-demo-counter-block.html.twig is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/modules/custom/sw_demo/tests/src/FunctionalJavascript/-CounterBlockTest.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/modules/custom/sw_demo/tests/src/Kernel/-CounterBlockTest.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/modules/custom/sw_demo/tests/src/Unit/-CounterBlockTest.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/sites/default/-default.services.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/sites/default/-default.settings.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/sites/default/-example.services.local.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/sites/default/-example.settings.local.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/sites/default/-services.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/sites/default/-settings.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/sites/default/includes/modules/-settings.automated_cron.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/sites/default/includes/modules/-settings.clamav.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/sites/default/includes/modules/-settings.config_split.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/sites/default/includes/modules/-settings.environment_indicator.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/sites/default/includes/modules/-settings.fast404.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/sites/default/includes/modules/-settings.redis.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/sites/default/includes/modules/-settings.robotstxt.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/sites/default/includes/modules/-settings.seckit.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/sites/default/includes/modules/-settings.shield.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/sites/default/includes/modules/-settings.stage_file_proxy.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/sites/default/includes/modules/-settings.sw_base.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/sites/default/includes/modules/-settings.system.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/sites/default/includes/modules/-settings.trusted_hosts.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/sites/default/includes/modules/-settings.xmlsitemap.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/sites/default/includes/providers/-settings.container.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/.!!k2C/web/sites/default/includes/providers/-settings.gha.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/_baseline/.github/workflows/build-test-deploy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/_baseline/jest.config.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/ciprovider_circleci/.circleci/config.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/custom_modules_no_demo/web/modules/custom/sw_demo/js/-sw_demo.test.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/custom_modules_none/web/modules/custom/sw_demo/js/-sw_demo.test.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/deploy_types_all_circleci/.circleci/config.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/deploy_types_none_circleci/.circleci/config.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/deploy_types_none_gha/.github/workflows/build-test-deploy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/deps_updates_provider_ci_circleci/.circleci/config.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/hosting_acquia/AGENTS.md is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/hosting_acquia/docroot/modules/custom/sw_demo/js/sw_demo.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/hosting_acquia/docroot/modules/custom/sw_demo/js/sw_demo.test.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/hosting_acquia/jest.config.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/hosting_acquia/package.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/hosting_acquia/web/modules/custom/sw_demo/js/-sw_demo.test.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/AGENTS.md is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/docroot/modules/custom/sw_demo/js/sw_demo.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/docroot/modules/custom/sw_demo/js/sw_demo.test.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/jest.config.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/package.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/web/modules/custom/sw_demo/js/-sw_demo.test.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/migration_disabled_circleci/.circleci/config.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/migration_download_source_acquia/.github/workflows/build-test-deploy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/migration_download_source_ftp/.github/workflows/build-test-deploy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/migration_download_source_lagoon/.github/workflows/build-test-deploy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/migration_download_source_s3/.github/workflows/build-test-deploy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/migration_download_source_url/.github/workflows/build-test-deploy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/migration_enabled/.github/workflows/build-test-deploy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/migration_enabled_circleci/.circleci/config.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/migration_enabled_lagoon/.github/workflows/build-test-deploy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/names/web/modules/custom/sw_demo/js/-sw_demo.test.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/names/web/modules/custom/the_force_demo/js/the_force_demo.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/names/web/modules/custom/the_force_demo/js/the_force_demo.test.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/provision_profile/.github/workflows/build-test-deploy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/timezone_circleci/.circleci/config.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_lint_circleci/.circleci/config.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests/.ahoy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests/.github/workflows/build-test-deploy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests/AGENTS.md is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests_circleci/.ahoy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests_circleci/.circleci/config.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_be_tests_circleci/AGENTS.md is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint/-jest.config.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint/.ahoy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint/.github/workflows/build-test-deploy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint/AGENTS.md is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint/web/modules/custom/sw_demo/js/-sw_demo.test.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_circleci/-jest.config.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_circleci/.ahoy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_circleci/AGENTS.md is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_circleci/web/modules/custom/sw_demo/js/-sw_demo.test.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme/-jest.config.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme/.ahoy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme/.github/workflows/build-test-deploy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme/AGENTS.md is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme/web/modules/custom/sw_demo/js/-sw_demo.test.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme_circleci/-jest.config.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme_circleci/.ahoy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme_circleci/AGENTS.md is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_groups_no_fe_lint_no_theme_circleci/web/modules/custom/sw_demo/js/-sw_demo.test.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_behat/.ahoy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_behat/.github/workflows/build-test-deploy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_behat/AGENTS.md is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_behat_circleci/.ahoy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_behat_circleci/.circleci/config.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_behat_circleci/AGENTS.md is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_eslint/package.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_circleci/.circleci/config.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_circleci/package.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_eslint_no_theme/package.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_jest/-jest.config.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_jest/.ahoy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_jest/.github/workflows/build-test-deploy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_jest/AGENTS.md is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_jest/package.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_jest/web/modules/custom/sw_demo/js/-sw_demo.test.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/-jest.config.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/.ahoy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/.circleci/config.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/.circleci/post-coverage-comment.sh is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/.github/workflows/-build-test-deploy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/AGENTS.md is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/README.md is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/docs/ci.md is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/package.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/tests/phpunit/CircleCiConfigTest.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/tests/phpunit/Drupal/EnvironmentSettingsTest.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/web/modules/custom/sw_demo/js/-sw_demo.test.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/web/sites/default/includes/providers/-settings.gha.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_jest_circleci/web/sites/default/includes/providers/settings.circleci.php is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_phpcs_circleci/.circleci/config.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_phpmd_circleci/.circleci/config.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_phpstan_circleci/.circleci/config.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_phpunit/.ahoy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_phpunit/.github/workflows/build-test-deploy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_phpunit/AGENTS.md is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_phpunit_circleci/.ahoy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_phpunit_circleci/.circleci/config.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_phpunit_circleci/AGENTS.md is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_rector_circleci/.circleci/config.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_stylelint/package.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_stylelint_circleci/.circleci/config.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_stylelint_circleci/package.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_no_stylelint_no_theme/package.json is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_none/-jest.config.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_none/.ahoy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_none/.github/workflows/build-test-deploy.yml is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_none/AGENTS.md is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/tools_none/web/modules/custom/sw_demo/js/-sw_demo.test.js is excluded by !.vortex/installer/tests/Fixtures/**
📒 Files selected for processing (1)
  • jest.config.js

Comment on lines +4 to +15
// 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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Comment claims symlink resolution, but path.resolve() does not resolve symlinks.

The comment states that symlinks are resolved to real paths, but path.resolve() only converts relative paths to absolute paths—it does not follow symlinks. If symlink resolution is actually needed (as the comment suggests), use fs.realpathSync() instead.

If symlink resolution isn't actually required and the current behavior is correct, consider updating the comment to avoid confusion.

♻️ Option A: If symlink resolution is needed
 dirs.forEach((dir) => {
   if (fs.existsSync(dir)) {
     fs.readdirSync(dir).forEach((name) => {
-      const jsDir = path.resolve(dir, name, 'js');
+      const jsDir = fs.realpathSync(path.join(dir, name, 'js'));
       if (fs.existsSync(jsDir)) {
         roots.push(jsDir);
       }
     });
   }
 });

Note: fs.realpathSync() will throw if the path doesn't exist, so you'd need to wrap it in a try-catch or check existence first.

♻️ Option B: If current behavior is correct, fix the comment
-// 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.
+// Discover js/ directories in custom modules, converting to absolute paths.
+// Jest resolves symlinks internally, so roots must use absolute paths for
+// test files to be matched correctly.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 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);
// Discover js/ directories in custom modules, converting to absolute paths.
// Jest resolves symlinks internally, so roots must use absolute paths for
// test files to be matched correctly.
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);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jest.config.js` around lines 4 - 15, The comment incorrectly claims symlinks
are resolved but the code uses path.resolve (which does not follow symlinks)
when building jsDir; update the code or comment: either replace
path.resolve(dir, name, 'js') with fs.realpathSync(path.join(dir, name, 'js'))
(guarded with exists checks or try/catch because realpathSync throws) so jsDir
uses the real path, or if symlink resolution is not required, change the comment
to remove the symlink claim and keep path.resolve; locate the logic around
dirs/roots, the forEach that computes jsDir, and adjust accordingly.

@AlexSkrypnyk

This comment has been minimized.

1 similar comment
@AlexSkrypnyk

This comment has been minimized.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@jest.config.js`:
- Line 25: testPathIgnorePatterns has inconsistent pattern formats: change the
'web/core/' entry to use a leading slash so it matches the others and anchors at
a path boundary (update the testPathIgnorePatterns array to use '/web/core/');
ensure you only modify the string inside testPathIgnorePatterns so patterns
remain ['/node_modules/', '/vendor/', '/web/core/'].

In `@web/modules/custom/ys_demo/js/ys_demo.test.js`:
- Around line 208-224: The test "should add updated class on click for visual
feedback" uses jest.useFakeTimers() and calls jest.useRealTimers() at the end
but can leak when it fails; update this test to ensure timers are always
restored by either moving jest.useFakeTimers() to a beforeEach and
jest.useRealTimers() to an afterEach for the describe containing this spec, or
wrap the body of the it block in a try/finally so jest.useRealTimers() is called
in finally; reference the test name and the existing
removeUpdatedClassAfterDelay tests as a pattern to implement the hooks or
try/finally around the calls to jest.useFakeTimers(),
jest.advanceTimersByTime(300) and jest.useRealTimers().

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ec641f2c-c5f5-42a5-928a-1eeaec2215ef

📥 Commits

Reviewing files that changed from the base of the PR and between 5d2f196 and 36c71ba.

⛔ Files ignored due to path filters (7)
  • .vortex/installer/tests/Fixtures/handler_process/_baseline/jest.config.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/_baseline/web/modules/custom/sw_demo/js/sw_demo.test.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/hosting_acquia/docroot/modules/custom/sw_demo/js/sw_demo.test.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/hosting_acquia/jest.config.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/docroot/modules/custom/sw_demo/js/sw_demo.test.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/jest.config.js is excluded by !.vortex/installer/tests/Fixtures/**
  • .vortex/installer/tests/Fixtures/handler_process/names/web/modules/custom/the_force_demo/js/the_force_demo.test.js is excluded by !.vortex/installer/tests/Fixtures/**
📒 Files selected for processing (2)
  • jest.config.js
  • web/modules/custom/ys_demo/js/ys_demo.test.js

jest.config.js Outdated
testEnvironment: 'jest-environment-jsdom',
roots: roots.length > 0 ? roots : ['web/modules/custom'],
testMatch: ['**/*.test.js'],
testPathIgnorePatterns: ['/node_modules/', '/vendor/', 'web/core/'],
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Inconsistent pattern format in testPathIgnorePatterns.

The first two patterns use leading slashes (/node_modules/, /vendor/) while web/core/ omits it. For consistency and precision:

-  testPathIgnorePatterns: ['/node_modules/', '/vendor/', 'web/core/'],
+  testPathIgnorePatterns: ['/node_modules/', '/vendor/', '/web/core/'],

The leading slash ensures the pattern matches at a path boundary, preventing potential false matches like someweb/core/.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
testPathIgnorePatterns: ['/node_modules/', '/vendor/', 'web/core/'],
testPathIgnorePatterns: ['/node_modules/', '/vendor/', '/web/core/'],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jest.config.js` at line 25, testPathIgnorePatterns has inconsistent pattern
formats: change the 'web/core/' entry to use a leading slash so it matches the
others and anchors at a path boundary (update the testPathIgnorePatterns array
to use '/web/core/'); ensure you only modify the string inside
testPathIgnorePatterns so patterns remain ['/node_modules/', '/vendor/',
'/web/core/'].

Comment on lines +208 to +224
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();
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider moving fake timer setup to beforeEach/afterEach for robustness.

If this test fails before line 223, jest.useRealTimers() won't execute, potentially affecting subsequent tests. For consistency with the approach used in removeUpdatedClassAfterDelay tests (lines 82-88):

♻️ Suggested approach

Either wrap this test in its own nested describe block with timer setup hooks, or use a try/finally pattern:

it('should add updated class on click for visual feedback', () => {
  jest.useFakeTimers();
  try {
    document.body.innerHTML = createCounterBlockHtml();
    Drupal.behaviors.ysDemo.initCounterBlock(document);
    // ... rest of test
    jest.advanceTimersByTime(300);
    expect(value.classList.contains('updated')).toBe(false);
  } finally {
    jest.useRealTimers();
  }
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/modules/custom/ys_demo/js/ys_demo.test.js` around lines 208 - 224, The
test "should add updated class on click for visual feedback" uses
jest.useFakeTimers() and calls jest.useRealTimers() at the end but can leak when
it fails; update this test to ensure timers are always restored by either moving
jest.useFakeTimers() to a beforeEach and jest.useRealTimers() to an afterEach
for the describe containing this spec, or wrap the body of the it block in a
try/finally so jest.useRealTimers() is called in finally; reference the test
name and the existing removeUpdatedClassAfterDelay tests as a pattern to
implement the hooks or try/finally around the calls to jest.useFakeTimers(),
jest.advanceTimersByTime(300) and jest.useRealTimers().

@AlexSkrypnyk

This comment has been minimized.

1 similar comment
@AlexSkrypnyk

This comment has been minimized.

@AlexSkrypnyk AlexSkrypnyk force-pushed the feature/2394-jest-js-testing branch from a2a0510 to a201945 Compare March 25, 2026 04:28
@github-actions

This comment has been minimized.

2 similar comments
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@AlexSkrypnyk

This comment has been minimized.

@github-actions

This comment has been minimized.

@AlexSkrypnyk

This comment has been minimized.

@drevops drevops deleted a comment from coderabbitai bot Mar 25, 2026
@AlexSkrypnyk

This comment has been minimized.

1 similar comment
@AlexSkrypnyk

This comment has been minimized.

@AlexSkrypnyk AlexSkrypnyk enabled auto-merge (squash) March 25, 2026 04:52
@github-actions

This comment has been minimized.

@AlexSkrypnyk

This comment has been minimized.

2 similar comments
@AlexSkrypnyk

This comment has been minimized.

@AlexSkrypnyk

This comment has been minimized.

@github-actions
Copy link
Copy Markdown

Code coverage (threshold: 90%)

  Classes: 100.00% (1/1)
  Methods: 100.00% (2/2)
  Lines:   100.00% (189/189)
Per-class coverage
Drupal\ys_demo\Plugin\Block\CounterBlock
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 10/ 10)

@AlexSkrypnyk

This comment has been minimized.

2 similar comments
@AlexSkrypnyk

This comment has been minimized.

@AlexSkrypnyk
Copy link
Copy Markdown
Member Author

Code coverage (threshold: 90%)

  Classes: 100.00% (1/1)
  Methods: 100.00% (2/2)
  Lines:   100.00% (189/189)
Per-class coverage
Drupal\ys_demo\Plugin\Block\CounterBlock
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 10/ 10)

@AlexSkrypnyk AlexSkrypnyk added this to the 1.38.0 milestone Mar 25, 2026
@AlexSkrypnyk AlexSkrypnyk merged commit f2fb245 into main Mar 25, 2026
30 checks passed
@AlexSkrypnyk AlexSkrypnyk deleted the feature/2394-jest-js-testing branch March 25, 2026 05:37
@github-project-automation github-project-automation bot moved this from BACKLOG to Release queue in Vortex Mar 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Release queue

Development

Successfully merging this pull request may close these issues.

Add support for Jest for JS unit testing

1 participant