@zohodesk/unit-testing-framework 0.0.26-experimental → 0.0.28-experimental

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # unit-testing-framework
1
+ # @zohodesk/unit-testing-framework
2
2
 
3
- A modular, Jest-based unit testing package designed to plug into existing CLI pipelines. Runs Jest **programmatically** (no shell execution), supports default + consumer config merging, custom reporters, global setup/teardown, and parallel execution.
3
+ A modular, Jest-based unit testing framework designed to plug into existing CLI pipelines. Runs Jest **programmatically** via `@jest/core` (no shell execution), ships with sensible defaults, HTML report generation via `jest-html-reporter`, and automatic Jest globals injection.
4
4
 
5
5
  ---
6
6
 
@@ -8,24 +8,22 @@ A modular, Jest-based unit testing package designed to plug into existing CLI pi
8
8
 
9
9
  ```
10
10
  unit-testing-framework/
11
- ├── package.json # ESM package with proper exports
11
+ ├── package.json # Package config (publishes build/ only)
12
+ ├── babel.config.json # Babel config – ESM → CJS for Node 18
12
13
  ├── index.js # Public API entry point
13
14
  ├── src/
14
15
  │ ├── runner/
15
- │ │ └── jest-runner.js # Programmatic Jest execution via @jest/core
16
+ │ │ ├── jest-runner.js # createJestRunner() main orchestrator
17
+ │ │ └── runner-base.js # buildArgv() & executeJest() via @jest/core
16
18
  │ ├── config/
17
- │ │ ├── config-loader.js # Config resolution & deep-merge logic
18
- │ └── default-config.js # Framework-level default Jest config
19
- ├── reporters/
20
- │ ├── reporter-handler.js # Reporter resolution (aliases, paths)
21
- └── default-reporter.js # Bundled custom Jest reporter
22
- │ └── environment/
23
- │ ├── setup.js # Global setup (runs once before suite)
24
- │ └── teardown.js # Global teardown (runs once after suite)
19
+ │ │ └── default-config.js # getDefaultConfig() framework defaults
20
+ ├── environment/
21
+ │ └── globals-inject.js # Injects jest into globalThis (setup file)
22
+ └── utils/
23
+ └── logger.js # Colored console logger utility
24
+ ├── build/ # Transpiled output (published to npm)
25
25
  ├── examples/
26
- ├── consumer-cli.js # Example integration in existing CLI
27
- │ └── jest.unit.config.js # Example consumer config file
28
- ├── .npmignore
26
+ └── consumer-cli.js # Example CLI integration
29
27
  └── README.md
30
28
  ```
31
29
 
@@ -34,24 +32,38 @@ unit-testing-framework/
34
32
  ## Installation
35
33
 
36
34
  ```bash
37
- npm install unit-testing-framework
38
- # jest is a required peer dependency
39
- npm install --save-dev jest
35
+ npm install @zohodesk/unit-testing-framework
40
36
  ```
41
37
 
38
+ All Jest dependencies (`@jest/core`, `jest-environment-jsdom`, `babel-jest`) are bundled — consumers don't need to install Jest separately.
39
+
42
40
  ---
43
41
 
44
42
  ## Quick Start
45
43
 
46
44
  ```js
47
- import createJestRunner from 'unit-testing-framework';
45
+ import { createJestRunner } from '@zohodesk/unit-testing-framework';
48
46
 
49
- // Run with defaults - auto-discovers jest.unit.config.js in project root
47
+ // Run all tests with framework defaults
50
48
  const results = await createJestRunner();
51
49
 
52
50
  process.exitCode = results.numFailedTests > 0 ? 1 : 0;
53
51
  ```
54
52
 
53
+ Run a specific test file by pattern:
54
+
55
+ ```js
56
+ const results = await createJestRunner({
57
+ testPathPattern: 'login.test.js',
58
+ });
59
+ ```
60
+
61
+ Enable watch mode:
62
+
63
+ ```js
64
+ await createJestRunner({ watch: true });
65
+ ```
66
+
55
67
  ---
56
68
 
57
69
  ## API
@@ -60,42 +72,81 @@ process.exitCode = results.numFailedTests > 0 ? 1 : 0;
60
72
 
