jsgui3-server 0.0.138 ā 0.0.140
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/AGENTS.md +87 -0
- package/README.md +12 -0
- package/docs/GUIDE_TO_AGENTIC_WORKFLOWS_BY_GROK.md +19 -0
- package/docs/advanced-usage-examples.md +1360 -0
- package/docs/agent-development-guide.md +386 -0
- package/docs/api-reference.md +916 -0
- package/docs/broken-functionality-tracker.md +285 -0
- package/docs/bundling-system-deep-dive.md +525 -0
- package/docs/cli-reference.md +393 -0
- package/docs/comprehensive-documentation.md +1403 -0
- package/docs/configuration-reference.md +808 -0
- package/docs/controls-development.md +859 -0
- package/docs/documentation-review/CURRENT_REVIEW.md +95 -0
- package/docs/function-publishers-json-apis.md +847 -0
- package/docs/getting-started-with-json.md +518 -0
- package/docs/minification-compression-sourcemaps-status.md +482 -0
- package/docs/minification-compression-sourcemaps-test-results.md +205 -0
- package/docs/publishers-guide.md +313 -0
- package/docs/resources-guide.md +615 -0
- package/docs/serve-helpers.md +406 -0
- package/docs/simple-server-api-design.md +13 -0
- package/docs/system-architecture.md +275 -0
- package/docs/troubleshooting.md +698 -0
- package/examples/json/README.md +115 -0
- package/examples/json/basic-api/README.md +345 -0
- package/examples/json/basic-api/server.js +199 -0
- package/examples/json/simple-api/README.md +125 -0
- package/examples/json/simple-api/diagnostic-report.json +73 -0
- package/examples/json/simple-api/diagnostic-test.js +433 -0
- package/examples/json/simple-api/server-debug.md +58 -0
- package/examples/json/simple-api/server.js +91 -0
- package/examples/json/simple-api/test.js +215 -0
- package/http/responders/static/Static_Route_HTTP_Responder.js +1 -2
- package/package.json +19 -8
- package/publishers/helpers/assigners/static-compressed-response-buffers/Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner.js +65 -12
- package/publishers/helpers/preparers/static/bundle/Static_Routes_Responses_Webpage_Bundle_Preparer.js +6 -1
- package/publishers/http-function-publisher.js +59 -38
- package/publishers/http-webpage-publisher.js +48 -1
- package/resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild.js +38 -146
- package/resources/processors/bundlers/js/esbuild/Core_JS_Non_Minifying_Bundler_Using_ESBuild.js +54 -5
- package/resources/processors/bundlers/js/esbuild/Core_JS_Single_File_Minifying_Bundler_Using_ESBuild.js +36 -4
- package/serve-factory.js +36 -9
- package/server.js +10 -4
- package/test-report.json +0 -0
- package/tests/README.md +250 -0
- package/tests/assigners.test.js +316 -0
- package/tests/bundlers.test.js +329 -0
- package/tests/configuration-validation.test.js +530 -0
- package/tests/content-analysis.test.js +641 -0
- package/tests/end-to-end.test.js +496 -0
- package/tests/error-handling.test.js +746 -0
- package/tests/performance.test.js +653 -0
- package/tests/publishers.test.js +395 -0
- package/tests/temp_invalid.js +7 -0
- package/tests/temp_invalid_utf8.js +1 -0
- package/tests/temp_malformed.js +10 -0
- package/tests/test-runner.js +261 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const { describe, it, beforeEach, afterEach } = require('mocha');
|
|
3
|
+
|
|
4
|
+
// Import publisher classes
|
|
5
|
+
const HTTP_Webpage_Publisher = require('../publishers/http-webpage-publisher');
|
|
6
|
+
|
|
7
|
+
describe('Publisher Component Isolation Tests', function() {
|
|
8
|
+
this.timeout(15000); // Increase timeout for publisher operations
|
|
9
|
+
|
|
10
|
+
let mockControl;
|
|
11
|
+
let mockWebpage;
|
|
12
|
+
|
|
13
|
+
beforeEach(function() {
|
|
14
|
+
// Create mock control class
|
|
15
|
+
mockControl = class MockControl {
|
|
16
|
+
constructor(spec) {
|
|
17
|
+
this.context = spec.context;
|
|
18
|
+
this.head = {
|
|
19
|
+
add: function(element) {
|
|
20
|
+
// Mock add method
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
this.body = {
|
|
24
|
+
add: function(element) {
|
|
25
|
+
// Mock add method
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
active() {
|
|
31
|
+
// Mock activation
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
all_html_render() {
|
|
35
|
+
return Promise.resolve(`<!DOCTYPE html>
|
|
36
|
+
<html>
|
|
37
|
+
<head><title>Test Page</title></head>
|
|
38
|
+
<body>
|
|
39
|
+
<div id="control-root">
|
|
40
|
+
<h1>Test Control</h1>
|
|
41
|
+
<p>This is a test control for publisher testing.</p>
|
|
42
|
+
</div>
|
|
43
|
+
<script src="/js/js.js"></script>
|
|
44
|
+
</body>
|
|
45
|
+
</html>`);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Create mock webpage
|
|
50
|
+
mockWebpage = {
|
|
51
|
+
content: mockControl
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('HTTP_Webpage_Publisher', function() {
|
|
56
|
+
it('should initialize with default configuration', function() {
|
|
57
|
+
const publisher = new HTTP_Webpage_Publisher({
|
|
58
|
+
webpage: mockWebpage
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
assert(publisher.webpage === mockWebpage, 'Should store webpage reference');
|
|
62
|
+
assert(publisher.bundler_config, 'Should initialize bundler config');
|
|
63
|
+
assert(publisher.static_routes_responses_webpage_bundle_preparer,
|
|
64
|
+
'Should create bundle preparer');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should accept and validate bundler configuration', function() {
|
|
68
|
+
const validConfig = {
|
|
69
|
+
webpage: mockWebpage,
|
|
70
|
+
bundler: {
|
|
71
|
+
compression: {
|
|
72
|
+
enabled: true,
|
|
73
|
+
algorithms: ['gzip', 'br'],
|
|
74
|
+
threshold: 1024
|
|
75
|
+
},
|
|
76
|
+
minify: {
|
|
77
|
+
enabled: true,
|
|
78
|
+
level: 'normal'
|
|
79
|
+
},
|
|
80
|
+
sourcemaps: {
|
|
81
|
+
enabled: true,
|
|
82
|
+
format: 'inline'
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const publisher = new HTTP_Webpage_Publisher(validConfig);
|
|
88
|
+
assert.deepStrictEqual(publisher.bundler_config, validConfig.bundler,
|
|
89
|
+
'Should store valid bundler configuration');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should validate compression configuration - boolean enabled', function() {
|
|
93
|
+
assert.throws(() => {
|
|
94
|
+
new HTTP_Webpage_Publisher({
|
|
95
|
+
webpage: mockWebpage,
|
|
96
|
+
bundler: {
|
|
97
|
+
compression: {
|
|
98
|
+
enabled: 'invalid' // Should be boolean
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}, /bundler\.compression\.enabled must be a boolean/);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should validate compression configuration - array algorithms', function() {
|
|
106
|
+
assert.throws(() => {
|
|
107
|
+
new HTTP_Webpage_Publisher({
|
|
108
|
+
webpage: mockWebpage,
|
|
109
|
+
bundler: {
|
|
110
|
+
compression: {
|
|
111
|
+
algorithms: 'invalid' // Should be array
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}, /bundler\.compression\.algorithms must be an array/);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should validate compression configuration - valid algorithms', function() {
|
|
119
|
+
assert.throws(() => {
|
|
120
|
+
new HTTP_Webpage_Publisher({
|
|
121
|
+
webpage: mockWebpage,
|
|
122
|
+
bundler: {
|
|
123
|
+
compression: {
|
|
124
|
+
algorithms: ['gzip', 'invalid_algorithm']
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}, /Invalid compression algorithm: invalid_algorithm/);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should validate compression configuration - threshold number', function() {
|
|
132
|
+
assert.throws(() => {
|
|
133
|
+
new HTTP_Webpage_Publisher({
|
|
134
|
+
webpage: mockWebpage,
|
|
135
|
+
bundler: {
|
|
136
|
+
compression: {
|
|
137
|
+
threshold: 'invalid' // Should be number
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}, /bundler\.compression\.threshold must be a non-negative number/);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should validate compression configuration - negative threshold', function() {
|
|
145
|
+
assert.throws(() => {
|
|
146
|
+
new HTTP_Webpage_Publisher({
|
|
147
|
+
webpage: mockWebpage,
|
|
148
|
+
bundler: {
|
|
149
|
+
compression: {
|
|
150
|
+
threshold: -1 // Should be non-negative
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}, /bundler\.compression\.threshold must be a non-negative number/);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should accept valid compression algorithms', function() {
|
|
158
|
+
const publisher = new HTTP_Webpage_Publisher({
|
|
159
|
+
webpage: mockWebpage,
|
|
160
|
+
bundler: {
|
|
161
|
+
compression: {
|
|
162
|
+
algorithms: ['gzip', 'br']
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
assert.deepStrictEqual(publisher.bundler_config.compression.algorithms, ['gzip', 'br']);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should initialize bundle preparer with configuration', function() {
|
|
171
|
+
const config = {
|
|
172
|
+
webpage: mockWebpage,
|
|
173
|
+
bundler: {
|
|
174
|
+
compression: {
|
|
175
|
+
enabled: true,
|
|
176
|
+
algorithms: ['gzip']
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const publisher = new HTTP_Webpage_Publisher(config);
|
|
182
|
+
|
|
183
|
+
// Verify preparer was created with config
|
|
184
|
+
assert(publisher.static_routes_responses_webpage_bundle_preparer,
|
|
185
|
+
'Should create bundle preparer');
|
|
186
|
+
assert.deepStrictEqual(
|
|
187
|
+
publisher.static_routes_responses_webpage_bundle_preparer.bundler_config,
|
|
188
|
+
config.bundler,
|
|
189
|
+
'Preparer should receive bundler config'
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should handle missing bundler configuration gracefully', function() {
|
|
194
|
+
const publisher = new HTTP_Webpage_Publisher({
|
|
195
|
+
webpage: mockWebpage
|
|
196
|
+
// No bundler config
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
assert.deepStrictEqual(publisher.bundler_config, {},
|
|
200
|
+
'Should default to empty bundler config');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should handle missing compression configuration gracefully', function() {
|
|
204
|
+
const publisher = new HTTP_Webpage_Publisher({
|
|
205
|
+
webpage: mockWebpage,
|
|
206
|
+
bundler: {
|
|
207
|
+
// Empty bundler config
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
assert.deepStrictEqual(publisher.bundler_config, {});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should handle partial bundler configuration', function() {
|
|
215
|
+
const partialConfig = {
|
|
216
|
+
webpage: mockWebpage,
|
|
217
|
+
bundler: {
|
|
218
|
+
compression: {
|
|
219
|
+
enabled: true
|
|
220
|
+
// Missing other compression options
|
|
221
|
+
}
|
|
222
|
+
// Missing minify and sourcemaps
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const publisher = new HTTP_Webpage_Publisher(partialConfig);
|
|
227
|
+
|
|
228
|
+
assert.strictEqual(publisher.bundler_config.compression.enabled, true);
|
|
229
|
+
assert(!publisher.bundler_config.minify, 'Should not have minify config');
|
|
230
|
+
assert(!publisher.bundler_config.sourcemaps, 'Should not have sourcemaps config');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should handle handle_http method', function() {
|
|
234
|
+
const publisher = new HTTP_Webpage_Publisher({
|
|
235
|
+
webpage: mockWebpage
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Mock request and response
|
|
239
|
+
const mockReq = {
|
|
240
|
+
url: '/test',
|
|
241
|
+
headers: {}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
let responseWritten = false;
|
|
245
|
+
let statusCode = null;
|
|
246
|
+
let headers = {};
|
|
247
|
+
|
|
248
|
+
const mockRes = {
|
|
249
|
+
writeHead: function(code, h) {
|
|
250
|
+
statusCode = code;
|
|
251
|
+
headers = h;
|
|
252
|
+
},
|
|
253
|
+
end: function() {
|
|
254
|
+
responseWritten = true;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
publisher.handle_http(mockReq, mockRes);
|
|
259
|
+
|
|
260
|
+
// Note: This test may need adjustment based on actual handle_http implementation
|
|
261
|
+
// For now, just verify it doesn't crash
|
|
262
|
+
assert(responseWritten || statusCode, 'Should handle HTTP request');
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('should pass configuration to bundle preparer correctly', function() {
|
|
266
|
+
const complexConfig = {
|
|
267
|
+
webpage: mockWebpage,
|
|
268
|
+
bundler: {
|
|
269
|
+
minify: {
|
|
270
|
+
enabled: true,
|
|
271
|
+
level: 'aggressive',
|
|
272
|
+
options: {
|
|
273
|
+
drop_console: true
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
sourcemaps: {
|
|
277
|
+
enabled: true,
|
|
278
|
+
format: 'inline',
|
|
279
|
+
includeInProduction: false
|
|
280
|
+
},
|
|
281
|
+
compression: {
|
|
282
|
+
enabled: true,
|
|
283
|
+
algorithms: ['br'],
|
|
284
|
+
brotli: { quality: 6 },
|
|
285
|
+
threshold: 2048
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const publisher = new HTTP_Webpage_Publisher(complexConfig);
|
|
291
|
+
|
|
292
|
+
// Verify complex configuration is passed through
|
|
293
|
+
assert.deepStrictEqual(publisher.bundler_config, complexConfig.bundler);
|
|
294
|
+
assert.strictEqual(publisher.static_routes_responses_webpage_bundle_preparer.bundler_config,
|
|
295
|
+
complexConfig.bundler);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('should handle configuration inheritance and defaults', function() {
|
|
299
|
+
// Test that defaults are applied when partial config is provided
|
|
300
|
+
const partialConfig = {
|
|
301
|
+
webpage: mockWebpage,
|
|
302
|
+
bundler: {
|
|
303
|
+
compression: {
|
|
304
|
+
// Only specify some options
|
|
305
|
+
enabled: true
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const publisher = new HTTP_Webpage_Publisher(partialConfig);
|
|
311
|
+
|
|
312
|
+
// Should have the specified option
|
|
313
|
+
assert.strictEqual(publisher.bundler_config.compression.enabled, true);
|
|
314
|
+
|
|
315
|
+
// Should not have unspecified options (they will be undefined)
|
|
316
|
+
assert(!publisher.bundler_config.compression.algorithms,
|
|
317
|
+
'Should not set default algorithms when not specified');
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
describe('Configuration Validation Edge Cases', function() {
|
|
322
|
+
it('should handle null/undefined webpage', function() {
|
|
323
|
+
// This might be allowed or not depending on implementation
|
|
324
|
+
const publisher = new HTTP_Webpage_Publisher({
|
|
325
|
+
// No webpage
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
assert(!publisher.webpage, 'Should handle missing webpage');
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('should handle empty bundler config object', function() {
|
|
332
|
+
const publisher = new HTTP_Webpage_Publisher({
|
|
333
|
+
webpage: mockWebpage,
|
|
334
|
+
bundler: {}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
assert.deepStrictEqual(publisher.bundler_config, {});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('should handle deeply nested configuration', function() {
|
|
341
|
+
const deepConfig = {
|
|
342
|
+
webpage: mockWebpage,
|
|
343
|
+
bundler: {
|
|
344
|
+
minify: {
|
|
345
|
+
options: {
|
|
346
|
+
compress: {
|
|
347
|
+
drop_console: true,
|
|
348
|
+
drop_debugger: true
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const publisher = new HTTP_Webpage_Publisher(deepConfig);
|
|
356
|
+
|
|
357
|
+
assert.strictEqual(
|
|
358
|
+
publisher.bundler_config.minify.options.compress.drop_console,
|
|
359
|
+
true
|
|
360
|
+
);
|
|
361
|
+
assert.strictEqual(
|
|
362
|
+
publisher.bundler_config.minify.options.compress.drop_debugger,
|
|
363
|
+
true
|
|
364
|
+
);
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
describe('Integration with Static_Routes_Responses_Webpage_Bundle_Preparer', function() {
|
|
369
|
+
it('should create preparer with correct configuration', function() {
|
|
370
|
+
const config = {
|
|
371
|
+
webpage: mockWebpage,
|
|
372
|
+
bundler: {
|
|
373
|
+
compression: {
|
|
374
|
+
enabled: true,
|
|
375
|
+
algorithms: ['gzip']
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
const publisher = new HTTP_Webpage_Publisher(config);
|
|
381
|
+
|
|
382
|
+
const preparer = publisher.static_routes_responses_webpage_bundle_preparer;
|
|
383
|
+
|
|
384
|
+
// Verify preparer has the config
|
|
385
|
+
assert.strictEqual(preparer.bundler_config, config.bundler);
|
|
386
|
+
|
|
387
|
+
// Verify preparer creates compressed response buffers assigner with config
|
|
388
|
+
assert(preparer.compressed_response_buffers_assigner);
|
|
389
|
+
assert.strictEqual(
|
|
390
|
+
preparer.compressed_response_buffers_assigner.compression_config,
|
|
391
|
+
config.bundler.compression
|
|
392
|
+
);
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
���
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Comprehensive Test Runner for JSGUI3 Minification, Compression, and Sourcemaps
|
|
5
|
+
*
|
|
6
|
+
* This test runner executes all test suites and provides detailed reporting
|
|
7
|
+
* for the minification, compression, and sourcemap features.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { spawn } = require('child_process');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const fs = require('fs').promises;
|
|
13
|
+
|
|
14
|
+
class TestRunner {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.testResults = {
|
|
17
|
+
total: 0,
|
|
18
|
+
passed: 0,
|
|
19
|
+
failed: 0,
|
|
20
|
+
skipped: 0,
|
|
21
|
+
duration: 0,
|
|
22
|
+
suites: []
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
this.testFiles = [
|
|
26
|
+
'bundlers.test.js',
|
|
27
|
+
'assigners.test.js',
|
|
28
|
+
'publishers.test.js',
|
|
29
|
+
'configuration-validation.test.js',
|
|
30
|
+
'end-to-end.test.js',
|
|
31
|
+
'content-analysis.test.js',
|
|
32
|
+
'performance.test.js',
|
|
33
|
+
'error-handling.test.js'
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async runAllTests() {
|
|
38
|
+
console.log('š Starting JSGUI3 Minification, Compression & Sourcemaps Test Suite\n');
|
|
39
|
+
console.log('=' .repeat(80));
|
|
40
|
+
|
|
41
|
+
const startTime = Date.now();
|
|
42
|
+
|
|
43
|
+
for (const testFile of this.testFiles) {
|
|
44
|
+
await this.runTestFile(testFile);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this.testResults.duration = Date.now() - startTime;
|
|
48
|
+
|
|
49
|
+
this.printSummary();
|
|
50
|
+
this.generateReport();
|
|
51
|
+
|
|
52
|
+
return this.testResults.failed === 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async runTestFile(testFile) {
|
|
56
|
+
const testPath = path.join(__dirname, testFile);
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
await fs.access(testPath);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.log(`ā ļø Skipping ${testFile} - file not found`);
|
|
62
|
+
this.testResults.suites.push({
|
|
63
|
+
name: testFile,
|
|
64
|
+
status: 'skipped',
|
|
65
|
+
error: 'File not found'
|
|
66
|
+
});
|
|
67
|
+
this.testResults.skipped++;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log(`\nš Running ${testFile}...`);
|
|
72
|
+
|
|
73
|
+
return new Promise((resolve) => {
|
|
74
|
+
const mocha = spawn('node', ['node_modules/mocha/bin/mocha.js', testPath, '--timeout', '30000', '--reporter', 'spec'], {
|
|
75
|
+
stdio: 'inherit',
|
|
76
|
+
cwd: process.cwd(),
|
|
77
|
+
env: { ...process.env, JSGUI_DEBUG: '0' }
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
let output = '';
|
|
81
|
+
let errorOutput = '';
|
|
82
|
+
|
|
83
|
+
mocha.stdout?.on('data', (data) => {
|
|
84
|
+
output += data.toString();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
mocha.stderr?.on('data', (data) => {
|
|
88
|
+
errorOutput += data.toString();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
mocha.on('close', (code) => {
|
|
92
|
+
const suiteResult = {
|
|
93
|
+
name: testFile,
|
|
94
|
+
status: code === 0 ? 'passed' : 'failed',
|
|
95
|
+
exitCode: code,
|
|
96
|
+
output: output,
|
|
97
|
+
errorOutput: errorOutput
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
this.testResults.suites.push(suiteResult);
|
|
101
|
+
|
|
102
|
+
if (code === 0) {
|
|
103
|
+
console.log(`ā
${testFile} passed`);
|
|
104
|
+
this.testResults.passed++;
|
|
105
|
+
} else {
|
|
106
|
+
console.log(`ā ${testFile} failed (exit code: ${code})`);
|
|
107
|
+
this.testResults.failed++;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.testResults.total++;
|
|
111
|
+
resolve();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
mocha.on('error', (error) => {
|
|
115
|
+
console.log(`ā ${testFile} error: ${error.message}`);
|
|
116
|
+
this.testResults.suites.push({
|
|
117
|
+
name: testFile,
|
|
118
|
+
status: 'error',
|
|
119
|
+
error: error.message
|
|
120
|
+
});
|
|
121
|
+
this.testResults.failed++;
|
|
122
|
+
this.testResults.total++;
|
|
123
|
+
resolve();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
printSummary() {
|
|
129
|
+
console.log('\n' + '='.repeat(80));
|
|
130
|
+
console.log('š TEST SUMMARY');
|
|
131
|
+
console.log('='.repeat(80));
|
|
132
|
+
|
|
133
|
+
console.log(`Total Test Suites: ${this.testResults.total}`);
|
|
134
|
+
console.log(`ā
Passed: ${this.testResults.passed}`);
|
|
135
|
+
console.log(`ā Failed: ${this.testResults.failed}`);
|
|
136
|
+
console.log(`ā ļø Skipped: ${this.testResults.skipped}`);
|
|
137
|
+
console.log(`ā±ļø Duration: ${(this.testResults.duration / 1000).toFixed(2)}s`);
|
|
138
|
+
|
|
139
|
+
const successRate = this.testResults.total > 0 ?
|
|
140
|
+
((this.testResults.passed / this.testResults.total) * 100).toFixed(1) : 0;
|
|
141
|
+
console.log(`š Success Rate: ${successRate}%`);
|
|
142
|
+
|
|
143
|
+
if (this.testResults.failed > 0) {
|
|
144
|
+
console.log('\nā Failed Test Suites:');
|
|
145
|
+
this.testResults.suites
|
|
146
|
+
.filter(suite => suite.status === 'failed' || suite.status === 'error')
|
|
147
|
+
.forEach(suite => {
|
|
148
|
+
console.log(` - ${suite.name}: ${suite.error || 'Exit code ' + suite.exitCode}`);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
console.log('\n' + '='.repeat(80));
|
|
153
|
+
|
|
154
|
+
if (this.testResults.failed === 0) {
|
|
155
|
+
console.log('š All tests passed! The minification, compression, and sourcemap features are working correctly.');
|
|
156
|
+
} else {
|
|
157
|
+
console.log('ā ļø Some tests failed. Please review the implementation and fix the issues.');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async generateReport() {
|
|
162
|
+
const reportPath = path.join(__dirname, '..', 'test-report.json');
|
|
163
|
+
|
|
164
|
+
const report = {
|
|
165
|
+
timestamp: new Date().toISOString(),
|
|
166
|
+
summary: {
|
|
167
|
+
total: this.testResults.total,
|
|
168
|
+
passed: this.testResults.passed,
|
|
169
|
+
failed: this.testResults.failed,
|
|
170
|
+
skipped: this.testResults.skipped,
|
|
171
|
+
duration: this.testResults.duration,
|
|
172
|
+
successRate: this.testResults.total > 0 ?
|
|
173
|
+
((this.testResults.passed / this.testResults.total) * 100).toFixed(1) : 0
|
|
174
|
+
},
|
|
175
|
+
suites: this.testResults.suites.map(suite => ({
|
|
176
|
+
name: suite.name,
|
|
177
|
+
status: suite.status,
|
|
178
|
+
exitCode: suite.exitCode,
|
|
179
|
+
error: suite.error
|
|
180
|
+
})),
|
|
181
|
+
environment: {
|
|
182
|
+
nodeVersion: process.version,
|
|
183
|
+
platform: process.platform,
|
|
184
|
+
arch: process.arch,
|
|
185
|
+
cwd: process.cwd()
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
await fs.writeFile(reportPath, JSON.stringify(report, null, 2));
|
|
191
|
+
console.log(`š Detailed report saved to: ${reportPath}`);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
console.log(`ā ļø Failed to save report: ${error.message}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async runSpecificTest(testName) {
|
|
198
|
+
if (!this.testFiles.includes(testName)) {
|
|
199
|
+
console.log(`ā Test file '${testName}' not found. Available tests:`);
|
|
200
|
+
this.testFiles.forEach(file => console.log(` - ${file}`));
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
console.log(`šÆ Running specific test: ${testName}\n`);
|
|
205
|
+
console.log('='.repeat(80));
|
|
206
|
+
|
|
207
|
+
const startTime = Date.now();
|
|
208
|
+
await this.runTestFile(testName);
|
|
209
|
+
this.testResults.duration = Date.now() - startTime;
|
|
210
|
+
|
|
211
|
+
this.printSummary();
|
|
212
|
+
this.generateReport();
|
|
213
|
+
|
|
214
|
+
return this.testResults.failed === 0;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async runWithOptions(options) {
|
|
218
|
+
if (options.debug) {
|
|
219
|
+
process.env.JSGUI_DEBUG = '1';
|
|
220
|
+
console.log('š Debug mode enabled');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (options.verbose) {
|
|
224
|
+
console.log('š Verbose output enabled');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (options.specific) {
|
|
228
|
+
return await this.runSpecificTest(options.specific);
|
|
229
|
+
} else {
|
|
230
|
+
return await this.runAllTests();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// CLI Interface
|
|
236
|
+
async function main() {
|
|
237
|
+
const args = process.argv.slice(2);
|
|
238
|
+
const options = {
|
|
239
|
+
debug: args.includes('--debug'),
|
|
240
|
+
verbose: args.includes('--verbose'),
|
|
241
|
+
specific: args.find(arg => arg.startsWith('--test='))?.split('=')[1]
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const runner = new TestRunner();
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
const success = await runner.runWithOptions(options);
|
|
248
|
+
process.exit(success ? 0 : 1);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
console.error('š„ Test runner failed:', error);
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Export for programmatic use
|
|
256
|
+
module.exports = TestRunner;
|
|
257
|
+
|
|
258
|
+
// Run if called directly
|
|
259
|
+
if (require.main === module) {
|
|
260
|
+
main();
|
|
261
|
+
}
|