jsgui3-server 0.0.144 → 0.0.145
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/docs/jsgui3-html-improvement-ideas.md +162 -0
- package/docs/jsgui3-html-improvement-ideas.svg +151 -0
- package/examples/controls/14d) window, canvas globe/EarthGlobeRenderer.js +19 -14
- package/examples/controls/14d) window, canvas globe/pipeline/TransformStage.js +5 -5
- package/examples/jsgui3-html/01) mvvm-counter/client.js +648 -0
- package/examples/jsgui3-html/01) mvvm-counter/server.js +21 -0
- package/examples/jsgui3-html/02) date-transform/client.js +764 -0
- package/examples/jsgui3-html/02) date-transform/server.js +21 -0
- package/examples/jsgui3-html/03) form-validation/client.js +1045 -0
- package/examples/jsgui3-html/03) form-validation/server.js +21 -0
- package/examples/jsgui3-html/04) data-grid/client.js +738 -0
- package/examples/jsgui3-html/04) data-grid/server.js +21 -0
- package/examples/jsgui3-html/05) master-detail/client.js +649 -0
- package/examples/jsgui3-html/05) master-detail/server.js +21 -0
- package/examples/jsgui3-html/06) theming/client.js +514 -0
- package/examples/jsgui3-html/06) theming/server.js +21 -0
- package/examples/jsgui3-html/07) mixins/client.js +465 -0
- package/examples/jsgui3-html/07) mixins/server.js +21 -0
- package/examples/jsgui3-html/08) router/client.js +372 -0
- package/examples/jsgui3-html/08) router/server.js +21 -0
- package/examples/jsgui3-html/09) resource-transform/client.js +692 -0
- package/examples/jsgui3-html/09) resource-transform/server.js +21 -0
- package/examples/jsgui3-html/10) binding-debugger/client.js +810 -0
- package/examples/jsgui3-html/10) binding-debugger/server.js +21 -0
- package/examples/jsgui3-html/README.md +48 -0
- package/http/responders/static/Static_Route_HTTP_Responder.js +25 -20
- package/package.json +3 -3
- package/publishers/http-webpageorsite-publisher.js +3 -1
- package/serve-factory.js +12 -5
- package/server.js +103 -85
- package/tests/README.md +7 -0
- package/tests/end-to-end.test.js +336 -365
- package/tests/examples-controls.e2e.test.js +13 -1
- package/tests/fixtures/end-to-end-client.js +54 -0
- package/tests/fixtures/jsgui3-html/binding_debugger_expectations.json +15 -0
- package/tests/fixtures/jsgui3-html/counter_expectations.json +31 -0
- package/tests/fixtures/jsgui3-html/data_grid_expectations.json +26 -0
- package/tests/fixtures/jsgui3-html/date_transform_expectations.json +26 -0
- package/tests/fixtures/jsgui3-html/form_validation_expectations.json +27 -0
- package/tests/fixtures/jsgui3-html/master_detail_expectations.json +15 -0
- package/tests/fixtures/jsgui3-html/mixins_expectations.json +10 -0
- package/tests/fixtures/jsgui3-html/resource_transform_expectations.json +11 -0
- package/tests/fixtures/jsgui3-html/router_expectations.json +10 -0
- package/tests/fixtures/jsgui3-html/theming_expectations.json +10 -0
- package/tests/jsgui3-html-examples.puppeteer.test.js +537 -0
- package/tests/test-runner.js +1 -0
- package/tests/window-examples.puppeteer.test.js +217 -1
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const jsgui = require('./client');
|
|
2
|
+
const Server = require('../../../server');
|
|
3
|
+
const { Demo_UI } = jsgui.controls;
|
|
4
|
+
|
|
5
|
+
if (require.main === module) {
|
|
6
|
+
const server_instance = new Server({
|
|
7
|
+
Ctrl: Demo_UI,
|
|
8
|
+
src_path_client_js: require.resolve('./client.js')
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
server_instance.allowed_addresses = ['127.0.0.1'];
|
|
12
|
+
|
|
13
|
+
server_instance.on('ready', () => {
|
|
14
|
+
server_instance.start(52000, (err) => {
|
|
15
|
+
if (err) {
|
|
16
|
+
throw err;
|
|
17
|
+
}
|
|
18
|
+
console.log('server started on port 52000');
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# JSGUI3-HTML Example Plans
|
|
2
|
+
|
|
3
|
+
This folder collects new jsgui3-html focused examples for the server repo.
|
|
4
|
+
|
|
5
|
+
## Outline plan (10 examples)
|
|
6
|
+
|
|
7
|
+
1. **01) mvvm-counter**
|
|
8
|
+
- Goal: MVVM binding, computed properties, watchers, transformations, validators.
|
|
9
|
+
- UI: counter display, step input, increment/decrement/reset buttons.
|
|
10
|
+
- Tests: Puppeteer click flows, class toggles, validation states.
|
|
11
|
+
|
|
12
|
+
2. **02) date-transform**
|
|
13
|
+
- Goal: date transformations, parsing, range validation, locale formatting.
|
|
14
|
+
- UI: ISO input + locale display, min/max validation, error state.
|
|
15
|
+
|
|
16
|
+
3. **03) form-validation**
|
|
17
|
+
- Goal: multi-field validation (required/email/url/length/pattern).
|
|
18
|
+
- UI: registration form with per-field errors and submit state.
|
|
19
|
+
|
|
20
|
+
4. **04) data-grid**
|
|
21
|
+
- Goal: collection binding, sorting, filtering, pagination.
|
|
22
|
+
- UI: simple data grid with search and page controls.
|
|
23
|
+
|
|
24
|
+
5. **05) master-detail**
|
|
25
|
+
- Goal: selection syncing, computed detail view, navigation.
|
|
26
|
+
- UI: list + detail panel with prev/next controls.
|
|
27
|
+
|
|
28
|
+
6. **06) theming**
|
|
29
|
+
- Goal: theme tokens, theme overrides, CSS variable application.
|
|
30
|
+
- UI: theme toggle with token changes shown in a small layout.
|
|
31
|
+
|
|
32
|
+
7. **07) mixins**
|
|
33
|
+
- Goal: dragable/resizable/selectable mixins.
|
|
34
|
+
- UI: cards with drag/resize handles and selection states.
|
|
35
|
+
|
|
36
|
+
8. **08) router**
|
|
37
|
+
- Goal: router contract + route switching.
|
|
38
|
+
- UI: simple nav that swaps controls per route.
|
|
39
|
+
|
|
40
|
+
9. **09) resource-transform**
|
|
41
|
+
- Goal: Resource + Data_Transform pipeline.
|
|
42
|
+
- UI: load data, transform it, render output with status.
|
|
43
|
+
|
|
44
|
+
10. **10) binding-debugger**
|
|
45
|
+
- Goal: BindingDebugger usage to inspect bindings and changes.
|
|
46
|
+
- UI: inspector panel showing tracked changes over time.
|
|
47
|
+
|
|
48
|
+
Examples 01-10 are fully implemented right now.
|
|
@@ -54,22 +54,27 @@ class Static_Route_HTTP_Responder extends HTTP_Responder {
|
|
|
54
54
|
if (typeof accept_encoding === 'string' && accept_encoding.includes('gzip')) supported_encodings.gzip = true;
|
|
55
55
|
|
|
56
56
|
if (typeof accept_encoding === 'string' && accept_encoding.includes('br')) supported_encodings.br = true;
|
|
57
|
+
|
|
58
|
+
const has_br = response_buffers && response_buffers.br;
|
|
59
|
+
const has_gzip = response_buffers && response_buffers.gzip;
|
|
60
|
+
const use_br = supported_encodings.br === true && has_br;
|
|
61
|
+
const use_gzip = supported_encodings.gzip === true && has_gzip;
|
|
57
62
|
|
|
58
63
|
//console.log('supported_encodings', supported_encodings);
|
|
59
64
|
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
for (const key in response_headers.br) {
|
|
63
|
-
const value = response_headers.br[key];
|
|
64
|
-
//console.log('[key, value]', [key, value]);
|
|
65
|
-
res.setHeader(key, value);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
} else if (
|
|
69
|
-
//console.log('should write headers for gzipped buffer...');
|
|
70
|
-
|
|
71
|
-
for (const key in response_headers.gzip) {
|
|
72
|
-
const value = response_headers.gzip[key];
|
|
65
|
+
if (use_br) {
|
|
66
|
+
|
|
67
|
+
for (const key in response_headers.br) {
|
|
68
|
+
const value = response_headers.br[key];
|
|
69
|
+
//console.log('[key, value]', [key, value]);
|
|
70
|
+
res.setHeader(key, value);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
} else if (use_gzip) {
|
|
74
|
+
//console.log('should write headers for gzipped buffer...');
|
|
75
|
+
|
|
76
|
+
for (const key in response_headers.gzip) {
|
|
77
|
+
const value = response_headers.gzip[key];
|
|
73
78
|
//console.log('[key, value]', [key, value]);
|
|
74
79
|
res.setHeader(key, value);
|
|
75
80
|
}
|
|
@@ -83,13 +88,13 @@ class Static_Route_HTTP_Responder extends HTTP_Responder {
|
|
|
83
88
|
|
|
84
89
|
// Then write the (hopefully compressed) response bodies...
|
|
85
90
|
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
res.write(response_buffers.br);
|
|
89
|
-
} else if (
|
|
90
|
-
//console.log('should write gzipped buffer...');
|
|
91
|
-
res.write(response_buffers.gzip);
|
|
92
|
-
} else {
|
|
91
|
+
if (use_br) {
|
|
92
|
+
|
|
93
|
+
res.write(response_buffers.br);
|
|
94
|
+
} else if (use_gzip) {
|
|
95
|
+
//console.log('should write gzipped buffer...');
|
|
96
|
+
res.write(response_buffers.gzip);
|
|
97
|
+
} else {
|
|
93
98
|
res.write(response_buffers.identity);
|
|
94
99
|
}
|
|
95
100
|
|
package/package.json
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"esbuild": "^0.27.1",
|
|
11
11
|
"fnl": "^0.0.37",
|
|
12
12
|
"fnlfs": "^0.0.34",
|
|
13
|
-
"jsgui3-client": "^0.0.
|
|
14
|
-
"jsgui3-html": "^0.0.
|
|
13
|
+
"jsgui3-client": "^0.0.125",
|
|
14
|
+
"jsgui3-html": "^0.0.176",
|
|
15
15
|
"jsgui3-webpage": "^0.0.8",
|
|
16
16
|
"jsgui3-website": "^0.0.8",
|
|
17
17
|
"lang-tools": "^0.0.44",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"type": "git",
|
|
44
44
|
"url": "https://github.com/metabench/jsgui3-server.git"
|
|
45
45
|
},
|
|
46
|
-
"version": "0.0.
|
|
46
|
+
"version": "0.0.145",
|
|
47
47
|
"scripts": {
|
|
48
48
|
"cli": "node cli.js",
|
|
49
49
|
"serve": "node cli.js serve",
|
|
@@ -99,6 +99,7 @@ class HTTP_Webpageorsite_Publisher extends HTTP_Publisher {
|
|
|
99
99
|
|
|
100
100
|
if (spec.debug !== undefined) this.debug = spec.debug;
|
|
101
101
|
this.style_config = spec.style || {};
|
|
102
|
+
this.bundler_config = spec.bundler || {};
|
|
102
103
|
|
|
103
104
|
// But then some properties to do with the js client(s?) file path.
|
|
104
105
|
|
|
@@ -129,7 +130,8 @@ class HTTP_Webpageorsite_Publisher extends HTTP_Publisher {
|
|
|
129
130
|
|
|
130
131
|
this.js_bundler = new JS_Bundler({
|
|
131
132
|
'debug': this.debug || false,
|
|
132
|
-
'style': this.style_config
|
|
133
|
+
'style': this.style_config,
|
|
134
|
+
'bundler': this.bundler_config
|
|
133
135
|
});
|
|
134
136
|
|
|
135
137
|
|
package/serve-factory.js
CHANGED
|
@@ -12,9 +12,9 @@ const Static_Route_HTTP_Responder = require('./http/responders/static/Static_Rou
|
|
|
12
12
|
const { get_port_or_free } = require('./port-utils');
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
const prepare_webpage_route = (server, route, page_options = {}, defaults = {}) => {
|
|
16
|
-
return new Promise((resolve, reject) => {
|
|
17
|
-
try {
|
|
15
|
+
const prepare_webpage_route = (server, route, page_options = {}, defaults = {}) => {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
try {
|
|
18
18
|
const {
|
|
19
19
|
title,
|
|
20
20
|
name,
|
|
@@ -40,6 +40,7 @@ const prepare_webpage_route = (server, route, page_options = {}, defaults = {})
|
|
|
40
40
|
if (guessed_client_path) publisher_options.src_path_client_js = guessed_client_path;
|
|
41
41
|
if (truthy(defaults.debug)) publisher_options.debug = true;
|
|
42
42
|
if (defaults.style !== undefined) publisher_options.style = defaults.style;
|
|
43
|
+
if (defaults.bundler !== undefined) publisher_options.bundler = defaults.bundler;
|
|
43
44
|
|
|
44
45
|
const webpage_publisher = new HTTP_Webpage_Publisher(publisher_options);
|
|
45
46
|
webpage_publisher.on('ready', (bundle) => {
|
|
@@ -142,6 +143,7 @@ module.exports = (Server) => {
|
|
|
142
143
|
const host = serve_options.host || process.env.HOST || null;
|
|
143
144
|
const debug_enabled = serve_options.debug !== undefined ? truthy(serve_options.debug) : truthy(process.env.JSGUI_DEBUG);
|
|
144
145
|
const style_config = serve_options.style;
|
|
146
|
+
const bundler_config = serve_options.bundler;
|
|
145
147
|
|
|
146
148
|
const server_spec = {
|
|
147
149
|
name: serve_options.name || 'jsgui3 server',
|
|
@@ -150,6 +152,9 @@ module.exports = (Server) => {
|
|
|
150
152
|
if (style_config !== undefined) {
|
|
151
153
|
server_spec.style = style_config;
|
|
152
154
|
}
|
|
155
|
+
if (bundler_config !== undefined) {
|
|
156
|
+
server_spec.bundler = bundler_config;
|
|
157
|
+
}
|
|
153
158
|
if (typeof serve_options.ctrl === 'function') {
|
|
154
159
|
server_spec.Ctrl = serve_options.ctrl;
|
|
155
160
|
} else if (serve_options.api && typeof serve_options.api === 'object') {
|
|
@@ -181,13 +186,15 @@ module.exports = (Server) => {
|
|
|
181
186
|
const extra_page_promises = additional_pages.map(([route, cfg]) => prepare_webpage_route(server_instance, route, cfg, {
|
|
182
187
|
caller_dir,
|
|
183
188
|
debug: debug_enabled,
|
|
184
|
-
style: style_config
|
|
189
|
+
style: style_config,
|
|
190
|
+
bundler: bundler_config
|
|
185
191
|
}));
|
|
186
192
|
if (serve_options.page_config && serve_options.page_route && serve_options.page_route !== '/') {
|
|
187
193
|
extra_page_promises.unshift(prepare_webpage_route(server_instance, serve_options.page_route, serve_options.page_config, {
|
|
188
194
|
caller_dir,
|
|
189
195
|
debug: debug_enabled,
|
|
190
|
-
style: style_config
|
|
196
|
+
style: style_config,
|
|
197
|
+
bundler: bundler_config
|
|
191
198
|
}));
|
|
192
199
|
}
|
|
193
200
|
|
package/server.js
CHANGED
|
@@ -50,6 +50,7 @@ class JSGUI_Single_Process_Server extends Evented_Class {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
const style_config = spec.style;
|
|
53
|
+
const bundler_config = spec.bundler;
|
|
53
54
|
|
|
54
55
|
// or src_path_client_js as well...
|
|
55
56
|
|
|
@@ -93,14 +94,15 @@ class JSGUI_Single_Process_Server extends Evented_Class {
|
|
|
93
94
|
|
|
94
95
|
const wp_app = new Webpage({content: Ctrl});
|
|
95
96
|
|
|
96
|
-
const opts_wp_publisher = {
|
|
97
|
-
'webpage': wp_app
|
|
98
|
-
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
97
|
+
const opts_wp_publisher = {
|
|
98
|
+
'webpage': wp_app
|
|
99
|
+
|
|
100
|
+
};
|
|
101
|
+
if (bundler_config !== undefined) opts_wp_publisher.bundler = bundler_config;
|
|
102
|
+
|
|
103
|
+
if (this.debug) {
|
|
104
|
+
opts_wp_publisher.debug = this.debug;
|
|
105
|
+
}
|
|
104
106
|
|
|
105
107
|
if (disk_path_client_js) opts_wp_publisher.src_path_client_js = disk_path_client_js;
|
|
106
108
|
if (style_config !== undefined) opts_wp_publisher.style = style_config;
|
|
@@ -163,12 +165,15 @@ class JSGUI_Single_Process_Server extends Evented_Class {
|
|
|
163
165
|
const ws_app = this.app = this.website = new Website(opts_website);
|
|
164
166
|
// Be able to treat Webpage as an app?
|
|
165
167
|
|
|
166
|
-
const opts_ws_publisher = {
|
|
167
|
-
'website': ws_app
|
|
168
|
-
};
|
|
168
|
+
const opts_ws_publisher = {
|
|
169
|
+
'website': ws_app
|
|
170
|
+
};
|
|
169
171
|
if (disk_path_client_js) {
|
|
170
172
|
opts_ws_publisher.disk_path_client_js = disk_path_client_js;
|
|
171
173
|
}
|
|
174
|
+
if (bundler_config !== undefined) {
|
|
175
|
+
opts_ws_publisher.bundler = bundler_config;
|
|
176
|
+
}
|
|
172
177
|
if (style_config !== undefined) {
|
|
173
178
|
opts_ws_publisher.style = style_config;
|
|
174
179
|
}
|
|
@@ -240,16 +245,33 @@ class JSGUI_Single_Process_Server extends Evented_Class {
|
|
|
240
245
|
if (this.allowed_addresses && this.allowed_addresses.length) {
|
|
241
246
|
arr_ipv4_addresses = arr_ipv4_addresses.filter(a => this.allowed_addresses.indexOf(a) > -1);
|
|
242
247
|
}
|
|
243
|
-
arr_ipv4_addresses = [...new Set(arr_ipv4_addresses)];
|
|
244
|
-
console.log('IPv4 addresses to bind:', arr_ipv4_addresses);
|
|
245
|
-
let num_to_start = arr_ipv4_addresses.length;
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
248
|
+
arr_ipv4_addresses = [...new Set(arr_ipv4_addresses)];
|
|
249
|
+
console.log('IPv4 addresses to bind:', arr_ipv4_addresses);
|
|
250
|
+
let num_to_start = arr_ipv4_addresses.length;
|
|
251
|
+
let started_count = 0;
|
|
252
|
+
let last_error = null;
|
|
253
|
+
let ready_raised = false;
|
|
254
|
+
if (num_to_start === 0) {
|
|
255
|
+
callback('No allowed network interfaces found.');
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const finalize_start = (err) => {
|
|
259
|
+
if (num_to_start !== 0) return;
|
|
260
|
+
if (started_count > 0) {
|
|
261
|
+
if (!ready_raised) {
|
|
262
|
+
console.log('Server ready');
|
|
263
|
+
this.raise('ready');
|
|
264
|
+
ready_raised = true;
|
|
265
|
+
}
|
|
266
|
+
if (callback) callback(null, true);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const final_error = err || last_error || new Error('No servers started.');
|
|
270
|
+
if (callback) callback(final_error);
|
|
271
|
+
};
|
|
272
|
+
const respond_not_found = (res) => {
|
|
273
|
+
if (!res.headersSent) {
|
|
274
|
+
const body = 'Not Found';
|
|
253
275
|
res.statusCode = 404;
|
|
254
276
|
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
255
277
|
res.setHeader('Content-Length', Buffer.byteLength(body));
|
|
@@ -308,70 +330,66 @@ class JSGUI_Single_Process_Server extends Evented_Class {
|
|
|
308
330
|
process_request(req, res);
|
|
309
331
|
});
|
|
310
332
|
this.http_servers.push(https_server);
|
|
311
|
-
https_server.on('error', (err) => {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
https_server.
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
http_server.
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
num_to_start--;
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
});
|
|
333
|
+
https_server.on('error', (err) => {
|
|
334
|
+
last_error = err;
|
|
335
|
+
if (err.code === 'EACCES') {
|
|
336
|
+
console.error('Permission denied:', err.message);
|
|
337
|
+
} else if (err.code === 'EADDRINUSE') {
|
|
338
|
+
console.error(`Address ${ipv4_address}:${port} already in use; skipping.`);
|
|
339
|
+
} else {
|
|
340
|
+
console.error('https_server error:', err);
|
|
341
|
+
}
|
|
342
|
+
num_to_start--;
|
|
343
|
+
finalize_start(err);
|
|
344
|
+
});
|
|
345
|
+
https_server.timeout = 10800000;
|
|
346
|
+
https_server.listen(port, ipv4_address, () => {
|
|
347
|
+
console.log('* Server running at https://' + ipv4_address + ':' + port + '/');
|
|
348
|
+
started_count++;
|
|
349
|
+
num_to_start--;
|
|
350
|
+
finalize_start(null);
|
|
351
|
+
});
|
|
352
|
+
} catch (err) {
|
|
353
|
+
console.log('https_server err', err);
|
|
354
|
+
num_to_start--;
|
|
355
|
+
finalize_start(err);
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
} else {
|
|
359
|
+
each(arr_ipv4_addresses, (ipv4_address) => {
|
|
360
|
+
try {
|
|
361
|
+
var http_server = http.createServer(function(req, res) {
|
|
362
|
+
process_request(req, res);
|
|
363
|
+
});
|
|
364
|
+
this.http_servers.push(http_server);
|
|
365
|
+
http_server.on('error', (err) => {
|
|
366
|
+
last_error = err;
|
|
367
|
+
if (err.code === 'EACCES') {
|
|
368
|
+
console.error('Permission denied:', err.message);
|
|
369
|
+
} else if (err.code === 'EADDRINUSE') {
|
|
370
|
+
console.error(`Address ${ipv4_address}:${port} already in use; skipping.`);
|
|
371
|
+
} else {
|
|
372
|
+
console.error('http_server error:', err);
|
|
373
|
+
}
|
|
374
|
+
num_to_start--;
|
|
375
|
+
finalize_start(err);
|
|
376
|
+
});
|
|
377
|
+
http_server.timeout = 10800000;
|
|
378
|
+
http_server.listen(port, ipv4_address, () => {
|
|
379
|
+
console.log('* Server running at http://' + ipv4_address + ':' + port + '/');
|
|
380
|
+
started_count++;
|
|
381
|
+
num_to_start--;
|
|
382
|
+
finalize_start(null);
|
|
383
|
+
});
|
|
384
|
+
} catch (err) {
|
|
385
|
+
console.log('http_server err', err);
|
|
386
|
+
num_to_start--;
|
|
387
|
+
finalize_start(err);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
});
|
|
375
393
|
}
|
|
376
394
|
});
|
|
377
395
|
}
|
package/tests/README.md
CHANGED
|
@@ -27,6 +27,7 @@ tests/
|
|
|
27
27
|
├── error-handling.test.js # Error handling and edge cases
|
|
28
28
|
├── examples-controls.e2e.test.js # Example apps regression (controls)
|
|
29
29
|
├── sass-controls.e2e.test.js # Sass/CSS controls E2E coverage
|
|
30
|
+
├── jsgui3-html-examples.puppeteer.test.js # Puppeteer interaction tests (jsgui3-html examples)
|
|
30
31
|
├── window-examples.puppeteer.test.js # Puppeteer interaction tests (window examples)
|
|
31
32
|
├── test-runner.js # Custom test runner with reporting
|
|
32
33
|
└── README.md # This file
|
|
@@ -159,6 +160,12 @@ Server-level integration tests for controls that define `.scss` or `.sass` style
|
|
|
159
160
|
|
|
160
161
|
Note: These tests are skipped if the `sass` dependency is not installed.
|
|
161
162
|
|
|
163
|
+
### 10. JSGUI3-HTML Example Puppeteer Tests (`jsgui3-html-examples.puppeteer.test.js`)
|
|
164
|
+
|
|
165
|
+
Browser-level interaction checks for jsgui3-html examples:
|
|
166
|
+
|
|
167
|
+
- MVVM counter interactions, bindings, and validation state
|
|
168
|
+
|
|
162
169
|
## Configuration Examples
|
|
163
170
|
|
|
164
171
|
### Basic Minification
|