jsgui3-server 0.0.142 → 0.0.144
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/publishers-guide.md +48 -32
- package/docs/troubleshooting.md +9 -8
- package/examples/controls/15) window, observable SSE/README.md +29 -17
- 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 +7 -2
- package/publishers/http-observable-publisher.js +318 -125
- package/publishers/http-webpageorsite-publisher.js +6 -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 +28 -21
- package/server.js +13 -7
- package/tests/README.md +31 -3
- package/tests/bundlers.test.js +41 -32
- package/tests/content-analysis.test.js +19 -18
- package/tests/error-handling.test.js +13 -11
- package/tests/observable-sse.test.js +5 -5
- package/tests/sass-controls.e2e.test.js +327 -0
- package/tests/test-runner.js +3 -1
- package/tests/window-examples.puppeteer.test.js +239 -0
package/tests/README.md
CHANGED
|
@@ -26,6 +26,8 @@ tests/
|
|
|
26
26
|
├── performance.test.js # Performance benchmarks
|
|
27
27
|
├── error-handling.test.js # Error handling and edge cases
|
|
28
28
|
├── examples-controls.e2e.test.js # Example apps regression (controls)
|
|
29
|
+
├── sass-controls.e2e.test.js # Sass/CSS controls E2E coverage
|
|
30
|
+
├── window-examples.puppeteer.test.js # Puppeteer interaction tests (window examples)
|
|
29
31
|
├── test-runner.js # Custom test runner with reporting
|
|
30
32
|
└── README.md # This file
|
|
31
33
|
```
|
|
@@ -51,6 +53,11 @@ npx mocha tests/bundlers.test.js
|
|
|
51
53
|
npm run test:examples:controls
|
|
52
54
|
```
|
|
53
55
|
|
|
56
|
+
### Run Puppeteer Window Example Tests
|
|
57
|
+
```bash
|
|
58
|
+
npm run test:puppeteer:windows
|
|
59
|
+
```
|
|
60
|
+
|
|
54
61
|
### Run Tests with Options
|
|
55
62
|
```bash
|
|
56
63
|
# Debug mode (enables sourcemaps)
|
|
@@ -132,6 +139,26 @@ Regression coverage for a representative set of `examples/controls/*` apps:
|
|
|
132
139
|
- Verifies `/`, `/js/js.js`, and `/css/css.css` routes
|
|
133
140
|
- Ensures HTML rendering works without `Accept-Encoding`
|
|
134
141
|
|
|
142
|
+
### 8. Puppeteer Window Example Tests (`window-examples.puppeteer.test.js`)
|
|
143
|
+
|
|
144
|
+
Browser-level interaction checks for selected window examples:
|
|
145
|
+
|
|
146
|
+
- Minimize/restore window state (class toggling)
|
|
147
|
+
- Tabbed panel tab switching visibility
|
|
148
|
+
- Checkbox label toggling checked state
|
|
149
|
+
- Date picker month header rendering
|
|
150
|
+
|
|
151
|
+
### 9. Sass/CSS Controls E2E Tests (`sass-controls.e2e.test.js`)
|
|
152
|
+
|
|
153
|
+
Server-level integration tests for controls that define `.scss` or `.sass` styles:
|
|
154
|
+
|
|
155
|
+
- Verifies CSS + SCSS mixing order in bundled CSS output
|
|
156
|
+
- Confirms indented Sass compiles and is removed from JS bundles
|
|
157
|
+
- Checks inline CSS sourcemaps include original Sass/SCSS sources
|
|
158
|
+
- Ensures mixed CSS + Sass output preserves order without emitting inaccurate sourcemaps
|
|
159
|
+
|
|
160
|
+
Note: These tests are skipped if the `sass` dependency is not installed.
|
|
161
|
+
|
|
135
162
|
## Configuration Examples
|
|
136
163
|
|
|
137
164
|
### Basic Minification
|
|
@@ -205,12 +232,13 @@ Total Test Suites: 8
|
|
|
205
232
|
🎉 All tests passed! The minification, compression, and sourcemap features are working correctly.
|
|
206
233
|
```
|
|
207
234
|
|
|
208
|
-
## Environment Requirements
|
|
235
|
+
## Environment Requirements
|
|
209
236
|
|
|
210
237
|
- Node.js >= 15.0.0
|
|
211
238
|
- Mocha test framework (included in package.json)
|
|
212
|
-
- ESBuild (included in package.json)
|
|
213
|
-
- zlib (built-in Node.js module)
|
|
239
|
+
- ESBuild (included in package.json)
|
|
240
|
+
- zlib (built-in Node.js module)
|
|
241
|
+
- Puppeteer (dev dependency; set `PUPPETEER_EXECUTABLE_PATH` to a local Chrome/Chromium if downloads are disabled)
|
|
214
242
|
|
|
215
243
|
## Troubleshooting
|
|
216
244
|
|
package/tests/bundlers.test.js
CHANGED
|
@@ -16,29 +16,34 @@ this.timeout(30000); // Increase timeout for bundling operations
|
|
|
16
16
|
|
|
17
17
|
beforeEach(async function() {
|
|
18
18
|
// Create a temporary test JS file
|
|
19
|
-
testJsContent = `
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
19
|
+
testJsContent = `
|
|
20
|
+
class Test_Class {
|
|
21
|
+
constructor() {}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
Test_Class.css = \`
|
|
25
|
+
.test-class {
|
|
26
|
+
color: red;
|
|
27
|
+
font-size: 14px;
|
|
28
|
+
}
|
|
29
|
+
\`;
|
|
30
|
+
|
|
31
|
+
Test_Class.scss = \`
|
|
32
|
+
$accent-color: #33aacc;
|
|
33
|
+
.sass-class {
|
|
34
|
+
color: $accent-color;
|
|
35
|
+
}
|
|
36
|
+
\`;
|
|
37
|
+
|
|
38
|
+
// Test function
|
|
39
|
+
function testFunction() {
|
|
40
|
+
console.log('Hello from test function');
|
|
41
|
+
return 'test result';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Export for testing
|
|
45
|
+
module.exports = { testFunction, Test_Class };
|
|
46
|
+
`;
|
|
42
47
|
|
|
43
48
|
testJsFile = path.join(__dirname, 'temp_test.js');
|
|
44
49
|
await fs.writeFile(testJsFile, testJsContent);
|
|
@@ -242,14 +247,18 @@ this.timeout(30000); // Increase timeout for bundling operations
|
|
|
242
247
|
assert.strictEqual(jsItem.extension, 'js');
|
|
243
248
|
assert.strictEqual(cssItem.extension, 'css');
|
|
244
249
|
|
|
245
|
-
// Verify CSS was extracted
|
|
246
|
-
assert(cssItem.text.includes('.test-class'), 'CSS should contain the test class');
|
|
247
|
-
assert(cssItem.text.includes('color: red'), 'CSS should contain the color property');
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
250
|
+
// Verify CSS was extracted
|
|
251
|
+
assert(cssItem.text.includes('.test-class'), 'CSS should contain the test class');
|
|
252
|
+
assert(cssItem.text.includes('color: red'), 'CSS should contain the color property');
|
|
253
|
+
assert(cssItem.text.includes('.sass-class'), 'CSS should contain the SCSS class');
|
|
254
|
+
assert(cssItem.text.includes('#33aacc'), 'CSS should contain compiled SCSS color value');
|
|
255
|
+
|
|
256
|
+
// Verify JS no longer contains CSS
|
|
257
|
+
assert(!jsItem.text.includes('.test-class'), 'JS should not contain CSS after extraction');
|
|
258
|
+
assert(!jsItem.text.includes('.sass-class'), 'JS should not contain SCSS after extraction');
|
|
259
|
+
assert(!jsItem.text.includes('$accent-color'), 'JS should not contain SCSS variables');
|
|
260
|
+
assert(jsItem.text.includes('testFunction'), 'JS should still contain the test function');
|
|
261
|
+
});
|
|
253
262
|
|
|
254
263
|
it('should handle debug mode with sourcemaps', async function() {
|
|
255
264
|
const bundler = new Advanced_JS_Bundler_Using_ESBuild({
|
|
@@ -326,4 +335,4 @@ this.timeout(30000); // Increase timeout for bundling operations
|
|
|
326
335
|
}
|
|
327
336
|
});
|
|
328
337
|
});
|
|
329
|
-
});
|
|
338
|
+
});
|
|
@@ -46,23 +46,24 @@ describe('Content Analysis Tests', function() {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
49
|
+
TestClass.css = \`
|
|
50
|
+
.test-class {
|
|
51
|
+
background-color: #ffffff;
|
|
52
|
+
border: 1px solid #cccccc;
|
|
53
|
+
padding: 10px;
|
|
54
|
+
margin: 5px;
|
|
55
|
+
}
|
|
56
|
+
.test-class:hover {
|
|
57
|
+
background-color: #f0f0f0;
|
|
58
|
+
}
|
|
59
|
+
\`;
|
|
60
|
+
|
|
61
|
+
TestClass.scss = \`
|
|
62
|
+
$accent-color: #33aacc;
|
|
63
|
+
.sass-class {
|
|
64
|
+
color: $accent-color;
|
|
65
|
+
}
|
|
66
|
+
\`;
|
|
66
67
|
|
|
67
68
|
// Export
|
|
68
69
|
module.exports = { testFunction, testObject, TestClass };
|
|
@@ -638,4 +639,4 @@ describe('Content Analysis Tests', function() {
|
|
|
638
639
|
assert(brotliSize < originalSize * 0.5, 'Brotli should achieve >50% compression on repetitive content');
|
|
639
640
|
});
|
|
640
641
|
});
|
|
641
|
-
});
|
|
642
|
+
});
|
|
@@ -193,16 +193,18 @@ describe('Error Handling Tests', function() {
|
|
|
193
193
|
const bundler = new Advanced_JS_Bundler_Using_ESBuild();
|
|
194
194
|
|
|
195
195
|
// Create JS with malformed CSS
|
|
196
|
-
const malformedCssContent = `
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
196
|
+
const malformedCssContent = `
|
|
197
|
+
class Test_Class {}
|
|
198
|
+
|
|
199
|
+
Test_Class.css = \`
|
|
200
|
+
.test-class {
|
|
201
|
+
color: red;
|
|
202
|
+
/* Missing closing brace
|
|
203
|
+
background: blue
|
|
204
|
+
\`;
|
|
205
|
+
|
|
206
|
+
console.log('test');
|
|
207
|
+
`;
|
|
206
208
|
|
|
207
209
|
const malformedFile = path.join(__dirname, 'temp_malformed.js');
|
|
208
210
|
await fs.writeFile(malformedFile, malformedCssContent);
|
|
@@ -743,4 +745,4 @@ describe('Error Handling Tests', function() {
|
|
|
743
745
|
}
|
|
744
746
|
});
|
|
745
747
|
});
|
|
746
|
-
});
|
|
748
|
+
});
|
|
@@ -9,8 +9,8 @@ const http = require('http');
|
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const { get_free_port } = require('../port-utils');
|
|
11
11
|
|
|
12
|
-
describe('Observable SSE Demo E2E Tests', function() {
|
|
13
|
-
this.timeout(
|
|
12
|
+
describe('Observable SSE Demo E2E Tests', function() {
|
|
13
|
+
this.timeout(90000); // Allow time for bundling + server startup + SSE streaming
|
|
14
14
|
|
|
15
15
|
let server_process;
|
|
16
16
|
let server_port;
|
|
@@ -140,9 +140,9 @@ describe('Observable SSE Demo E2E Tests', function() {
|
|
|
140
140
|
// Wait for server to be ready
|
|
141
141
|
await new Promise((resolve, reject) => {
|
|
142
142
|
let output = '';
|
|
143
|
-
const timeout = setTimeout(() => {
|
|
144
|
-
reject(new Error('Server startup timeout'));
|
|
145
|
-
},
|
|
143
|
+
const timeout = setTimeout(() => {
|
|
144
|
+
reject(new Error('Server startup timeout'));
|
|
145
|
+
}, 45000);
|
|
146
146
|
|
|
147
147
|
server_process.stdout.on('data', (data) => {
|
|
148
148
|
output += data.toString();
|
|
@@ -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,9 @@ 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
|
+
'window-examples.puppeteer.test.js'
|
|
35
37
|
];
|
|
36
38
|
}
|
|
37
39
|
|