@zohodesk/unit-testing-framework 0.0.5-experimental → 0.0.7-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/build/index.js +7 -7
- package/build/src/config/default-config.js +4 -1
- package/build/src/helpers/mergeObjects.js +13 -0
- package/build/src/reporters/html-reporter.js +355 -0
- package/build/src/reporters/reporter-handler.js +4 -3
- package/build/src/runner/jest-runner.js +3 -11
- package/package.json +2 -2
- package/build/src/config/config-loader.js +0 -119
package/build/index.js
CHANGED
|
@@ -15,22 +15,22 @@ Object.defineProperty(exports, "default", {
|
|
|
15
15
|
return _jestRunner.default;
|
|
16
16
|
}
|
|
17
17
|
});
|
|
18
|
-
Object.defineProperty(exports, "
|
|
18
|
+
Object.defineProperty(exports, "getDefaultConfig", {
|
|
19
19
|
enumerable: true,
|
|
20
20
|
get: function () {
|
|
21
|
-
return
|
|
21
|
+
return _defaultConfig.getDefaultConfig;
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
|
-
Object.defineProperty(exports, "
|
|
24
|
+
Object.defineProperty(exports, "globalSetup", {
|
|
25
25
|
enumerable: true,
|
|
26
26
|
get: function () {
|
|
27
|
-
return
|
|
27
|
+
return _setup.default;
|
|
28
28
|
}
|
|
29
29
|
});
|
|
30
|
-
Object.defineProperty(exports, "
|
|
30
|
+
Object.defineProperty(exports, "globalTeardown", {
|
|
31
31
|
enumerable: true,
|
|
32
32
|
get: function () {
|
|
33
|
-
return
|
|
33
|
+
return _teardown.default;
|
|
34
34
|
}
|
|
35
35
|
});
|
|
36
36
|
Object.defineProperty(exports, "resolveReporters", {
|
|
@@ -40,7 +40,7 @@ Object.defineProperty(exports, "resolveReporters", {
|
|
|
40
40
|
}
|
|
41
41
|
});
|
|
42
42
|
var _jestRunner = _interopRequireDefault(require("./src/runner/jest-runner.js"));
|
|
43
|
-
var
|
|
43
|
+
var _defaultConfig = require("./src/config/default-config.js");
|
|
44
44
|
var _reporterHandler = require("./src/reporters/reporter-handler.js");
|
|
45
45
|
var _setup = _interopRequireDefault(require("./src/environment/setup.js"));
|
|
46
46
|
var _teardown = _interopRequireDefault(require("./src/environment/teardown.js"));
|
|
@@ -57,7 +57,10 @@ function getDefaultConfig(projectRoot) {
|
|
|
57
57
|
coverageReporters: ['text', 'lcov', 'clover'],
|
|
58
58
|
coveragePathIgnorePatterns: ['/node_modules/', '/tests/'],
|
|
59
59
|
// --------------- Reporters ---------------
|
|
60
|
-
reporters: ['default'
|
|
60
|
+
reporters: ['default', ['html-report', {
|
|
61
|
+
outputDir: 'unit_reports',
|
|
62
|
+
fileName: 'report.html'
|
|
63
|
+
}]],
|
|
61
64
|
// --------------- Parallelism & Performance ---------------
|
|
62
65
|
maxWorkers: '50%',
|
|
63
66
|
// Use half of available CPUs
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.mergeObjects = mergeObjects;
|
|
7
|
+
// Utility function to merge objects using spread operator
|
|
8
|
+
function mergeObjects(obj1, obj2) {
|
|
9
|
+
return {
|
|
10
|
+
...obj1,
|
|
11
|
+
...obj2
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,355 @@
|
|
|
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
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
+
/**
|
|
11
|
+
* html-reporter.js
|
|
12
|
+
*
|
|
13
|
+
* A zero-dependency custom Jest reporter that generates a self-contained
|
|
14
|
+
* HTML report after each test run. The report includes:
|
|
15
|
+
* - Summary (total / passed / failed / skipped / duration)
|
|
16
|
+
* - Per-suite breakdown with expandable test cases
|
|
17
|
+
* - Failure messages and stack traces
|
|
18
|
+
* - Filtering controls (All / Passed / Failed / Skipped)
|
|
19
|
+
*
|
|
20
|
+
* Usage in reporters config:
|
|
21
|
+
* ['html-report'] → defaults to <projectRoot>/unit_reports/report.html
|
|
22
|
+
* ['html-report', { outputPath: '...' }] → custom output path
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
class HtmlReporter {
|
|
26
|
+
/**
|
|
27
|
+
* @param {object} globalConfig - Jest global config
|
|
28
|
+
* @param {object} reporterOptions
|
|
29
|
+
* @param {string} [reporterOptions.outputDir] - Directory for the HTML file (default: <rootDir>/unit_reports)
|
|
30
|
+
* @param {string} [reporterOptions.fileName] - HTML file name (default: report.html)
|
|
31
|
+
* @param {string} [reporterOptions.title] - Page title (default: Unit Test Report)
|
|
32
|
+
*/
|
|
33
|
+
constructor(globalConfig, reporterOptions = {}) {
|
|
34
|
+
this.globalConfig = globalConfig;
|
|
35
|
+
this.options = reporterOptions;
|
|
36
|
+
this._suites = [];
|
|
37
|
+
this._startTime = 0;
|
|
38
|
+
}
|
|
39
|
+
onRunStart() {
|
|
40
|
+
this._startTime = Date.now();
|
|
41
|
+
this._suites = [];
|
|
42
|
+
}
|
|
43
|
+
onTestResult(_test, testResult) {
|
|
44
|
+
this._suites.push(testResult);
|
|
45
|
+
}
|
|
46
|
+
onRunComplete(_contexts, aggregatedResults) {
|
|
47
|
+
const elapsed = ((Date.now() - this._startTime) / 1000).toFixed(2);
|
|
48
|
+
const rootDir = this.globalConfig.rootDir || process.cwd();
|
|
49
|
+
const outputDir = this.options.outputDir ? _path.default.resolve(rootDir, this.options.outputDir) : _path.default.resolve(rootDir, 'unit_reports');
|
|
50
|
+
const fileName = this.options.fileName || 'report.html';
|
|
51
|
+
const title = this.options.title || 'Unit Test Report';
|
|
52
|
+
const outputPath = _path.default.join(outputDir, fileName);
|
|
53
|
+
|
|
54
|
+
// Ensure output directory exists
|
|
55
|
+
_fs.default.mkdirSync(outputDir, {
|
|
56
|
+
recursive: true
|
|
57
|
+
});
|
|
58
|
+
const html = this._generateHtml(aggregatedResults, elapsed, title);
|
|
59
|
+
_fs.default.writeFileSync(outputPath, html, 'utf-8');
|
|
60
|
+
console.log(`\n 📄 HTML report written to: ${outputPath}\n`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Private ────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
_generateHtml(results, elapsed, title) {
|
|
66
|
+
const {
|
|
67
|
+
numPassedTests,
|
|
68
|
+
numFailedTests,
|
|
69
|
+
numPendingTests,
|
|
70
|
+
numTotalTests,
|
|
71
|
+
numPassedTestSuites,
|
|
72
|
+
numFailedTestSuites,
|
|
73
|
+
numTotalTestSuites
|
|
74
|
+
} = results;
|
|
75
|
+
const suitesHtml = this._suites.map(suite => this._renderSuite(suite)).join('\n');
|
|
76
|
+
const timestamp = new Date().toLocaleString();
|
|
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>${title}</title>
|
|
83
|
+
<style>
|
|
84
|
+
:root {
|
|
85
|
+
--pass: #2ecc71;
|
|
86
|
+
--fail: #e74c3c;
|
|
87
|
+
--skip: #f39c12;
|
|
88
|
+
--bg: #1a1a2e;
|
|
89
|
+
--card: #16213e;
|
|
90
|
+
--text: #eaeaea;
|
|
91
|
+
--muted: #8892a4;
|
|
92
|
+
--border: #2a2a4a;
|
|
93
|
+
}
|
|
94
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
95
|
+
body {
|
|
96
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, sans-serif;
|
|
97
|
+
background: var(--bg);
|
|
98
|
+
color: var(--text);
|
|
99
|
+
padding: 24px;
|
|
100
|
+
line-height: 1.5;
|
|
101
|
+
}
|
|
102
|
+
.header {
|
|
103
|
+
text-align: center;
|
|
104
|
+
margin-bottom: 32px;
|
|
105
|
+
}
|
|
106
|
+
.header h1 { font-size: 1.8rem; margin-bottom: 4px; }
|
|
107
|
+
.header .meta { color: var(--muted); font-size: 0.85rem; }
|
|
108
|
+
|
|
109
|
+
/* ── Summary Cards ─── */
|
|
110
|
+
.summary {
|
|
111
|
+
display: flex;
|
|
112
|
+
gap: 16px;
|
|
113
|
+
justify-content: center;
|
|
114
|
+
flex-wrap: wrap;
|
|
115
|
+
margin-bottom: 28px;
|
|
116
|
+
}
|
|
117
|
+
.card {
|
|
118
|
+
background: var(--card);
|
|
119
|
+
border: 1px solid var(--border);
|
|
120
|
+
border-radius: 10px;
|
|
121
|
+
padding: 18px 28px;
|
|
122
|
+
min-width: 140px;
|
|
123
|
+
text-align: center;
|
|
124
|
+
}
|
|
125
|
+
.card .value { font-size: 2rem; font-weight: 700; }
|
|
126
|
+
.card .label { font-size: 0.8rem; color: var(--muted); text-transform: uppercase; letter-spacing: 1px; }
|
|
127
|
+
.card.pass .value { color: var(--pass); }
|
|
128
|
+
.card.fail .value { color: var(--fail); }
|
|
129
|
+
.card.skip .value { color: var(--skip); }
|
|
130
|
+
.card.total .value { color: #58a6ff; }
|
|
131
|
+
|
|
132
|
+
/* ── Filters ─── */
|
|
133
|
+
.filters {
|
|
134
|
+
display: flex;
|
|
135
|
+
gap: 8px;
|
|
136
|
+
justify-content: center;
|
|
137
|
+
margin-bottom: 24px;
|
|
138
|
+
flex-wrap: wrap;
|
|
139
|
+
}
|
|
140
|
+
.filters button {
|
|
141
|
+
background: var(--card);
|
|
142
|
+
color: var(--text);
|
|
143
|
+
border: 1px solid var(--border);
|
|
144
|
+
padding: 6px 18px;
|
|
145
|
+
border-radius: 20px;
|
|
146
|
+
cursor: pointer;
|
|
147
|
+
font-size: 0.85rem;
|
|
148
|
+
transition: all 0.2s;
|
|
149
|
+
}
|
|
150
|
+
.filters button:hover,
|
|
151
|
+
.filters button.active { background: #58a6ff; color: #000; border-color: #58a6ff; }
|
|
152
|
+
|
|
153
|
+
/* ── Suites ─── */
|
|
154
|
+
.suite {
|
|
155
|
+
background: var(--card);
|
|
156
|
+
border: 1px solid var(--border);
|
|
157
|
+
border-radius: 10px;
|
|
158
|
+
margin-bottom: 16px;
|
|
159
|
+
overflow: hidden;
|
|
160
|
+
}
|
|
161
|
+
.suite-header {
|
|
162
|
+
display: flex;
|
|
163
|
+
align-items: center;
|
|
164
|
+
padding: 14px 18px;
|
|
165
|
+
cursor: pointer;
|
|
166
|
+
user-select: none;
|
|
167
|
+
gap: 10px;
|
|
168
|
+
}
|
|
169
|
+
.suite-header:hover { background: rgba(255,255,255,0.03); }
|
|
170
|
+
.suite-header .icon { font-size: 1.1rem; }
|
|
171
|
+
.suite-header .name {
|
|
172
|
+
flex: 1;
|
|
173
|
+
font-weight: 600;
|
|
174
|
+
font-size: 0.95rem;
|
|
175
|
+
word-break: break-all;
|
|
176
|
+
}
|
|
177
|
+
.suite-header .counts { font-size: 0.8rem; color: var(--muted); }
|
|
178
|
+
.suite-header .arrow {
|
|
179
|
+
transition: transform 0.2s;
|
|
180
|
+
color: var(--muted);
|
|
181
|
+
}
|
|
182
|
+
.suite.open .suite-header .arrow { transform: rotate(90deg); }
|
|
183
|
+
|
|
184
|
+
.suite-body { display: none; border-top: 1px solid var(--border); }
|
|
185
|
+
.suite.open .suite-body { display: block; }
|
|
186
|
+
|
|
187
|
+
.test-row {
|
|
188
|
+
display: flex;
|
|
189
|
+
align-items: flex-start;
|
|
190
|
+
padding: 10px 18px 10px 36px;
|
|
191
|
+
gap: 10px;
|
|
192
|
+
border-bottom: 1px solid var(--border);
|
|
193
|
+
font-size: 0.9rem;
|
|
194
|
+
}
|
|
195
|
+
.test-row:last-child { border-bottom: none; }
|
|
196
|
+
.test-row .status {
|
|
197
|
+
flex-shrink: 0;
|
|
198
|
+
width: 20px;
|
|
199
|
+
text-align: center;
|
|
200
|
+
}
|
|
201
|
+
.test-row .test-title { flex: 1; }
|
|
202
|
+
.test-row .duration { color: var(--muted); font-size: 0.8rem; flex-shrink: 0; }
|
|
203
|
+
.test-row.passed .status { color: var(--pass); }
|
|
204
|
+
.test-row.failed .status { color: var(--fail); }
|
|
205
|
+
.test-row.pending .status { color: var(--skip); }
|
|
206
|
+
|
|
207
|
+
.failure-msg {
|
|
208
|
+
background: rgba(231, 76, 60, 0.1);
|
|
209
|
+
border-left: 3px solid var(--fail);
|
|
210
|
+
margin: 6px 0 4px 30px;
|
|
211
|
+
padding: 10px 14px;
|
|
212
|
+
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
213
|
+
font-size: 0.8rem;
|
|
214
|
+
white-space: pre-wrap;
|
|
215
|
+
word-break: break-word;
|
|
216
|
+
color: #f5a5a5;
|
|
217
|
+
border-radius: 4px;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.footer {
|
|
221
|
+
text-align: center;
|
|
222
|
+
color: var(--muted);
|
|
223
|
+
font-size: 0.75rem;
|
|
224
|
+
margin-top: 32px;
|
|
225
|
+
padding-top: 16px;
|
|
226
|
+
border-top: 1px solid var(--border);
|
|
227
|
+
}
|
|
228
|
+
</style>
|
|
229
|
+
</head>
|
|
230
|
+
<body>
|
|
231
|
+
<div class="header">
|
|
232
|
+
<h1>${title}</h1>
|
|
233
|
+
<div class="meta">${timestamp} · Duration: ${elapsed}s</div>
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
<div class="summary">
|
|
237
|
+
<div class="card total"><div class="value">${numTotalTests}</div><div class="label">Total</div></div>
|
|
238
|
+
<div class="card pass"><div class="value">${numPassedTests}</div><div class="label">Passed</div></div>
|
|
239
|
+
<div class="card fail"><div class="value">${numFailedTests}</div><div class="label">Failed</div></div>
|
|
240
|
+
<div class="card skip"><div class="value">${numPendingTests}</div><div class="label">Skipped</div></div>
|
|
241
|
+
<div class="card"><div class="value">${numTotalTestSuites}</div><div class="label">Suites</div></div>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
<div class="filters">
|
|
245
|
+
<button class="active" data-filter="all">All</button>
|
|
246
|
+
<button data-filter="passed">Passed</button>
|
|
247
|
+
<button data-filter="failed">Failed</button>
|
|
248
|
+
<button data-filter="pending">Skipped</button>
|
|
249
|
+
</div>
|
|
250
|
+
|
|
251
|
+
<div id="suites">
|
|
252
|
+
${suitesHtml}
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
<div class="footer">
|
|
256
|
+
Generated by @zohodesk/unit-testing-framework
|
|
257
|
+
</div>
|
|
258
|
+
|
|
259
|
+
<script>
|
|
260
|
+
// Toggle suite expand/collapse
|
|
261
|
+
document.querySelectorAll('.suite-header').forEach(header => {
|
|
262
|
+
header.addEventListener('click', () => {
|
|
263
|
+
header.parentElement.classList.toggle('open');
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Filter buttons
|
|
268
|
+
document.querySelectorAll('.filters button').forEach(btn => {
|
|
269
|
+
btn.addEventListener('click', () => {
|
|
270
|
+
document.querySelectorAll('.filters button').forEach(b => b.classList.remove('active'));
|
|
271
|
+
btn.classList.add('active');
|
|
272
|
+
const filter = btn.dataset.filter;
|
|
273
|
+
document.querySelectorAll('.suite').forEach(suite => {
|
|
274
|
+
if (filter === 'all') {
|
|
275
|
+
suite.style.display = '';
|
|
276
|
+
suite.querySelectorAll('.test-row').forEach(r => r.style.display = '');
|
|
277
|
+
} else {
|
|
278
|
+
const rows = suite.querySelectorAll('.test-row');
|
|
279
|
+
let visible = 0;
|
|
280
|
+
rows.forEach(r => {
|
|
281
|
+
if (r.classList.contains(filter)) {
|
|
282
|
+
r.style.display = '';
|
|
283
|
+
visible++;
|
|
284
|
+
} else {
|
|
285
|
+
r.style.display = 'none';
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
suite.style.display = visible > 0 ? '' : 'none';
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Auto-expand suites with failures
|
|
295
|
+
document.querySelectorAll('.suite').forEach(suite => {
|
|
296
|
+
if (suite.querySelector('.test-row.failed')) {
|
|
297
|
+
suite.classList.add('open');
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
</script>
|
|
301
|
+
</body>
|
|
302
|
+
</html>`;
|
|
303
|
+
}
|
|
304
|
+
_renderSuite(suite) {
|
|
305
|
+
const suitePath = suite.testFilePath || 'Unknown suite';
|
|
306
|
+
// Show path relative to rootDir for readability
|
|
307
|
+
const rootDir = this.globalConfig.rootDir || process.cwd();
|
|
308
|
+
const relativePath = _path.default.relative(rootDir, suitePath);
|
|
309
|
+
const passed = suite.numPassingTests || 0;
|
|
310
|
+
const failed = suite.numFailingTests || 0;
|
|
311
|
+
const pending = suite.numPendingTests || 0;
|
|
312
|
+
const suiteIcon = failed > 0 ? '✖' : pending > 0 && passed === 0 ? '○' : '✔';
|
|
313
|
+
const suiteIconColor = failed > 0 ? 'var(--fail)' : 'var(--pass)';
|
|
314
|
+
const testsHtml = (suite.testResults || []).map(t => this._renderTest(t)).join('\n');
|
|
315
|
+
return `
|
|
316
|
+
<div class="suite">
|
|
317
|
+
<div class="suite-header">
|
|
318
|
+
<span class="icon" style="color:${suiteIconColor}">${suiteIcon}</span>
|
|
319
|
+
<span class="name">${this._escape(relativePath)}</span>
|
|
320
|
+
<span class="counts">${passed} passed · ${failed} failed · ${pending} skipped</span>
|
|
321
|
+
<span class="arrow">▶</span>
|
|
322
|
+
</div>
|
|
323
|
+
<div class="suite-body">
|
|
324
|
+
${testsHtml}
|
|
325
|
+
</div>
|
|
326
|
+
</div>`;
|
|
327
|
+
}
|
|
328
|
+
_renderTest(testResult) {
|
|
329
|
+
const status = testResult.status; // 'passed' | 'failed' | 'pending'
|
|
330
|
+
const icon = status === 'passed' ? '✔' : status === 'failed' ? '✖' : '○';
|
|
331
|
+
const duration = testResult.duration != null ? `${testResult.duration}ms` : '';
|
|
332
|
+
const title = testResult.ancestorTitles ? [...testResult.ancestorTitles, testResult.title].join(' › ') : testResult.title;
|
|
333
|
+
let failureHtml = '';
|
|
334
|
+
if (status === 'failed' && testResult.failureMessages?.length) {
|
|
335
|
+
failureHtml = testResult.failureMessages.map(msg => `<div class="failure-msg">${this._escape(this._stripAnsi(msg))}</div>`).join('\n');
|
|
336
|
+
}
|
|
337
|
+
return `
|
|
338
|
+
<div class="test-row ${status}">
|
|
339
|
+
<span class="status">${icon}</span>
|
|
340
|
+
<div class="test-title">
|
|
341
|
+
${this._escape(title)}
|
|
342
|
+
${failureHtml}
|
|
343
|
+
</div>
|
|
344
|
+
<span class="duration">${duration}</span>
|
|
345
|
+
</div>`;
|
|
346
|
+
}
|
|
347
|
+
_escape(str) {
|
|
348
|
+
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
349
|
+
}
|
|
350
|
+
_stripAnsi(str) {
|
|
351
|
+
// eslint-disable-next-line no-control-regex
|
|
352
|
+
return String(str).replace(/\x1B\[[0-9;]*m/g, '');
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
exports.default = HtmlReporter;
|
|
@@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.resolveReporters = resolveReporters;
|
|
7
|
-
var
|
|
7
|
+
var _path = _interopRequireDefault(require("path"));
|
|
8
8
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
9
|
/**
|
|
10
10
|
* reporter-handler.js
|
|
@@ -22,7 +22,8 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
|
|
|
22
22
|
// Do not add import.meta.url workarounds here.
|
|
23
23
|
|
|
24
24
|
const BUILTIN_ALIASES = {
|
|
25
|
-
'framework-default':
|
|
25
|
+
'framework-default': _path.default.resolve(__dirname, 'default-reporter.js'),
|
|
26
|
+
'html-report': _path.default.resolve(__dirname, 'html-reporter.js')
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
/**
|
|
@@ -49,7 +50,7 @@ function resolveEntry(entry, projectRoot) {
|
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
// Relative paths → resolve from consumer project root
|
|
52
|
-
const abs =
|
|
53
|
+
const abs = _path.default.resolve(projectRoot, name);
|
|
53
54
|
return opts ? [abs, opts] : abs;
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.default = createJestRunner;
|
|
7
7
|
var _path = _interopRequireDefault(require("path"));
|
|
8
|
-
var
|
|
8
|
+
var _defaultConfig = require("../config/default-config.js");
|
|
9
9
|
var _reporterHandler = require("../reporters/reporter-handler.js");
|
|
10
10
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
11
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } /**
|
|
@@ -20,8 +20,6 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
|
|
|
20
20
|
/**
|
|
21
21
|
* @typedef {object} RunnerOptions
|
|
22
22
|
* @property {string} [projectRoot] - Absolute path to the consumer project (default: cwd).
|
|
23
|
-
* @property {string} [configPath] - Path to consumer jest.unit.config.{js,mjs,json}.
|
|
24
|
-
* @property {object} [inlineConfig] - Inline Jest config overrides (highest priority).
|
|
25
23
|
* @property {boolean} [coverage] - Enable coverage collection.
|
|
26
24
|
* @property {string[]} [testFiles] - Specific test file patterns to run.
|
|
27
25
|
* @property {boolean} [verbose] - Verbose output.
|
|
@@ -39,8 +37,6 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
|
|
|
39
37
|
async function createJestRunner(options = {}) {
|
|
40
38
|
const {
|
|
41
39
|
projectRoot = process.cwd(),
|
|
42
|
-
configPath,
|
|
43
|
-
inlineConfig = {},
|
|
44
40
|
coverage,
|
|
45
41
|
testFiles,
|
|
46
42
|
verbose,
|
|
@@ -49,12 +45,8 @@ async function createJestRunner(options = {}) {
|
|
|
49
45
|
watch = false
|
|
50
46
|
} = options;
|
|
51
47
|
|
|
52
|
-
// ── 1.
|
|
53
|
-
const mergedConfig =
|
|
54
|
-
projectRoot,
|
|
55
|
-
configPath,
|
|
56
|
-
inlineConfig
|
|
57
|
-
});
|
|
48
|
+
// ── 1. Get default configuration ───────────────────────────
|
|
49
|
+
const mergedConfig = (0, _defaultConfig.getDefaultConfig)(projectRoot);
|
|
58
50
|
|
|
59
51
|
// ── 2. Apply CLI-level overrides ───────────────────────────
|
|
60
52
|
if (coverage !== undefined) mergedConfig.collectCoverage = coverage;
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zohodesk/unit-testing-framework",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7-experimental",
|
|
4
4
|
"description": "A modular Jest-based unit testing framework",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./build/index.js",
|
|
8
|
-
"./config": "./build/src/config/config
|
|
8
|
+
"./config": "./build/src/config/default-config.js",
|
|
9
9
|
"./reporters": "./build/src/reporters/reporter-handler.js",
|
|
10
10
|
"./runner": "./build/src/runner/jest-runner.js"
|
|
11
11
|
},
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.loadConfig = loadConfig;
|
|
7
|
-
var _fs = _interopRequireDefault(require("fs"));
|
|
8
|
-
var _path = _interopRequireDefault(require("path"));
|
|
9
|
-
var _url = require("url");
|
|
10
|
-
var _defaultConfig = require("./default-config.js");
|
|
11
|
-
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
|
-
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } /**
|
|
13
|
-
* config-loader.js
|
|
14
|
-
*
|
|
15
|
-
* Responsible for:
|
|
16
|
-
* 1. Loading the framework default config.
|
|
17
|
-
* 2. Loading optional consumer-level config (file or inline).
|
|
18
|
-
* 3. Deep-merging them with consumer config taking priority.
|
|
19
|
-
*
|
|
20
|
-
* Merge priority (highest → lowest):
|
|
21
|
-
* inline options > consumer config file > framework defaults
|
|
22
|
-
*/
|
|
23
|
-
/**
|
|
24
|
-
* Deep-merge two plain objects. Arrays are concatenated & de-duped.
|
|
25
|
-
* `source` values override `target` values.
|
|
26
|
-
*/
|
|
27
|
-
function deepMerge(target, source) {
|
|
28
|
-
const output = {
|
|
29
|
-
...target
|
|
30
|
-
};
|
|
31
|
-
for (const key of Object.keys(source)) {
|
|
32
|
-
const srcVal = source[key];
|
|
33
|
-
const tgtVal = target[key];
|
|
34
|
-
if (srcVal === undefined) continue;
|
|
35
|
-
if (Array.isArray(srcVal) && Array.isArray(tgtVal)) {
|
|
36
|
-
// Concatenate and de-duplicate
|
|
37
|
-
output[key] = [...new Set([...tgtVal, ...srcVal])];
|
|
38
|
-
} else if (srcVal !== null && typeof srcVal === 'object' && !Array.isArray(srcVal) && tgtVal !== null && typeof tgtVal === 'object' && !Array.isArray(tgtVal)) {
|
|
39
|
-
output[key] = deepMerge(tgtVal, srcVal);
|
|
40
|
-
} else {
|
|
41
|
-
output[key] = srcVal;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return output;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Attempt to load a consumer config file.
|
|
49
|
-
* Supports: .js (ESM), .mjs, .json, .ts (if ts-node is available).
|
|
50
|
-
*
|
|
51
|
-
* @param {string} configPath - Absolute path to the config file.
|
|
52
|
-
* @returns {Promise<object>} Loaded configuration or empty object.
|
|
53
|
-
*/
|
|
54
|
-
async function loadConfigFile(configPath) {
|
|
55
|
-
if (!_fs.default.existsSync(configPath)) {
|
|
56
|
-
return {};
|
|
57
|
-
}
|
|
58
|
-
const ext = _path.default.extname(configPath).toLowerCase();
|
|
59
|
-
if (ext === '.json') {
|
|
60
|
-
const raw = _fs.default.readFileSync(configPath, 'utf-8');
|
|
61
|
-
return JSON.parse(raw);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ESM dynamic import works for .js / .mjs
|
|
65
|
-
const fileUrl = (0, _url.pathToFileURL)(configPath).href;
|
|
66
|
-
const mod = await (specifier => new Promise(r => r(`${specifier}`)).then(s => _interopRequireWildcard(require(s))))(fileUrl);
|
|
67
|
-
return mod.default ?? mod;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Resolve the consumer config file path.
|
|
72
|
-
* Search order:
|
|
73
|
-
* 1. Explicit `configPath` option
|
|
74
|
-
* 2. `jest.unit.config.js` in project root
|
|
75
|
-
* 3. `jest.unit.config.mjs` in project root
|
|
76
|
-
* 4. `jest.unit.config.json` in project root
|
|
77
|
-
*
|
|
78
|
-
* @param {string} projectRoot
|
|
79
|
-
* @param {string} [explicitPath]
|
|
80
|
-
* @returns {string|null}
|
|
81
|
-
*/
|
|
82
|
-
function resolveConfigFilePath(projectRoot, explicitPath) {
|
|
83
|
-
if (explicitPath) {
|
|
84
|
-
const abs = _path.default.isAbsolute(explicitPath) ? explicitPath : _path.default.resolve(projectRoot, explicitPath);
|
|
85
|
-
return _fs.default.existsSync(abs) ? abs : null;
|
|
86
|
-
}
|
|
87
|
-
const candidates = ['jest.unit.config.js', 'jest.unit.config.mjs', 'jest.unit.config.json'];
|
|
88
|
-
for (const name of candidates) {
|
|
89
|
-
const full = _path.default.resolve(projectRoot, name);
|
|
90
|
-
if (_fs.default.existsSync(full)) return full;
|
|
91
|
-
}
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Load and merge configuration.
|
|
97
|
-
*
|
|
98
|
-
* @param {object} options
|
|
99
|
-
* @param {string} options.projectRoot - Consumer project root (default: process.cwd()).
|
|
100
|
-
* @param {string} [options.configPath] - Explicit path to consumer config.
|
|
101
|
-
* @param {object} [options.inlineConfig] - Inline overrides (highest priority).
|
|
102
|
-
* @returns {Promise<import('@jest/types').Config.InitialOptions>}
|
|
103
|
-
*/
|
|
104
|
-
async function loadConfig({
|
|
105
|
-
projectRoot = process.cwd(),
|
|
106
|
-
configPath,
|
|
107
|
-
inlineConfig = {}
|
|
108
|
-
} = {}) {
|
|
109
|
-
// 1. Framework defaults
|
|
110
|
-
const defaults = (0, _defaultConfig.getDefaultConfig)(projectRoot);
|
|
111
|
-
|
|
112
|
-
// 2. Consumer config file
|
|
113
|
-
const resolvedPath = resolveConfigFilePath(projectRoot, configPath);
|
|
114
|
-
const consumerFileConfig = resolvedPath ? await loadConfigFile(resolvedPath) : {};
|
|
115
|
-
|
|
116
|
-
// 3. Merge: defaults ← consumer file ← inline
|
|
117
|
-
const merged = deepMerge(deepMerge(defaults, consumerFileConfig), inlineConfig);
|
|
118
|
-
return merged;
|
|
119
|
-
}
|