qunitx-cli 0.17.7 → 0.18.0
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/dist/cli.js +178 -133
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -301,16 +301,16 @@ var init_perf_logger = __esm({
|
|
|
301
301
|
}
|
|
302
302
|
});
|
|
303
303
|
|
|
304
|
-
// lib/utils/
|
|
305
|
-
async function
|
|
304
|
+
// lib/utils/chrome-prelaunch.ts
|
|
305
|
+
async function shutdownPrelaunch() {
|
|
306
306
|
if (!earlyChrome) return;
|
|
307
307
|
const { shutdown } = earlyChrome;
|
|
308
308
|
earlyChrome = null;
|
|
309
309
|
await shutdown();
|
|
310
310
|
}
|
|
311
|
-
var NON_RUN_COMMANDS, isRunCommand, browserFromArgv, openFromArgv, watchFromArgv, openWatchMode, earlyChrome,
|
|
312
|
-
var
|
|
313
|
-
"lib/utils/
|
|
311
|
+
var NON_RUN_COMMANDS, isRunCommand, browserFromArgv, openFromArgv, watchFromArgv, openWatchMode, earlyChrome, prelaunchPromise;
|
|
312
|
+
var init_chrome_prelaunch = __esm({
|
|
313
|
+
"lib/utils/chrome-prelaunch.ts"() {
|
|
314
314
|
init_find_chrome();
|
|
315
315
|
init_pre_launch_chrome();
|
|
316
316
|
init_kill_process_group();
|
|
@@ -335,12 +335,12 @@ var init_early_chrome = __esm({
|
|
|
335
335
|
killProcessGroup(earlyChrome.proc.pid);
|
|
336
336
|
});
|
|
337
337
|
}
|
|
338
|
-
perfLog("
|
|
339
|
-
|
|
340
|
-
perfLog("
|
|
338
|
+
perfLog("chrome-prelaunch.ts: module evaluated");
|
|
339
|
+
prelaunchPromise = isRunCommand && browserFromArgv === "chromium" ? findChrome().then((chromePath) => {
|
|
340
|
+
perfLog("chrome-prelaunch.ts: findChrome resolved", chromePath);
|
|
341
341
|
return preLaunchChrome(chromePath, CHROMIUM_ARGS, !openWatchMode);
|
|
342
342
|
}).then((info) => {
|
|
343
|
-
perfLog("
|
|
343
|
+
perfLog("chrome-prelaunch.ts: Chrome CDP ready", info?.cdpEndpoint ?? null);
|
|
344
344
|
if (info) earlyChrome = info;
|
|
345
345
|
return info;
|
|
346
346
|
}) : Promise.resolve(null);
|
|
@@ -400,11 +400,11 @@ var init_path_exists = __esm({
|
|
|
400
400
|
}
|
|
401
401
|
});
|
|
402
402
|
|
|
403
|
-
// lib/utils/read-
|
|
403
|
+
// lib/utils/read-template.ts
|
|
404
404
|
import fs3 from "node:fs/promises";
|
|
405
405
|
import { dirname, join as join2 } from "node:path";
|
|
406
406
|
import { fileURLToPath } from "node:url";
|
|
407
|
-
async function
|
|
407
|
+
async function readTemplate(relativePath) {
|
|
408
408
|
const sea = await import("node:sea").catch(() => null);
|
|
409
409
|
if (sea?.isSea()) return sea.getAsset(relativePath, "utf8");
|
|
410
410
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -418,8 +418,8 @@ async function readBoilerplate(relativePath) {
|
|
|
418
418
|
`qunitx-cli: template "${relativePath}" not found \u2014 try reinstalling the package.`
|
|
419
419
|
);
|
|
420
420
|
}
|
|
421
|
-
var
|
|
422
|
-
"lib/utils/read-
|
|
421
|
+
var init_read_template = __esm({
|
|
422
|
+
"lib/utils/read-template.ts"() {
|
|
423
423
|
}
|
|
424
424
|
});
|
|
425
425
|
|
|
@@ -438,19 +438,19 @@ var init_find_internal_assets_from_html = __esm({
|
|
|
438
438
|
}
|
|
439
439
|
});
|
|
440
440
|
|
|
441
|
-
// lib/utils/html
|
|
442
|
-
function
|
|
443
|
-
return html.includes(
|
|
441
|
+
// lib/utils/html.ts
|
|
442
|
+
function findScriptPlaceholder(html) {
|
|
443
|
+
return html.includes(SCRIPT_PLACEHOLDER) ? SCRIPT_PLACEHOLDER : void 0;
|
|
444
444
|
}
|
|
445
|
-
function
|
|
446
|
-
return !!
|
|
445
|
+
function isCustomTemplate(html) {
|
|
446
|
+
return !!findScriptPlaceholder(html) || HANDLEBARS_TOKEN_REGEX.test(html);
|
|
447
447
|
}
|
|
448
|
-
function
|
|
449
|
-
const
|
|
450
|
-
if (
|
|
451
|
-
return html.replace(
|
|
448
|
+
function injectScript(html, content) {
|
|
449
|
+
const placeholder = findScriptPlaceholder(html);
|
|
450
|
+
if (placeholder) {
|
|
451
|
+
return html.replace(placeholder, content);
|
|
452
452
|
}
|
|
453
|
-
if (
|
|
453
|
+
if (isCustomTemplate(html)) {
|
|
454
454
|
if (html.includes("</body>")) {
|
|
455
455
|
return html.replace("</body>", `${content}</body>`);
|
|
456
456
|
}
|
|
@@ -461,10 +461,10 @@ function replaceHTMLContentMarker(html, content) {
|
|
|
461
461
|
}
|
|
462
462
|
return html;
|
|
463
463
|
}
|
|
464
|
-
var
|
|
465
|
-
var
|
|
466
|
-
"lib/utils/html
|
|
467
|
-
|
|
464
|
+
var SCRIPT_PLACEHOLDER, HANDLEBARS_TOKEN_REGEX;
|
|
465
|
+
var init_html = __esm({
|
|
466
|
+
"lib/utils/html.ts"() {
|
|
467
|
+
SCRIPT_PLACEHOLDER = "{{qunitxScript}}";
|
|
468
468
|
HANDLEBARS_TOKEN_REGEX = /{{\s*[^}]+\s*}}/;
|
|
469
469
|
}
|
|
470
470
|
});
|
|
@@ -488,12 +488,18 @@ function dumpValue(value, indent) {
|
|
|
488
488
|
if (Array.isArray(value)) {
|
|
489
489
|
if (value.length === 0) return "[]";
|
|
490
490
|
const next2 = `${indent} `;
|
|
491
|
-
return "\n" + value.map((item) =>
|
|
491
|
+
return "\n" + value.map((item) => {
|
|
492
|
+
const v = dumpValue(item, next2);
|
|
493
|
+
return v[0] === "\n" ? `${next2}-${v}` : `${next2}- ${v}`;
|
|
494
|
+
}).join("\n");
|
|
492
495
|
}
|
|
493
496
|
const entries = Object.entries(value);
|
|
494
497
|
if (entries.length === 0) return "{}";
|
|
495
498
|
const next = `${indent} `;
|
|
496
|
-
return "\n" + entries.map(([entryKey, entryValue]) =>
|
|
499
|
+
return "\n" + entries.map(([entryKey, entryValue]) => {
|
|
500
|
+
const v = dumpValue(entryValue, next);
|
|
501
|
+
return v[0] === "\n" ? `${next}${entryKey}:${v}` : `${next}${entryKey}: ${v}`;
|
|
502
|
+
}).join("\n");
|
|
497
503
|
}
|
|
498
504
|
function yamlLine(key, value) {
|
|
499
505
|
const serialized = dumpValue(value, "");
|
|
@@ -510,12 +516,12 @@ function dumpYaml({
|
|
|
510
516
|
at
|
|
511
517
|
}) {
|
|
512
518
|
return `name: ${dumpString(name, "")}
|
|
513
|
-
` + yamlLine("actual", actual) + yamlLine("expected", expected) + yamlLine("message", message) + yamlLine("stack", stack) + yamlLine("at", at);
|
|
519
|
+
` + yamlLine("actual", actual) + yamlLine("expected", expected) + (message !== null ? yamlLine("message", message) : "") + (stack !== null ? yamlLine("stack", stack) : "") + (at !== null ? yamlLine("at", at) : "");
|
|
514
520
|
}
|
|
515
521
|
var NEEDS_QUOTING;
|
|
516
522
|
var init_dump_yaml = __esm({
|
|
517
523
|
"lib/tap/dump-yaml.ts"() {
|
|
518
|
-
NEEDS_QUOTING =
|
|
524
|
+
NEEDS_QUOTING = /^$|^\s|^(null|true|false|~|yes|no|on|off|y|n)$|^[{[!|>'"#%@`]|^[-?:](\s|$)|^---|^[-+]?(\d|\.\d)|^\d{4}-\d{2}-\d{2}|: |#/i;
|
|
519
525
|
}
|
|
520
526
|
});
|
|
521
527
|
|
|
@@ -538,46 +544,55 @@ function TAPDisplayTestResult(COUNTER, details) {
|
|
|
538
544
|
COUNTER.testCount++;
|
|
539
545
|
if (details.status === "skipped") {
|
|
540
546
|
COUNTER.skipCount++;
|
|
541
|
-
|
|
547
|
+
process.stdout.write(`ok ${COUNTER.testCount} ${details.fullName.join(" | ")} # skip
|
|
548
|
+
`);
|
|
542
549
|
} else if (details.status === "todo") {
|
|
543
|
-
|
|
550
|
+
process.stdout.write(`not ok ${COUNTER.testCount} ${details.fullName.join(" | ")} # skip
|
|
551
|
+
`);
|
|
544
552
|
} else if (details.status === "failed") {
|
|
545
553
|
COUNTER.failCount++;
|
|
546
|
-
|
|
547
|
-
`not ok ${COUNTER.testCount}
|
|
548
|
-
|
|
549
|
-
`# (${details.runtime.toFixed(0)} ms)`
|
|
554
|
+
process.stdout.write(
|
|
555
|
+
`not ok ${COUNTER.testCount} ${details.fullName.join(" | ")} # (${details.runtime.toFixed(0)} ms)
|
|
556
|
+
`
|
|
550
557
|
);
|
|
551
558
|
details.assertions.forEach((assertion, index) => {
|
|
552
559
|
if (!assertion.passed && assertion.todo === false) {
|
|
553
560
|
COUNTER.errorCount = (COUNTER.errorCount ?? 0) + 1;
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
console.log(
|
|
561
|
+
process.stdout.write(" ---\n");
|
|
562
|
+
process.stdout.write(
|
|
557
563
|
indentString(
|
|
558
564
|
dumpYaml({
|
|
559
565
|
name: `Assertion #${index + 1}`,
|
|
560
|
-
actual: assertion.actual ? JSON.parse(JSON.stringify(assertion.actual, getCircularReplacer())) : assertion.actual,
|
|
561
|
-
expected: assertion.expected ? JSON.parse(JSON.stringify(assertion.expected, getCircularReplacer())) : assertion.expected,
|
|
566
|
+
actual: assertion.actual !== null && typeof assertion.actual === "object" ? JSON.parse(JSON.stringify(assertion.actual, getCircularReplacer())) : assertion.actual,
|
|
567
|
+
expected: assertion.expected !== null && typeof assertion.expected === "object" ? JSON.parse(JSON.stringify(assertion.expected, getCircularReplacer())) : assertion.expected,
|
|
562
568
|
message: assertion.message || null,
|
|
563
|
-
|
|
564
|
-
|
|
569
|
+
// Trim leading/trailing whitespace: Chrome stacks start with " at ..."
|
|
570
|
+
// (4 spaces per frame) which would otherwise render as "stack: at ..." in YAML.
|
|
571
|
+
stack: assertion.stack?.trim() || null,
|
|
572
|
+
at: extractStackAt(assertion.stack)
|
|
565
573
|
}),
|
|
566
574
|
4
|
|
567
575
|
)
|
|
568
576
|
);
|
|
569
|
-
|
|
577
|
+
process.stdout.write(" ...\n");
|
|
570
578
|
}
|
|
571
579
|
});
|
|
572
580
|
} else if (details.status === "passed") {
|
|
573
581
|
COUNTER.passCount++;
|
|
574
|
-
|
|
575
|
-
`ok ${COUNTER.testCount}
|
|
576
|
-
|
|
577
|
-
`# (${details.runtime.toFixed(0)} ms)`
|
|
582
|
+
process.stdout.write(
|
|
583
|
+
`ok ${COUNTER.testCount} ${details.fullName.join(" | ")} # (${details.runtime.toFixed(0)} ms)
|
|
584
|
+
`
|
|
578
585
|
);
|
|
579
586
|
}
|
|
580
587
|
}
|
|
588
|
+
function extractStackAt(stack) {
|
|
589
|
+
if (!stack) return null;
|
|
590
|
+
const chromeMatch = stack.match(/\(([^)\n]+:[0-9]+:[0-9]+)\)/);
|
|
591
|
+
if (chromeMatch) return chromeMatch[1].replace("file://", "");
|
|
592
|
+
const geckoMatch = stack.match(/@([^\s\n@]+:[0-9]+:[0-9]+)/);
|
|
593
|
+
if (geckoMatch) return geckoMatch[1];
|
|
594
|
+
return null;
|
|
595
|
+
}
|
|
581
596
|
function getCircularReplacer() {
|
|
582
597
|
const ancestors = [];
|
|
583
598
|
return function(_key, value) {
|
|
@@ -627,8 +642,8 @@ async function bindServerToPort(server, config) {
|
|
|
627
642
|
var EXPLICIT_PORT_RETRIES, EXPLICIT_PORT_RETRY_DELAY_MS;
|
|
628
643
|
var init_bind_server_to_port = __esm({
|
|
629
644
|
"lib/setup/bind-server-to-port.ts"() {
|
|
630
|
-
EXPLICIT_PORT_RETRIES =
|
|
631
|
-
EXPLICIT_PORT_RETRY_DELAY_MS =
|
|
645
|
+
EXPLICIT_PORT_RETRIES = 20;
|
|
646
|
+
EXPLICIT_PORT_RETRY_DELAY_MS = 50;
|
|
632
647
|
}
|
|
633
648
|
});
|
|
634
649
|
|
|
@@ -898,7 +913,7 @@ function setupWebServer(config, cachedContent) {
|
|
|
898
913
|
config._onWsOpen?.();
|
|
899
914
|
} else if (event === "connection") {
|
|
900
915
|
config._phase = "running";
|
|
901
|
-
if (!config._groupMode)
|
|
916
|
+
if (!config._groupMode) process.stdout.write("TAP version 13\n");
|
|
902
917
|
if (config.debug && config._groupMode) {
|
|
903
918
|
const allFiles = Object.keys(config.fsTree);
|
|
904
919
|
const relFiles = allFiles.map(
|
|
@@ -907,7 +922,8 @@ function setupWebServer(config, cachedContent) {
|
|
|
907
922
|
const shown = relFiles.slice(0, 2);
|
|
908
923
|
const rest = relFiles.length - shown.length;
|
|
909
924
|
const fileList = rest > 0 ? `${shown.join(" ")} +${rest} more` : shown.join(" ");
|
|
910
|
-
|
|
925
|
+
process.stdout.write(`# ${blue(`\u2500\u2500 ${fileList} \u2500\u2500`)}
|
|
926
|
+
`);
|
|
911
927
|
}
|
|
912
928
|
config._resetTestTimeout?.();
|
|
913
929
|
} else if (event === "testEnd" && !abort) {
|
|
@@ -915,8 +931,9 @@ function setupWebServer(config, cachedContent) {
|
|
|
915
931
|
config.lastFailedTestFiles = config.lastRanTestFiles;
|
|
916
932
|
}
|
|
917
933
|
if (config.debug && details.runtime > config.timeout * 0.8) {
|
|
918
|
-
|
|
919
|
-
`# SLOW (${details.runtime.toFixed(0)}ms / ${config.timeout}ms timeout): ${details.fullName.join(" | ")}
|
|
934
|
+
process.stdout.write(
|
|
935
|
+
`# SLOW (${details.runtime.toFixed(0)}ms / ${config.timeout}ms timeout): ${details.fullName.join(" | ")}
|
|
936
|
+
`
|
|
920
937
|
);
|
|
921
938
|
}
|
|
922
939
|
config._resetTestTimeout?.();
|
|
@@ -924,8 +941,9 @@ function setupWebServer(config, cachedContent) {
|
|
|
924
941
|
} else if (event === "done") {
|
|
925
942
|
config._phase = "done";
|
|
926
943
|
if (config.debug && config._groupMode) {
|
|
927
|
-
|
|
928
|
-
`# group done: ${details.passed} passed, ${details.failed} failed (${details.runtime}ms)
|
|
944
|
+
process.stdout.write(
|
|
945
|
+
`# group done: ${details.passed} passed, ${details.failed} failed (${details.runtime}ms)
|
|
946
|
+
`
|
|
929
947
|
);
|
|
930
948
|
}
|
|
931
949
|
if (typeof config._testRunDone === "function") {
|
|
@@ -937,8 +955,9 @@ function setupWebServer(config, cachedContent) {
|
|
|
937
955
|
});
|
|
938
956
|
server.get("/tests.js", (_req, res) => {
|
|
939
957
|
const bytes = cachedContent.allTestCode?.length ?? null;
|
|
940
|
-
|
|
941
|
-
`# [HTTPServer] GET /tests.js \u2192 ${bytes !== null ? `${bytes} bytes` : "NOT READY (allTestCode is null)"}
|
|
958
|
+
process.stdout.write(
|
|
959
|
+
`# [HTTPServer] GET /tests.js \u2192 ${bytes !== null ? `${bytes} bytes` : "NOT READY (allTestCode is null)"}
|
|
960
|
+
`
|
|
942
961
|
);
|
|
943
962
|
if (bytes === null) {
|
|
944
963
|
res.writeHead(503, { "Content-Type": "application/javascript", "Cache-Control": "no-store" });
|
|
@@ -953,8 +972,9 @@ function setupWebServer(config, cachedContent) {
|
|
|
953
972
|
});
|
|
954
973
|
server.get("/filtered-tests.js", (_req, res) => {
|
|
955
974
|
const bytes = cachedContent.filteredTestCode?.length ?? null;
|
|
956
|
-
|
|
957
|
-
`# [HTTPServer] GET /filtered-tests.js \u2192 ${bytes !== null ? `${bytes} bytes` : "NOT READY (filteredTestCode is null)"}
|
|
975
|
+
process.stdout.write(
|
|
976
|
+
`# [HTTPServer] GET /filtered-tests.js \u2192 ${bytes !== null ? `${bytes} bytes` : "NOT READY (filteredTestCode is null)"}
|
|
977
|
+
`
|
|
958
978
|
);
|
|
959
979
|
if (bytes === null) {
|
|
960
980
|
res.writeHead(503, { "Content-Type": "application/javascript", "Cache-Control": "no-store" });
|
|
@@ -1026,7 +1046,10 @@ function setupWebServer(config, cachedContent) {
|
|
|
1026
1046
|
} else {
|
|
1027
1047
|
fs8.createReadStream(filePath).pipe(res);
|
|
1028
1048
|
}
|
|
1029
|
-
|
|
1049
|
+
process.stdout.write(
|
|
1050
|
+
`# [HTTPServer] GET ${url} ${statusCode} - ${/* @__PURE__ */ new Date() - requestStartedAt}ms
|
|
1051
|
+
`
|
|
1052
|
+
);
|
|
1030
1053
|
});
|
|
1031
1054
|
return server;
|
|
1032
1055
|
}
|
|
@@ -1189,17 +1212,14 @@ function testRuntimeToInject(port, config) {
|
|
|
1189
1212
|
</script>`;
|
|
1190
1213
|
}
|
|
1191
1214
|
function escapeAndInjectTestsToHTML(html, testRuntimeCode, testBundleUrl) {
|
|
1192
|
-
return
|
|
1193
|
-
|
|
1194
|
-
`${testRuntimeCode}
|
|
1195
|
-
<script src="${testBundleUrl}" async></script>`
|
|
1196
|
-
);
|
|
1215
|
+
return injectScript(html, `${testRuntimeCode}
|
|
1216
|
+
<script src="${testBundleUrl}" async></script>`);
|
|
1197
1217
|
}
|
|
1198
1218
|
var fsPromise;
|
|
1199
1219
|
var init_web_server = __esm({
|
|
1200
1220
|
"lib/setup/web-server.ts"() {
|
|
1201
1221
|
init_find_internal_assets_from_html();
|
|
1202
|
-
|
|
1222
|
+
init_html();
|
|
1203
1223
|
init_display_test_result();
|
|
1204
1224
|
init_color();
|
|
1205
1225
|
init_path_exists();
|
|
@@ -1213,18 +1233,18 @@ async function launchBrowser(config) {
|
|
|
1213
1233
|
const browserName = config.browser || "chromium";
|
|
1214
1234
|
if (browserName === "chromium") {
|
|
1215
1235
|
const waitStart = Date.now();
|
|
1216
|
-
const [playwrightCore2,
|
|
1236
|
+
const [playwrightCore2, prelaunch] = await Promise.all([
|
|
1217
1237
|
playwrightCorePromise,
|
|
1218
|
-
|
|
1238
|
+
prelaunchPromise
|
|
1219
1239
|
]);
|
|
1220
1240
|
perfLog(
|
|
1221
|
-
`browser.js: playwright-core +
|
|
1222
|
-
|
|
1241
|
+
`browser.js: playwright-core + prelaunch resolved in ${Date.now() - waitStart}ms, prelaunch:`,
|
|
1242
|
+
prelaunch?.cdpEndpoint ?? null
|
|
1223
1243
|
);
|
|
1224
|
-
if (
|
|
1244
|
+
if (prelaunch) {
|
|
1225
1245
|
const connectStart = Date.now();
|
|
1226
1246
|
const browser = await playwrightCore2.chromium.connectOverCDP({
|
|
1227
|
-
endpointURL:
|
|
1247
|
+
endpointURL: prelaunch.cdpEndpoint
|
|
1228
1248
|
});
|
|
1229
1249
|
perfLog(`browser.js: connectOverCDP took ${Date.now() - connectStart}ms`);
|
|
1230
1250
|
return browser;
|
|
@@ -1279,8 +1299,8 @@ async function setupBrowser(config, cachedContent, existingBrowser = null) {
|
|
|
1279
1299
|
}
|
|
1280
1300
|
});
|
|
1281
1301
|
page.on("pageerror", (error) => {
|
|
1282
|
-
console.log(error.toString());
|
|
1283
1302
|
console.error(error.toString());
|
|
1303
|
+
config.COUNTER.failCount++;
|
|
1284
1304
|
});
|
|
1285
1305
|
return { server, browser, page };
|
|
1286
1306
|
}
|
|
@@ -1291,7 +1311,7 @@ var init_browser = __esm({
|
|
|
1291
1311
|
init_bind_server_to_port();
|
|
1292
1312
|
init_find_chrome();
|
|
1293
1313
|
init_chromium_args();
|
|
1294
|
-
|
|
1314
|
+
init_chrome_prelaunch();
|
|
1295
1315
|
init_perf_logger();
|
|
1296
1316
|
playwrightCorePromise = import("playwright-core");
|
|
1297
1317
|
perfLog("browser.js: playwright-core import started");
|
|
@@ -1369,14 +1389,20 @@ var init_run_user_module = __esm({
|
|
|
1369
1389
|
|
|
1370
1390
|
// lib/tap/display-final-result.ts
|
|
1371
1391
|
function TAPDisplayFinalResult({ testCount, passCount, skipCount, failCount }, timeTaken) {
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1392
|
+
process.stdout.write("\n");
|
|
1393
|
+
process.stdout.write(`1..${testCount}
|
|
1394
|
+
`);
|
|
1395
|
+
process.stdout.write(`# tests ${testCount}
|
|
1396
|
+
`);
|
|
1397
|
+
process.stdout.write(`# pass ${passCount}
|
|
1398
|
+
`);
|
|
1399
|
+
process.stdout.write(`# skip ${skipCount}
|
|
1400
|
+
`);
|
|
1401
|
+
process.stdout.write(`# fail ${failCount}
|
|
1402
|
+
`);
|
|
1403
|
+
process.stdout.write(`# duration ${timeTaken}
|
|
1404
|
+
`);
|
|
1405
|
+
process.stdout.write("\n");
|
|
1380
1406
|
}
|
|
1381
1407
|
var init_display_final_result = __esm({
|
|
1382
1408
|
"lib/tap/display-final-result.ts"() {
|
|
@@ -1397,28 +1423,26 @@ async function buildTestBundle(config, cachedContent) {
|
|
|
1397
1423
|
await fs9.mkdir(`${projectRoot}/${output}`, { recursive: true });
|
|
1398
1424
|
const sourcemap = config.debug ? "inline" : config.watch ? "linked" : false;
|
|
1399
1425
|
const needsDisk = true;
|
|
1426
|
+
const buildOptions = {
|
|
1427
|
+
stdin: {
|
|
1428
|
+
contents: allTestFilePaths.map((filePath) => `import "${filePath}";`).join(""),
|
|
1429
|
+
resolveDir: process.cwd()
|
|
1430
|
+
},
|
|
1431
|
+
bundle: true,
|
|
1432
|
+
logLevel: "error",
|
|
1433
|
+
outfile,
|
|
1434
|
+
keepNames: true,
|
|
1435
|
+
legalComments: "none",
|
|
1436
|
+
target: esbuildTarget(config.browser),
|
|
1437
|
+
sourcemap,
|
|
1438
|
+
// Signal the runtime that all test modules are registered. The runtime's maybeStart()
|
|
1439
|
+
// waits for both this event and the WebSocket 'open' event before calling QUnit.start().
|
|
1440
|
+
// Dispatching from the bundle (rather than from a script onload attr) is reliable across
|
|
1441
|
+
// all browsers and does not require changes to user test code.
|
|
1442
|
+
footer: { js: 'window.dispatchEvent(new CustomEvent("qunitx:tests-ready"));' }
|
|
1443
|
+
};
|
|
1400
1444
|
const [allTestCode] = await Promise.all([
|
|
1401
|
-
buildWithOverlayfsRetry(
|
|
1402
|
-
{
|
|
1403
|
-
stdin: {
|
|
1404
|
-
contents: allTestFilePaths.map((filePath) => `import "${filePath}";`).join(""),
|
|
1405
|
-
resolveDir: process.cwd()
|
|
1406
|
-
},
|
|
1407
|
-
bundle: true,
|
|
1408
|
-
logLevel: "error",
|
|
1409
|
-
outfile,
|
|
1410
|
-
keepNames: true,
|
|
1411
|
-
legalComments: "none",
|
|
1412
|
-
target: esbuildTarget(config.browser),
|
|
1413
|
-
sourcemap,
|
|
1414
|
-
// Signal the runtime that all test modules are registered. The runtime's maybeStart()
|
|
1415
|
-
// waits for both this event and the WebSocket 'open' event before calling QUnit.start().
|
|
1416
|
-
// Dispatching from the bundle (rather than from a script onload attr) is reliable across
|
|
1417
|
-
// all browsers and does not require changes to user test code.
|
|
1418
|
-
footer: { js: 'window.dispatchEvent(new CustomEvent("qunitx:tests-ready"));' }
|
|
1419
|
-
},
|
|
1420
|
-
needsDisk
|
|
1421
|
-
),
|
|
1445
|
+
config.watch ? buildIncrementally(buildOptions, allTestFilePaths.join("\0"), cachedContent, needsDisk) : buildWithOverlayfsRetry(buildOptions, needsDisk),
|
|
1422
1446
|
Promise.all(
|
|
1423
1447
|
cachedContent.htmlPathsToRunTests.map(async (htmlPath) => {
|
|
1424
1448
|
const targetPath = `${config.projectRoot}/${config.output}${htmlPath}`;
|
|
@@ -1440,8 +1464,10 @@ async function runTestsInBrowser(config, cachedContent = {}, connections, target
|
|
|
1440
1464
|
}
|
|
1441
1465
|
config.lastRanTestFiles = targetTestFilesToFilter || allTestFilePaths;
|
|
1442
1466
|
try {
|
|
1467
|
+
const preBuildPromise = cachedContent._preBuildPromise;
|
|
1468
|
+
cachedContent._preBuildPromise = null;
|
|
1443
1469
|
if (!cachedContent.allTestCode) {
|
|
1444
|
-
await buildTestBundle(config, cachedContent);
|
|
1470
|
+
await (preBuildPromise ?? buildTestBundle(config, cachedContent));
|
|
1445
1471
|
}
|
|
1446
1472
|
if (!cachedContent.allTestCode) {
|
|
1447
1473
|
return connections;
|
|
@@ -1475,7 +1501,7 @@ async function runTestsInBrowser(config, cachedContent = {}, connections, target
|
|
|
1475
1501
|
connections.server && connections.server.close(),
|
|
1476
1502
|
connections.browser && connections.browser.close()
|
|
1477
1503
|
]);
|
|
1478
|
-
await
|
|
1504
|
+
await shutdownPrelaunch();
|
|
1479
1505
|
return process.exit(config.COUNTER.failCount > 0 ? 1 : 0);
|
|
1480
1506
|
}
|
|
1481
1507
|
}
|
|
@@ -1511,16 +1537,10 @@ function buildFilteredTests(filteredTests, outputPath, config) {
|
|
|
1511
1537
|
needsDisk
|
|
1512
1538
|
);
|
|
1513
1539
|
}
|
|
1514
|
-
async function
|
|
1540
|
+
async function runWithOverlayfsRetry(getContents, needsDisk) {
|
|
1515
1541
|
const RETRY_DELAY_MS = 100;
|
|
1516
1542
|
const MAX_RETRIES = 3;
|
|
1517
1543
|
const EMPTY_BUNDLE_THRESHOLD = 500;
|
|
1518
|
-
const buildOpts = { ...options, write: false };
|
|
1519
|
-
const getContents = async () => {
|
|
1520
|
-
const result2 = await esbuild.build(buildOpts);
|
|
1521
|
-
const jsFile = result2.outputFiles.find((outputFile) => !outputFile.path.endsWith(".map"));
|
|
1522
|
-
return { result: result2, js: Buffer.from(jsFile.contents) };
|
|
1523
|
-
};
|
|
1524
1544
|
let { result, js } = await getContents();
|
|
1525
1545
|
for (let retry = 1; retry <= MAX_RETRIES; retry++) {
|
|
1526
1546
|
if (js.length >= EMPTY_BUNDLE_THRESHOLD) break;
|
|
@@ -1542,6 +1562,29 @@ async function buildWithOverlayfsRetry(options, needsDisk) {
|
|
|
1542
1562
|
}
|
|
1543
1563
|
return js;
|
|
1544
1564
|
}
|
|
1565
|
+
function buildWithOverlayfsRetry(options, needsDisk) {
|
|
1566
|
+
const buildOpts = { ...options, write: false };
|
|
1567
|
+
return runWithOverlayfsRetry(async () => {
|
|
1568
|
+
const result = await esbuild.build(buildOpts);
|
|
1569
|
+
const jsFile = result.outputFiles.find((f) => !f.path.endsWith(".map"));
|
|
1570
|
+
return { result, js: Buffer.from(jsFile.contents) };
|
|
1571
|
+
}, needsDisk);
|
|
1572
|
+
}
|
|
1573
|
+
async function buildIncrementally(options, fileKey, cachedContent, needsDisk) {
|
|
1574
|
+
const buildOpts = { ...options, write: false };
|
|
1575
|
+
if (!cachedContent._esbuildContext || cachedContent._esbuildContextKey !== fileKey) {
|
|
1576
|
+
cachedContent._esbuildContext?.dispose().catch(() => {
|
|
1577
|
+
});
|
|
1578
|
+
cachedContent._esbuildContext = await esbuild.context(buildOpts);
|
|
1579
|
+
cachedContent._esbuildContextKey = fileKey;
|
|
1580
|
+
}
|
|
1581
|
+
const ctx = cachedContent._esbuildContext;
|
|
1582
|
+
return runWithOverlayfsRetry(async () => {
|
|
1583
|
+
const result = await ctx.rebuild();
|
|
1584
|
+
const jsFile = result.outputFiles.find((f) => !f.path.endsWith(".map"));
|
|
1585
|
+
return { result, js: Buffer.from(jsFile.contents) };
|
|
1586
|
+
}, needsDisk);
|
|
1587
|
+
}
|
|
1545
1588
|
function esbuildTarget(browser) {
|
|
1546
1589
|
if (browser === "firefox") return ["firefox115"];
|
|
1547
1590
|
if (browser === "webkit") return ["safari16"];
|
|
@@ -1626,7 +1669,7 @@ async function failOnNonWatchMode(watchMode = false, connections = {}, groupMode
|
|
|
1626
1669
|
connections.server && connections.server.close(),
|
|
1627
1670
|
connections.browser && connections.browser.close()
|
|
1628
1671
|
]);
|
|
1629
|
-
await
|
|
1672
|
+
await shutdownPrelaunch();
|
|
1630
1673
|
process.exit(1);
|
|
1631
1674
|
}
|
|
1632
1675
|
}
|
|
@@ -1634,7 +1677,7 @@ var BundleError;
|
|
|
1634
1677
|
var init_tests_in_browser = __esm({
|
|
1635
1678
|
"lib/commands/run/tests-in-browser.ts"() {
|
|
1636
1679
|
init_color();
|
|
1637
|
-
|
|
1680
|
+
init_chrome_prelaunch();
|
|
1638
1681
|
init_time_counter();
|
|
1639
1682
|
init_run_user_module();
|
|
1640
1683
|
init_display_final_result();
|
|
@@ -1947,6 +1990,7 @@ import { availableParallelism } from "node:os";
|
|
|
1947
1990
|
async function run(config) {
|
|
1948
1991
|
const cachedContent = await buildCachedContent(config, config.htmlPaths);
|
|
1949
1992
|
if (config.watch) {
|
|
1993
|
+
cachedContent._preBuildPromise = buildTestBundle(config, cachedContent);
|
|
1950
1994
|
const [connections] = await Promise.all([
|
|
1951
1995
|
setupBrowser(config, cachedContent),
|
|
1952
1996
|
writeOutputStaticFiles(config, cachedContent)
|
|
@@ -2011,9 +2055,10 @@ async function run(config) {
|
|
|
2011
2055
|
_phase: "bundling"
|
|
2012
2056
|
}));
|
|
2013
2057
|
const groupCachedContents = groups.map(() => ({ ...cachedContent }));
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
`# Running ${allFiles.length} test file${allFiles.length === 1 ? "" : "s"} across ${groupCount} group${groupCount === 1 ? "" : "s"}
|
|
2058
|
+
process.stdout.write("TAP version 13\n");
|
|
2059
|
+
process.stdout.write(
|
|
2060
|
+
`# Running ${allFiles.length} test file${allFiles.length === 1 ? "" : "s"} across ${groupCount} group${groupCount === 1 ? "" : "s"}
|
|
2061
|
+
`
|
|
2017
2062
|
);
|
|
2018
2063
|
const [browser] = await Promise.all([
|
|
2019
2064
|
launchBrowser(config),
|
|
@@ -2099,7 +2144,7 @@ async function run(config) {
|
|
|
2099
2144
|
clearInterval(keepAlive);
|
|
2100
2145
|
await browser.close().catch(() => {
|
|
2101
2146
|
});
|
|
2102
|
-
await
|
|
2147
|
+
await shutdownPrelaunch();
|
|
2103
2148
|
process.exit(exitCode);
|
|
2104
2149
|
});
|
|
2105
2150
|
}
|
|
@@ -2114,7 +2159,7 @@ async function buildCachedContent(config, htmlPaths) {
|
|
|
2114
2159
|
if (buffer === null) return result;
|
|
2115
2160
|
const filePath = config.htmlPaths[index];
|
|
2116
2161
|
const html = buffer.toString();
|
|
2117
|
-
if (
|
|
2162
|
+
if (isCustomTemplate(html)) {
|
|
2118
2163
|
result.dynamicContentHTMLs[filePath] = html;
|
|
2119
2164
|
result.htmlPathsToRunTests.push(filePath.replace(config.projectRoot, ""));
|
|
2120
2165
|
} else {
|
|
@@ -2153,7 +2198,7 @@ async function addCachedContentMainHTML(projectRoot, cachedContent) {
|
|
|
2153
2198
|
html: cachedContent.dynamicContentHTMLs[mainHTMLPath]
|
|
2154
2199
|
};
|
|
2155
2200
|
} else {
|
|
2156
|
-
const html = await
|
|
2201
|
+
const html = await readTemplate("setup/tests.hbs");
|
|
2157
2202
|
cachedContent.mainHTML = { filePath: `${projectRoot}/test/tests.html`, html };
|
|
2158
2203
|
cachedContent.assets.add(`${projectRoot}/node_modules/qunitx/vendor/qunit.css`);
|
|
2159
2204
|
}
|
|
@@ -2184,7 +2229,7 @@ function normalizeInternalAssetPathFromHTML(projectRoot, assetPath, htmlPath) {
|
|
|
2184
2229
|
var init_run = __esm({
|
|
2185
2230
|
"lib/commands/run.ts"() {
|
|
2186
2231
|
init_browser();
|
|
2187
|
-
|
|
2232
|
+
init_chrome_prelaunch();
|
|
2188
2233
|
init_open_output_in_browser();
|
|
2189
2234
|
init_color();
|
|
2190
2235
|
init_tests_in_browser();
|
|
@@ -2195,13 +2240,13 @@ var init_run = __esm({
|
|
|
2195
2240
|
init_write_output_static_files();
|
|
2196
2241
|
init_time_counter();
|
|
2197
2242
|
init_display_final_result();
|
|
2198
|
-
|
|
2199
|
-
|
|
2243
|
+
init_read_template();
|
|
2244
|
+
init_html();
|
|
2200
2245
|
}
|
|
2201
2246
|
});
|
|
2202
2247
|
|
|
2203
2248
|
// cli.ts
|
|
2204
|
-
|
|
2249
|
+
init_chrome_prelaunch();
|
|
2205
2250
|
import process4 from "node:process";
|
|
2206
2251
|
|
|
2207
2252
|
// lib/commands/help.ts
|
|
@@ -2211,7 +2256,7 @@ init_color();
|
|
|
2211
2256
|
var package_default = {
|
|
2212
2257
|
name: "qunitx-cli",
|
|
2213
2258
|
type: "module",
|
|
2214
|
-
version: "0.
|
|
2259
|
+
version: "0.18.0",
|
|
2215
2260
|
description: "Browser runner for QUnitx: run your qunitx tests in google-chrome",
|
|
2216
2261
|
author: "Izel Nakri",
|
|
2217
2262
|
license: "MIT",
|
|
@@ -2270,7 +2315,7 @@ var package_default = {
|
|
|
2270
2315
|
express: "^5.2.1",
|
|
2271
2316
|
"js-yaml": "^4.1.1",
|
|
2272
2317
|
prettier: "^3.8.2",
|
|
2273
|
-
qunitx: "^1.2.
|
|
2318
|
+
qunitx: "^1.2.7",
|
|
2274
2319
|
typescript: "^6.0.2"
|
|
2275
2320
|
},
|
|
2276
2321
|
volta: {
|
|
@@ -2370,7 +2415,7 @@ var defaultProjectConfigValues = {
|
|
|
2370
2415
|
};
|
|
2371
2416
|
|
|
2372
2417
|
// lib/commands/init.ts
|
|
2373
|
-
|
|
2418
|
+
init_read_template();
|
|
2374
2419
|
async function initializeProject() {
|
|
2375
2420
|
const projectRoot = await findProjectRoot();
|
|
2376
2421
|
const oldPackageJSON = JSON.parse(await fs4.readFile(`${projectRoot}/package.json`));
|
|
@@ -2386,7 +2431,7 @@ async function initializeProject() {
|
|
|
2386
2431
|
]);
|
|
2387
2432
|
}
|
|
2388
2433
|
async function writeTestsHTML(projectRoot, config, oldPackageJSON) {
|
|
2389
|
-
const testHTMLTemplateBuffer = await
|
|
2434
|
+
const testHTMLTemplateBuffer = await readTemplate("setup/tests.hbs");
|
|
2390
2435
|
return await Promise.all(
|
|
2391
2436
|
config.htmlPaths.map(async (htmlPath) => {
|
|
2392
2437
|
const targetPath = `${projectRoot}/${htmlPath}`;
|
|
@@ -2416,7 +2461,7 @@ async function rewritePackageJSON(projectRoot, config, oldPackageJSON) {
|
|
|
2416
2461
|
async function writeTSConfigIfNeeded(projectRoot) {
|
|
2417
2462
|
const targetPath = `${projectRoot}/tsconfig.json`;
|
|
2418
2463
|
if (!await pathExists(targetPath)) {
|
|
2419
|
-
const tsConfigTemplate = await
|
|
2464
|
+
const tsConfigTemplate = await readTemplate("setup/tsconfig.json");
|
|
2420
2465
|
await fs4.writeFile(targetPath, tsConfigTemplate);
|
|
2421
2466
|
console.log(`${targetPath} written`);
|
|
2422
2467
|
}
|
|
@@ -2426,7 +2471,7 @@ async function writeTSConfigIfNeeded(projectRoot) {
|
|
|
2426
2471
|
init_color();
|
|
2427
2472
|
import fs5 from "node:fs/promises";
|
|
2428
2473
|
init_path_exists();
|
|
2429
|
-
|
|
2474
|
+
init_read_template();
|
|
2430
2475
|
|
|
2431
2476
|
// lib/utils/convert-to-pascal-case.ts
|
|
2432
2477
|
function convertToPascalCase(str) {
|
|
@@ -2448,7 +2493,7 @@ async function generateTestFiles() {
|
|
|
2448
2493
|
console.log(`${path6} already exists!`);
|
|
2449
2494
|
return;
|
|
2450
2495
|
}
|
|
2451
|
-
const testJSContent = await
|
|
2496
|
+
const testJSContent = await readTemplate("test.js");
|
|
2452
2497
|
const targetFolderPaths = path6.split("/");
|
|
2453
2498
|
targetFolderPaths.pop();
|
|
2454
2499
|
await fs5.mkdir(targetFolderPaths.join("/"), { recursive: true });
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qunitx-cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.18.0",
|
|
5
5
|
"description": "Browser runner for QUnitx: run your qunitx tests in google-chrome",
|
|
6
6
|
"author": "Izel Nakri",
|
|
7
7
|
"license": "MIT",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"express": "^5.2.1",
|
|
61
61
|
"js-yaml": "^4.1.1",
|
|
62
62
|
"prettier": "^3.8.2",
|
|
63
|
-
"qunitx": "^1.2.
|
|
63
|
+
"qunitx": "^1.2.7",
|
|
64
64
|
"typescript": "^6.0.2"
|
|
65
65
|
},
|
|
66
66
|
"volta": {
|