61
73
  | Option | Type | Default | Description |
62
74
  |---|---|---|---|
63
- | `projectRoot` | `string` | `process.cwd()` | Consumer project root |
64
- | `configPath` | `string` | (auto-discovered) | Path to consumer config file |
65
- | `inlineConfig` | `object` | `{}` | Inline Jest config overrides (highest priority) |
66
- | `coverage` | `boolean` | from config | Enable coverage collection |
67
- | `testFiles` | `string[]` | all | Specific test file patterns |
68
- | `verbose` | `boolean` | `true` | Verbose output |
69
- | `maxWorkers` | `number\|string` | `'50%'` | Worker concurrency |
70
- | `silent` | `boolean` | `false` | Suppress console output |
71
- | `watch` | `boolean` | `false` | Watch mode |
75
+ | `projectRoot` | `string` | `process.cwd()` | Consumer project root directory |
76
+ | `testPathPattern` | `string` | | Filter tests by file path pattern |
77
+ | `watch` | `boolean` | `false` | Enable Jest watch mode |
78
+
79
+ Returns: `Promise<AggregatedResult>` Jest's aggregated results object.
72
80
 
73
- Returns: `Promise<AggregatedResult>` (Jest results object)
81
+ ---
74
82
 
75
- ### Named exports
83
+ ## Default Configuration
76
84
 
