jsgui3-server 0.0.147 → 0.0.149

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 (145) hide show
  1. package/.github/workflows/control-scan-manifest-check.yml +31 -0
  2. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-071799b982906680f5fd699d.js +40 -0
  3. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-07352945ad5c92654fcb8b65.js +39 -0
  4. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-138a601fadb6191ea314c6fd.js +39 -0
  5. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-171f6c381c2cadf2e9fa7087.js +39 -0
  6. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-1d973388156b84a04373fac9.js +39 -0
  7. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-20e117bc8a10d2cd16234bbe.js +40 -0
  8. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-2b028a82b0e5efddba42425f.js +39 -0
  9. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-4518556cd5c7e059e82b22b8.js +40 -0
  10. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5bac1aa0f213902f718ed74f.js +40 -0
  11. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-5f9996ac7822caf777d92f56.js +39 -0
  12. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-60a92c702e65fd9cf748e3ec.js +39 -0
  13. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6164c1f8f738995c541895d2.js +44 -0
  14. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-6718a85eb9e5aa782dd47a05.js +45 -0
  15. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-69e280f14e37aee76a1d4675.js +39 -0
  16. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7570d1b030d44b111ed59c4c.js +39 -0
  17. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7798c9bbd55e510d5039f936.js +42 -0
  18. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-78cd511ea1ef18ecb03d1be5.js +40 -0
  19. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-7d482e0b95bcb5e3c543118b.js +43 -0
  20. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-80e9476d1127c55b40fdb36f.js +40 -0
  21. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-810ced55d5320a3088a05b13.js +40 -0
  22. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-8423565f1a40e329afc8c6cf.js +40 -0
  23. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-900bef783b8cee36506ec282.js +39 -0
  24. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-a1a37aff6416fdad74040ddf.js +39 -0
  25. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-ad48d5e8eda40f175b4df090.js +39 -0
  26. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-aec5a2d963015528c9099462.js +39 -0
  27. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-af9d34e0f1722fab9e28c269.js +39 -0
  28. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-b818e4015e2f1fe86280b5ab.js +41 -0
  29. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bcb2541adc70b7aba61768c5.js +44 -0
  30. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-bfe89d2c78ed44f95ed7dd73.js +40 -0
  31. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c06f04806a1e688e1187110c.js +40 -0
  32. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-c3f3adf904f585afc544b96a.js +39 -0
  33. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-d45acb873e1d8e32d5e60f2e.js +39 -0
  34. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-db06f132533706f4a0163b8c.js +39 -0
  35. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f660f40d78b135fc8560a862.js +39 -0
  36. package/.jsgui3-server-cache/jsgui3-html-shims/jsgui3-html-controls-shim-f9dee4ec18a96e09bee06bae.js +39 -0
  37. package/README.md +85 -3
  38. package/admin-ui/client.js +213 -0
  39. package/admin-ui/server.js +104 -0
  40. package/client/controls/auto-observable.js +207 -0
  41. package/dev-status.svg +139 -0
  42. package/docs/api-reference.md +301 -43
  43. package/docs/books/admin-ui/01-introduction.md +32 -0
  44. package/docs/books/admin-ui/02-architecture.md +92 -0
  45. package/docs/books/admin-ui/03-controls.md +194 -0
  46. package/docs/books/admin-ui/04-implementation-plan.md +62 -0
  47. package/docs/books/admin-ui/README.md +26 -0
  48. package/docs/books/jsgui3-bundling-research-book/00-table-of-contents.md +35 -0
  49. package/docs/books/jsgui3-bundling-research-book/01-pipeline-and-runtime-semantics.md +34 -0
  50. package/docs/books/jsgui3-bundling-research-book/02-javascript-bundling-core.md +36 -0
  51. package/docs/books/jsgui3-bundling-research-book/03-style-extraction-and-css-compilation.md +35 -0
  52. package/docs/books/jsgui3-bundling-research-book/04-static-publishing-and-delivery.md +39 -0
  53. package/docs/books/jsgui3-bundling-research-book/05-current-limits-and-size-bloat-vectors.md +25 -0
  54. package/docs/books/jsgui3-bundling-research-book/06-unused-module-elimination-strategy.md +77 -0
  55. package/docs/books/jsgui3-bundling-research-book/07-jsgui3-html-control-and-mixin-pruning.md +63 -0
  56. package/docs/books/jsgui3-bundling-research-book/08-test-and-verification-methodology.md +43 -0
  57. package/docs/books/jsgui3-bundling-research-book/09-roadmap-and-rollout.md +42 -0
  58. package/docs/books/jsgui3-bundling-research-book/10-further-research-strategies-and-upgrades.md +211 -0
  59. package/docs/books/jsgui3-bundling-research-book/README.md +35 -0
  60. package/docs/bundling-system-deep-dive.md +9 -4
  61. package/docs/comprehensive-documentation.md +49 -18
  62. package/docs/configuration-reference.md +152 -27
  63. package/docs/core/README.md +19 -0
  64. package/docs/core/jsgui3-server-core-book/00-table-of-contents.md +21 -0
  65. package/docs/core/jsgui3-server-core-book/01-startup-readiness-state-machine.md +41 -0
  66. package/docs/core/jsgui3-server-core-book/02-resource-abstraction-and-lifecycle.md +92 -0
  67. package/docs/core/jsgui3-server-core-book/03-resource-pool-and-event-topology.md +47 -0
  68. package/docs/core/jsgui3-server-core-book/04-sse-publisher-semantics.md +41 -0
  69. package/docs/core/jsgui3-server-core-book/05-serve-factory-resource-wiring.md +46 -0
  70. package/docs/core/jsgui3-server-core-book/06-e2e-testing-methodology.md +48 -0
  71. package/docs/core/jsgui3-server-core-book/07-defect-detection-and-hardening-loop.md +47 -0
  72. package/docs/publishers-guide.md +59 -4
  73. package/docs/resources-guide.md +184 -35
  74. package/docs/simple-server-api-design.md +72 -17
  75. package/docs/system-architecture.md +18 -14
  76. package/examples/controls/15) window, observable SSE/server.js +6 -1
  77. package/examples/controls/19) window, auto observable ui/client.js +125 -0
  78. package/examples/controls/19) window, auto observable ui/server.js +73 -0
  79. package/examples/controls/20) window, task manager app/README.md +133 -0
  80. package/examples/controls/20) window, task manager app/client.js +797 -0
  81. package/examples/controls/20) window, task manager app/server.js +178 -0
  82. package/examples/controls/6) window, color_palette/client.js +165 -68
  83. package/examples/controls/9) window, date picker/client.js +362 -76
  84. package/examples/controls/9b) window, shared data.model mirrored date pickers/client.js +104 -83
  85. package/examples/jsgui3-html/06) theming/client.js +22 -1
  86. package/examples/jsgui3-html/10) binding-debugger/client.js +137 -1
  87. package/http/responders/static/Static_Route_HTTP_Responder.js +52 -34
  88. package/lab/experiments/capture-color-controls.js +196 -0
  89. package/lab/results/screenshots/color-controls/full_page.png +0 -0
  90. package/lab/results/screenshots/color-controls/section_1_color_grid_12x12.png +0 -0
  91. package/lab/results/screenshots/color-controls/section_2_color_grid_4x2.png +0 -0
  92. package/lab/results/screenshots/color-controls/section_3_color_palette.png +0 -0
  93. package/lab/results/screenshots/color-controls/section_4_palette_comparison.png +0 -0
  94. package/lab/results/screenshots/color-controls/section_5_raw_swatches.png +0 -0
  95. package/lab/results/screenshots/color-controls/section_6_optimized_crayola.png +0 -0
  96. package/lab/results/screenshots/color-controls/section_7_pastel_palette.png +0 -0
  97. package/lab/results/screenshots/color-controls/section_8_extended_144.png +0 -0
  98. package/lab/screenshot-utils.js +248 -0
  99. package/module.js +11 -4
  100. package/package.json +14 -4
  101. package/publishers/Publishers.js +4 -3
  102. package/publishers/helpers/assigners/static-compressed-response-buffers/Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner.js +5 -5
  103. package/publishers/http-observable-publisher.js +8 -0
  104. package/publishers/http-sse-publisher.js +341 -0
  105. package/publishers/http-webpage-publisher.js +13 -3
  106. package/publishers/http-webpageorsite-publisher.js +18 -0
  107. package/resources/process-resource.js +950 -0
  108. package/resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild.js +164 -46
  109. package/resources/processors/bundlers/js/esbuild/Core_JS_Non_Minifying_Bundler_Using_ESBuild.js +18 -7
  110. package/resources/processors/bundlers/js/esbuild/JSGUI3_HTML_Control_Optimizer.js +829 -0
  111. package/resources/remote-process-resource.js +355 -0
  112. package/resources/server-resource-pool.js +354 -41
  113. package/serve-factory.js +441 -259
  114. package/server.js +161 -16
  115. package/tests/README.md +66 -4
  116. package/tests/admin-ui-render.test.js +24 -0
  117. package/tests/assigners.test.js +56 -40
  118. package/tests/bundling-default-control-elimination.puppeteer.test.js +260 -0
  119. package/tests/configuration-validation.test.js +21 -18
  120. package/tests/content-analysis.test.js +7 -6
  121. package/tests/control-optimizer-cache-behavior.test.js +52 -0
  122. package/tests/control-scan-manifest-regression.test.js +144 -0
  123. package/tests/end-to-end.test.js +15 -14
  124. package/tests/error-handling.test.js +222 -179
  125. package/tests/fixtures/bundling-default-button-client.js +37 -0
  126. package/tests/fixtures/bundling-default-window-client.js +34 -0
  127. package/tests/fixtures/control_scan_manifest_expectations.json +48 -0
  128. package/tests/fixtures/resource-monitor-client.js +319 -0
  129. package/tests/helpers/puppeteer-e2e-harness.js +317 -0
  130. package/tests/http-sse-publisher.test.js +136 -0
  131. package/tests/performance.test.js +69 -65
  132. package/tests/process-resource.test.js +138 -0
  133. package/tests/publishers.test.js +7 -7
  134. package/tests/remote-process-resource.test.js +160 -0
  135. package/tests/sass-controls.e2e.test.js +7 -1
  136. package/tests/serve-resources.test.js +270 -0
  137. package/tests/serve.test.js +120 -50
  138. package/tests/server-resource-pool.test.js +106 -0
  139. package/tests/small-controls-bundle-size.test.js +252 -0
  140. package/tests/test-runner.js +13 -1
  141. package/tests/window-examples.puppeteer.test.js +204 -1
  142. package/tests/window-resource-integration.puppeteer.test.js +585 -0
  143. package/tests/temp_invalid.js +0 -7
  144. package/tests/temp_invalid_utf8.js +0 -1
  145. package/tests/temp_malformed.js +0 -10
