qunitx-cli 0.5.0 → 0.5.2
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 +3 -2
- package/cli.js +1 -1
- package/deno.json +7 -0
- package/docs/browser-1-all-passed.png +0 -0
- package/docs/browser-1-failing.png +0 -0
- package/docs/browser-2-all-passed.png +0 -0
- package/docs/browser-2-filtered.png +0 -0
- package/docs/browser-3-expanded.png +0 -0
- package/docs/browser-3-filtered.png +0 -0
- package/docs/browser-4-expanded.png +0 -0
- package/docs/demo-math-test.failing.js +59 -0
- package/docs/demo-math-test.js +59 -0
- package/docs/demo.gif +0 -0
- package/docs/demo.tape +92 -0
- package/docs/make-demo-gif.sh +88 -0
- package/docs/qunitx-help-stdout.png +0 -0
- package/docs/take-browser-screenshots.js +226 -0
- package/docs/terminal.gif +0 -0
- package/lib/boilerplates/test.js +4 -4
- package/lib/commands/generate.js +5 -5
- package/lib/commands/init.js +12 -12
- package/lib/commands/run.js +2 -2
- package/lib/servers/http.js +11 -8
- package/lib/setup/browser.js +7 -15
- package/lib/setup/config.js +7 -7
- package/lib/setup/file-watcher.js +5 -10
- package/lib/setup/fs-tree.js +7 -7
- package/lib/setup/keyboard-events.js +1 -6
- package/lib/setup/test-file-paths.js +6 -6
- package/lib/setup/web-server.js +20 -19
- package/lib/setup/write-output-static-files.js +4 -4
- package/lib/tap/display-test-result.js +2 -2
- package/lib/utils/find-chrome.js +2 -2
- package/lib/utils/find-project-root.js +2 -2
- package/lib/utils/listen-to-keyboard-key.js +7 -7
- package/lib/utils/resolve-port-number-for.js +5 -7
- package/lib/utils/run-user-module.js +1 -1
- package/lib/utils/search-in-parent-directories.js +5 -5
- package/package.json +6 -5
- package/.claude/settings.local.json +0 -7
- package/.env +0 -1
- package/Makefile +0 -35
- package/cliff.toml +0 -23
- package/demo/demo.gif +0 -0
- package/demo/demo.tape +0 -59
- package/demo/example-test.js +0 -53
- package/demo/failing-test.js +0 -22
- package/flake.lock +0 -64
- package/flake.nix +0 -55
package/lib/commands/init.js
CHANGED
|
@@ -8,9 +8,9 @@ import defaultProjectConfigValues from '../boilerplates/default-project-config-v
|
|
|
8
8
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
9
|
|
|
10
10
|
export default async function () {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
const projectRoot = await findProjectRoot();
|
|
12
|
+
const oldPackageJSON = JSON.parse(await fs.readFile(`${projectRoot}/package.json`));
|
|
13
|
+
const htmlPaths = process.argv.slice(2).reduce(
|
|
14
14
|
(result, arg) => {
|
|
15
15
|
if (arg.endsWith('.html')) {
|
|
16
16
|
result.push(arg);
|
|
@@ -20,7 +20,7 @@ export default async function () {
|
|
|
20
20
|
},
|
|
21
21
|
oldPackageJSON.qunitx && oldPackageJSON.qunitx.htmlPaths ? oldPackageJSON.qunitx.htmlPaths : [],
|
|
22
22
|
);
|
|
23
|
-
|
|
23
|
+
const newQunitxConfig = Object.assign(
|
|
24
24
|
defaultProjectConfigValues,
|
|
25
25
|
htmlPaths.length > 0 ? { htmlPaths } : { htmlPaths: ['test/tests.html'] },
|
|
26
26
|
oldPackageJSON.qunitx,
|
|
@@ -34,20 +34,20 @@ export default async function () {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
async function writeTestsHTML(projectRoot, newQunitxConfig, oldPackageJSON) {
|
|
37
|
-
|
|
37
|
+
const testHTMLTemplateBuffer = await fs.readFile(`${__dirname}/../boilerplates/setup/tests.hbs`);
|
|
38
38
|
|
|
39
39
|
return await Promise.all(
|
|
40
40
|
newQunitxConfig.htmlPaths.map(async (htmlPath) => {
|
|
41
|
-
|
|
41
|
+
const targetPath = `${projectRoot}/${htmlPath}`;
|
|
42
42
|
if (await pathExists(targetPath)) {
|
|
43
43
|
return console.log(`${htmlPath} already exists`);
|
|
44
44
|
} else {
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
const targetDirectory = path.dirname(targetPath);
|
|
46
|
+
const _targetOutputPath = path.relative(
|
|
47
47
|
targetDirectory,
|
|
48
48
|
`${projectRoot}/${newQunitxConfig.output}/tests.js`,
|
|
49
49
|
);
|
|
50
|
-
|
|
50
|
+
const testHTMLTemplate = testHTMLTemplateBuffer
|
|
51
51
|
.toString()
|
|
52
52
|
.replace('{{applicationName}}', oldPackageJSON.name);
|
|
53
53
|
|
|
@@ -61,15 +61,15 @@ async function writeTestsHTML(projectRoot, newQunitxConfig, oldPackageJSON) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
async function rewritePackageJSON(projectRoot, newQunitxConfig, oldPackageJSON) {
|
|
64
|
-
|
|
64
|
+
const newPackageJSON = Object.assign(oldPackageJSON, { qunitx: newQunitxConfig });
|
|
65
65
|
|
|
66
66
|
await fs.writeFile(`${projectRoot}/package.json`, JSON.stringify(newPackageJSON, null, 2));
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
async function writeTSConfigIfNeeded(projectRoot) {
|
|
70
|
-
|
|
70
|
+
const targetPath = `${projectRoot}/tsconfig.json`;
|
|
71
71
|
if (!(await pathExists(targetPath))) {
|
|
72
|
-
|
|
72
|
+
const tsConfigTemplateBuffer = await fs.readFile(
|
|
73
73
|
`${__dirname}/../boilerplates/setup/tsconfig.json`,
|
|
74
74
|
);
|
|
75
75
|
|
package/lib/commands/run.js
CHANGED
|
@@ -146,7 +146,7 @@ export default async function (config) {
|
|
|
146
146
|
async function buildCachedContent(config, htmlPaths) {
|
|
147
147
|
const htmlBuffers = await Promise.all(config.htmlPaths.map((htmlPath) => fs.readFile(htmlPath)));
|
|
148
148
|
const cachedContent = htmlPaths.reduce(
|
|
149
|
-
(result,
|
|
149
|
+
(result, _htmlPath, index) => {
|
|
150
150
|
const filePath = config.htmlPaths[index];
|
|
151
151
|
const html = htmlBuffers[index].toString();
|
|
152
152
|
|
|
@@ -208,7 +208,7 @@ function splitIntoGroups(files, groupCount) {
|
|
|
208
208
|
return groups.filter((g) => g.length > 0);
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
function logWatcherAndKeyboardShortcutInfo(config,
|
|
211
|
+
function logWatcherAndKeyboardShortcutInfo(config, _server) {
|
|
212
212
|
console.log(
|
|
213
213
|
'#',
|
|
214
214
|
kleur.blue(`Watching files... You can browse the tests on http://localhost:${config.port} ...`),
|
package/lib/servers/http.js
CHANGED
|
@@ -15,14 +15,13 @@ export const MIME_TYPES = {
|
|
|
15
15
|
|
|
16
16
|
export default class HTTPServer {
|
|
17
17
|
static serve(config = { port: 1234 }, handler) {
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
const onListen = config.onListen || ((_server) => {});
|
|
19
|
+
const onError = config.onError || ((_error) => {});
|
|
20
20
|
|
|
21
21
|
return new Promise((resolve, reject) => {
|
|
22
|
-
|
|
22
|
+
const server = http.createServer((req, res) => {
|
|
23
23
|
return handler(req, res);
|
|
24
24
|
});
|
|
25
|
-
server = server;
|
|
26
25
|
server
|
|
27
26
|
.on('error', (error) => {
|
|
28
27
|
onError(error);
|
|
@@ -133,10 +132,14 @@ export default class HTTPServer {
|
|
|
133
132
|
|
|
134
133
|
handleRequest(req, res) {
|
|
135
134
|
const { method, url } = req;
|
|
136
|
-
const
|
|
135
|
+
const urlObj = new URL(url, 'http://localhost');
|
|
136
|
+
const pathname = urlObj.pathname;
|
|
137
|
+
req.path = pathname;
|
|
138
|
+
req.query = Object.fromEntries(urlObj.searchParams);
|
|
139
|
+
const matchingRoute = this.findRouteHandler(method, pathname);
|
|
137
140
|
|
|
138
141
|
if (matchingRoute) {
|
|
139
|
-
req.params = this.extractParams(matchingRoute,
|
|
142
|
+
req.params = this.extractParams(matchingRoute, pathname);
|
|
140
143
|
this.runMiddleware(req, res, matchingRoute.handler);
|
|
141
144
|
} else {
|
|
142
145
|
res.statusCode = 404;
|
|
@@ -217,7 +220,7 @@ export default class HTTPServer {
|
|
|
217
220
|
return true;
|
|
218
221
|
}
|
|
219
222
|
|
|
220
|
-
buildRegexPattern(path,
|
|
223
|
+
buildRegexPattern(path, _paramNames) {
|
|
221
224
|
let regexPattern = path.replace(/:[^/]+/g, '([^/]+)');
|
|
222
225
|
regexPattern = regexPattern.replace(/\//g, '\\/');
|
|
223
226
|
|
|
@@ -231,7 +234,7 @@ export default class HTTPServer {
|
|
|
231
234
|
return paramMatches ? paramMatches.map((match) => match.slice(1)) : [];
|
|
232
235
|
}
|
|
233
236
|
|
|
234
|
-
extractParams(route,
|
|
237
|
+
extractParams(route, _url) {
|
|
235
238
|
const { paramNames, paramValues } = route;
|
|
236
239
|
const params = {};
|
|
237
240
|
|
package/lib/setup/browser.js
CHANGED
|
@@ -13,7 +13,7 @@ export default async function setupBrowser(
|
|
|
13
13
|
cachedContent,
|
|
14
14
|
existingBrowser = null,
|
|
15
15
|
) {
|
|
16
|
-
|
|
16
|
+
const [server, browser] = await Promise.all([
|
|
17
17
|
setupWebServer(config, cachedContent),
|
|
18
18
|
existingBrowser
|
|
19
19
|
? Promise.resolve(existingBrowser)
|
|
@@ -29,7 +29,7 @@ export default async function setupBrowser(
|
|
|
29
29
|
headless: true,
|
|
30
30
|
}),
|
|
31
31
|
]);
|
|
32
|
-
|
|
32
|
+
const [page] = await Promise.all([browser.newPage(), bindServerToPort(server, config)]);
|
|
33
33
|
|
|
34
34
|
page.on('console', async (msg) => {
|
|
35
35
|
if (config.debug) {
|
|
@@ -39,20 +39,12 @@ export default async function setupBrowser(
|
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
41
|
page.on('error', (msg) => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
} catch (e) {
|
|
45
|
-
console.error(e, e.stack);
|
|
46
|
-
console.log(e, e.stack);
|
|
47
|
-
}
|
|
42
|
+
console.error(msg, msg.stack);
|
|
43
|
+
console.log(msg, msg.stack);
|
|
48
44
|
});
|
|
49
|
-
page.on('pageerror',
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
} catch (e) {
|
|
53
|
-
console.log(e.toString());
|
|
54
|
-
console.error(e.toString());
|
|
55
|
-
}
|
|
45
|
+
page.on('pageerror', (error) => {
|
|
46
|
+
console.log(error.toString());
|
|
47
|
+
console.error(error.toString());
|
|
56
48
|
});
|
|
57
49
|
|
|
58
50
|
return { server, browser, page };
|
package/lib/setup/config.js
CHANGED
|
@@ -6,11 +6,11 @@ import setupTestFilePaths from './test-file-paths.js';
|
|
|
6
6
|
import parseCliFlags from '../utils/parse-cli-flags.js';
|
|
7
7
|
|
|
8
8
|
export default async function setupConfig() {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
const projectRoot = await findProjectRoot();
|
|
10
|
+
const cliConfigFlags = parseCliFlags(projectRoot);
|
|
11
|
+
const projectPackageJSON = await readConfigFromPackageJSON(projectRoot);
|
|
12
|
+
const inputs = cliConfigFlags.inputs.concat(readInputsFromPackageJSON(projectPackageJSON));
|
|
13
|
+
const config = {
|
|
14
14
|
...defaultProjectConfigValues,
|
|
15
15
|
htmlPaths: [],
|
|
16
16
|
...projectPackageJSON.qunitx,
|
|
@@ -28,7 +28,7 @@ export default async function setupConfig() {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
async function readConfigFromPackageJSON(projectRoot) {
|
|
31
|
-
|
|
31
|
+
const packageJSON = await fs.readFile(`${projectRoot}/package.json`);
|
|
32
32
|
|
|
33
33
|
return JSON.parse(packageJSON.toString());
|
|
34
34
|
}
|
|
@@ -38,7 +38,7 @@ function normalizeHTMLPaths(projectRoot, htmlPaths) {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
function readInputsFromPackageJSON(packageJSON) {
|
|
41
|
-
|
|
41
|
+
const qunitx = packageJSON.qunitx;
|
|
42
42
|
|
|
43
43
|
return qunitx && qunitx.inputs ? qunitx.inputs : [];
|
|
44
44
|
}
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import chokidar from 'chokidar';
|
|
2
2
|
import kleur from 'kleur';
|
|
3
3
|
|
|
4
|
-
export default
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
onEventFunc,
|
|
8
|
-
onFinishFunc,
|
|
9
|
-
) {
|
|
10
|
-
let extensions = ['js', 'ts'];
|
|
11
|
-
let fileWatchers = testFileLookupPaths.reduce((watcher, watchPath) => {
|
|
4
|
+
export default function setupFileWatchers(testFileLookupPaths, config, onEventFunc, onFinishFunc) {
|
|
5
|
+
const extensions = ['js', 'ts'];
|
|
6
|
+
const fileWatchers = testFileLookupPaths.reduce((watcher, watchPath) => {
|
|
12
7
|
return Object.assign(watcher, {
|
|
13
8
|
[watchPath]: chokidar.watch(watchPath, { ignoreInitial: true }).on('all', (event, path) => {
|
|
14
9
|
if (extensions.some((extension) => path.endsWith(extension))) {
|
|
@@ -31,7 +26,7 @@ export default async function setupFileWatchers(
|
|
|
31
26
|
if (!global.chokidarBuild) {
|
|
32
27
|
global.chokidarBuild = true;
|
|
33
28
|
|
|
34
|
-
|
|
29
|
+
const result = extensions.some((extension) => path.endsWith(extension))
|
|
35
30
|
? onEventFunc(event, path)
|
|
36
31
|
: null;
|
|
37
32
|
|
|
@@ -72,7 +67,7 @@ function mutateFSTree(fsTree, event, path) {
|
|
|
72
67
|
} else if (event === 'unlink') {
|
|
73
68
|
delete fsTree[path];
|
|
74
69
|
} else if (event === 'unlinkDir') {
|
|
75
|
-
|
|
70
|
+
const targetPaths = Object.keys(config.fsTree).filter((treePath) => treePath.startsWith(path));
|
|
76
71
|
|
|
77
72
|
targetPaths.forEach((path) => delete config.fsTree[path]);
|
|
78
73
|
}
|
package/lib/setup/fs-tree.js
CHANGED
|
@@ -2,19 +2,19 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import picomatch from 'picomatch';
|
|
3
3
|
import recursiveLookup from 'recursive-lookup';
|
|
4
4
|
|
|
5
|
-
export default async function buildFSTree(fileAbsolutePaths,
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
export default async function buildFSTree(fileAbsolutePaths, _config = {}) {
|
|
6
|
+
const targetExtensions = ['js', 'ts'];
|
|
7
|
+
const fsTree = {};
|
|
8
8
|
|
|
9
9
|
await Promise.all(
|
|
10
10
|
fileAbsolutePaths.map(async (fileAbsolutePath) => {
|
|
11
|
-
|
|
11
|
+
const glob = picomatch.scan(fileAbsolutePath);
|
|
12
12
|
|
|
13
13
|
// TODO: maybe allow absolute path references
|
|
14
14
|
|
|
15
15
|
try {
|
|
16
16
|
if (glob.isGlob) {
|
|
17
|
-
|
|
17
|
+
const fileNames = await recursiveLookup(glob.base, (path) => {
|
|
18
18
|
return targetExtensions.some((extension) => path.endsWith(extension));
|
|
19
19
|
});
|
|
20
20
|
|
|
@@ -24,12 +24,12 @@ export default async function buildFSTree(fileAbsolutePaths, config = {}) {
|
|
|
24
24
|
}
|
|
25
25
|
});
|
|
26
26
|
} else {
|
|
27
|
-
|
|
27
|
+
const entry = await fs.stat(fileAbsolutePath);
|
|
28
28
|
|
|
29
29
|
if (entry.isFile()) {
|
|
30
30
|
fsTree[fileAbsolutePath] = null;
|
|
31
31
|
} else if (entry.isDirectory()) {
|
|
32
|
-
|
|
32
|
+
const fileNames = await recursiveLookup(glob.base, (path) => {
|
|
33
33
|
return targetExtensions.some((extension) => path.endsWith(extension));
|
|
34
34
|
});
|
|
35
35
|
|
|
@@ -27,11 +27,6 @@ export default function setupKeyboardEvents(config, cachedContent, connections)
|
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
function abortBrowserQUnit(
|
|
30
|
+
function abortBrowserQUnit(_config, connections) {
|
|
31
31
|
connections.server.publish('abort', 'abort');
|
|
32
32
|
}
|
|
33
|
-
|
|
34
|
-
function abortNodejsQUnit(config) {
|
|
35
|
-
window.QUnit.config.queue.length = 0;
|
|
36
|
-
config.aborted = true;
|
|
37
|
-
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import picomatch from 'picomatch';
|
|
2
2
|
|
|
3
|
-
export default function setupTestFilePaths(
|
|
3
|
+
export default function setupTestFilePaths(_projectRoot, inputs) {
|
|
4
4
|
// NOTE: very complex algorithm, order is very important
|
|
5
|
-
|
|
5
|
+
const [folders, filesWithGlob, filesWithoutGlob] = inputs.reduce(
|
|
6
6
|
(result, input) => {
|
|
7
|
-
|
|
7
|
+
const isGlob = picomatch.scan(input).isGlob;
|
|
8
8
|
|
|
9
9
|
if (!pathIsFile(input)) {
|
|
10
10
|
result[0].push({
|
|
@@ -25,7 +25,7 @@ export default function setupTestFilePaths(projectRoot, inputs) {
|
|
|
25
25
|
[[], [], []],
|
|
26
26
|
);
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
const result = folders.reduce((folderResult, folder) => {
|
|
29
29
|
if (!pathIsIncludedInPaths(folders, folder)) {
|
|
30
30
|
folderResult.push(folder);
|
|
31
31
|
}
|
|
@@ -48,7 +48,7 @@ export default function setupTestFilePaths(projectRoot, inputs) {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
function pathIsFile(path) {
|
|
51
|
-
|
|
51
|
+
const inputs = path.split('/');
|
|
52
52
|
|
|
53
53
|
return inputs[inputs.length - 1].includes('.');
|
|
54
54
|
}
|
|
@@ -59,7 +59,7 @@ function pathIsIncludedInPaths(paths, targetPath) {
|
|
|
59
59
|
return false;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
const globFormat = buildGlobFormat(path);
|
|
63
63
|
|
|
64
64
|
return picomatch.isMatch(targetPath.input, globFormat, { bash: true });
|
|
65
65
|
});
|
package/lib/setup/web-server.js
CHANGED
|
@@ -7,7 +7,7 @@ import HTTPServer, { MIME_TYPES } from '../servers/http.js';
|
|
|
7
7
|
|
|
8
8
|
const fsPromise = fs.promises;
|
|
9
9
|
|
|
10
|
-
export default
|
|
10
|
+
export default function setupWebServer(
|
|
11
11
|
config = {
|
|
12
12
|
port: 1234,
|
|
13
13
|
debug: false,
|
|
@@ -16,8 +16,8 @@ export default async function setupWebServer(
|
|
|
16
16
|
},
|
|
17
17
|
cachedContent,
|
|
18
18
|
) {
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
const STATIC_FILES_PATH = path.join(config.projectRoot, config.output);
|
|
20
|
+
const server = new HTTPServer();
|
|
21
21
|
|
|
22
22
|
server.wss.on('connection', function connection(socket) {
|
|
23
23
|
socket.on('message', function message(data) {
|
|
@@ -42,9 +42,9 @@ export default async function setupWebServer(
|
|
|
42
42
|
});
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
server.get('/', async (
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
server.get('/', async (_req, res) => {
|
|
46
|
+
const TEST_RUNTIME_TO_INJECT = testRuntimeToInject(config.port, config);
|
|
47
|
+
const htmlContent = escapeAndInjectTestsToHTML(
|
|
48
48
|
replaceAssetPaths(
|
|
49
49
|
cachedContent.mainHTML.html,
|
|
50
50
|
cachedContent.mainHTML.filePath,
|
|
@@ -63,9 +63,9 @@ export default async function setupWebServer(
|
|
|
63
63
|
);
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
-
server.get('/qunitx.html', async (
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
server.get('/qunitx.html', async (_req, res) => {
|
|
67
|
+
const TEST_RUNTIME_TO_INJECT = testRuntimeToInject(config.port, config);
|
|
68
|
+
const htmlContent = escapeAndInjectTestsToHTML(
|
|
69
69
|
replaceAssetPaths(
|
|
70
70
|
cachedContent.mainHTML.html,
|
|
71
71
|
cachedContent.mainHTML.filePath,
|
|
@@ -85,10 +85,11 @@ export default async function setupWebServer(
|
|
|
85
85
|
});
|
|
86
86
|
|
|
87
87
|
server.get('/*', async (req, res) => {
|
|
88
|
-
|
|
88
|
+
const possibleDynamicHTML =
|
|
89
|
+
cachedContent.dynamicContentHTMLs[`${config.projectRoot}${req.path}`];
|
|
89
90
|
if (possibleDynamicHTML) {
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
const TEST_RUNTIME_TO_INJECT = testRuntimeToInject(config.port, config);
|
|
92
|
+
const htmlContent = escapeAndInjectTestsToHTML(
|
|
92
93
|
possibleDynamicHTML,
|
|
93
94
|
TEST_RUNTIME_TO_INJECT,
|
|
94
95
|
cachedContent.allTestCode,
|
|
@@ -103,12 +104,12 @@ export default async function setupWebServer(
|
|
|
103
104
|
);
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
const url = req.url;
|
|
108
|
+
const requestStartedAt = new Date();
|
|
109
|
+
const filePath = (
|
|
109
110
|
url.endsWith('/') ? [STATIC_FILES_PATH, url, 'index.html'] : [STATIC_FILES_PATH, url]
|
|
110
111
|
).join('');
|
|
111
|
-
|
|
112
|
+
const statusCode = (await pathExists(filePath)) ? 200 : 404;
|
|
112
113
|
|
|
113
114
|
res.writeHead(statusCode, {
|
|
114
115
|
'Content-Type': req.headers.accept?.includes('text/html')
|
|
@@ -129,11 +130,11 @@ export default async function setupWebServer(
|
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
function replaceAssetPaths(html, htmlPath, projectRoot) {
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
const assetPaths = findInternalAssetsFromHTML(html);
|
|
134
|
+
const htmlDirectory = htmlPath.split('/').slice(0, -1).join('/');
|
|
134
135
|
|
|
135
136
|
return assetPaths.reduce((result, assetPath) => {
|
|
136
|
-
|
|
137
|
+
const normalizedFullAbsolutePath = path.normalize(`${htmlDirectory}/${assetPath}`);
|
|
137
138
|
|
|
138
139
|
return result.replace(assetPath, normalizedFullAbsolutePath.replace(projectRoot, '.'));
|
|
139
140
|
}, html);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
|
|
3
3
|
export default async function writeOutputStaticFiles({ projectRoot, output }, cachedContent) {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
const staticHTMLPromises = Object.keys(cachedContent.staticHTMLs).map(async (staticHTMLKey) => {
|
|
5
|
+
const htmlRelativePath = staticHTMLKey.replace(`${projectRoot}/`, '');
|
|
6
6
|
|
|
7
7
|
await ensureFolderExists(`${projectRoot}/${output}/${htmlRelativePath}`);
|
|
8
8
|
await fs.writeFile(
|
|
@@ -10,8 +10,8 @@ export default async function writeOutputStaticFiles({ projectRoot, output }, ca
|
|
|
10
10
|
cachedContent.staticHTMLs[staticHTMLKey],
|
|
11
11
|
);
|
|
12
12
|
});
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
const assetPromises = Array.from(cachedContent.assets).map(async (assetAbsolutePath) => {
|
|
14
|
+
const assetRelativePath = assetAbsolutePath.replace(`${projectRoot}/`, '');
|
|
15
15
|
|
|
16
16
|
await ensureFolderExists(`${projectRoot}/${output}/${assetRelativePath}`);
|
|
17
17
|
await fs.copyFile(assetAbsolutePath, `${projectRoot}/${output}/${assetRelativePath}`);
|
|
@@ -22,7 +22,7 @@ export default function (COUNTER, details) {
|
|
|
22
22
|
details.assertions.reduce((errorCount, assertion, index) => {
|
|
23
23
|
if (!assertion.passed && assertion.todo === false) {
|
|
24
24
|
COUNTER.errorCount++;
|
|
25
|
-
|
|
25
|
+
const stack = assertion.stack?.match(/\(.+\)/g);
|
|
26
26
|
|
|
27
27
|
console.log(' ---');
|
|
28
28
|
console.log(
|
|
@@ -59,7 +59,7 @@ export default function (COUNTER, details) {
|
|
|
59
59
|
|
|
60
60
|
function getCircularReplacer() {
|
|
61
61
|
const ancestors = [];
|
|
62
|
-
return function (
|
|
62
|
+
return function (_key, value) {
|
|
63
63
|
if (typeof value !== 'object' || value === null) {
|
|
64
64
|
return value;
|
|
65
65
|
}
|
package/lib/utils/find-chrome.js
CHANGED
|
@@ -2,8 +2,8 @@ import { exec } from 'node:child_process';
|
|
|
2
2
|
|
|
3
3
|
const CANDIDATES = ['google-chrome-stable', 'google-chrome', 'chromium', 'chromium-browser'];
|
|
4
4
|
|
|
5
|
-
export default
|
|
6
|
-
if (process.env.CHROME_BIN) return process.env.CHROME_BIN;
|
|
5
|
+
export default function findChrome() {
|
|
6
|
+
if (process.env.CHROME_BIN) return Promise.resolve(process.env.CHROME_BIN);
|
|
7
7
|
|
|
8
8
|
return Promise.any(
|
|
9
9
|
CANDIDATES.map(
|
|
@@ -3,13 +3,13 @@ import searchInParentDirectories from './search-in-parent-directories.js';
|
|
|
3
3
|
|
|
4
4
|
export default async function () {
|
|
5
5
|
try {
|
|
6
|
-
|
|
6
|
+
const absolutePath = await searchInParentDirectories('.', 'package.json');
|
|
7
7
|
if (!absolutePath.includes('package.json')) {
|
|
8
8
|
throw new Error('package.json mising');
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
return absolutePath.replace('/package.json', '');
|
|
12
|
-
} catch (
|
|
12
|
+
} catch (_error) {
|
|
13
13
|
console.log('couldnt find projects package.json, did you run $ npm init ??');
|
|
14
14
|
process.exit(1);
|
|
15
15
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
const stdin = process.stdin;
|
|
4
|
+
const targetInputs = {};
|
|
5
|
+
const inputs = [];
|
|
6
6
|
let listenerAdded = false;
|
|
7
7
|
|
|
8
8
|
export default function listenToKeyboardKey(
|
|
@@ -22,10 +22,10 @@ export default function listenToKeyboardKey(
|
|
|
22
22
|
inputs.shift();
|
|
23
23
|
inputs.push(key);
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (targetListener && targetListenerConformsToCase(targetListener,
|
|
28
|
-
targetListener.closure(
|
|
25
|
+
const currentInput = inputs.join('');
|
|
26
|
+
const targetListener = targetInputs[currentInput.toUpperCase()];
|
|
27
|
+
if (targetListener && targetListenerConformsToCase(targetListener, currentInput)) {
|
|
28
|
+
targetListener.closure(currentInput);
|
|
29
29
|
inputs.fill(undefined);
|
|
30
30
|
}
|
|
31
31
|
});
|
|
@@ -6,15 +6,13 @@ export default async function resolvePortNumberFor(portNumber) {
|
|
|
6
6
|
return await resolvePortNumberFor(portNumber + 1);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
function portIsAvailable(portNumber) {
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
async function portIsAvailable(portNumber) {
|
|
10
|
+
const net = await import('net');
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
12
|
const server = net.createServer();
|
|
13
13
|
|
|
14
|
-
server.once('error', function (
|
|
15
|
-
|
|
16
|
-
resolve(false);
|
|
17
|
-
}
|
|
14
|
+
server.once('error', function (_err) {
|
|
15
|
+
resolve(false);
|
|
18
16
|
});
|
|
19
17
|
|
|
20
18
|
server.once('listening', function () {
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import pathExists from './path-exists.js';
|
|
2
2
|
|
|
3
3
|
async function searchInParentDirectories(directory, targetEntry) {
|
|
4
|
-
|
|
4
|
+
const resolvedDirectory = directory === '.' ? process.cwd() : directory;
|
|
5
5
|
|
|
6
|
-
if (await pathExists(`${
|
|
7
|
-
return `${
|
|
8
|
-
} else if (
|
|
6
|
+
if (await pathExists(`${resolvedDirectory}/${targetEntry}`)) {
|
|
7
|
+
return `${resolvedDirectory}/${targetEntry}`;
|
|
8
|
+
} else if (resolvedDirectory === '') {
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
return await searchInParentDirectories(
|
|
13
|
-
|
|
13
|
+
resolvedDirectory.slice(0, resolvedDirectory.lastIndexOf('/')),
|
|
14
14
|
targetEntry,
|
|
15
15
|
);
|
|
16
16
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qunitx-cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.2",
|
|
5
5
|
"description": "Browser runner for QUnitx: run your qunitx tests in google-chrome",
|
|
6
6
|
"main": "cli.js",
|
|
7
7
|
"author": "Izel Nakri",
|
|
@@ -16,14 +16,15 @@
|
|
|
16
16
|
],
|
|
17
17
|
"scripts": {
|
|
18
18
|
"bin": "chmod +x cli.js && ./cli.js",
|
|
19
|
-
"
|
|
20
|
-
"
|
|
19
|
+
"format": "prettier --check \"lib/**/*.js\" \"test/**/*.js\" \"*.js\" \"package.json\" \".github/**/*.yml\"",
|
|
20
|
+
"format:fix": "prettier --write \"lib/**/*.js\" \"test/**/*.js\" \"*.js\" \"package.json\" \".github/**/*.yml\"",
|
|
21
|
+
"lint": "deno lint lib/ cli.js",
|
|
21
22
|
"build": "node build.js",
|
|
22
23
|
"changelog:unreleased": "git-cliff --unreleased --strip all",
|
|
23
24
|
"changelog:preview": "git-cliff",
|
|
24
25
|
"changelog:update": "git-cliff --output CHANGELOG.md",
|
|
25
26
|
"prepack": "npm run build",
|
|
26
|
-
"test": "node test/setup.js && node --test test/**/*-test.js",
|
|
27
|
+
"test": "node test/setup.js && FORCE_COLOR=0 node --test test/**/*-test.js",
|
|
27
28
|
"test:sanity-first": "./cli.js test/helpers/failing-tests.js test/helpers/failing-tests.ts",
|
|
28
29
|
"test:sanity-second": "./cli.js test/helpers/passing-tests.js test/helpers/passing-tests.ts"
|
|
29
30
|
},
|
|
@@ -53,7 +54,7 @@
|
|
|
53
54
|
"express": "^5.2.1",
|
|
54
55
|
"prettier": "^3.8.1",
|
|
55
56
|
"qunit": "^2.25.0",
|
|
56
|
-
"qunitx": "^0.
|
|
57
|
+
"qunitx": "^1.0.0"
|
|
57
58
|
},
|
|
58
59
|
"volta": {
|
|
59
60
|
"node": "24.14.0"
|
package/.env
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export CHROME_BIN=$(which google-chrome-stable)
|