77
- ```js
78
- import {
79
- createJestRunner,
80
- loadConfig,
81
- resolveReporters,
82
- globalSetup,
83
- globalTeardown,
84
- } from 'unit-testing-framework';
85
- ```
85
+ The framework provides a complete Jest config via `getDefaultConfig(projectRoot)`:
86
+
87
+ | Setting | Value |
88
+ |---|---|
89
+ | `testMatch` | `['**/__tests__/**/*.test.js']` |
90
+ | `testEnvironment` | `jsdom` |
91
+ | `transform` | Babel with `@babel/preset-env` |
92
+ | `testTimeout` | 20 000 ms |
93
+ | `automock` | `false` |
94
+ | `clearMocks` | `true` |
95
+ | `passWithNoTests` | `true` |
96
+ | `testPathIgnorePatterns` | `['/build/']` |
97
+
98
+ ### Reporters
99
+
100
+ Two reporters are enabled by default:
101
+
102
+ 1. **`default`** — Jest's built-in console reporter, prints pass/fail/skip summary to the terminal.
103
+ 2. **`jest-html-reporter`** — generates an HTML test report using the [`jest-html-reporter`](https://www.npmjs.com/package/jest-html-reporter) package.
104
+
105
+ #### HTML Reporter Configuration
106
+
107
+ | Option | Value | Description |
108
+ |---|---|---|
109
+ | `outputPath` | `<rootDir>/test-slices/unit-test/unit_reports/report.html` | Path for the generated report |
110
+ | `pageTitle` | `Unit Test Report` | Page title |
111
+ | `includeFailureMsg` | `true` | Show failure messages in the report |
112
+ | `includeSuiteFailure` | `true` | Show suite-level failures |
113
+ | `theme` | `darkTheme` | Dark-themed report |
114
+
115
+ ### Globals Injection
116
+
117
+ The framework's `setupFiles` automatically injects the `jest` object into `globalThis`, so test files can call `jest.fn()`, `jest.mock()`, etc. without explicit imports.
118
+
119
+ ---
120
+
121
+ ## Example: CLI Integration
122
+
123
+ See [examples/consumer-cli.js](examples/consumer-cli.js) for a complete example:
86
124
 
87
- Sub-path exports:
88
125
  ```js
89
- import { loadConfig } from 'unit-testing-framework/config';
90
- import { resolveReporters } from 'unit-testing-framework/reporters';
126
+ import { createJestRunner } from '@zohodesk/unit-testing-framework';
127
+
128
+ const command = process.argv[2];
129
+
130
+ if (command === 'unit-test') {
131
+ const testFile = process.argv[3];
132
+ const results = await createJestRunner({
133
+ testPathPattern: testFile,
134
+ });
135
+ process.exitCode = results.numFailedTests > 0 ? 1 : 0;
136
+ } else if (command === 'unit-test:watch') {
137
+ await createJestRunner({ watch: true });
138
+ }
91
139
  ```
92
140
 
93
141
  ---
94
142
 
95
- ## Config Priority
143
+ ## Build
96
144
 
97
- ```
98
- inline options > consumer config file > framework defaults
145
+ Source is written in ESM and transpiled to CommonJS via Babel.
146
+
147
+ ```bash
148
+ npm run build # Transpile src/ → build/
149
+ npm run clean # Delete build/ and rebuild
99
150
  ```
100
151
 
101
152
  ### Consumer Config File
@@ -7,6 +7,7 @@ exports.getDefaultConfig = getDefaultConfig;
7
7
  var _path = _interopRequireDefault(require("path"));
8
8
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
9
9
  function getDefaultConfig(projectRoot) {
10
+ const reportOutputDir = _path.default.resolve(projectRoot, 'test-slices', 'unit-test', 'unit_reports');
10
11
  return {
11
12
  rootDir: projectRoot,
12
13
  testMatch: ['**/__tests__/**/*.test.js'],
@@ -21,7 +22,13 @@ function getDefaultConfig(projectRoot) {
21
22
  }]]
22
23
  }]
23
24
  },
24
- reporters: ['default', _path.default.resolve(__dirname, '..', 'reporters', 'html-reporter.js')],
25
+ reporters: ['default', ['jest-html-reporter', {
26
+ outputPath: _path.default.join(reportOutputDir, 'report.html'),
27
+ pageTitle: 'Unit Test Report',
28
+ includeFailureMsg: true,
29
+ includeSuiteFailure: true,
30
+ theme: 'darkTheme'
31
+ }]],
25
32
  testTimeout: 20_000,
26
33
  // 20 seconds per test
27
34
 
@@ -10,7 +10,7 @@ async function createJestRunner(options = {}) {
10
10
  const {
11
11
  projectRoot = process.cwd(),
12
12
  testPathPattern,
13
- watch = false
13
+ watch = false // Re-runs tests automatically when source files change
14
14
  } = options;
15
15
 
16
16
  // ── 1. Build framework-controlled config ───────────────────
@@ -4,25 +4,27 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.Logger = void 0;
7
- class LoggerImpl {
8
- constructor() {
9
- this.SUCCESS_TYPE = 'success';
10
- this.FAILURE_TYPE = 'failure';
11
- this.INFO_TYPE = 'info';
12
- this.colors = {
13
- 'success': ['\x1b[36m', '\x1b[0m'],
14
- 'failure': ['\x1b[31m', '\x1b[0m'],
15
- 'info': ['\x1b[33m', '\x1b[0m']
16
- };
17
- this.consoleLogger = console;
18
- }
19
- error(err) {
20
- this.consoleLogger.error(err);
21
- }
22
- info() {}
7
+ const COLORS = {
8
+ success: '\x1b[36m',
9
+ failure: '\x1b[31m',
10
+ info: '\x1b[33m',
11
+ reset: '\x1b[0m'
12
+ };
13
+
14
+ // eslint-disable-next-line no-console
15
+ const consoleLogger = console;
16
+ const Logger = exports.Logger = {
23
17
  log(type, message) {
24
- const color = this.colors[type];
25
- this.consoleLogger.log(`${color[0]}${message}${color[1]}\n`);
18
+ const color = COLORS[type] || COLORS.reset;
19
+ consoleLogger.log(`${color}${message}${COLORS.reset}`);
20
+ },
21
+ error(message) {
22
+ consoleLogger.error(`${COLORS.failure}${message}${COLORS.reset}`);
23
+ },
24
+ info(message) {
25
+ consoleLogger.log(`${COLORS.info}${message}${COLORS.reset}`);
26
+ },
27
+ success(message) {
28
+ consoleLogger.log(`${COLORS.success}${message}${COLORS.reset}`);
26
29
  }
27
- }
28
- const Logger = exports.Logger = new LoggerImpl();
30
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zohodesk/unit-testing-framework",
3
- "version": "0.0.26-experimental",
3
+ "version": "0.0.28-experimental",
4
4
  "description": "A modular Jest-based unit testing framework",
5
5
  "main": "./build/index.js",
6
6
  "exports": {
@@ -11,7 +11,6 @@
11
11
  ],
12
12
  "scripts": {
13
13
  "test": "node --experimental-vm-modules node_modules/.bin/jest",
14
- "lint": "eslint src/ index.js",
15
14
  "build": "babel src -d build/src --copy-files && babel index.js --out-file build/index.js",
16
15
  "prepublishOnly": "npm run build",
17
16
  "clean": "rm -rf build && npm run build"
@@ -25,7 +24,8 @@
25
24
  "@jest/core": "30.2.0",
26
25
  "@jest/types": "30.2.0",
27
26
  "babel-jest": "30.2.0",
28
- "jest-environment-jsdom": "30.2.0"
27
+ "jest-environment-jsdom": "30.2.0",
28
+ "jest-html-reporter": "^4.3.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@babel/cli": "7.28.6",
@@ -1,54 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
- var _logger = require("../utils/logger");
8
- var _reporterUtils = require("./reporter-utils.js");
9
- /**
10
- * default-reporter.js
11
- *
12
- * A lightweight custom Jest reporter bundled with the framework.
13
- * Outputs a formatted summary to the console after each test run.
14
- */
15
-
16
- class DefaultReporter {
17
- constructor(globalConfig, _reporterOptions) {
18
- this.globalConfig = globalConfig;
19
- this._timer = (0, _reporterUtils.createTimer)();
20
- }
21
- onRunStart(_results, _options) {
22
- this._timer.start();
23
- _logger.Logger.log(_logger.Logger.INFO_TYPE, '\n╔══════════════════════════════════════════╗');
24
- _logger.Logger.log(_logger.Logger.INFO_TYPE, '║ Unit Testing Framework – Test Run ║');
25
- _logger.Logger.log(_logger.Logger.INFO_TYPE, '╚══════════════════════════════════════════╝\n');
26
- }
27
- onTestStart(test) {
28
- _logger.Logger.log(_logger.Logger.INFO_TYPE, ` ▶ Running: ${test.path}`);
29
- }
30
- onTestResult(_test, testResult, _aggregatedResults) {
31
- const {
32
- numPassingTests,
33
- numFailingTests,
34
- numPendingTests
35
- } = testResult;
36
- const icon = numFailingTests > 0 ? '✖' : '✔';
37
- _logger.Logger.log(_logger.Logger.INFO_TYPE, ` ${icon} Passed: ${numPassingTests} Failed: ${numFailingTests} Skipped: ${numPendingTests}`);
38
- }
39
- onRunComplete(_contexts, results) {
40
- const elapsed = this._timer.elapsed();
41
- const {
42
- numPassedTests,
43
- numFailedTests,
44
- numTotalTests
45
- } = results;
46
- _logger.Logger.log(_logger.Logger.INFO_TYPE, '\n──────────────────────────────────────────');
47
- _logger.Logger.log(_logger.Logger.INFO_TYPE, ` Total: ${numTotalTests}`);
48
- _logger.Logger.log(_logger.Logger.INFO_TYPE, ` Passed: ${numPassedTests}`);
49
- _logger.Logger.log(_logger.Logger.INFO_TYPE, ` Failed: ${numFailedTests}`);
50
- _logger.Logger.log(_logger.Logger.INFO_TYPE, ` Time: ${elapsed}s`);
51
- _logger.Logger.log(_logger.Logger.INFO_TYPE, '──────────────────────────────────────────\n');
52
- }
53
- }
54
- exports.default = DefaultReporter;
@@ -1,61 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
- var _fs = _interopRequireDefault(require("fs"));
8
- var _path = _interopRequireDefault(require("path"));
9
- var _reporterUtils = require("./reporter-utils.js");
10
- var _htmlTemplate = require("./html-template.js");
11
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
- /**
13
- * html-reporter.js
14
- *
15
- * Jest reporter that generates a self-contained HTML report.
16
- * This class handles only the Jest lifecycle hooks.
17
- * HTML generation is delegated to html-template.js.
18
- */
19
-
20
- class HtmlReporter {
21
- /**
22
- * @param {object} globalConfig - Jest global config
23
- * @param {object} reporterOptions
24
- * @param {string} [reporterOptions.outputDir] - Directory for the HTML file (default: <rootDir>/unit_reports)
25
- * @param {string} [reporterOptions.fileName] - HTML file name (default: report.html)
26
- * @param {string} [reporterOptions.title] - Page title (default: Unit Test Report)
27
- */
28
- constructor(globalConfig, reporterOptions = {}) {
29
- this.globalConfig = globalConfig;
30
- this.options = reporterOptions;
31
- this._suites = [];
32
- this._timer = (0, _reporterUtils.createTimer)();
33
- }
34
- onRunStart() {
35
- this._timer.start();
36
- this._suites = [];
37
- }
38
- onTestResult(_test, testResult) {
39
- this._suites.push(testResult);
40
- }
41
- onRunComplete(_contexts, aggregatedResults) {
42
- const rootDir = this.globalConfig.rootDir || process.cwd();
43
- const outputDir = this.options.outputDir ? _path.default.resolve(rootDir, this.options.outputDir) : _path.default.resolve(rootDir, 'test-slices', 'unit-test', 'unit_reports');
44
- const fileName = this.options.fileName || 'report.html';
45
- const title = this.options.title || 'Unit Test Report';
46
- const outputPath = _path.default.join(outputDir, fileName);
47
- _fs.default.mkdirSync(outputDir, {
48
- recursive: true
49
- });
50
- const html = (0, _htmlTemplate.generateHtml)({
51
- aggregatedResults,
52
- suites: this._suites,
53
- rootDir,
54
- elapsed: this._timer.elapsed(),
55
- title
56
- });
57
- _fs.default.writeFileSync(outputPath, html, 'utf-8');
58
- console.log(`\n 📄 HTML report written to: ${outputPath}\n`);
59
- }
60
- }
61
- exports.default = HtmlReporter;
@@ -1,177 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.generateHtml = generateHtml;
7
- exports.renderSuite = renderSuite;
8
- exports.renderTest = renderTest;
9
- var _fs = _interopRequireDefault(require("fs"));
10
- var _path = _interopRequireDefault(require("path"));
11
- var _reporterUtils = require("./reporter-utils.js");
12
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
13
- /**
14
- * html-template.js
15
- *
16
- * Pure functions that generate the HTML report string.
17
- * No file I/O, no Jest API — just data in, HTML string out.
18
- *
19
- * CSS and client-side JS are read from separate files under
20
- * templates/ and inlined at report-generation time so the
21
- * output remains a single self-contained HTML file.
22
- */
23
-
24
- // NOTE: __dirname is available after Babel transpiles ESM → CJS.
25
-
26
- const CSS_PATH = _path.default.resolve(__dirname, 'templates', 'report.css');
27
- const SCRIPT_PATH = _path.default.resolve(__dirname, 'templates', 'report-script.js');
28
-
29
- /**
30
- * Read template assets once and cache them in memory.
31
- * Lazy-loaded on first call to avoid reading files at import time.
32
- */
33
- let _cssCache = null;
34
- let _scriptCache = null;
35
- function getCSS() {
36
- if (!_cssCache) {
37
- _cssCache = _fs.default.readFileSync(CSS_PATH, 'utf-8');
38
- }
39
- return _cssCache;
40
- }
41
- function getScript() {
42
- if (!_scriptCache) {
43
- _scriptCache = _fs.default.readFileSync(SCRIPT_PATH, 'utf-8');
44
- }
45
- return _scriptCache;
46
- }
47
-
48
- /**
49
- * Generate the full HTML report string.
50
- *
51
- * @param {object} params
52
- * @param {object} params.aggregatedResults - Jest aggregated results.
53
- * @param {object[]} params.suites - Collected test suite results.
54
- * @param {string} params.rootDir - Project root for relative paths.
55
- * @param {string} params.elapsed - Elapsed time string (e.g. "1.23").
56
- * @param {string} params.title - Report page title.
57
- * @returns {string} Self-contained HTML string.
58
- */
59
- function generateHtml({
60
- aggregatedResults,
61
- suites,
62
- rootDir,
63
- elapsed,
64
- title
65
- }) {
66
- const {
67
- numPassedTests,
68
- numFailedTests,
69
- numPendingTests,
70
- numTotalTests,
71
- numTotalTestSuites
72
- } = aggregatedResults;
73
- const suitesHtml = suites.map(suite => renderSuite(suite, rootDir)).join('\n');
74
- const timestamp = new Date().toLocaleString();
75
- const css = getCSS();
76
- const script = getScript();
77
- return `<!DOCTYPE html>
78
- <html lang="en">
79
- <head>
80
- <meta charset="UTF-8" />
81
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
82
- <title>${(0, _reporterUtils.escapeHtml)(title)}</title>
83
- <style>
84
- ${css}
85
- </style>
86
- </head>
87
- <body>
88
- <div class="header">
89
- <h1>${(0, _reporterUtils.escapeHtml)(title)}</h1>
90
- <div class="meta">${timestamp} &nbsp;·&nbsp; Duration: ${elapsed}s</div>
91
- </div>
92
-
93
- <div class="summary">
94
- <div class="card total"><div class="value">${numTotalTests}</div><div class="label">Total</div></div>
95
- <div class="card pass"><div class="value">${numPassedTests}</div><div class="label">Passed</div></div>
96
- <div class="card fail"><div class="value">${numFailedTests}</div><div class="label">Failed</div></div>
97
- <div class="card skip"><div class="value">${numPendingTests}</div><div class="label">Skipped</div></div>
98
- <div class="card"><div class="value">${numTotalTestSuites}</div><div class="label">Suites</div></div>
99
- </div>
100
-
101
- <div class="filters">
102
- <button class="active" data-filter="all">All</button>
103
- <button data-filter="passed">Passed</button>
104
- <button data-filter="failed">Failed</button>
105
- <button data-filter="pending">Skipped</button>
106
- </div>
107
-
108
- <div id="suites">
109
- ${suitesHtml}
110
- </div>
111
-
112
- <div class="footer">
113
- Generated by @zohodesk/unit-testing-framework
114
- </div>
115
-
116
- <script>
117
- ${script}
118
- </script>
119
- </body>
120
- </html>`;
121
- }
122
-
123
- /**
124
- * Render a single test suite section.
125
- *
126
- * @param {object} suite - Jest test suite result.
127
- * @param {string} rootDir - Project root for computing relative paths.
128
- * @returns {string} HTML string for the suite.
129
- */
130
- function renderSuite(suite, rootDir) {
131
- const suitePath = suite.testFilePath || 'Unknown suite';
132
- const relativePath = _path.default.relative(rootDir, suitePath);
133
- const passed = suite.numPassingTests || 0;
134
- const failed = suite.numFailingTests || 0;
135
- const pending = suite.numPendingTests || 0;
136
- const suiteIcon = failed > 0 ? '✖' : pending > 0 && passed === 0 ? '○' : '✔';
137
- const suiteIconColor = failed > 0 ? 'var(--fail)' : 'var(--pass)';
138
- const testsHtml = (suite.testResults || []).map(t => renderTest(t)).join('\n');
139
- return `
140
- <div class="suite">
141
- <div class="suite-header">
142
- <span class="icon" style="color:${suiteIconColor}">${suiteIcon}</span>
143
- <span class="name">${(0, _reporterUtils.escapeHtml)(relativePath)}</span>
144
- <span class="counts">${passed} passed · ${failed} failed · ${pending} skipped</span>
145
- <span class="arrow">▶</span>
146
- </div>
147
- <div class="suite-body">
148
- ${testsHtml}
149
- </div>
150
- </div>`;
151
- }
152
-
153
- /**
154
- * Render a single test row.
155
- *
156
- * @param {object} testResult - Individual test result from Jest.
157
- * @returns {string} HTML string for the test row.
158
- */
159
- function renderTest(testResult) {
160
- const status = testResult.status; // 'passed' | 'failed' | 'pending'
161
- const icon = status === 'passed' ? '✔' : status === 'failed' ? '✖' : '○';
162
- const duration = testResult.duration != null ? `${testResult.duration}ms` : '';
163
- const title = testResult.ancestorTitles ? [...testResult.ancestorTitles, testResult.title].join(' › ') : testResult.title;
164
- let failureHtml = '';
165
- if (status === 'failed' && testResult.failureMessages?.length) {
166
- failureHtml = testResult.failureMessages.map(msg => `<div class="failure-msg">${(0, _reporterUtils.escapeHtml)((0, _reporterUtils.stripAnsi)(msg))}</div>`).join('\n');
167
- }
168
- return `
169
- <div class="test-row ${status}">
170
- <span class="status">${icon}</span>
171
- <div class="test-title">
172
- ${(0, _reporterUtils.escapeHtml)(title)}
173
- ${failureHtml}
174
- </div>
175
- <span class="duration">${duration}</span>
176
- </div>`;
177
- }
@@ -1,51 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.createTimer = createTimer;
7
- exports.escapeHtml = escapeHtml;
8
- exports.stripAnsi = stripAnsi;
9
- /**
10
- * reporter-utils.js
11
- *
12
- * Shared utilities used by both the HTML and default reporters.
13
- */
14
-
15
- /**
16
- * Create a simple timer that tracks elapsed seconds.
17
- *
18
- * @returns {{ start(): void, elapsed(): string }}
19
- */
20
- function createTimer() {
21
- let startTime = 0;
22
- return {
23
- start() {
24
- startTime = Date.now();
25
- },
26
- elapsed() {
27
- return ((Date.now() - startTime) / 1000).toFixed(2);
28
- }
29
- };
30
- }
31
-
32
- /**
33
- * Escape HTML special characters to prevent injection.
34
- *
35
- * @param {string} str
36
- * @returns {string}
37
- */
38
- function escapeHtml(str) {
39
- return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
40
- }
41
-
42
- /**
43
- * Strip ANSI escape codes from a string.
44
- *
45
- * @param {string} str
46
- * @returns {string}
47
- */
48
- function stripAnsi(str) {
49
- // eslint-disable-next-line no-control-regex
50
- return String(str).replace(/\x1B\[[0-9;]*m/g, '');
51
- }
@@ -1,44 +0,0 @@
1
- // Toggle suite expand/collapse
2
- document.querySelectorAll('.suite-header').forEach(function (header) {
3
- header.addEventListener('click', function () {
4
- header.parentElement.classList.toggle('open');
5
- });
6
- });
7
-
8
- // Filter buttons
9
- document.querySelectorAll('.filters button').forEach(function (btn) {
10
- btn.addEventListener('click', function () {
11
- document.querySelectorAll('.filters button').forEach(function (b) {
12
- b.classList.remove('active');
13
- });
14
- btn.classList.add('active');
15
- var filter = btn.dataset.filter;
16
- document.querySelectorAll('.suite').forEach(function (suite) {
17
- if (filter === 'all') {
18
- suite.style.display = '';
19
- suite.querySelectorAll('.test-row').forEach(function (r) {
20
- r.style.display = '';
21
- });
22
- } else {
23
- var rows = suite.querySelectorAll('.test-row');
24
- var visible = 0;
25
- rows.forEach(function (r) {
26
- if (r.classList.contains(filter)) {
27
- r.style.display = '';
28
- visible++;
29
- } else {
30
- r.style.display = 'none';
31
- }
32
- });
33
- suite.style.display = visible > 0 ? '' : 'none';
34
- }
35
- });
36
- });
37
- });
38
-
39
- // Auto-expand suites with failures
40
- document.querySelectorAll('.suite').forEach(function (suite) {
41
- if (suite.querySelector('.test-row.failed')) {
42
- suite.classList.add('open');
43
- }
44
- });
@@ -1,144 +0,0 @@
1
- :root {
2
- --pass: #2ecc71;
3
- --fail: #e74c3c;
4
- --skip: #f39c12;
5
- --bg: #1a1a2e;
6
- --card: #16213e;
7
- --text: #eaeaea;
8
- --muted: #8892a4;
9
- --border: #2a2a4a;
10
- }
11
- * { margin: 0; padding: 0; box-sizing: border-box; }
12
- body {
13
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, sans-serif;
14
- background: var(--bg);
15
- color: var(--text);
16
- padding: 24px;
17
- line-height: 1.5;
18
- }
19
- .header {
20
- text-align: center;
21
- margin-bottom: 32px;
22
- }
23
- .header h1 { font-size: 1.8rem; margin-bottom: 4px; }
24
- .header .meta { color: var(--muted); font-size: 0.85rem; }
25
-
26
- /* ── Summary Cards ─── */
27
- .summary {
28
- display: flex;
29
- gap: 16px;
30
- justify-content: center;
31
- flex-wrap: wrap;
32
- margin-bottom: 28px;
33
- }
34
- .card {
35
- background: var(--card);
36
- border: 1px solid var(--border);
37
- border-radius: 10px;
38
- padding: 18px 28px;
39
- min-width: 140px;
40
- text-align: center;
41
- }
42
- .card .value { font-size: 2rem; font-weight: 700; }
43
- .card .label { font-size: 0.8rem; color: var(--muted); text-transform: uppercase; letter-spacing: 1px; }
44
- .card.pass .value { color: var(--pass); }
45
- .card.fail .value { color: var(--fail); }
46
- .card.skip .value { color: var(--skip); }
47
- .card.total .value { color: #58a6ff; }
48
-
49
- /* ── Filters ─── */
50
- .filters {
51
- display: flex;
52
- gap: 8px;
53
- justify-content: center;
54
- margin-bottom: 24px;
55
- flex-wrap: wrap;
56
- }
57
- .filters button {
58
- background: var(--card);
59
- color: var(--text);
60
- border: 1px solid var(--border);
61
- padding: 6px 18px;
62
- border-radius: 20px;
63
- cursor: pointer;
64
- font-size: 0.85rem;
65
- transition: all 0.2s;
66
- }
67
- .filters button:hover,
68
- .filters button.active { background: #58a6ff; color: #000; border-color: #58a6ff; }
69
-
70
- /* ── Suites ─── */
71
- .suite {
72
- background: var(--card);
73
- border: 1px solid var(--border);
74
- border-radius: 10px;
75
- margin-bottom: 16px;
76
- overflow: hidden;
77
- }
78
- .suite-header {
79
- display: flex;
80
- align-items: center;
81
- padding: 14px 18px;
82
- cursor: pointer;
83
- user-select: none;
84
- gap: 10px;
85
- }
86
- .suite-header:hover { background: rgba(255,255,255,0.03); }
87
- .suite-header .icon { font-size: 1.1rem; }
88
- .suite-header .name {
89
- flex: 1;
90
- font-weight: 600;
91
- font-size: 0.95rem;
92
- word-break: break-all;
93
- }
94
- .suite-header .counts { font-size: 0.8rem; color: var(--muted); }
95
- .suite-header .arrow {
96
- transition: transform 0.2s;
97
- color: var(--muted);
98
- }
99
- .suite.open .suite-header .arrow { transform: rotate(90deg); }
100
-
101
- .suite-body { display: none; border-top: 1px solid var(--border); }
102
- .suite.open .suite-body { display: block; }
103
-
104
- .test-row {
105
- display: flex;
106
- align-items: flex-start;
107
- padding: 10px 18px 10px 36px;
108
- gap: 10px;
109
- border-bottom: 1px solid var(--border);
110
- font-size: 0.9rem;
111
- }
112
- .test-row:last-child { border-bottom: none; }
113
- .test-row .status {
114
- flex-shrink: 0;
115
- width: 20px;
116
- text-align: center;
117
- }
118
- .test-row .test-title { flex: 1; }
119
- .test-row .duration { color: var(--muted); font-size: 0.8rem; flex-shrink: 0; }
120
- .test-row.passed .status { color: var(--pass); }
121
- .test-row.failed .status { color: var(--fail); }
122
- .test-row.pending .status { color: var(--skip); }
123
-
124
- .failure-msg {
125
- background: rgba(231, 76, 60, 0.1);
126
- border-left: 3px solid var(--fail);
127
- margin: 6px 0 4px 30px;
128
- padding: 10px 14px;
129
- font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
130
- font-size: 0.8rem;
131
- white-space: pre-wrap;
132
- word-break: break-word;
133
- color: #f5a5a5;
134
- border-radius: 4px;
135
- }
136
-
137
- .footer {
138
- text-align: center;
139
- color: var(--muted);
140
- font-size: 0.75rem;
141
- margin-top: 32px;
142
- padding-top: 16px;
143
- border-top: 1px solid var(--border);
144
- }