Skip to content

Commit ac45c3b

Browse files
committed
Refactored Jest demo test to use 'require()' instead of 'eval()'.
1 parent 7c6ad85 commit ac45c3b

5 files changed

Lines changed: 71 additions & 29 deletions

File tree

.eslintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
"files": ["*.test.js"],
6767
"env": { "jest": true },
6868
"rules": {
69-
"no-eval": "off",
69+
"global-require": "off",
7070
"max-nested-callbacks": ["warn", 5],
7171
"jsdoc/check-tag-names": "off"
7272
}

.vortex/docs/content/development/jest.mdx

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,26 @@ For running tests, configuration, and CI settings, see the
1414

1515
## Test file structure
1616

17-
Test files are co-located with source files in the `js/` directory of each
18-
custom module:
17+
Test files are placed in a `tests/` subdirectory within the `js/` directory of
18+
each custom module:
1919

2020
```text
2121
web/modules/custom/my_module/
2222
└── js/
23-
├── my_module.js # Source file (Drupal behavior)
24-
└── my_module.test.js # Jest test file
23+
├── my_module.js # Source file (Drupal behavior)
24+
└── tests/
25+
└── my_module.test.js # Jest test file
2526
```
2627

2728
Jest automatically discovers `*.test.js` files in `web/modules/custom/*/js/`
28-
directories. Adding a new module with tests requires no configuration changes.
29+
directories and their subdirectories. Adding a new module with tests requires
30+
no configuration changes.
2931

3032
## Writing tests
3133

3234
Drupal JavaScript uses the IIFE pattern `((Drupal) => { ... })(Drupal)` where
33-
`Drupal` is a global object. Tests load these source files using `eval()` after
34-
setting up the required globals.
35+
`Drupal` is a global object. Tests load these source files using `require()`
36+
after setting up the required globals.
3537

3638
### Test template
3739

@@ -40,17 +42,14 @@ setting up the required globals.
4042
* @jest-environment jsdom
4143
*/
4244

