From 04fda0c0b28a6e553eff0fa6fd57f41b37e03cfe Mon Sep 17 00:00:00 2001 From: claude code agent 227 Date: Mon, 29 Jun 2026 00:04:56 +0300 Subject: [PATCH] test(#248 F2): exercise <,> escape branches in raw-HTML export round-trip The escaping round-trip test's data (A & "B") only contained & and ", so the <,> branches of escapeHtmlAttr (&,",<,>) and escapeHtmlText (&,<,>) were never exercised; a regression dropping <,> escaping would still pass. Extend the data to A & "C" in both the data-label attribute and the visible text so both functions' <,> branches are genuinely covered. Assert the well-formed escaped tag (attr: A & <B> "C", text: A & <B> "C"), explicitly reject the raw tag-corrupting forms, and confirm markdownToHtml restores the originals. Comment updated to match. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../markdown/utils/turndown.dataloss.test.ts | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/packages/editor-ext/src/lib/markdown/utils/turndown.dataloss.test.ts b/packages/editor-ext/src/lib/markdown/utils/turndown.dataloss.test.ts index dd5a8950..9bec4e78 100644 --- a/packages/editor-ext/src/lib/markdown/utils/turndown.dataloss.test.ts +++ b/packages/editor-ext/src/lib/markdown/utils/turndown.dataloss.test.ts @@ -111,22 +111,28 @@ describe("htmlToMarkdown — custom nodes are preserved losslessly (#206 mdrt-2) // HTML special chars in an attribute value or in a node's text must be // ESCAPED when re-emitted as raw HTML, otherwise the exported tag is // malformed and `markdownToHtml`'s parser cannot restore the original value - // (the same silent data loss this PR fixes). This exercises the escape - // branches of escapeHtmlAttr (&, ", <, >) and escapeHtmlText (&, <, >) that - // the alphanumeric-only cases above never hit. The mention's data-label and - // visible text both carry `&` and `"`. - it("escapes HTML special chars in attrs + text and round-trips them", async () => { + // (the same silent data loss this PR fixes). Dropping `<`/`>` escaping is the + // dangerous regression: a stray `<` or `>` corrupts the tag (or injects new + // markup), so the test data carries ALL of `&`, `"`, `<`, `>` in BOTH the + // data-label attribute and the visible text. That fully exercises + // escapeHtmlAttr's `&,",<,>` branches and escapeHtmlText's `&,<,>` branches + // (escapeHtmlText leaves `"` literal); the alphanumeric-only cases above hit + // none of them. + it("escapes HTML special chars (& \" < >) in attrs + text and round-trips them", async () => { const md = htmlToMarkdown( - `

hi @A & "B" there

`, + `

hi @A & <B> "C" there

`, ); // (a) The exported Markdown carries a WELL-FORMED, correctly-escaped tag: - // the attribute escapes both `&` and `"`; the text escapes `&` (a `"` - // inside text content is legal, so it stays literal). - expect(md).toContain('data-label="A & "B""'); - expect(md).toContain('>@A & "B"'); - // And NOT the raw, malformed form that would break the attribute. - expect(md).not.toContain('data-label="A & "B""'); + // the attribute escapes `&`, `<`, `>` AND `"`; the text escapes `&`, `<`, + // `>` (a `"` inside text content is legal, so it stays literal). + expect(md).toContain('data-label="A & <B> "C""'); + expect(md).toContain('>@A & <B> "C"'); + // And explicitly NOT the raw, tag-corrupting forms: a literal `` (would + // mean `<`/`>` escaping was dropped in either the attr or the text)... + expect(md).not.toContain(""); + // ...nor the malformed attribute that an unescaped `"` would produce. + expect(md).not.toContain('data-label="A & <B> "C""'); // (b) Import restores the ORIGINAL (unescaped) values, attribute and text. const html = await markdownToHtml(md); @@ -134,8 +140,8 @@ describe("htmlToMarkdown — custom nodes are preserved losslessly (#206 mdrt-2) const span = dom.querySelector('span[data-type="mention"]'); expect(span).not.toBeNull(); expect(span!.getAttribute("data-id")).toBe("u1"); - expect(span!.getAttribute("data-label")).toBe('A & "B"'); - expect(span!.textContent).toBe('@A & "B"'); + expect(span!.getAttribute("data-label")).toBe('A & "C"'); + expect(span!.textContent).toBe('@A & "C"'); }); }); });