@@ -0,0 +1,270 @@
1
+ const assert = require('assert');
2
+ const http = require('http');
3
+ const { describe, it, afterEach, after } = require('mocha');
4
+ const EventEmitter = require('events');
5
+ const { Resource } = require('jsgui3-html');
6
+
7
+ const fake_webpage_publisher_path = require.resolve('../publishers/http-webpage-publisher');
8
+ const fake_website_publisher_path = require.resolve('../publishers/http-website-publisher');
9
+ const original_webpage_publisher_module = require.cache[fake_webpage_publisher_path];
10
+ const original_website_publisher_module = require.cache[fake_website_publisher_path];
11
+
12
+ class Fake_Publisher_Base extends EventEmitter {
13
+ constructor(html_route, html_body) {
14
+ super();
15
+ const body_buffer = Buffer.from(html_body, 'utf8');
16
+ this.route = html_route;
17
+ this.response_headers = {
18
+ identity: {
19
+ 'Content-Type': 'text/html; charset=utf-8',
20
+ 'Content-Length': body_buffer.length
21
+ }
22
+ };
23
+ this.response_buffers = {
24
+ identity: body_buffer
25
+ };
26
+ this.type = 'html';
27
+ this.extension = 'html';
28
+
29
+ setImmediate(() => {
30
+ this.emit('ready', {
31
+ _arr: [{
32
+ type: this.type,
33
+ extension: this.extension,
34
+ route: this.route,
35
+ response_headers: this.response_headers,
36
+ response_buffers: this.response_buffers
37
+ }]
38
+ });
39
+ });
40
+ }
41
+
42
+ handle_http(req, res) {
43
+ const body_buffer = this.response_buffers.identity;
44
+ res.writeHead(200, this.response_headers.identity);
45
+ res.end(body_buffer);
46
+ }
47
+
48
+ meets_requirements() {
49
+ return true;
50
+ }
51
+
52
+ start(callback) {
53
+ if (typeof callback === 'function') {
54
+ callback(null, true);
55
+ }
56
+ return Promise.resolve(true);
57
+ }
58
+
59
+ stop(callback) {
60
+ if (typeof callback === 'function') {
61
+ callback(null, true);
62
+ }
63
+ return Promise.resolve(true);
64
+ }
65
+ }
66
+
67
+ class Fake_Webpage_Publisher extends Fake_Publisher_Base {
68
+ constructor(options = {}) {
69
+ const webpage = options.webpage || {};
70
+ const route = webpage.path || '/';
71
+ const title = webpage.title || webpage.name || 'Fake Page';
72
+ const html_body = `<html><head><title>${title}</title></head><body>${route}</body></html>`;
73
+ super(route, html_body);
74
+ }
75
+ }
76
+
77
+ class Fake_Website_Publisher extends Fake_Publisher_Base {
78
+ constructor(options = {}) {
79
+ const title = (options.website && options.website.name) || 'Fake Site';
80
+ const html_body = `<html><head><title>${title}</title></head><body>site</body></html>`;
81
+ super('/*', html_body);
82
+ }
83
+ }
84
+
85
+ require.cache[fake_webpage_publisher_path] = { exports: Fake_Webpage_Publisher };
86
+ require.cache[fake_website_publisher_path] = { exports: Fake_Website_Publisher };
87
+
88
+ const Server = require('../server');
89
+ const { get_free_port } = require('../port-utils');
90
+
91
+ class In_Process_Test_Resource extends Resource {
92
+ constructor(spec = {}) {
93
+ super(spec);
94
+ this.current_state = 'stopped';
95
+ this.start_calls = 0;
96
+ this.stop_calls = 0;
97
+ }
98
+
99
+ start(callback) {
100
+ this.start_calls += 1;
101
+ const previous_state = this.current_state;
102
+ this.current_state = 'running';
103
+ this.raise('state_change', {
104
+ from: previous_state,
105
+ to: this.current_state,
106
+ timestamp: Date.now()
107
+ });
108
+ if (typeof callback === 'function') callback(null, true);
109
+ return Promise.resolve(true);
110
+ }
111
+
112
+ stop(callback) {
113
+ this.stop_calls += 1;
114
+ const previous_state = this.current_state;
115
+ this.current_state = 'stopped';
116
+ this.raise('state_change', {
117
+ from: previous_state,
118
+ to: this.current_state,
119
+ timestamp: Date.now()
120
+ });
121
+ if (typeof callback === 'function') callback(null, true);
122
+ return Promise.resolve(true);
123
+ }
124
+
125
+ get status() {
126
+ return {
127
+ state: this.current_state
128
+ };
129
+ }
130
+
131
+ get_abstract() {
132
+ return {
133
+ name: this.name,
134
+ state: this.current_state
135
+ };
136
+ }
137
+ }
138
+
139
+ const get_http_response = (port, path_name) => {
140
+ return new Promise((resolve, reject) => {
141
+ const request = http.get({
142
+ hostname: '127.0.0.1',
143
+ port,
144
+ path: path_name
145
+ }, (response) => {
146
+ let body_text = '';
147
+ response.setEncoding('utf8');
148
+ response.on('data', (chunk) => {
149
+ body_text += chunk;
150
+ });
151
+ response.on('end', () => {
152
+ resolve({
153
+ response,
154
+ body_text
155
+ });
156
+ });
157
+ });
158
+ request.on('error', reject);
159
+ });
160
+ };
161
+
162
+ describe('Server.serve resources integration', function() {
163
+ this.timeout(20000);
164
+
165
+ const started_servers = [];
166
+
167
+ after(() => {
168
+ if (original_webpage_publisher_module) {
169
+ require.cache[fake_webpage_publisher_path] = original_webpage_publisher_module;
170
+ } else {
171
+ delete require.cache[fake_webpage_publisher_path];
172
+ }
173
+
174
+ if (original_website_publisher_module) {
175
+ require.cache[fake_website_publisher_path] = original_website_publisher_module;
176
+ } else {
177
+ delete require.cache[fake_website_publisher_path];
178
+ }
179
+
180
+ delete require.cache[require.resolve('../server')];
181
+ });
182
+
183
+ afterEach(async () => {
184
+ while (started_servers.length > 0) {
185
+ const server_instance = started_servers.pop();
186
+ await new Promise((resolve) => {
187
+ server_instance.close(() => resolve());
188
+ });
189
+ }
190
+ });
191
+
192
+ it('supports in-process resource instances and stops them on server close', async () => {
193
+ const port = await get_free_port();
194
+ const in_process_resource = new In_Process_Test_Resource({
195
+ name: 'in_process_resource'
196
+ });
197
+
198
+ const server_instance = await Server.serve({
199
+ host: '127.0.0.1',
200
+ port,
201
+ api: {
202
+ ping: () => 'pong'
203
+ },
204
+ resources: {
205
+ in_process_resource
206
+ }
207
+ });
208
+ started_servers.push(server_instance);
209
+
210
+ assert.strictEqual(in_process_resource.start_calls, 1);
211
+ assert.strictEqual(in_process_resource.status.state, 'running');
212
+
213
+ const { response, body_text } = await get_http_response(port, '/api/ping');
214
+ assert.strictEqual(response.statusCode, 200);
215
+ assert.strictEqual(body_text, 'pong');
216
+
217
+ await new Promise((resolve) => server_instance.close(() => resolve()));
218
+ started_servers.pop();
219
+
220
+ assert.strictEqual(in_process_resource.stop_calls >= 1, true);
221
+ assert.strictEqual(in_process_resource.status.state, 'stopped');
222
+ });
223
+
224
+ it('supports direct process resources with consistent status API', async () => {
225
+ const port = await get_free_port();
226
+ let server_instance = null;
227
+
228
+ server_instance = await Server.serve({
229
+ host: '127.0.0.1',
230
+ port,
231
+ api: {
232
+ process_status: () => {
233
+ const process_resource = server_instance.resource_pool.get_resource('worker_process');
234
+ return process_resource ? process_resource.status : null;
235
+ }
236
+ },
237
+ resources: {
238
+ worker_process: {
239
+ type: 'process',
240
+ command: process.execPath,
241
+ args: ['-e', 'setInterval(() => {}, 1000);']
242
+ }
243
+ }
244
+ });
245
+ started_servers.push(server_instance);
246
+
247
+ const process_resource = server_instance.resource_pool.get_resource('worker_process');
248
+ assert(process_resource, 'Expected worker_process resource in the pool');
249
+
250
+ const process_status = process_resource.status;
251
+ assert.strictEqual(process_status.state, 'running');
252
+ assert.strictEqual(typeof process_status.processManager.type, 'string');
253
+ assert.strictEqual(process_status.processManager.type, 'direct');
254
+ assert(Number.isFinite(process_status.pid), 'Expected running process PID');
255
+
256
+ const { response, body_text } = await get_http_response(port, '/api/process_status');
257
+ assert.strictEqual(response.statusCode, 200);
258
+
259
+ const parsed_status = JSON.parse(body_text);
260
+ assert.strictEqual(parsed_status.state, 'running');
261
+ assert.strictEqual(typeof parsed_status.restartCount, 'number');
262
+ assert('memoryUsage' in parsed_status);
263
+ assert('lastHealthCheck' in parsed_status);
264
+
265
+ await new Promise((resolve) => server_instance.close(() => resolve()));
266
+ started_servers.pop();
267
+
268
+ assert.strictEqual(process_resource.status.state, 'stopped');
269
+ });
270
+ });
@@ -6,12 +6,14 @@ const EventEmitter = require('events');
6
6
 
