Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
73e1401
[#2394] Added Jest for JavaScript unit testing.
AlexSkrypnyk Mar 24, 2026
1d73987
[#2394] Fixed missing 'jest' in expected default tools list in Abstra…
AlexSkrypnyk Mar 24, 2026
536b605
Updated baseline.
AlexSkrypnyk Mar 24, 2026
11c5bd1
[#2394] Moved Jest CI step to build job and fixed duplicate yarn inst…
AlexSkrypnyk Mar 24, 2026
61a33df
Updated snapshots.
AlexSkrypnyk Mar 24, 2026
473be89
[#2394] Fixed Jest config for CI: explicit jsdom environment and core…
AlexSkrypnyk Mar 24, 2026
5d2f196
Updated snapshots.
AlexSkrypnyk Mar 24, 2026
da3180c
[#2394] Fixed jsdom docblock and core exclusion pattern for CI.
AlexSkrypnyk Mar 24, 2026
36c71ba
Updated snapshots.
AlexSkrypnyk Mar 24, 2026
8987347
[#2394] Added modulePathIgnorePatterns to exclude Drupal core from Jest.
AlexSkrypnyk Mar 24, 2026
10ae309
Updated snapshots.
AlexSkrypnyk Mar 24, 2026
c9e5020
[#2394] Fixed testPathIgnorePatterns for core exclusion and ESLint js…
AlexSkrypnyk Mar 24, 2026
f0eff21
Updated snapshots.
AlexSkrypnyk Mar 24, 2026
1600374
[#2394] Used explicit testMatch pattern to restrict Jest to custom mo…
AlexSkrypnyk Mar 24, 2026
71ddb2a
Updated snapshots.
AlexSkrypnyk Mar 24, 2026
1f8c6d8
[#2394] Added modulePathIgnorePatterns to prevent Jest scanning Drupa…
AlexSkrypnyk Mar 25, 2026
227c31d
Updated snapshots.
AlexSkrypnyk Mar 25, 2026
d6fecee
[#2394] Added debug logging to jest.config.js for CI investigation.
AlexSkrypnyk Mar 25, 2026
a88e2aa
[#2394] Used testRegex to strictly match only custom module test files.
AlexSkrypnyk Mar 25, 2026
225e62f
Updated snapshots.
AlexSkrypnyk Mar 25, 2026
58c8d91
[#2394] Added explicit --config flag to Jest command.
AlexSkrypnyk Mar 25, 2026
c0086e6
Updated snapshots.
AlexSkrypnyk Mar 25, 2026
7eacfc6
[#2394] Moved Jest config into package.json for Docker CI compatibility.
AlexSkrypnyk Mar 25, 2026
a93acde
Updated snapshots.
AlexSkrypnyk Mar 25, 2026
0f7475a
[#2394] Restored jest.config.js and added to .dockerignore whitelist.
AlexSkrypnyk Mar 25, 2026
202b7a2
t
AlexSkrypnyk Mar 25, 2026
0399e35
Updated snapshots.
AlexSkrypnyk Mar 25, 2026
9033e55
[#2394] Regenerated CircleCI test config.
AlexSkrypnyk Mar 25, 2026
26732e7
[#2394] Added Jest documentation for tools and development sections.
AlexSkrypnyk Mar 25, 2026
88f3776
[#2394] Added Jest workflow test for pass, path pattern, and failure …
AlexSkrypnyk Mar 25, 2026
a201945
[#2394] Added test-by-name scenario to Jest workflow test.
AlexSkrypnyk Mar 25, 2026
cbfa260
[#2394] Refocused development Jest docs on writing tests, linked to t…
AlexSkrypnyk Mar 25, 2026
eea6d1f
[#2394] Renamed CI var to VORTEX_CI_JEST_IGNORE_FAILURE and cross-lin…
AlexSkrypnyk Mar 25, 2026
0c4482e
Updated snapshots.
AlexSkrypnyk Mar 25, 2026
1296604
[#2394] Added VORTEX_CI_JEST_IGNORE_FAILURE to CI variables and updat…
AlexSkrypnyk Mar 25, 2026
fb0d465
[#2394] Fixed broken doc links to use /docs/ prefix.
AlexSkrypnyk Mar 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .ahoy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
9 changes: 9 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
9 changes: 9 additions & 0 deletions .circleci/vortex-test-common.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ drush/contrib/
!composer.lock
!gherkinlint.json
!package-lock.json
!jest.config.js
!package.json
!patches
!phpcs.xml
Expand Down
11 changes: 11 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/build-test-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand All @@ -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 }}
Expand Down
2 changes: 0 additions & 2 deletions .vortex/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
3 changes: 3 additions & 0 deletions .vortex/docs/.utils/variables/extra/ci.variables.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
135 changes: 135 additions & 0 deletions .vortex/docs/content/development/jest.mdx
Original file line number Diff line number Diff line change
@@ -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 = '<div data-my-module></div>';
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 = `
<div data-my-widget>
<button data-action="save">Save</button>
<span data-status></span>
</div>
`;

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.
Loading
Loading