kempo-testing-framework 1.2.4 → 1.3.3
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/.github/copilot-instructions.md +105 -105
- package/.github/workflows/publish-npm.yml +29 -0
- package/CONTRIBUTING.md +107 -107
- package/gui/components/Collapsible.js +54 -54
- package/gui/components/Icon.js +151 -151
- package/gui/components/Logs.js +73 -73
- package/gui/components/SettingCheckbox.js +42 -42
- package/gui/components/SettingNumber.js +77 -77
- package/gui/components/SettingSelect.js +67 -67
- package/gui/components/TestFramework.js +378 -378
- package/gui/components/TestSuite.js +181 -181
- package/gui/components/TestSummary.js +189 -189
- package/gui/components/Theme.js +40 -40
- package/gui/components/settingsStore.js +46 -46
- package/gui/icons/fail.svg +1 -1
- package/gui/icons/pass.svg +1 -1
- package/gui/icons/running.svg +1 -1
- package/gui/icons/settings.svg +1 -1
- package/package.json +6 -1
- package/src/browserTestServer.js +115 -115
- package/src/cli.js +198 -198
- package/src/gui.js +15 -2
- package/src/runBrowserTests.js +87 -87
- package/src/runTestFiles.js +94 -94
- package/src/runTests.js +83 -83
- package/src/utils/logLevels.js +7 -7
- package/test.html +30 -30
- package/tests/Counter.js +33 -33
- package/tests/cli-flags.node-test.js +54 -54
- package/tests/cli-help.node-test.js +61 -61
- package/tests/cli-loglevel.node-test.js +40 -40
- package/tests/collapsible.browser-test.js +49 -49
- package/tests/counter.browser-test.js +140 -140
- package/tests/example.node-test.js +103 -103
- package/tests/fibonacci.js +92 -92
- package/tests/fibonacci.test.js +285 -285
- package/tests/icon.browser-test.js +54 -54
- package/tests/logs.browser-test.js +47 -47
- package/tests/setting-checkbox.browser-test.js +48 -48
- package/tests/setting-number.browser-test.js +54 -54
- package/tests/setting-select.browser-test.js +47 -47
- package/tests/settings-store.browser-test.js +26 -26
- package/tests/src-browserTestServer.node-test.js +47 -47
- package/tests/src-cli.node-test.js +32 -32
- package/tests/src-findTests.node-test.js +41 -41
- package/tests/src-logLevels.node-test.js +29 -29
- package/tests/src-runBrowserTests.node-test.js +41 -41
- package/tests/src-runTestFiles.node-test.js +42 -42
- package/tests/src-runTests.node-test.js +56 -56
- package/tests/test-framework.browser-test.js +65 -65
- package/tests/test-summary.browser-test.js +78 -78
- package/tests/test.browser-test.js +56 -56
- package/tests/testfile.browser-test.js +60 -60
- package/tests/theme.browser-test.js +38 -38
package/src/browserTestServer.js
CHANGED
|
@@ -1,115 +1,115 @@
|
|
|
1
|
-
import http from 'http';
|
|
2
|
-
import { readFile } from 'fs/promises';
|
|
3
|
-
import { fileURLToPath } from 'url';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
|
|
6
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
-
const __dirname = path.dirname(__filename);
|
|
8
|
-
|
|
9
|
-
let server;
|
|
10
|
-
let port;
|
|
11
|
-
|
|
12
|
-
export const startServer = async (_port = 3000) => {
|
|
13
|
-
if(!server){
|
|
14
|
-
port = _port;
|
|
15
|
-
server = http.createServer(async (req, res) => {
|
|
16
|
-
const basePath = req.url.split('?')[0];
|
|
17
|
-
|
|
18
|
-
/*
|
|
19
|
-
* Route Handling
|
|
20
|
-
*/
|
|
21
|
-
if(basePath === '/'){
|
|
22
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
23
|
-
const testHtmlPath = path.join(__dirname, '..', 'test.html');
|
|
24
|
-
res.end(await readFile(testHtmlPath, 'utf8'));
|
|
25
|
-
} else if(basePath === '/runTests.js'){
|
|
26
|
-
res.writeHead(200, { 'Content-Type': 'application/javascript' });
|
|
27
|
-
const filePath = path.join(__dirname, 'runTests.js');
|
|
28
|
-
res.end(await readFile(filePath, 'utf8'));
|
|
29
|
-
}
|
|
30
|
-
// Serve kempo.css like the GUI server does
|
|
31
|
-
else if (basePath === '/kempo.css') {
|
|
32
|
-
try {
|
|
33
|
-
const cssPath = path.join(__dirname, '../node_modules/kempo-css/dist/kempo.min.css');
|
|
34
|
-
const css = await readFile(cssPath, 'utf8');
|
|
35
|
-
res.writeHead(200, { 'Content-Type': 'text/css' });
|
|
36
|
-
res.end(css);
|
|
37
|
-
} catch (error) {
|
|
38
|
-
console.error(`Error serving kempo.css:`, error);
|
|
39
|
-
res.writeHead(404);
|
|
40
|
-
res.end('Not found');
|
|
41
|
-
}
|
|
42
|
-
} else if(['/favicon.ico', '/.well-known/appspecific/com.chrome.devtools.json'].includes(basePath)){
|
|
43
|
-
res.writeHead(404);
|
|
44
|
-
res.end('');
|
|
45
|
-
} else {
|
|
46
|
-
/*
|
|
47
|
-
* Static File Serving
|
|
48
|
-
*/
|
|
49
|
-
try {
|
|
50
|
-
// First try from project root, e.g. /gui/components/* works already
|
|
51
|
-
const primaryPath = `.${basePath}`;
|
|
52
|
-
let fileContent = await readFile(primaryPath);
|
|
53
|
-
const extension = basePath.split('.').pop().toLowerCase();
|
|
54
|
-
let contentType = 'text/plain';
|
|
55
|
-
switch(extension){
|
|
56
|
-
case 'html': contentType = 'text/html'; break;
|
|
57
|
-
case 'css': contentType = 'text/css'; break;
|
|
58
|
-
case 'js': contentType = 'application/javascript'; break;
|
|
59
|
-
case 'json': contentType = 'application/json'; break;
|
|
60
|
-
case 'png': contentType = 'image/png'; break;
|
|
61
|
-
case 'jpg': case 'jpeg': contentType = 'image/jpeg'; break;
|
|
62
|
-
case 'gif': contentType = 'image/gif'; break;
|
|
63
|
-
case 'svg': contentType = 'image/svg+xml'; break;
|
|
64
|
-
case 'map': contentType = 'application/json'; break;
|
|
65
|
-
}
|
|
66
|
-
res.writeHead(200, { 'Content-Type': contentType });
|
|
67
|
-
res.end(fileContent);
|
|
68
|
-
} catch (primaryErr) {
|
|
69
|
-
// If requesting a sourcemap, quietly 404 without noisy logs
|
|
70
|
-
if (basePath.endsWith('.map')) {
|
|
71
|
-
res.writeHead(404);
|
|
72
|
-
res.end('Not found');
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
// If not found, try resolving under gui/ for assets like /icons/*
|
|
76
|
-
try {
|
|
77
|
-
const rel = basePath.replace(/^\//, '');
|
|
78
|
-
const relUnderGui = rel.startsWith('gui/') ? rel : path.join('gui', rel);
|
|
79
|
-
const fallbackPath = path.join(__dirname, '..', relUnderGui);
|
|
80
|
-
const fileContent = await readFile(fallbackPath);
|
|
81
|
-
const extension = basePath.split('.').pop().toLowerCase();
|
|
82
|
-
let contentType = 'text/plain';
|
|
83
|
-
switch(extension){
|
|
84
|
-
case 'html': contentType = 'text/html'; break;
|
|
85
|
-
case 'css': contentType = 'text/css'; break;
|
|
86
|
-
case 'js': contentType = 'application/javascript'; break;
|
|
87
|
-
case 'json': contentType = 'application/json'; break;
|
|
88
|
-
case 'png': contentType = 'image/png'; break;
|
|
89
|
-
case 'jpg': case 'jpeg': contentType = 'image/jpeg'; break;
|
|
90
|
-
case 'gif': contentType = 'image/gif'; break;
|
|
91
|
-
case 'svg': contentType = 'image/svg+xml'; break;
|
|
92
|
-
case 'map': contentType = 'application/json'; break;
|
|
93
|
-
}
|
|
94
|
-
res.writeHead(200, { 'Content-Type': contentType });
|
|
95
|
-
res.end(fileContent);
|
|
96
|
-
} catch (fallbackErr) {
|
|
97
|
-
res.writeHead(404);
|
|
98
|
-
res.end('Not found');
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
await new Promise((resolve, reject) => {
|
|
104
|
-
server.listen(port, err => err ? reject(err) : resolve());
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
return `http://localhost:${port}`;
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
export const stopServer = async () => {
|
|
111
|
-
if(server){
|
|
112
|
-
await new Promise(resolve => server.close(resolve));
|
|
113
|
-
server = null;
|
|
114
|
-
}
|
|
115
|
-
};
|
|
1
|
+
import http from 'http';
|
|
2
|
+
import { readFile } from 'fs/promises';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
|
|
9
|
+
let server;
|
|
10
|
+
let port;
|
|
11
|
+
|
|
12
|
+
export const startServer = async (_port = 3000) => {
|
|
13
|
+
if(!server){
|
|
14
|
+
port = _port;
|
|
15
|
+
server = http.createServer(async (req, res) => {
|
|
16
|
+
const basePath = req.url.split('?')[0];
|
|
17
|
+
|
|
18
|
+
/*
|
|
19
|
+
* Route Handling
|
|
20
|
+
*/
|
|
21
|
+
if(basePath === '/'){
|
|
22
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
23
|
+
const testHtmlPath = path.join(__dirname, '..', 'test.html');
|
|
24
|
+
res.end(await readFile(testHtmlPath, 'utf8'));
|
|
25
|
+
} else if(basePath === '/runTests.js'){
|
|
26
|
+
res.writeHead(200, { 'Content-Type': 'application/javascript' });
|
|
27
|
+
const filePath = path.join(__dirname, 'runTests.js');
|
|
28
|
+
res.end(await readFile(filePath, 'utf8'));
|
|
29
|
+
}
|
|
30
|
+
// Serve kempo.css like the GUI server does
|
|
31
|
+
else if (basePath === '/kempo.css') {
|
|
32
|
+
try {
|
|
33
|
+
const cssPath = path.join(__dirname, '../node_modules/kempo-css/dist/kempo.min.css');
|
|
34
|
+
const css = await readFile(cssPath, 'utf8');
|
|
35
|
+
res.writeHead(200, { 'Content-Type': 'text/css' });
|
|
36
|
+
res.end(css);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error(`Error serving kempo.css:`, error);
|
|
39
|
+
res.writeHead(404);
|
|
40
|
+
res.end('Not found');
|
|
41
|
+
}
|
|
42
|
+
} else if(['/favicon.ico', '/.well-known/appspecific/com.chrome.devtools.json'].includes(basePath)){
|
|
43
|
+
res.writeHead(404);
|
|
44
|
+
res.end('');
|
|
45
|
+
} else {
|
|
46
|
+
/*
|
|
47
|
+
* Static File Serving
|
|
48
|
+
*/
|
|
49
|
+
try {
|
|
50
|
+
// First try from project root, e.g. /gui/components/* works already
|
|
51
|
+
const primaryPath = `.${basePath}`;
|
|
52
|
+
let fileContent = await readFile(primaryPath);
|
|
53
|
+
const extension = basePath.split('.').pop().toLowerCase();
|
|
54
|
+
let contentType = 'text/plain';
|
|
55
|
+
switch(extension){
|
|
56
|
+
case 'html': contentType = 'text/html'; break;
|
|
57
|
+
case 'css': contentType = 'text/css'; break;
|
|
58
|
+
case 'js': contentType = 'application/javascript'; break;
|
|
59
|
+
case 'json': contentType = 'application/json'; break;
|
|
60
|
+
case 'png': contentType = 'image/png'; break;
|
|
61
|
+
case 'jpg': case 'jpeg': contentType = 'image/jpeg'; break;
|
|
62
|
+
case 'gif': contentType = 'image/gif'; break;
|
|
63
|
+
case 'svg': contentType = 'image/svg+xml'; break;
|
|
64
|
+
case 'map': contentType = 'application/json'; break;
|
|
65
|
+
}
|
|
66
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
67
|
+
res.end(fileContent);
|
|
68
|
+
} catch (primaryErr) {
|
|
69
|
+
// If requesting a sourcemap, quietly 404 without noisy logs
|
|
70
|
+
if (basePath.endsWith('.map')) {
|
|
71
|
+
res.writeHead(404);
|
|
72
|
+
res.end('Not found');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
// If not found, try resolving under gui/ for assets like /icons/*
|
|
76
|
+
try {
|
|
77
|
+
const rel = basePath.replace(/^\//, '');
|
|
78
|
+
const relUnderGui = rel.startsWith('gui/') ? rel : path.join('gui', rel);
|
|
79
|
+
const fallbackPath = path.join(__dirname, '..', relUnderGui);
|
|
80
|
+
const fileContent = await readFile(fallbackPath);
|
|
81
|
+
const extension = basePath.split('.').pop().toLowerCase();
|
|
82
|
+
let contentType = 'text/plain';
|
|
83
|
+
switch(extension){
|
|
84
|
+
case 'html': contentType = 'text/html'; break;
|
|
85
|
+
case 'css': contentType = 'text/css'; break;
|
|
86
|
+
case 'js': contentType = 'application/javascript'; break;
|
|
87
|
+
case 'json': contentType = 'application/json'; break;
|
|
88
|
+
case 'png': contentType = 'image/png'; break;
|
|
89
|
+
case 'jpg': case 'jpeg': contentType = 'image/jpeg'; break;
|
|
90
|
+
case 'gif': contentType = 'image/gif'; break;
|
|
91
|
+
case 'svg': contentType = 'image/svg+xml'; break;
|
|
92
|
+
case 'map': contentType = 'application/json'; break;
|
|
93
|
+
}
|
|
94
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
95
|
+
res.end(fileContent);
|
|
96
|
+
} catch (fallbackErr) {
|
|
97
|
+
res.writeHead(404);
|
|
98
|
+
res.end('Not found');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
await new Promise((resolve, reject) => {
|
|
104
|
+
server.listen(port, err => err ? reject(err) : resolve());
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return `http://localhost:${port}`;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const stopServer = async () => {
|
|
111
|
+
if(server){
|
|
112
|
+
await new Promise(resolve => server.close(resolve));
|
|
113
|
+
server = null;
|
|
114
|
+
}
|
|
115
|
+
};
|
package/src/cli.js
CHANGED
|
@@ -1,198 +1,198 @@
|
|
|
1
|
-
import runTestFiles from './runTestFiles.js';
|
|
2
|
-
import LOG_LEVELS from './utils/logLevels.js';
|
|
3
|
-
|
|
4
|
-
export default async (flags, args) => {
|
|
5
|
-
const [suiteFilter, testFilter] = args;
|
|
6
|
-
|
|
7
|
-
/*
|
|
8
|
-
* Log Level Configuration
|
|
9
|
-
*/
|
|
10
|
-
let logLevel = LOG_LEVELS.NORMAL;
|
|
11
|
-
if (typeof flags.logLevel === 'number' && Number.isFinite(flags.logLevel)) {
|
|
12
|
-
const lvl = Math.max(0, Math.min(4, flags.logLevel));
|
|
13
|
-
logLevel = lvl;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if(logLevel >= LOG_LEVELS.DEBUG){
|
|
17
|
-
console.log('\x1b[90m[Debug] Flags:', flags);
|
|
18
|
-
console.log('[Debug] Args:', args);
|
|
19
|
-
console.log(`[Debug] Log level: ${logLevel}\x1b[0m`);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/*
|
|
23
|
-
* Test Configuration
|
|
24
|
-
*/
|
|
25
|
-
const showBrowser = Boolean(flags['show-browser']);
|
|
26
|
-
const runBrowser = Boolean(flags.browser);
|
|
27
|
-
const runNode = Boolean(flags.node);
|
|
28
|
-
const shouldRunBrowser = runBrowser || (!runBrowser && !runNode);
|
|
29
|
-
const shouldRunNode = runNode || (!runBrowser && !runNode);
|
|
30
|
-
const port = flags.port ? parseInt(flags.port, 10) : 3000;
|
|
31
|
-
// Parse delay (milliseconds). Non-numeric or missing => 0
|
|
32
|
-
const delayMs = Number.isFinite(parseInt(flags.delay, 10)) ? Math.max(0, parseInt(flags.delay, 10)) : 0;
|
|
33
|
-
|
|
34
|
-
/*
|
|
35
|
-
* Color Configuration
|
|
36
|
-
*/
|
|
37
|
-
const colors = {
|
|
38
|
-
reset: '\x1b[0m',
|
|
39
|
-
bright: '\x1b[1m',
|
|
40
|
-
green: '\x1b[32m',
|
|
41
|
-
red: '\x1b[31m',
|
|
42
|
-
cyan: '\x1b[36m',
|
|
43
|
-
magenta: '\x1b[35m',
|
|
44
|
-
gray: '\x1b[90m',
|
|
45
|
-
blue: '\x1b[34m'
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
/*
|
|
49
|
-
* Test Execution
|
|
50
|
-
*/
|
|
51
|
-
const { nodeResults, browserResults } = await runTestFiles({
|
|
52
|
-
suiteFilter,
|
|
53
|
-
testFilter,
|
|
54
|
-
shouldRunBrowser,
|
|
55
|
-
shouldRunNode,
|
|
56
|
-
showBrowser,
|
|
57
|
-
port,
|
|
58
|
-
logLevel,
|
|
59
|
-
delayMs,
|
|
60
|
-
onNodeTestStart: logLevel > LOG_LEVELS.MINIMAL ? (file) => {
|
|
61
|
-
console.log(`${colors.cyan}Running Node test: ${file}${colors.reset}`);
|
|
62
|
-
} : undefined,
|
|
63
|
-
onBrowserTestStart: logLevel > LOG_LEVELS.MINIMAL ? (file) => {
|
|
64
|
-
console.log(`${colors.magenta}Running Browser test: ${file}${colors.reset}`);
|
|
65
|
-
} : undefined
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
/*
|
|
69
|
-
* Results Display
|
|
70
|
-
*/
|
|
71
|
-
if(logLevel >= LOG_LEVELS.SILENT){
|
|
72
|
-
displayResults('Node', nodeResults, logLevel, colors);
|
|
73
|
-
displayResults('Browser', browserResults, logLevel, colors);
|
|
74
|
-
displaySummary(nodeResults, browserResults, colors);
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const displayResults = (type, results, logLevel, colors) => {
|
|
79
|
-
if(Object.keys(results).length === 0) return;
|
|
80
|
-
|
|
81
|
-
if(logLevel > LOG_LEVELS.MINIMAL){
|
|
82
|
-
console.log(`\n${colors.bright}=== ${type} Test Results ====${colors.reset}`);
|
|
83
|
-
}
|
|
84
|
-
let hasFailedTests = false;
|
|
85
|
-
if(logLevel === LOG_LEVELS.MINIMAL){
|
|
86
|
-
for(const [_, result] of Object.entries(results)){
|
|
87
|
-
for(const [_, testResult] of Object.entries(result.tests)){
|
|
88
|
-
if(!testResult.passed){
|
|
89
|
-
hasFailedTests = true;
|
|
90
|
-
break;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
if(hasFailedTests) break;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if(hasFailedTests){
|
|
97
|
-
console.log(`\n${colors.red}Failed ${type} Tests:${colors.reset}`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
for(const [file, result] of Object.entries(results)){
|
|
102
|
-
let fileHeaderShown = logLevel > LOG_LEVELS.MINIMAL;
|
|
103
|
-
|
|
104
|
-
if(logLevel >= LOG_LEVELS.VERBOSE && result.beforeAllLogs?.length){
|
|
105
|
-
if(!fileHeaderShown){
|
|
106
|
-
console.log(`\n${colors.bright}${file}${colors.reset}`);
|
|
107
|
-
fileHeaderShown = true;
|
|
108
|
-
}
|
|
109
|
-
console.log(`${colors.blue}${colors.bright}beforeAll${colors.reset}`);
|
|
110
|
-
result.beforeAllLogs.forEach(log => {
|
|
111
|
-
if(log.level <= logLevel){
|
|
112
|
-
console.log(` ${colors.blue}${log.message}${colors.reset}`);
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
for(const [testName, testResult] of Object.entries(result.tests)){
|
|
117
|
-
|
|
118
|
-
if(logLevel === LOG_LEVELS.MINIMAL && testResult.passed){
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
const passStatus = testResult.passed
|
|
122
|
-
? `${colors.green}PASS${colors.reset}`
|
|
123
|
-
: `${colors.red}FAIL${colors.reset}`;
|
|
124
|
-
|
|
125
|
-
if(!fileHeaderShown){
|
|
126
|
-
console.log(`\n${colors.bright}${file}${colors.reset}`);
|
|
127
|
-
fileHeaderShown = true;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if(logLevel >= LOG_LEVELS.MINIMAL){
|
|
131
|
-
console.log(` ${passStatus} ${testName}`);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if(logLevel >= LOG_LEVELS.VERBOSE || (!testResult.passed && logLevel >= LOG_LEVELS.NORMAL)){
|
|
135
|
-
if(testResult.logs && testResult.logs.length > 0){
|
|
136
|
-
if(logLevel >= LOG_LEVELS.VERBOSE){
|
|
137
|
-
console.log(` ${colors.cyan}--Test Logs--${colors.reset}`);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const logsToShow = testResult.logs.filter(log => {
|
|
141
|
-
if(logLevel >= LOG_LEVELS.VERBOSE) return true;
|
|
142
|
-
return log.type !== 'progress' && log.level <= logLevel;
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
logsToShow.forEach(log => {
|
|
146
|
-
let logColor = colors.gray;
|
|
147
|
-
if(log.type === 'fail') logColor = colors.red;
|
|
148
|
-
if(log.type === 'pass') logColor = colors.green;
|
|
149
|
-
|
|
150
|
-
console.log(` ${logColor}${log.message}${colors.reset}`);
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if(logLevel >= LOG_LEVELS.VERBOSE && result.afterAllLogs?.length){
|
|
157
|
-
if(!fileHeaderShown){
|
|
158
|
-
console.log(`\n${colors.bright}${file}${colors.reset}`);
|
|
159
|
-
fileHeaderShown = true;
|
|
160
|
-
}
|
|
161
|
-
console.log(`${colors.blue}${colors.bright}afterAll${colors.reset}`);
|
|
162
|
-
result.afterAllLogs.forEach(log => {
|
|
163
|
-
if(log.level <= logLevel){
|
|
164
|
-
console.log(` ${colors.blue}${log.message}${colors.reset}`);
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
const displaySummary = (nodeResults, browserResults, colors) => {
|
|
172
|
-
let totalTests = 0;
|
|
173
|
-
let passedTests = 0;
|
|
174
|
-
let failedTests = 0;
|
|
175
|
-
Object.values(nodeResults).forEach(result => {
|
|
176
|
-
Object.values(result.tests || {}).forEach(test => {
|
|
177
|
-
totalTests++;
|
|
178
|
-
if(test.passed) passedTests++;
|
|
179
|
-
else failedTests++;
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
Object.values(browserResults).forEach(result => {
|
|
183
|
-
Object.values(result.tests || {}).forEach(test => {
|
|
184
|
-
totalTests++;
|
|
185
|
-
if(test.passed) passedTests++;
|
|
186
|
-
else failedTests++;
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
console.log(`\n${colors.bright}=== Test Summary ====${colors.reset}`);
|
|
190
|
-
console.log(`${colors.bright}Total Tests:${colors.reset} ${totalTests}`);
|
|
191
|
-
console.log(`${colors.green}Passed:${colors.reset} ${passedTests}`);
|
|
192
|
-
console.log(`${colors.red}Failed:${colors.reset} ${failedTests}`);
|
|
193
|
-
if(failedTests === 0){
|
|
194
|
-
console.log(`\n${colors.green}${colors.bright}All tests passed!${colors.reset}`);
|
|
195
|
-
} else {
|
|
196
|
-
console.log(`\n${colors.red}${colors.bright}Some tests failed. See details above.${colors.reset}`);
|
|
197
|
-
}
|
|
198
|
-
};
|
|
1
|
+
import runTestFiles from './runTestFiles.js';
|
|
2
|
+
import LOG_LEVELS from './utils/logLevels.js';
|
|
3
|
+
|
|
4
|
+
export default async (flags, args) => {
|
|
5
|
+
const [suiteFilter, testFilter] = args;
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* Log Level Configuration
|
|
9
|
+
*/
|
|
10
|
+
let logLevel = LOG_LEVELS.NORMAL;
|
|
11
|
+
if (typeof flags.logLevel === 'number' && Number.isFinite(flags.logLevel)) {
|
|
12
|
+
const lvl = Math.max(0, Math.min(4, flags.logLevel));
|
|
13
|
+
logLevel = lvl;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if(logLevel >= LOG_LEVELS.DEBUG){
|
|
17
|
+
console.log('\x1b[90m[Debug] Flags:', flags);
|
|
18
|
+
console.log('[Debug] Args:', args);
|
|
19
|
+
console.log(`[Debug] Log level: ${logLevel}\x1b[0m`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/*
|
|
23
|
+
* Test Configuration
|
|
24
|
+
*/
|
|
25
|
+
const showBrowser = Boolean(flags['show-browser']);
|
|
26
|
+
const runBrowser = Boolean(flags.browser);
|
|
27
|
+
const runNode = Boolean(flags.node);
|
|
28
|
+
const shouldRunBrowser = runBrowser || (!runBrowser && !runNode);
|
|
29
|
+
const shouldRunNode = runNode || (!runBrowser && !runNode);
|
|
30
|
+
const port = flags.port ? parseInt(flags.port, 10) : 3000;
|
|
31
|
+
// Parse delay (milliseconds). Non-numeric or missing => 0
|
|
32
|
+
const delayMs = Number.isFinite(parseInt(flags.delay, 10)) ? Math.max(0, parseInt(flags.delay, 10)) : 0;
|
|
33
|
+
|
|
34
|
+
/*
|
|
35
|
+
* Color Configuration
|
|
36
|
+
*/
|
|
37
|
+
const colors = {
|
|
38
|
+
reset: '\x1b[0m',
|
|
39
|
+
bright: '\x1b[1m',
|
|
40
|
+
green: '\x1b[32m',
|
|
41
|
+
red: '\x1b[31m',
|
|
42
|
+
cyan: '\x1b[36m',
|
|
43
|
+
magenta: '\x1b[35m',
|
|
44
|
+
gray: '\x1b[90m',
|
|
45
|
+
blue: '\x1b[34m'
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/*
|
|
49
|
+
* Test Execution
|
|
50
|
+
*/
|
|
51
|
+
const { nodeResults, browserResults } = await runTestFiles({
|
|
52
|
+
suiteFilter,
|
|
53
|
+
testFilter,
|
|
54
|
+
shouldRunBrowser,
|
|
55
|
+
shouldRunNode,
|
|
56
|
+
showBrowser,
|
|
57
|
+
port,
|
|
58
|
+
logLevel,
|
|
59
|
+
delayMs,
|
|
60
|
+
onNodeTestStart: logLevel > LOG_LEVELS.MINIMAL ? (file) => {
|
|
61
|
+
console.log(`${colors.cyan}Running Node test: ${file}${colors.reset}`);
|
|
62
|
+
} : undefined,
|
|
63
|
+
onBrowserTestStart: logLevel > LOG_LEVELS.MINIMAL ? (file) => {
|
|
64
|
+
console.log(`${colors.magenta}Running Browser test: ${file}${colors.reset}`);
|
|
65
|
+
} : undefined
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/*
|
|
69
|
+
* Results Display
|
|
70
|
+
*/
|
|
71
|
+
if(logLevel >= LOG_LEVELS.SILENT){
|
|
72
|
+
displayResults('Node', nodeResults, logLevel, colors);
|
|
73
|
+
displayResults('Browser', browserResults, logLevel, colors);
|
|
74
|
+
displaySummary(nodeResults, browserResults, colors);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const displayResults = (type, results, logLevel, colors) => {
|
|
79
|
+
if(Object.keys(results).length === 0) return;
|
|
80
|
+
|
|
81
|
+
if(logLevel > LOG_LEVELS.MINIMAL){
|
|
82
|
+
console.log(`\n${colors.bright}=== ${type} Test Results ====${colors.reset}`);
|
|
83
|
+
}
|
|
84
|
+
let hasFailedTests = false;
|
|
85
|
+
if(logLevel === LOG_LEVELS.MINIMAL){
|
|
86
|
+
for(const [_, result] of Object.entries(results)){
|
|
87
|
+
for(const [_, testResult] of Object.entries(result.tests)){
|
|
88
|
+
if(!testResult.passed){
|
|
89
|
+
hasFailedTests = true;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if(hasFailedTests) break;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if(hasFailedTests){
|
|
97
|
+
console.log(`\n${colors.red}Failed ${type} Tests:${colors.reset}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
for(const [file, result] of Object.entries(results)){
|
|
102
|
+
let fileHeaderShown = logLevel > LOG_LEVELS.MINIMAL;
|
|
103
|
+
|
|
104
|
+
if(logLevel >= LOG_LEVELS.VERBOSE && result.beforeAllLogs?.length){
|
|
105
|
+
if(!fileHeaderShown){
|
|
106
|
+
console.log(`\n${colors.bright}${file}${colors.reset}`);
|
|
107
|
+
fileHeaderShown = true;
|
|
108
|
+
}
|
|
109
|
+
console.log(`${colors.blue}${colors.bright}beforeAll${colors.reset}`);
|
|
110
|
+
result.beforeAllLogs.forEach(log => {
|
|
111
|
+
if(log.level <= logLevel){
|
|
112
|
+
console.log(` ${colors.blue}${log.message}${colors.reset}`);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
for(const [testName, testResult] of Object.entries(result.tests)){
|
|
117
|
+
|
|
118
|
+
if(logLevel === LOG_LEVELS.MINIMAL && testResult.passed){
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const passStatus = testResult.passed
|
|
122
|
+
? `${colors.green}PASS${colors.reset}`
|
|
123
|
+
: `${colors.red}FAIL${colors.reset}`;
|
|
124
|
+
|
|
125
|
+
if(!fileHeaderShown){
|
|
126
|
+
console.log(`\n${colors.bright}${file}${colors.reset}`);
|
|
127
|
+
fileHeaderShown = true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if(logLevel >= LOG_LEVELS.MINIMAL){
|
|
131
|
+
console.log(` ${passStatus} ${testName}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if(logLevel >= LOG_LEVELS.VERBOSE || (!testResult.passed && logLevel >= LOG_LEVELS.NORMAL)){
|
|
135
|
+
if(testResult.logs && testResult.logs.length > 0){
|
|
136
|
+
if(logLevel >= LOG_LEVELS.VERBOSE){
|
|
137
|
+
console.log(` ${colors.cyan}--Test Logs--${colors.reset}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const logsToShow = testResult.logs.filter(log => {
|
|
141
|
+
if(logLevel >= LOG_LEVELS.VERBOSE) return true;
|
|
142
|
+
return log.type !== 'progress' && log.level <= logLevel;
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
logsToShow.forEach(log => {
|
|
146
|
+
let logColor = colors.gray;
|
|
147
|
+
if(log.type === 'fail') logColor = colors.red;
|
|
148
|
+
if(log.type === 'pass') logColor = colors.green;
|
|
149
|
+
|
|
150
|
+
console.log(` ${logColor}${log.message}${colors.reset}`);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if(logLevel >= LOG_LEVELS.VERBOSE && result.afterAllLogs?.length){
|
|
157
|
+
if(!fileHeaderShown){
|
|
158
|
+
console.log(`\n${colors.bright}${file}${colors.reset}`);
|
|
159
|
+
fileHeaderShown = true;
|
|
160
|
+
}
|
|
161
|
+
console.log(`${colors.blue}${colors.bright}afterAll${colors.reset}`);
|
|
162
|
+
result.afterAllLogs.forEach(log => {
|
|
163
|
+
if(log.level <= logLevel){
|
|
164
|
+
console.log(` ${colors.blue}${log.message}${colors.reset}`);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const displaySummary = (nodeResults, browserResults, colors) => {
|
|
172
|
+
let totalTests = 0;
|
|
173
|
+
let passedTests = 0;
|
|
174
|
+
let failedTests = 0;
|
|
175
|
+
Object.values(nodeResults).forEach(result => {
|
|
176
|
+
Object.values(result.tests || {}).forEach(test => {
|
|
177
|
+
totalTests++;
|
|
178
|
+
if(test.passed) passedTests++;
|
|
179
|
+
else failedTests++;
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
Object.values(browserResults).forEach(result => {
|
|
183
|
+
Object.values(result.tests || {}).forEach(test => {
|
|
184
|
+
totalTests++;
|
|
185
|
+
if(test.passed) passedTests++;
|
|
186
|
+
else failedTests++;
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
console.log(`\n${colors.bright}=== Test Summary ====${colors.reset}`);
|
|
190
|
+
console.log(`${colors.bright}Total Tests:${colors.reset} ${totalTests}`);
|
|
191
|
+
console.log(`${colors.green}Passed:${colors.reset} ${passedTests}`);
|
|
192
|
+
console.log(`${colors.red}Failed:${colors.reset} ${failedTests}`);
|
|
193
|
+
if(failedTests === 0){
|
|
194
|
+
console.log(`\n${colors.green}${colors.bright}All tests passed!${colors.reset}`);
|
|
195
|
+
} else {
|
|
196
|
+
console.log(`\n${colors.red}${colors.bright}Some tests failed. See details above.${colors.reset}`);
|
|
197
|
+
}
|
|
198
|
+
};
|
package/src/gui.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import http from 'http';
|
|
2
2
|
import { readFile } from 'fs/promises';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
+
import { createRequire } from 'module';
|
|
4
5
|
import path from 'path';
|
|
5
6
|
import { exec } from 'child_process';
|
|
6
7
|
import { platform } from 'os';
|
|
@@ -9,6 +10,7 @@ import runTestFiles from './runTestFiles.js';
|
|
|
9
10
|
|
|
10
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
12
|
const __dirname = path.dirname(__filename);
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
12
14
|
|
|
13
15
|
export default async (flags, args) => {
|
|
14
16
|
const server = http.createServer(async (req, res) => {
|
|
@@ -22,8 +24,19 @@ export default async (flags, args) => {
|
|
|
22
24
|
Serve kempo.css
|
|
23
25
|
*/
|
|
24
26
|
if(basePath === '/kempo.css'){
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
try {
|
|
28
|
+
// Try to resolve kempo-css package from current location
|
|
29
|
+
// This will work whether this package is standalone or installed as a dependency
|
|
30
|
+
const kempoPackagePath = path.dirname(require.resolve('kempo-css/package.json', { paths: [__dirname] }));
|
|
31
|
+
const kempoCssPath = path.join(kempoPackagePath, 'dist/kempo.min.css');
|
|
32
|
+
|
|
33
|
+
res.writeHead(200, { 'Content-Type': 'text/css' });
|
|
34
|
+
res.end(await readFile(kempoCssPath, 'utf8'));
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error('Error serving kempo.css:', error);
|
|
37
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
38
|
+
res.end('kempo-css not found');
|
|
39
|
+
}
|
|
27
40
|
}
|
|
28
41
|
|
|
29
42
|
/*
|