7
7
  const dummy_client_path = require.resolve('./dummy-client.js');
8
8
 
9
- const fake_webpage_publisher_path = require.resolve('../publishers/http-webpage-publisher');
10
- const fake_website_publisher_path = require.resolve('../publishers/http-website-publisher');
9
+ const fake_webpage_publisher_path = require.resolve('../publishers/http-webpage-publisher');
10
+ const fake_website_publisher_path = require.resolve('../publishers/http-website-publisher');
11
+ const original_webpage_publisher_module = require.cache[fake_webpage_publisher_path];
12
+ const original_website_publisher_module = require.cache[fake_website_publisher_path];
11
13
 
12
- class Fake_Publisher_Base extends EventEmitter {
13
- constructor(html_route, html_body) {
14
- super();
14
+ class Fake_Publisher_Base extends EventEmitter {
15
+ constructor(html_route, html_body) {
16
+ super();
15
17
  this.html_route = html_route;
16
18
  this.html_body = html_body;
17
19
  const buffer = Buffer.from(this.html_body, 'utf8');
@@ -40,27 +42,48 @@ class Fake_Publisher_Base extends EventEmitter {
40
42
  };
41
43
  this.type = 'html';
42
44
  this.extension = 'html';
43
- setImmediate(() => {
44
- this.emit('ready', {
45
- _arr: [{
46
- type: this.type,
47
- extension: this.extension,
48
- route: this.route,
49
- response_headers: this.response_headers,
50
- response_buffers: this.response_buffers
51
- }]
52
- });
53
- });
54
- }
45
+ const ready_delay_ms = Number(this.constructor.ready_delay_ms) || 0;
46
+ const emit_ready = () => {
47
+ this.emit('ready', {
48
+ _arr: [{
49
+ type: this.type,
50
+ extension: this.extension,
51
+ route: this.route,
52
+ response_headers: this.response_headers,
53
+ response_buffers: this.response_buffers
54
+ }]
55
+ });
56
+ };
57
+
58
+ if (ready_delay_ms > 0) {
59
+ setTimeout(emit_ready, ready_delay_ms);
60
+ } else {
61
+ setImmediate(emit_ready);
62
+ }
63
+ }
55
64
 
56
- handle_http(req, res) {
57
- res.writeHead(200, {
58
- 'Content-Type': 'text/html; charset=utf-8',
59
- 'Content-Length': Buffer.byteLength(this.html_body, 'utf8')
60
- });
61
- res.end(this.html_body);
62
- }
63
- }
65
+ handle_http(req, res) {
66
+ res.writeHead(200, {
67
+ 'Content-Type': 'text/html; charset=utf-8',
68
+ 'Content-Length': Buffer.byteLength(this.html_body, 'utf8')
69
+ });
70
+ res.end(this.html_body);
71
+ }
72
+
73
+ meets_requirements() {
74
+ return true;
75
+ }
76
+
77
+ start(callback) {
78
+ if (typeof callback === 'function') callback(null, true);
79
+ return Promise.resolve(true);
80
+ }
81
+
82
+ stop(callback) {
83
+ if (typeof callback === 'function') callback(null, true);
84
+ return Promise.resolve(true);
85
+ }
86
+ }
64
87
 
