`)
+ const result = render(Harness, {
+ props: {
+ content: '
',
+ components: new Map([['Mdz', MdzComponent]]),
+ },
+ });
+ // SSR interleaves `` markers, so assert structure not an exact
+ // `
hi` slice: bold text is present, and a `
` (the
+ // inline wrapper, not a block ``) encloses the recursively-rendered
+ // bold
+ assert.include(result.body, '
');
+ assert.include(result.body, 'hi');
+ assert.match(result.body, /[\s\S]*?[\s\S]*?hi/);
+ });
+});
diff --git a/src/test/mdz_scaling.test.ts b/src/test/mdz_scaling.test.ts
index 9e876a7..ba7494d 100644
--- a/src/test/mdz_scaling.test.ts
+++ b/src/test/mdz_scaling.test.ts
@@ -1,8 +1,9 @@
/**
* Complexity regression guards for the parser's anti-quadratic / anti-
* exponential work — the one measurement class that's machine-state
- * independent enough to gate CI (see the "Measurement robustness" note in the
- * mdz perf lore). These are deliberately NOT wall-clock-threshold assertions
+ * independent enough to gate CI: complexity is a property of the algorithm,
+ * so it holds across machines, while throughput is not.
+ * These are deliberately NOT wall-clock-threshold assertions
* (the dev machine swings ~2×); each input is sized so the fixed parser
* finishes in well under a millisecond while a reverted (super-linear) parser
* would blow past the vitest timeout by orders of magnitude. The timeout is
@@ -88,3 +89,19 @@ describe('streaming held-candidate scans stay linear', () => {
assert.isAbove(feed_once('['.repeat(50_000) + 'x'), 0);
});
});
+
+// A single tag with a huge number of distinct attributes. The `Set`-based
+// duplicate-name check is O(1) per attribute, so the sync parse stays linear in
+// the attribute count; a reverted O(n) rescan of the collected list would be
+// O(n²) and blow past the timeout. The guard uses the sync parser: there the
+// dedup is the only super-linear risk, whereas the streaming-chunked path also
+// re-scans the held open-tag prefix per feed (the acknowledged residual term for
+// a long single line in small chunks, unrelated to the dedup).
+describe('tag attributes stay linear', () => {
+ const many_attributes = (n: number): string =>
+ ' `a${i}="${i}"`).join(' ') + '>x';
+
+ test('sync: many distinct attributes on one tag', {timeout: GUARD_TIMEOUT}, () => {
+ assert.isArray(mdz_parse(many_attributes(50_000)));
+ });
+});
diff --git a/src/test/mdz_scaling_ratio.test.ts b/src/test/mdz_scaling_ratio.test.ts
index 145d1a4..aa45d88 100644
--- a/src/test/mdz_scaling_ratio.test.ts
+++ b/src/test/mdz_scaling_ratio.test.ts
@@ -123,4 +123,31 @@ describe('parser scan work is sub-quadratic (deterministic)', () => {
(input) => stream_work(input, input.length + 1),
);
});
+
+ test('sync: many distinct attributes on one tag (attribute scan is linear)', () => {
+ // the counted scan work on an attribute-dense tag grows with input length,
+ // not its square (the `Set` dedup is O(1)/attr — its wall-clock guard is in
+ // `mdz_scaling.test.ts`; this pins the scan-work side)
+ assert_subquadratic(
+ 'many attributes',
+ [2000, 4000, 8000].map(
+ (n) =>
+ ' `a${i}="${i}"`).join(' ') +
+ '>x',
+ ),
+ sync_work,
+ );
+ });
+
+ test('streaming: hold-line attribute value (buffer_index_of memo)', () => {
+ // the attribute analog of hold-line code/link: an unterminated quoted value
+ // grows across feeds and the tag holds; the closing-quote/newline search must
+ // be memoized or chunked feeds go quadratic in the value length
+ assert_subquadratic(
+ 'hold attribute value',
+ [4000, 8000, 16000].map((chars) => 'hi', {}, {aside: true});
+ assert.equal(result.markup, ``);
+ });
+
+ test('drops a disallowed element attribute', () => {
+ const result = convert('', {}, {aside: true});
+ assert.equal(result.markup, ``);
+ assert.notInclude(result.markup, 'onclick');
+ });
+
+ test('emits a bare boolean element attribute', () => {
+ const result = convert('', {}, {aside: true});
+ assert.equal(result.markup, ``);
+ });
+
+ test('passes component attributes through as props (string + bare boolean)', () => {
+ const result = convert('hi', {
+ Alert: '$lib/Alert.svelte',
+ });
+ assert.equal(result.markup, `hi`);
+ assert_import(result, 'Alert', '$lib/Alert.svelte', 'default');
+ });
+
+ test('escapes a string value through escape_js_string', () => {
+ // a value with a single-quote and a backslash must be escaped for the
+ // single-quoted JS-expression attribute (not emitted verbatim)
+ const value = "a'b\\c";
+ const result = convert(`hi`, {Alert: '$lib/Alert.svelte'});
+ assert.equal(result.markup, `hi`);
+ // the raw quote/backslash never appear unescaped in the output
+ assert.include(result.markup, escape_js_string(value));
+ });
+ });
});