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.
Files changed (31) hide show
  1. package/docs/comprehensive-documentation.md +25 -6
  2. package/docs/configuration-reference.md +46 -11
  3. package/docs/controls-development.md +54 -26
  4. package/docs/publishers-guide.md +48 -32
  5. package/docs/troubleshooting.md +9 -8
  6. package/examples/controls/15) window, observable SSE/README.md +29 -17
  7. package/lab/README.md +19 -0
  8. package/lab/experiments/window_examples_dom_audit.js +241 -0
  9. package/lab/results/window_examples_dom_audit.json +131 -0
  10. package/lab/results/window_examples_dom_audit.md +46 -0
  11. package/package.json +7 -2
  12. package/publishers/http-observable-publisher.js +318 -125
  13. package/publishers/http-webpageorsite-publisher.js +6 -4
  14. package/resources/processors/bundlers/css-bundler.js +28 -173
  15. package/resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild.js +32 -20
  16. package/resources/processors/bundlers/style-bundler.js +288 -0
  17. package/resources/processors/compilers/SASS_Compiler.js +88 -0
  18. package/resources/processors/extractors/js/css_and_js/AST_Node/CSS_And_JS_From_JS_String_Using_AST_Node_Extractor.js +64 -68
  19. package/resources/website-css-resource.js +24 -20
  20. package/resources/website-javascript-resource-processor.js +17 -57
  21. package/resources/website-javascript-resource.js +17 -57
  22. package/serve-factory.js +28 -21
  23. package/server.js +13 -7
  24. package/tests/README.md +31 -3
  25. package/tests/bundlers.test.js +41 -32
  26. package/tests/content-analysis.test.js +19 -18
  27. package/tests/error-handling.test.js +13 -11
  28. package/tests/observable-sse.test.js +5 -5
  29. package/tests/sass-controls.e2e.test.js +327 -0
  30. package/tests/test-runner.js +3 -1
  31. 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
 
@@ -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
- // Test JavaScript with CSS
21
- const css = \`
22
- .test-class {
23
- color: red;
24
- font-size: 14px;
25
- }
26
- \`;
27
-
28
- // Add CSS to document
29
- const style = document.createElement('style');
30
- style.textContent = css;
31
- document.head.appendChild(style);
32
-
33
- // Test function
34
- function testFunction() {
35
- console.log('Hello from test function');
36
- return 'test result';
37
- }
38
-
39
- // Export for testing
40
- module.exports = { testFunction };
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
- // Verify JS no longer contains CSS
250
- assert(!jsItem.text.includes('.test-class'), 'JS should not contain CSS after extraction');
251
- assert(jsItem.text.includes('testFunction'), 'JS should still contain the test function');
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
- // Some CSS embedded in JS
50
- const css = \`
51
- .test-class {
52
- background-color: #ffffff;
53
- border: 1px solid #cccccc;
54
- padding: 10px;
55
- margin: 5px;
56
- }
57
- .test-class:hover {
58
- background-color: #f0f0f0;
59
- }
60
- \`;
61
-
62
- // Add CSS to document
63
- const style = document.createElement('style');
64
- style.textContent = css;
65
- document.head.appendChild(style);
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
- const css = \`
198
- .test-class {
199
- color: red;
200
- /* Missing closing brace
201
- background: blue
202
- \`;
203
-
204
- console.log('test');
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(30000); // Allow time for server startup and SSE streaming
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
- }, 15000);
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
+ });
@@ -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