43-
const fs = require('fs');
44-
const path = require('path');
45-
4645
describe('Drupal.behaviors.myModule', () => {
4746
beforeEach(() => {
4847
localStorage.clear();
4948
global.Drupal = { behaviors: {} };
5049

51-
const filePath = path.resolve(__dirname, 'my_module.js');
52-
const code = fs.readFileSync(filePath, 'utf8');
53-
eval(code);
50+
jest.resetModules();
51+
// eslint-disable-next-line global-require
52+
require('../my_module.js');
5453
});
5554

5655
afterEach(() => {
@@ -69,9 +68,12 @@ describe('Drupal.behaviors.myModule', () => {
6968

7069
### Loading Drupal behaviors
7170

72-
The `eval(fs.readFileSync(...))` pattern executes the source file's IIFE, which
73-
receives `global.Drupal` as its `Drupal` parameter and registers the behavior.
74-
After `eval()`, the behavior is accessible via `Drupal.behaviors.myModule`.
71+
The `require()` call executes the source file's IIFE, which receives
72+
`global.Drupal` as its `Drupal` parameter and registers the behavior.
73+
After `require()`, the behavior is accessible via `Drupal.behaviors.myModule`.
74+
75+
The `jest.resetModules()` call before `require()` clears the module cache so
76+
the IIFE re-executes on each test with a fresh `global.Drupal` object.
7577

7678
### Mocking globals
7779

@@ -121,12 +123,35 @@ jest.useRealTimers();
121123
### ESLint compatibility
122124

123125
The `.eslintrc.json` includes an override for `*.test.js` files that enables
124-
the `jest` environment and allows `eval()`. No additional ESLint configuration
125-
is needed for test files.
126+
the `jest` environment and allows `global-require`. No additional ESLint
127+
configuration is needed for test files.
128+
129+
## Coverage
130+
131+
Jest collects code coverage automatically when running tests via `ahoy test-js`.
132+
Coverage is configured in [`jest.config.js`](https://github.com/drevops/vortex/blob/main/jest.config.js)
133+
with the following settings:
134+
135+
- **Source files**: all `*.js` files in `web/modules/custom/*/js/` directories,
136+
excluding `*.test.js` files
137+
- **Reporters**: text (terminal summary), lcov, HTML, and Cobertura XML
138+
- **Output directory**: `.logs/coverage/jest`
139+
140+
After running tests, coverage reports are available at:
141+
142+
| Report | Location |
143+
|--------|----------|
144+
| Terminal summary | Printed to stdout during test run |
145+
| HTML report | `.logs/coverage/jest/lcov-report/index.html` |
146+
| LCOV data | `.logs/coverage/jest/lcov.info` |
147+
| Cobertura XML | `.logs/coverage/jest/cobertura.xml` |
148+
149+
The Cobertura XML report is used by continuous integration to track coverage
150+
trends and can be consumed by CI tools that support the Cobertura format.
126151

127152
## Boilerplate
128153

129-
**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)
154+
**Vortex** provides a Jest test boilerplate for the [demo module](https://github.com/drevops/vortex/blob/main/web/modules/custom/ys_demo/js/tests/ys_demo.test.js)
130155
that demonstrates testing a counter block with DOM manipulation, localStorage
131156
interaction, and event handling.
132157

.vortex/tests/phpunit/Traits/Subtests/SubtestAhoyTrait.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,12 +536,20 @@ protected function runAhoyTestPhpunit(string $type, string $file): void {
536536
protected function subtestAhoyTestJs(string $webroot = 'web'): void {
537537
$this->logStepStart();
538538

539-
$file = $webroot . '/modules/custom/sw_demo/js/sw_demo.test.js';
539+
$file = $webroot . '/modules/custom/sw_demo/js/tests/sw_demo.test.js';
540540
$this->assertFileExists($file);
541541

542542
$this->logSubstep('Run all Jest tests');
543543
$this->cmd('ahoy test-js', 'Tests:');
544544

545+
$this->logSubstep('Assert Jest coverage files are present');
546+
$this->syncToHost('.logs');
547+
$this->assertDirectoryExists('.logs/coverage/jest');
548+
$this->assertFileExists('.logs/coverage/jest/cobertura.xml');
549+
$this->assertDirectoryExists('.logs/coverage/jest/lcov-report');
550+
$this->assertFileContainsString('.logs/coverage/jest/cobertura.xml', 'line-rate="1"');
551+
$this->assertFileContainsString('.logs/coverage/jest/cobertura.xml', 'branch-rate="1"');
552+
545553
$this->logSubstep('Run Jest tests matching a path pattern');
546554
$this->cmd('ahoy test-js -- --testPathPattern=sw_demo', 'Tests:');
547555

web/modules/custom/ys_demo/js/ys_demo.test.js renamed to web/modules/custom/ys_demo/js/tests/ys_demo.test.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,21 @@
22
* @jest-environment jsdom
33
*/
44

5-
const fs = require('fs');
6-
const path = require('path');
7-
85
describe('Drupal.behaviors.ysDemo', () => {
96
beforeEach(() => {
107
localStorage.clear();
118
global.Drupal = { behaviors: {} };
129

13-
const filePath = path.resolve(__dirname, 'ys_demo.js');
14-
const code = fs.readFileSync(filePath, 'utf8');
15-
eval(code);
10+
jest.spyOn(console, 'log').mockImplementation(() => {});
11+
12+
jest.resetModules();
13+
// eslint-disable-next-line global-require
14+
require('../ys_demo.js');
1615
});
1716

1817
afterEach(() => {
1918
delete global.Drupal;
19+
jest.restoreAllMocks();
2020
});
2121

2222
function createCounterBlockHtml() {

web/modules/custom/ys_demo/js/ys_demo.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,7 @@
5151
this.removeUpdatedClassAfterDelay(valueElement);
5252

5353
// Log action for debugging.
54-
// eslint-disable-next-line no-console
55-
console.log(`Counter ${action}: ${currentValue}`);
54+
this.log(`Counter ${action}: ${currentValue}`);
5655
});
5756
});
5857
});
@@ -97,5 +96,15 @@
9796
getCounterValue() {
9897
return parseInt(localStorage.getItem(this.storageKey), 10) || 0;
9998
},
99+
100+
/**
101+
* Log a message to the console.
102+
*
103+
* @param {string} message The message to log.
104+
*/
105+
log(message) {
106+
// eslint-disable-next-line no-console
107+
console.log(message);
108+
},
100109
};
101110
})(Drupal);

0 commit comments

Comments
 (0)