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.
Files changed (54) hide show
  1. package/.github/copilot-instructions.md +105 -105
  2. package/.github/workflows/publish-npm.yml +29 -0
  3. package/CONTRIBUTING.md +107 -107
  4. package/gui/components/Collapsible.js +54 -54
  5. package/gui/components/Icon.js +151 -151
  6. package/gui/components/Logs.js +73 -73
  7. package/gui/components/SettingCheckbox.js +42 -42
  8. package/gui/components/SettingNumber.js +77 -77
  9. package/gui/components/SettingSelect.js +67 -67
  10. package/gui/components/TestFramework.js +378 -378
  11. package/gui/components/TestSuite.js +181 -181
  12. package/gui/components/TestSummary.js +189 -189
  13. package/gui/components/Theme.js +40 -40
  14. package/gui/components/settingsStore.js +46 -46
  15. package/gui/icons/fail.svg +1 -1
  16. package/gui/icons/pass.svg +1 -1
  17. package/gui/icons/running.svg +1 -1
  18. package/gui/icons/settings.svg +1 -1
  19. package/package.json +6 -1
  20. package/src/browserTestServer.js +115 -115
  21. package/src/cli.js +198 -198
  22. package/src/gui.js +15 -2
  23. package/src/runBrowserTests.js +87 -87
  24. package/src/runTestFiles.js +94 -94
  25. package/src/runTests.js +83 -83
  26. package/src/utils/logLevels.js +7 -7
  27. package/test.html +30 -30
  28. package/tests/Counter.js +33 -33
  29. package/tests/cli-flags.node-test.js +54 -54
  30. package/tests/cli-help.node-test.js +61 -61
  31. package/tests/cli-loglevel.node-test.js +40 -40
  32. package/tests/collapsible.browser-test.js +49 -49
  33. package/tests/counter.browser-test.js +140 -140
  34. package/tests/example.node-test.js +103 -103
  35. package/tests/fibonacci.js +92 -92
  36. package/tests/fibonacci.test.js +285 -285
  37. package/tests/icon.browser-test.js +54 -54
  38. package/tests/logs.browser-test.js +47 -47
  39. package/tests/setting-checkbox.browser-test.js +48 -48
  40. package/tests/setting-number.browser-test.js +54 -54
  41. package/tests/setting-select.browser-test.js +47 -47
  42. package/tests/settings-store.browser-test.js +26 -26
  43. package/tests/src-browserTestServer.node-test.js +47 -47
  44. package/tests/src-cli.node-test.js +32 -32
  45. package/tests/src-findTests.node-test.js +41 -41
  46. package/tests/src-logLevels.node-test.js +29 -29
  47. package/tests/src-runBrowserTests.node-test.js +41 -41
  48. package/tests/src-runTestFiles.node-test.js +42 -42
  49. package/tests/src-runTests.node-test.js +56 -56
  50. package/tests/test-framework.browser-test.js +65 -65
  51. package/tests/test-summary.browser-test.js +78 -78
  52. package/tests/test.browser-test.js +56 -56
  53. package/tests/testfile.browser-test.js +60 -60
  54. package/tests/theme.browser-test.js +38 -38
@@ -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
- res.writeHead(200, { 'Content-Type': 'text/css' });
26
- res.end(await readFile(path.join(__dirname, '../node_modules/kempo-css/dist/kempo.min.css'), 'utf8'));
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
  /*