jsgui3-server 0.0.143 → 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/comprehensive-documentation.md +25 -6
- package/docs/configuration-reference.md +46 -11
- package/docs/controls-development.md +54 -26
- package/docs/jsgui3-html-improvement-ideas.md +162 -0
- package/docs/jsgui3-html-improvement-ideas.svg +151 -0
- package/docs/troubleshooting.md +9 -8
- 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/lab/README.md +19 -0
- package/lab/experiments/window_examples_dom_audit.js +241 -0
- package/lab/results/window_examples_dom_audit.json +131 -0
- package/lab/results/window_examples_dom_audit.md +46 -0
- package/package.json +8 -3
- package/publishers/http-webpageorsite-publisher.js +8 -4
- package/resources/processors/bundlers/css-bundler.js +28 -173
- package/resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild.js +32 -20
- package/resources/processors/bundlers/style-bundler.js +288 -0
- package/resources/processors/compilers/SASS_Compiler.js +88 -0
- package/resources/processors/extractors/js/css_and_js/AST_Node/CSS_And_JS_From_JS_String_Using_AST_Node_Extractor.js +64 -68
- package/resources/website-css-resource.js +24 -20
- package/resources/website-javascript-resource-processor.js +17 -57
- package/resources/website-javascript-resource.js +17 -57
- package/serve-factory.js +38 -24
- package/server.js +116 -92
- package/tests/README.md +38 -3
- package/tests/bundlers.test.js +41 -32
- package/tests/content-analysis.test.js +19 -18
- package/tests/end-to-end.test.js +336 -365
- package/tests/error-handling.test.js +13 -11
- 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/sass-controls.e2e.test.js +327 -0
- package/tests/test-runner.js +4 -1
- package/tests/window-examples.puppeteer.test.js +455 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const http = require('http');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs').promises;
|
|
5
|
+
const { describe, it, before } = require('mocha');
|
|
6
|
+
|
|
7
|
+
const Server = require('../server');
|
|
8
|
+
const { get_free_port } = require('../port-utils');
|
|
9
|
+
|
|
10
|
+
const style_config = {
|
|
11
|
+
sourcemaps: {
|
|
12
|
+
enabled: true,
|
|
13
|
+
inline: true,
|
|
14
|
+
include_sources: true
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
let sass_available = true;
|
|
19
|
+
try {
|
|
20
|
+
require('sass');
|
|
21
|
+
} catch (error) {
|
|
22
|
+
sass_available = false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const sass_mix_client_js = [
|
|
26
|
+
"const jsgui = require('jsgui3-html');",
|
|
27
|
+
"const { Control } = jsgui;",
|
|
28
|
+
"const { controls } = jsgui;",
|
|
29
|
+
"",
|
|
30
|
+
"class Sass_Mix_Control extends Control {",
|
|
31
|
+
" constructor(spec = {}) {",
|
|
32
|
+
" super(spec);",
|
|
33
|
+
" const { context } = this;",
|
|
34
|
+
" if (!spec.el) {",
|
|
35
|
+
" const container = new controls.div({ context, class: 'sass-mix-control' });",
|
|
36
|
+
" this.add(container);",
|
|
37
|
+
" }",
|
|
38
|
+
" }",
|
|
39
|
+
"}",
|
|
40
|
+
"",
|
|
41
|
+
"Sass_Mix_Control.css = `",
|
|
42
|
+
".sass-mix-control { border-color: red; }",
|
|
43
|
+
"`;",
|
|
44
|
+
"",
|
|
45
|
+
"Sass_Mix_Control.scss = `",
|
|
46
|
+
"$accent_color: #ff7700;",
|
|
47
|
+
".sass-mix-control {",
|
|
48
|
+
" border-color: blue;",
|
|
49
|
+
" background: $accent_color;",
|
|
50
|
+
" &:hover { color: #222; }",
|
|
51
|
+
"}",
|
|
52
|
+
"`;",
|
|
53
|
+
"",
|
|
54
|
+
"controls.Sass_Mix_Control = Sass_Mix_Control;",
|
|
55
|
+
"module.exports = jsgui;",
|
|
56
|
+
""
|
|
57
|
+
].join('\n');
|
|
58
|
+
|
|
59
|
+
const sass_only_client_js = [
|
|
60
|
+
"const jsgui = require('jsgui3-html');",
|
|
61
|
+
"const { Control } = jsgui;",
|
|
62
|
+
"const { controls } = jsgui;",
|
|
63
|
+
"",
|
|
64
|
+
"class Sass_Only_Control extends Control {",
|
|
65
|
+
" constructor(spec = {}) {",
|
|
66
|
+
" super(spec);",
|
|
67
|
+
" const { context } = this;",
|
|
68
|
+
" if (!spec.el) {",
|
|
69
|
+
" const container = new controls.div({ context, class: 'sass-only-control' });",
|
|
70
|
+
" this.add(container);",
|
|
71
|
+
" }",
|
|
72
|
+
" }",
|
|
73
|
+
"}",
|
|
74
|
+
"",
|
|
75
|
+
"Sass_Only_Control.sass = `",
|
|
76
|
+
"$primary_color: #336699",
|
|
77
|
+
".sass-only-control",
|
|
78
|
+
" color: $primary_color",
|
|
79
|
+
" &:hover",
|
|
80
|
+
" color: #000000",
|
|
81
|
+
"`;",
|
|
82
|
+
"",
|
|
83
|
+
"controls.Sass_Only_Control = Sass_Only_Control;",
|
|
84
|
+
"module.exports = jsgui;",
|
|
85
|
+
""
|
|
86
|
+
].join('\n');
|
|
87
|
+
|
|
88
|
+
const sass_css_mix_client_js = [
|
|
89
|
+
"const jsgui = require('jsgui3-html');",
|
|
90
|
+
"const { Control } = jsgui;",
|
|
91
|
+
"const { controls } = jsgui;",
|
|
92
|
+
"",
|
|
93
|
+
"class Sass_Css_Mix_Control extends Control {",
|
|
94
|
+
" constructor(spec = {}) {",
|
|
95
|
+
" super(spec);",
|
|
96
|
+
" const { context } = this;",
|
|
97
|
+
" if (!spec.el) {",
|
|
98
|
+
" const container = new controls.div({ context, class: 'sass-css-mix-control' });",
|
|
99
|
+
" this.add(container);",
|
|
100
|
+
" }",
|
|
101
|
+
" }",
|
|
102
|
+
"}",
|
|
103
|
+
"",
|
|
104
|
+
"Sass_Css_Mix_Control.css = `",
|
|
105
|
+
".sass-css-mix-control { padding: 4px; }",
|
|
106
|
+
"`;",
|
|
107
|
+
"",
|
|
108
|
+
"Sass_Css_Mix_Control.sass = `",
|
|
109
|
+
"$mix_color: #123456",
|
|
110
|
+
".sass-css-mix-control",
|
|
111
|
+
" color: $mix_color",
|
|
112
|
+
"`;",
|
|
113
|
+
"",
|
|
114
|
+
"controls.Sass_Css_Mix_Control = Sass_Css_Mix_Control;",
|
|
115
|
+
"module.exports = jsgui;",
|
|
116
|
+
""
|
|
117
|
+
].join('\n');
|
|
118
|
+
|
|
119
|
+
const make_request = (url, { headers = {} } = {}) => {
|
|
120
|
+
return new Promise((resolve, reject) => {
|
|
121
|
+
const parsed_url = new URL(url);
|
|
122
|
+
const options = {
|
|
123
|
+
hostname: parsed_url.hostname,
|
|
124
|
+
port: parsed_url.port,
|
|
125
|
+
path: parsed_url.pathname,
|
|
126
|
+
method: 'GET',
|
|
127
|
+
headers: {
|
|
128
|
+
'User-Agent': 'JSGUI3-Sass-E2E/1.0',
|
|
129
|
+
...headers
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const req = http.request(options, (res) => {
|
|
134
|
+
const chunks = [];
|
|
135
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
136
|
+
res.on('end', () => {
|
|
137
|
+
resolve({
|
|
138
|
+
status_code: res.statusCode,
|
|
139
|
+
headers: res.headers,
|
|
140
|
+
body: Buffer.concat(chunks).toString('utf8')
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
req.on('error', reject);
|
|
146
|
+
req.setTimeout(15000, () => {
|
|
147
|
+
req.destroy();
|
|
148
|
+
reject(new Error('Request timeout'));
|
|
149
|
+
});
|
|
150
|
+
req.end();
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const write_temp_client_file = async (file_name, file_contents) => {
|
|
155
|
+
const file_path = path.join(__dirname, file_name);
|
|
156
|
+
await fs.writeFile(file_path, file_contents, 'utf8');
|
|
157
|
+
return file_path;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const remove_file_if_exists = async (file_path) => {
|
|
161
|
+
try {
|
|
162
|
+
await fs.unlink(file_path);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
if (error.code !== 'ENOENT') {
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const start_test_server = async ({ client_path, control_name }) => {
|
|
171
|
+
delete require.cache[require.resolve(client_path)];
|
|
172
|
+
const client_module = require(client_path);
|
|
173
|
+
const ctrl = client_module.controls && client_module.controls[control_name];
|
|
174
|
+
assert(ctrl, `Missing exported control jsgui.controls.${control_name} in ${client_path}`);
|
|
175
|
+
|
|
176
|
+
const server = new Server({
|
|
177
|
+
Ctrl: ctrl,
|
|
178
|
+
src_path_client_js: client_path,
|
|
179
|
+
name: `tests/${control_name}`,
|
|
180
|
+
debug: true,
|
|
181
|
+
style: style_config
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
server.allowed_addresses = ['127.0.0.1'];
|
|
185
|
+
|
|
186
|
+
await new Promise((resolve, reject) => {
|
|
187
|
+
const timeout = setTimeout(() => reject(new Error('Publisher ready timeout')), 60000);
|
|
188
|
+
server.on('ready', () => {
|
|
189
|
+
clearTimeout(timeout);
|
|
190
|
+
resolve();
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const port = await get_free_port();
|
|
195
|
+
await new Promise((resolve, reject) => {
|
|
196
|
+
server.start(port, (err) => (err ? reject(err) : resolve()));
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
return { server, port };
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const close_server = async (server) => {
|
|
203
|
+
await new Promise((resolve) => server.close(resolve));
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const extract_inline_sourcemap = (css_text) => {
|
|
207
|
+
const sourcemap_match = css_text.match(/\/\*# sourceMappingURL=data:application\/json;base64,([A-Za-z0-9+/=]+)\s*\*\//);
|
|
208
|
+
assert(sourcemap_match, 'Expected inline CSS sourcemap comment');
|
|
209
|
+
const sourcemap_json = Buffer.from(sourcemap_match[1], 'base64').toString('utf8');
|
|
210
|
+
return JSON.parse(sourcemap_json);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const sourcemap_contains = (sourcemap, needle) => {
|
|
214
|
+
const sources_content = Array.isArray(sourcemap.sourcesContent) ? sourcemap.sourcesContent : [];
|
|
215
|
+
return sources_content.some((content) => typeof content === 'string' && content.includes(needle));
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
describe('Sass/CSS Control E2E Tests', function() {
|
|
219
|
+
this.timeout(90000);
|
|
220
|
+
|
|
221
|
+
before(function() {
|
|
222
|
+
if (!sass_available) {
|
|
223
|
+
this.skip();
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('should compile mixed css + scss in order with sourcemaps', async function() {
|
|
228
|
+
const client_path = await write_temp_client_file('temp_sass_mix_client.js', sass_mix_client_js);
|
|
229
|
+
const { server, port } = await start_test_server({
|
|
230
|
+
client_path,
|
|
231
|
+
control_name: 'Sass_Mix_Control'
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
const base_url = `http://127.0.0.1:${port}`;
|
|
236
|
+
const css_response = await make_request(`${base_url}/css/css.css`);
|
|
237
|
+
assert.strictEqual(css_response.status_code, 200);
|
|
238
|
+
assert((css_response.headers['content-type'] || '').includes('css'), 'Expected CSS content-type');
|
|
239
|
+
|
|
240
|
+
const css_text = css_response.body;
|
|
241
|
+
const red_index = css_text.indexOf('border-color: red');
|
|
242
|
+
const blue_index = css_text.indexOf('border-color: blue');
|
|
243
|
+
assert(red_index !== -1, 'Expected CSS from .css template literal');
|
|
244
|
+
assert(blue_index !== -1, 'Expected CSS from .scss template literal');
|
|
245
|
+
assert(red_index < blue_index, 'Expected .css output to precede .scss output');
|
|
246
|
+
assert(css_text.includes('background: #ff7700'), 'Expected SCSS variable compilation');
|
|
247
|
+
assert(css_text.includes('.sass-mix-control:hover'), 'Expected nested SCSS selector output');
|
|
248
|
+
|
|
249
|
+
const css_sourcemap = extract_inline_sourcemap(css_text);
|
|
250
|
+
assert(Array.isArray(css_sourcemap.sources), 'Expected sourcemap sources array');
|
|
251
|
+
assert(Array.isArray(css_sourcemap.sourcesContent), 'Expected sourcemap sourcesContent array');
|
|
252
|
+
assert(sourcemap_contains(css_sourcemap, '$accent_color'), 'Expected sourcemap to include SCSS source content');
|
|
253
|
+
assert(sourcemap_contains(css_sourcemap, 'border-color: red'), 'Expected sourcemap to include CSS source content');
|
|
254
|
+
|
|
255
|
+
const js_response = await make_request(`${base_url}/js/js.js`);
|
|
256
|
+
assert.strictEqual(js_response.status_code, 200);
|
|
257
|
+
assert(!js_response.body.includes('border-color: red'), 'Expected CSS template literal to be stripped from JS');
|
|
258
|
+
assert(!js_response.body.includes('$accent_color'), 'Expected SCSS template literal to be stripped from JS');
|
|
259
|
+
} finally {
|
|
260
|
+
await close_server(server);
|
|
261
|
+
await remove_file_if_exists(client_path);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('should compile indented sass with sourcemaps', async function() {
|
|
266
|
+
const client_path = await write_temp_client_file('temp_sass_only_client.js', sass_only_client_js);
|
|
267
|
+
const { server, port } = await start_test_server({
|
|
268
|
+
client_path,
|
|
269
|
+
control_name: 'Sass_Only_Control'
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
const base_url = `http://127.0.0.1:${port}`;
|
|
274
|
+
const css_response = await make_request(`${base_url}/css/css.css`);
|
|
275
|
+
assert.strictEqual(css_response.status_code, 200);
|
|
276
|
+
assert((css_response.headers['content-type'] || '').includes('css'), 'Expected CSS content-type');
|
|
277
|
+
|
|
278
|
+
const css_text = css_response.body;
|
|
279
|
+
assert(css_text.includes('.sass-only-control'), 'Expected Sass selector output');
|
|
280
|
+
assert(css_text.includes('color: #336699'), 'Expected Sass variable compilation');
|
|
281
|
+
assert(css_text.includes('.sass-only-control:hover'), 'Expected nested Sass selector output');
|
|
282
|
+
|
|
283
|
+
const css_sourcemap = extract_inline_sourcemap(css_text);
|
|
284
|
+
assert(Array.isArray(css_sourcemap.sources), 'Expected sourcemap sources array');
|
|
285
|
+
assert(Array.isArray(css_sourcemap.sourcesContent), 'Expected sourcemap sourcesContent array');
|
|
286
|
+
assert(sourcemap_contains(css_sourcemap, '$primary_color'), 'Expected sourcemap to include Sass source content');
|
|
287
|
+
|
|
288
|
+
const js_response = await make_request(`${base_url}/js/js.js`);
|
|
289
|
+
assert.strictEqual(js_response.status_code, 200);
|
|
290
|
+
assert(!js_response.body.includes('$primary_color'), 'Expected Sass template literal to be stripped from JS');
|
|
291
|
+
} finally {
|
|
292
|
+
await close_server(server);
|
|
293
|
+
await remove_file_if_exists(client_path);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('should compile mixed css + sass in order without inaccurate sourcemaps', async function() {
|
|
298
|
+
const client_path = await write_temp_client_file('temp_sass_css_mix_client.js', sass_css_mix_client_js);
|
|
299
|
+
const { server, port } = await start_test_server({
|
|
300
|
+
client_path,
|
|
301
|
+
control_name: 'Sass_Css_Mix_Control'
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
const base_url = `http://127.0.0.1:${port}`;
|
|
306
|
+
const css_response = await make_request(`${base_url}/css/css.css`);
|
|
307
|
+
assert.strictEqual(css_response.status_code, 200);
|
|
308
|
+
assert((css_response.headers['content-type'] || '').includes('css'), 'Expected CSS content-type');
|
|
309
|
+
|
|
310
|
+
const css_text = css_response.body;
|
|
311
|
+
const padding_index = css_text.indexOf('padding: 4px');
|
|
312
|
+
const color_index = css_text.indexOf('color: #123456');
|
|
313
|
+
assert(padding_index !== -1, 'Expected CSS output from .css template literal');
|
|
314
|
+
assert(color_index !== -1, 'Expected Sass output from .sass template literal');
|
|
315
|
+
assert(padding_index < color_index, 'Expected .css output to precede .sass output');
|
|
316
|
+
assert(!css_text.includes('/*# sourceMappingURL='), 'Expected no inline sourcemap for mixed css + sass compilation');
|
|
317
|
+
|
|
318
|
+
const js_response = await make_request(`${base_url}/js/js.js`);
|
|
319
|
+
assert.strictEqual(js_response.status_code, 200);
|
|
320
|
+
assert(!js_response.body.includes('padding: 4px'), 'Expected CSS template literal to be stripped from JS');
|
|
321
|
+
assert(!js_response.body.includes('$mix_color'), 'Expected Sass template literal to be stripped from JS');
|
|
322
|
+
} finally {
|
|
323
|
+
await close_server(server);
|
|
324
|
+
await remove_file_if_exists(client_path);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
});
|
package/tests/test-runner.js
CHANGED
|
@@ -31,7 +31,10 @@ class TestRunner {
|
|
|
31
31
|
'content-analysis.test.js',
|
|
32
32
|
'performance.test.js',
|
|
33
33
|
'error-handling.test.js',
|
|
34
|
-
'examples-controls.e2e.test.js'
|
|
34
|
+
'examples-controls.e2e.test.js',
|
|
35
|
+
'sass-controls.e2e.test.js',
|
|
36
|
+
'jsgui3-html-examples.puppeteer.test.js',
|
|
37
|
+
'window-examples.puppeteer.test.js'
|
|
35
38
|
];
|
|
36
39
|
}
|
|
37
40
|
|