diff --git a/.eslintrc.json b/.eslintrc.json index 091de0679..97387452c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -66,7 +66,8 @@ "files": ["*.test.js"], "env": { "jest": true }, "rules": { - "no-eval": "off", + "global-require": "off", + "import/extensions": "off", "max-nested-callbacks": ["warn", 5], "jsdoc/check-tag-names": "off" } diff --git a/.vortex/docs/content/development/jest.mdx b/.vortex/docs/content/development/jest.mdx index caf990892..c02c46bc8 100644 --- a/.vortex/docs/content/development/jest.mdx +++ b/.vortex/docs/content/development/jest.mdx @@ -14,24 +14,26 @@ For running tests, configuration, and CI settings, see the ## Test file structure -Test files are co-located with source files in the `js/` directory of each -custom module: +Test files are placed in a `tests/` subdirectory within 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 + ├── my_module.js # Source file (Drupal behavior) + └── tests/ + └── 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. +directories and their subdirectories. 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. +`Drupal` is a global object. Tests load these source files using `require()` +after setting up the required globals. ### Test template @@ -40,17 +42,14 @@ setting up the required globals. * @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); + jest.resetModules(); + // eslint-disable-next-line global-require + require('../my_module.js'); }); afterEach(() => { @@ -69,9 +68,12 @@ describe('Drupal.behaviors.myModule', () => { ### 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`. +The `require()` call executes the source file's IIFE, which receives +`global.Drupal` as its `Drupal` parameter and registers the behavior. +After `require()`, the behavior is accessible via `Drupal.behaviors.myModule`. + +The `jest.resetModules()` call before `require()` clears the module cache so +the IIFE re-executes on each test with a fresh `global.Drupal` object. ### Mocking globals @@ -121,12 +123,35 @@ 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. +the `jest` environment and allows `global-require`. No additional ESLint +configuration is needed for test files. + +## Coverage + +Jest collects code coverage automatically when running tests via `ahoy test-js`. +Coverage is configured in [`jest.config.js`](https://github.com/drevops/vortex/blob/main/jest.config.js) +with the following settings: + +- **Source files**: all `*.js` files in `web/modules/custom/*/js/` directories, + excluding `*.test.js` files +- **Reporters**: text (terminal summary), lcov, HTML, and Cobertura XML +- **Output directory**: `.logs/coverage/jest` + +After running tests, coverage reports are available at: + +| Report | Location | +|--------|----------| +| Terminal summary | Printed to stdout during test run | +| HTML report | `.logs/coverage/jest/lcov-report/index.html` | +| LCOV data | `.logs/coverage/jest/lcov.info` | +| Cobertura XML | `.logs/coverage/jest/cobertura.xml` | + +The Cobertura XML report is used by continuous integration to track coverage +trends and can be consumed by CI tools that support the Cobertura format. ## 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) +**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) that demonstrates testing a counter block with DOM manipulation, localStorage interaction, and event handling. diff --git a/.vortex/installer/tests/Fixtures/handler_process/_baseline/.eslintrc.json b/.vortex/installer/tests/Fixtures/handler_process/_baseline/.eslintrc.json index 091de0679..97387452c 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/_baseline/.eslintrc.json +++ b/.vortex/installer/tests/Fixtures/handler_process/_baseline/.eslintrc.json @@ -66,7 +66,8 @@ "files": ["*.test.js"], "env": { "jest": true }, "rules": { - "no-eval": "off", + "global-require": "off", + "import/extensions": "off", "max-nested-callbacks": ["warn", 5], "jsdoc/check-tag-names": "off" } diff --git a/.vortex/installer/tests/Fixtures/handler_process/_baseline/jest.config.js b/.vortex/installer/tests/Fixtures/handler_process/_baseline/jest.config.js index 0ac9851fc..2eb63f90c 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/_baseline/jest.config.js +++ b/.vortex/installer/tests/Fixtures/handler_process/_baseline/jest.config.js @@ -29,4 +29,7 @@ module.exports = { '/web/themes/', '/.vortex/', ], + collectCoverageFrom: ['web/modules/custom/**/js/**/*.js', '!**/*.test.js'], + coverageReporters: ['text', 'lcov', 'html', ['cobertura', { file: 'cobertura.xml' }]], + coverageDirectory: '.logs/coverage/jest', }; diff --git a/.vortex/installer/tests/Fixtures/handler_process/_baseline/package.json b/.vortex/installer/tests/Fixtures/handler_process/_baseline/package.json index 62eac946c..599a38163 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/_baseline/package.json +++ b/.vortex/installer/tests/Fixtures/handler_process/_baseline/package.json @@ -14,7 +14,7 @@ "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" + "test": "jest --coverage" }, "devDependencies": { "@homer0/prettier-plugin-jsdoc": "__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 886028723..64d20adea 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 @@ -51,8 +51,7 @@ this.removeUpdatedClassAfterDelay(valueElement); // Log action for debugging. - // eslint-disable-next-line no-console - console.log(`Counter ${action}: ${currentValue}`); + this.log(`Counter ${action}: ${currentValue}`); }); }); }); @@ -97,5 +96,15 @@ getCounterValue() { return parseInt(localStorage.getItem(this.storageKey), 10) || 0; }, + + /** + * Log a message to the console. + * + * @param {string} message The message to log. + */ + log(message) { + // eslint-disable-next-line no-console + console.log(message); + }, }; })(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/tests/sw_demo.test.js similarity index 97% rename from .vortex/installer/tests/Fixtures/handler_process/_baseline/web/modules/custom/sw_demo/js/sw_demo.test.js rename to .vortex/installer/tests/Fixtures/handler_process/_baseline/web/modules/custom/sw_demo/js/tests/sw_demo.test.js index a423fda42..6998b8d57 100644 --- 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/tests/sw_demo.test.js @@ -2,21 +2,21 @@ * @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); + jest.spyOn(console, 'log').mockImplementation(() => {}); + + jest.resetModules(); + // eslint-disable-next-line global-require + require('../sw_demo.js'); }); afterEach(() => { delete global.Drupal; + jest.restoreAllMocks(); }); function createCounterBlockHtml() { 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/tests/-sw_demo.test.js similarity index 100% rename from .vortex/installer/tests/Fixtures/handler_process/custom_modules_no_demo/web/modules/custom/sw_demo/js/-sw_demo.test.js rename to .vortex/installer/tests/Fixtures/handler_process/custom_modules_no_demo/web/modules/custom/sw_demo/js/tests/-sw_demo.test.js 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/tests/-sw_demo.test.js similarity index 100% rename from .vortex/installer/tests/Fixtures/handler_process/custom_modules_none/web/modules/custom/sw_demo/js/-sw_demo.test.js rename to .vortex/installer/tests/Fixtures/handler_process/custom_modules_none/web/modules/custom/sw_demo/js/tests/-sw_demo.test.js 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 886028723..64d20adea 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 @@ -51,8 +51,7 @@ this.removeUpdatedClassAfterDelay(valueElement); // Log action for debugging. - // eslint-disable-next-line no-console - console.log(`Counter ${action}: ${currentValue}`); + this.log(`Counter ${action}: ${currentValue}`); }); }); }); @@ -97,5 +96,15 @@ getCounterValue() { return parseInt(localStorage.getItem(this.storageKey), 10) || 0; }, + + /** + * Log a message to the console. + * + * @param {string} message The message to log. + */ + log(message) { + // eslint-disable-next-line no-console + console.log(message); + }, }; })(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/tests/sw_demo.test.js similarity index 97% rename from .vortex/installer/tests/Fixtures/handler_process/hosting_acquia/docroot/modules/custom/sw_demo/js/sw_demo.test.js rename to .vortex/installer/tests/Fixtures/handler_process/hosting_acquia/docroot/modules/custom/sw_demo/js/tests/sw_demo.test.js index a423fda42..6998b8d57 100644 --- 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/tests/sw_demo.test.js @@ -2,21 +2,21 @@ * @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); + jest.spyOn(console, 'log').mockImplementation(() => {}); + + jest.resetModules(); + // eslint-disable-next-line global-require + require('../sw_demo.js'); }); afterEach(() => { delete global.Drupal; + jest.restoreAllMocks(); }); function createCounterBlockHtml() { 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 index bf6ae3613..b65c8d373 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/jest.config.js +++ b/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/jest.config.js @@ -7,7 +7,7 @@ const roots = []; dirs.forEach((dir) => { -@@ -20,13 +20,13 @@ +@@ -20,16 +20,16 @@ module.exports = { testEnvironment: 'jest-environment-jsdom', @@ -25,4 +25,8 @@ + '/docroot/themes/', '/.vortex/', ], +- collectCoverageFrom: ['web/modules/custom/**/js/**/*.js', '!**/*.test.js'], ++ collectCoverageFrom: ['docroot/modules/custom/**/js/**/*.js', '!**/*.test.js'], + coverageReporters: ['text', 'lcov', 'html', ['cobertura', { file: 'cobertura.xml' }]], + coverageDirectory: '.logs/coverage/jest', }; 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 40c40de58..988ece7a0 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/package.json +++ b/.vortex/installer/tests/Fixtures/handler_process/hosting_acquia/package.json @@ -12,5 +12,5 @@ + "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", - "test": "jest" + "test": "jest --coverage" }, 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/tests/-sw_demo.test.js similarity index 100% rename from .vortex/installer/tests/Fixtures/handler_process/hosting_acquia/web/modules/custom/sw_demo/js/-sw_demo.test.js rename to .vortex/installer/tests/Fixtures/handler_process/hosting_acquia/web/modules/custom/sw_demo/js/tests/-sw_demo.test.js 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 886028723..64d20adea 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 @@ -51,8 +51,7 @@ this.removeUpdatedClassAfterDelay(valueElement); // Log action for debugging. - // eslint-disable-next-line no-console - console.log(`Counter ${action}: ${currentValue}`); + this.log(`Counter ${action}: ${currentValue}`); }); }); }); @@ -97,5 +96,15 @@ getCounterValue() { return parseInt(localStorage.getItem(this.storageKey), 10) || 0; }, + + /** + * Log a message to the console. + * + * @param {string} message The message to log. + */ + log(message) { + // eslint-disable-next-line no-console + console.log(message); + }, }; })(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/tests/sw_demo.test.js similarity index 97% rename from .vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/docroot/modules/custom/sw_demo/js/sw_demo.test.js rename to .vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/docroot/modules/custom/sw_demo/js/tests/sw_demo.test.js index a423fda42..6998b8d57 100644 --- 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/tests/sw_demo.test.js @@ -2,21 +2,21 @@ * @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); + jest.spyOn(console, 'log').mockImplementation(() => {}); + + jest.resetModules(); + // eslint-disable-next-line global-require + require('../sw_demo.js'); }); afterEach(() => { delete global.Drupal; + jest.restoreAllMocks(); }); function createCounterBlockHtml() { 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 index bf6ae3613..b65c8d373 100644 --- 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 @@ -7,7 +7,7 @@ const roots = []; dirs.forEach((dir) => { -@@ -20,13 +20,13 @@ +@@ -20,16 +20,16 @@ module.exports = { testEnvironment: 'jest-environment-jsdom', @@ -25,4 +25,8 @@ + '/docroot/themes/', '/.vortex/', ], +- collectCoverageFrom: ['web/modules/custom/**/js/**/*.js', '!**/*.test.js'], ++ collectCoverageFrom: ['docroot/modules/custom/**/js/**/*.js', '!**/*.test.js'], + coverageReporters: ['text', 'lcov', 'html', ['cobertura', { file: 'cobertura.xml' }]], + coverageDirectory: '.logs/coverage/jest', }; 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 40c40de58..988ece7a0 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 @@ -12,5 +12,5 @@ + "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", - "test": "jest" + "test": "jest --coverage" }, 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/tests/-sw_demo.test.js similarity index 100% rename from .vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/web/modules/custom/sw_demo/js/-sw_demo.test.js rename to .vortex/installer/tests/Fixtures/handler_process/hosting_project_name___acquia/web/modules/custom/sw_demo/js/tests/-sw_demo.test.js 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/tests/-sw_demo.test.js similarity index 100% rename from .vortex/installer/tests/Fixtures/handler_process/names/web/modules/custom/sw_demo/js/-sw_demo.test.js rename to .vortex/installer/tests/Fixtures/handler_process/names/web/modules/custom/sw_demo/js/tests/-sw_demo.test.js 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/tests/the_force_demo.test.js similarity index 97% rename from .vortex/installer/tests/Fixtures/handler_process/names/web/modules/custom/the_force_demo/js/the_force_demo.test.js rename to .vortex/installer/tests/Fixtures/handler_process/names/web/modules/custom/the_force_demo/js/tests/the_force_demo.test.js index 5f3df0f6a..b6ef93dfc 100644 --- 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/tests/the_force_demo.test.js @@ -2,21 +2,21 @@ * @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); + jest.spyOn(console, 'log').mockImplementation(() => {}); + + jest.resetModules(); + // eslint-disable-next-line global-require + require('../the_force_demo.js'); }); afterEach(() => { delete global.Drupal; + jest.restoreAllMocks(); }); function createCounterBlockHtml() { 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 e17060cb4..e52c63f92 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 @@ -51,8 +51,7 @@ this.removeUpdatedClassAfterDelay(valueElement); // Log action for debugging. - // eslint-disable-next-line no-console - console.log(`Counter ${action}: ${currentValue}`); + this.log(`Counter ${action}: ${currentValue}`); }); }); }); @@ -97,5 +96,15 @@ getCounterValue() { return parseInt(localStorage.getItem(this.storageKey), 10) || 0; }, + + /** + * Log a message to the console. + * + * @param {string} message The message to log. + */ + log(message) { + // eslint-disable-next-line no-console + console.log(message); + }, }; })(Drupal); 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 718afcd48..f6b26c02e 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 @@ -10,7 +10,7 @@ "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", - "test": "jest" + "test": "jest --coverage" }, "devDependencies": { - "@homer0/prettier-plugin-jsdoc": "__VERSION__", 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 718afcd48..f6b26c02e 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 @@ -10,7 +10,7 @@ "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", - "test": "jest" + "test": "jest --coverage" }, "devDependencies": { - "@homer0/prettier-plugin-jsdoc": "__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 718afcd48..f6b26c02e 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 @@ -10,7 +10,7 @@ "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", - "test": "jest" + "test": "jest --coverage" }, "devDependencies": { - "@homer0/prettier-plugin-jsdoc": "__VERSION__", 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 index cc07aebbd..ed7727d03 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest/package.json +++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_jest/package.json @@ -3,7 +3,7 @@ "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" +- "test": "jest --coverage" + "lint-fix": "yarn run lint-fix-js && yarn run lint-fix-css" }, "devDependencies": { 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 deleted file mode 100644 index e69de29bb..000000000 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 index cc07aebbd..ed7727d03 100644 --- 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 @@ -3,7 +3,7 @@ "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" +- "test": "jest --coverage" + "lint-fix": "yarn run lint-fix-js && yarn run lint-fix-css" }, "devDependencies": { 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 deleted file mode 100644 index e69de29bb..000000000 diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_stylelint/package.json b/.vortex/installer/tests/Fixtures/handler_process/tools_no_stylelint/package.json index 02f7d388b..1ab1708cd 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/tools_no_stylelint/package.json +++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_stylelint/package.json @@ -9,7 +9,7 @@ - "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", - "test": "jest" + "test": "jest --coverage" }, "devDependencies": { @@ -28,9 +26,6 @@ diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_stylelint_circleci/package.json b/.vortex/installer/tests/Fixtures/handler_process/tools_no_stylelint_circleci/package.json index 02f7d388b..1ab1708cd 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/tools_no_stylelint_circleci/package.json +++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_stylelint_circleci/package.json @@ -9,7 +9,7 @@ - "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", - "test": "jest" + "test": "jest --coverage" }, "devDependencies": { @@ -28,9 +26,6 @@ diff --git a/.vortex/installer/tests/Fixtures/handler_process/tools_no_stylelint_no_theme/package.json b/.vortex/installer/tests/Fixtures/handler_process/tools_no_stylelint_no_theme/package.json index 02f7d388b..1ab1708cd 100644 --- a/.vortex/installer/tests/Fixtures/handler_process/tools_no_stylelint_no_theme/package.json +++ b/.vortex/installer/tests/Fixtures/handler_process/tools_no_stylelint_no_theme/package.json @@ -9,7 +9,7 @@ - "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", - "test": "jest" + "test": "jest --coverage" }, "devDependencies": { @@ -28,9 +26,6 @@ diff --git a/.vortex/tests/phpunit/Traits/Subtests/SubtestAhoyTrait.php b/.vortex/tests/phpunit/Traits/Subtests/SubtestAhoyTrait.php index a3d2e9e37..84949a411 100644 --- a/.vortex/tests/phpunit/Traits/Subtests/SubtestAhoyTrait.php +++ b/.vortex/tests/phpunit/Traits/Subtests/SubtestAhoyTrait.php @@ -536,12 +536,20 @@ protected function runAhoyTestPhpunit(string $type, string $file): void { protected function subtestAhoyTestJs(string $webroot = 'web'): void { $this->logStepStart(); - $file = $webroot . '/modules/custom/sw_demo/js/sw_demo.test.js'; + $file = $webroot . '/modules/custom/sw_demo/js/tests/sw_demo.test.js'; $this->assertFileExists($file); $this->logSubstep('Run all Jest tests'); $this->cmd('ahoy test-js', 'Tests:'); + $this->logSubstep('Assert Jest coverage files are present'); + $this->syncToHost('.logs'); + $this->assertDirectoryExists('.logs/coverage/jest'); + $this->assertFileExists('.logs/coverage/jest/cobertura.xml'); + $this->assertDirectoryExists('.logs/coverage/jest/lcov-report'); + $this->assertFileContainsString('.logs/coverage/jest/cobertura.xml', 'line-rate="1"'); + $this->assertFileContainsString('.logs/coverage/jest/cobertura.xml', 'branch-rate="1"'); + $this->logSubstep('Run Jest tests matching a path pattern'); $this->cmd('ahoy test-js -- --testPathPattern=sw_demo', 'Tests:'); diff --git a/jest.config.js b/jest.config.js index 0ac9851fc..2eb63f90c 100644 --- a/jest.config.js +++ b/jest.config.js @@ -29,4 +29,7 @@ module.exports = { '/web/themes/', '/.vortex/', ], + collectCoverageFrom: ['web/modules/custom/**/js/**/*.js', '!**/*.test.js'], + coverageReporters: ['text', 'lcov', 'html', ['cobertura', { file: 'cobertura.xml' }]], + coverageDirectory: '.logs/coverage/jest', }; diff --git a/package.json b/package.json index 0fbe69c09..f357e0100 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "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" + "test": "jest --coverage" }, "devDependencies": { "@homer0/prettier-plugin-jsdoc": "^11.0.1", diff --git a/web/modules/custom/ys_demo/js/ys_demo.test.js b/web/modules/custom/ys_demo/js/tests/ys_demo.test.js similarity index 97% rename from web/modules/custom/ys_demo/js/ys_demo.test.js rename to web/modules/custom/ys_demo/js/tests/ys_demo.test.js index 03c8f24d5..64ff4d4e8 100644 --- a/web/modules/custom/ys_demo/js/ys_demo.test.js +++ b/web/modules/custom/ys_demo/js/tests/ys_demo.test.js @@ -2,21 +2,21 @@ * @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); + jest.spyOn(console, 'log').mockImplementation(() => {}); + + jest.resetModules(); + // eslint-disable-next-line global-require + require('../ys_demo.js'); }); afterEach(() => { delete global.Drupal; + jest.restoreAllMocks(); }); function createCounterBlockHtml() { diff --git a/web/modules/custom/ys_demo/js/ys_demo.js b/web/modules/custom/ys_demo/js/ys_demo.js index 3b027b983..4924be04d 100644 --- a/web/modules/custom/ys_demo/js/ys_demo.js +++ b/web/modules/custom/ys_demo/js/ys_demo.js @@ -51,8 +51,7 @@ this.removeUpdatedClassAfterDelay(valueElement); // Log action for debugging. - // eslint-disable-next-line no-console - console.log(`Counter ${action}: ${currentValue}`); + this.log(`Counter ${action}: ${currentValue}`); }); }); }); @@ -97,5 +96,15 @@ getCounterValue() { return parseInt(localStorage.getItem(this.storageKey), 10) || 0; }, + + /** + * Log a message to the console. + * + * @param {string} message The message to log. + */ + log(message) { + // eslint-disable-next-line no-console + console.log(message); + }, }; })(Drupal);