kempo-testing-framework 1.2.3 → 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 (56) 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/Test.js +1 -1
  11. package/gui/components/TestFramework.js +378 -378
  12. package/gui/components/TestSuite.js +181 -181
  13. package/gui/components/TestSummary.js +189 -189
  14. package/gui/components/Theme.js +40 -40
  15. package/gui/components/settingsStore.js +46 -46
  16. package/gui/icons/fail.svg +1 -1
  17. package/gui/icons/pass.svg +1 -1
  18. package/gui/icons/running.svg +1 -1
  19. package/gui/icons/settings.svg +1 -1
  20. package/gui/index.html +1 -1
  21. package/package.json +7 -2
  22. package/src/browserTestServer.js +115 -115
  23. package/src/cli.js +198 -198
  24. package/src/gui.js +17 -4
  25. package/src/runBrowserTests.js +87 -87
  26. package/src/runTestFiles.js +94 -94
  27. package/src/runTests.js +83 -83
  28. package/src/utils/logLevels.js +7 -7
  29. package/test.html +30 -30
  30. package/tests/Counter.js +33 -33
  31. package/tests/cli-flags.node-test.js +54 -54
  32. package/tests/cli-help.node-test.js +61 -61
  33. package/tests/cli-loglevel.node-test.js +40 -40
  34. package/tests/collapsible.browser-test.js +49 -49
  35. package/tests/counter.browser-test.js +140 -140
  36. package/tests/example.node-test.js +103 -103
  37. package/tests/fibonacci.js +92 -92
  38. package/tests/fibonacci.test.js +285 -285
  39. package/tests/icon.browser-test.js +54 -54
  40. package/tests/logs.browser-test.js +47 -47
  41. package/tests/setting-checkbox.browser-test.js +48 -48
  42. package/tests/setting-number.browser-test.js +54 -54
  43. package/tests/setting-select.browser-test.js +47 -47
  44. package/tests/settings-store.browser-test.js +26 -26
  45. package/tests/src-browserTestServer.node-test.js +47 -47
  46. package/tests/src-cli.node-test.js +32 -32
  47. package/tests/src-findTests.node-test.js +41 -41
  48. package/tests/src-logLevels.node-test.js +29 -29
  49. package/tests/src-runBrowserTests.node-test.js +41 -41
  50. package/tests/src-runTestFiles.node-test.js +42 -42
  51. package/tests/src-runTests.node-test.js +56 -56
  52. package/tests/test-framework.browser-test.js +65 -65
  53. package/tests/test-summary.browser-test.js +78 -78
  54. package/tests/test.browser-test.js +56 -56
  55. package/tests/testfile.browser-test.js +60 -60
  56. 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 essential.css like the GUI server does
31
- else if (basePath === '/essential.css') {
32
- try {
33
- const cssPath = path.join(__dirname, '../node_modules/essentialcss/dist/essential.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 essential.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
+ };