Skip to content

Commit f16465f

Browse files
authored
Merge pull request #228 from mi-examples/pp-3990
fix: Variables Editor client script fails to parse, blanking the whole UI
2 parents 813d565 + 2e48b2e commit f16465f

2 files changed

Lines changed: 22 additions & 1 deletion

File tree

src/lib/variables-editor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,7 @@ function escapeHtml(s) {
526526
// (see DistService.saveTemplateVariablesFile). Backslash/quote-escape for the JS-string
527527
// context first, then HTML-escape the result so it can't break out of the attribute either.
528528
function escapeJsAttr(s) {
529-
return escapeHtml(String(s).replace(/\\/g, '\\\\').replace(/'/g, "\\'"));
529+
return escapeHtml(String(s).replace(/\\\\/g, '\\\\\\\\').replace(/'/g, "\\\\'"));
530530
}
531531
532532
function showBanner(type, html) {

tests/unit/lib/variables-editor.spec.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,5 +341,26 @@ describe('registerVariablesEditorRoutes', () => {
341341
expect(res.body).not.toContain('id="tab-schema"');
342342
expect(res.body).not.toContain('id="save-btn"');
343343
});
344+
345+
// Every `<script>` block is built via string concatenation inside one giant template
346+
// literal — a backslash-escaping mistake there (e.g. a regex like /\\/) is invisible to
347+
// `tsc`/eslint (it's just characters inside a string) and only breaks at runtime when the
348+
// browser parses it, which none of the tests above would ever catch. `new Function` parses
349+
// without executing, so this fails fast on any embedded-script SyntaxError.
350+
it('embeds syntactically valid client-side JavaScript in every <script> block', async () => {
351+
const app = register({ miAPI: { isTemplateLess: false } as unknown as MiAPI });
352+
const res = makeRes();
353+
354+
app.handlers.get(`GET ${VARIABLES_EDITOR_PATH}`)!({}, res);
355+
356+
const html = res.body as string;
357+
const scripts = [...html.matchAll(/<script>([\s\S]*?)<\/script>/g)].map((m) => m[1]);
358+
359+
expect(scripts.length).toBeGreaterThan(0);
360+
361+
for (const script of scripts) {
362+
expect(() => new Function(script)).not.toThrow();
363+
}
364+
});
344365
});
345366
});

0 commit comments

Comments
 (0)