65
88
  class Fake_Webpage_Publisher extends Fake_Publisher_Base {
66
89
  constructor(opts = {}) {
@@ -72,14 +95,17 @@ class Fake_Webpage_Publisher extends Fake_Publisher_Base {
72
95
  }
73
96
  }
74
97
 
75
- class Fake_Website_Publisher extends Fake_Publisher_Base {
98
+ class Fake_Website_Publisher extends Fake_Publisher_Base {
76
99
  constructor(opts = {}) {
77
100
  const route = '/*';
78
101
  const title = (opts.website && opts.website.name) || 'Test Website';
79
102
  const body = `<html><head><title>${title}</title></head><body><div class="dummy-control">website</div></body></html>`;
80
103
  super(route, body);
81
104
  }
82
- }
105
+ }
106
+
107
+ Fake_Webpage_Publisher.ready_delay_ms = 0;
108
+ Fake_Website_Publisher.ready_delay_ms = 0;
83
109
 
84
110
  require.cache[fake_webpage_publisher_path] = { exports: Fake_Webpage_Publisher };
85
111
  require.cache[fake_website_publisher_path] = { exports: Fake_Website_Publisher };
@@ -120,16 +146,35 @@ const get_http_response = (port, route_path = '/') => new Promise((resolve, reje
120
146
  req.on('error', reject);
121
147
  });
122
148
 
123
- describe('Server.serve', function() {
124
- this.timeout(10000);
125
- let server_instance;
126
-
127
- afterEach(async () => {
128
- if (server_instance) {
129
- await new Promise(resolve => server_instance.close(resolve));
130
- server_instance = null;
131
- }
132
- });
149
+ describe('Server.serve', function() {
150
+ this.timeout(10000);
151
+ let server_instance;
152
+
153
+ after(() => {
154
+ if (original_webpage_publisher_module) {
155
+ require.cache[fake_webpage_publisher_path] = original_webpage_publisher_module;
156
+ } else {
157
+ delete require.cache[fake_webpage_publisher_path];
158
+ }
159
+
160
+ if (original_website_publisher_module) {
161
+ require.cache[fake_website_publisher_path] = original_website_publisher_module;
162
+ } else {
163
+ delete require.cache[fake_website_publisher_path];
164
+ }
165
+
166
+ delete require.cache[require.resolve('../server')];
167
+ });
168
+
169
+ afterEach(async () => {
170
+ Fake_Webpage_Publisher.ready_delay_ms = 0;
171
+ Fake_Website_Publisher.ready_delay_ms = 0;
172
+
173
+ if (server_instance) {
174
+ await new Promise(resolve => server_instance.close(resolve));
175
+ server_instance = null;
176
+ }
177
+ });
133
178
 
134
179
  it('should serve a simple control', async () => {
135
180
  const port = await get_free_port();
@@ -195,16 +240,41 @@ describe('Server.serve', function() {
195
240
  assert.strictEqual(body, 'ok');
196
241
  });
197
242
 
198
- it('should return 404 for unknown routes', async () => {
199
- const port = await get_free_port();
200
- server_instance = await Server.serve({
201
- Ctrl: Dummy_Control,
202
- src_path_client_js: dummy_client_path,
203
- host: '127.0.0.1',
204
- port
205
- });
206
- const { res, body } = await get_http_response(port, '/missing');
207
- assert.strictEqual(res.statusCode, 404);
208
- assert.strictEqual(body, 'Not Found');
209
- });
210
- });
243
+ it('should return 404 for unknown routes', async () => {
244
+ const port = await get_free_port();
245
+ server_instance = await Server.serve({
246
+ Ctrl: Dummy_Control,
247
+ src_path_client_js: dummy_client_path,
248
+ host: '127.0.0.1',
249
+ port
250
+ });
251
+ const { res, body } = await get_http_response(port, '/missing');
252
+ assert.strictEqual(res.statusCode, 404);
253
+ assert.strictEqual(body, 'Not Found');
254
+ });
255
+
256
+ it('waits for webpage publisher readiness before resolving serve()', async () => {
257
+ Fake_Webpage_Publisher.ready_delay_ms = 2600;
258
+
259
+ const port = await get_free_port();
260
+ const started_at = Date.now();
261
+
262
+ server_instance = await Server.serve({
263
+ Ctrl: Dummy_Control,
264
+ src_path_client_js: dummy_client_path,
265
+ host: '127.0.0.1',
266
+ port,
267
+ readyTimeoutMs: 12000
268
+ });
269
+
270
+ const elapsed_ms = Date.now() - started_at;
271
+ assert(
272
+ elapsed_ms >= 2400,
273
+ `Expected serve() to wait for delayed readiness, elapsed=${elapsed_ms}ms`
274
+ );
275
+
276
+ const { res, body } = await get_http_response(port, '/');
277
+ assert.strictEqual(res.statusCode, 200);
278
+ assert(body.includes('<div class="dummy-control"'));
279
+ });
280
+ });
@@ -0,0 +1,106 @@
1
+ const assert = require('assert');
2
+ const { describe, it } = require('mocha');
3
+ const { Resource } = require('jsgui3-html');
4
+
5
+ const Server_Resource_Pool = require('../resources/server-resource-pool');
6
+
7
+ class Dummy_Managed_Resource extends Resource {
8
+ constructor(spec = {}) {
9
+ super(spec);
10
+ this.current_state = spec.initial_state || 'stopped';
11
+ this.start_calls = 0;
12
+ this.stop_calls = 0;
13
+ }
14
+
15
+ start(callback) {
16
+ const previous_state = this.current_state;
17
+ this.start_calls += 1;
18
+ this.current_state = 'running';
19
+ this.raise('state_change', {
20
+ from: previous_state,
21
+ to: this.current_state,
22
+ timestamp: Date.now()
23
+ });
24
+ if (typeof callback === 'function') callback(null, true);
25
+ return Promise.resolve(true);
26
+ }
27
+
28
+ stop(callback) {
29
+ const previous_state = this.current_state;
30
+ this.stop_calls += 1;
31
+ this.current_state = 'stopped';
32
+ this.raise('state_change', {
33
+ from: previous_state,
34
+ to: this.current_state,
35
+ timestamp: Date.now()
36
+ });
37
+ if (typeof callback === 'function') callback(null, true);
38
+ return Promise.resolve(true);
39
+ }
40
+
41
+ get status() {
42
+ return {
43
+ state: this.current_state
44
+ };
45
+ }
46
+
47
+ get_abstract() {
48
+ return {
49
+ name: this.name,
50
+ state: this.current_state
51
+ };
52
+ }
53
+ }
54
+
55
+ describe('Server_Resource_Pool lifecycle', function() {
56
+ it('forwards resource state events and supports removal with stop()', async () => {
57
+ const resource_pool = new Server_Resource_Pool();
58
+ const managed_resource = new Dummy_Managed_Resource({
59
+ name: 'managed_resource_one'
60
+ });
61
+
62
+ resource_pool.add(managed_resource);
63
+
64
+ const forwarded_events = [];
65
+ resource_pool.on('resource_state_change', (event_data) => {
66
+ forwarded_events.push(event_data);
67
+ });
68
+
69
+ await managed_resource.start();
70
+ assert(forwarded_events.some((event_data) => event_data.resourceName === 'managed_resource_one' && event_data.to === 'running'));
71
+
72
+ const did_remove = await resource_pool.remove('managed_resource_one');
73
+ assert.strictEqual(did_remove, true);
74
+ assert.strictEqual(managed_resource.stop_calls > 0, true);
75
+ assert.strictEqual(resource_pool.has_resource('managed_resource_one'), false);
76
+ });
77
+
78
+ it('supports stop-all and summary aggregation', async () => {
79
+ const resource_pool = new Server_Resource_Pool();
80
+
81
+ const resource_one = new Dummy_Managed_Resource({
82
+ name: 'managed_resource_a',
83
+ initial_state: 'running'
84
+ });
85
+ const resource_two = new Dummy_Managed_Resource({
86
+ name: 'managed_resource_b',
87
+ initial_state: 'crashed'
88
+ });
89
+
90
+ resource_pool.add(resource_one);
91
+ resource_pool.add(resource_two);
92
+
93
+ const by_constructor = resource_pool.get_resources_by_type(Dummy_Managed_Resource);
94
+ const by_name = resource_pool.get_resources_by_type('Dummy_Managed_Resource');
95
+ assert.strictEqual(by_constructor.length >= 2, true);
96
+ assert.strictEqual(by_name.length >= 2, true);
97
+
98
+ const summary_before_stop = resource_pool.summary;
99
+ assert(summary_before_stop.byType.Dummy_Managed_Resource);
100
+ assert(summary_before_stop.byType.Dummy_Managed_Resource.some((entry) => entry.name === 'managed_resource_a'));
101
+
102
+ await resource_pool.stop();
103
+ assert.strictEqual(resource_one.stop_calls > 0, true);
104
+ assert.strictEqual(resource_two.stop_calls > 0, true);
105
+ });
106
+ });