From 7298436ea9ff60399d5e77bf0ed3831a2a909c33 Mon Sep 17 00:00:00 2001 From: robinqu Date: Mon, 10 Aug 2026 12:12:12 +0800 Subject: [PATCH 1/6] fix: fold a linked image's trailing magic comment into the image mark MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An image followed by a sizing magic comment folds that comment into the image mark so it round-trips as source while supplying the width — but only for a standalone image. Inside a link label (`[![alt](img)](target)`) the comment was emitted as plain visible text instead, leaking raw markup into the rendering. Thread takeMagicComment through walkResolvedLink's image call site, the same folding the standalone-image path already does, and extract the per-child dispatch into walkResolvedLinkChild to stay within the statement limit. --- .../inline-text-to-mark-chunks.test.ts | 12 ++ .../extensions/inline-text-to-mark-chunks.ts | 112 +++++++++++------- 2 files changed, 82 insertions(+), 42 deletions(-) diff --git a/packages/core/src/extensions/inline-text-to-mark-chunks.test.ts b/packages/core/src/extensions/inline-text-to-mark-chunks.test.ts index 1b042b53..baa4ba33 100644 --- a/packages/core/src/extensions/inline-text-to-mark-chunks.test.ts +++ b/packages/core/src/extensions/inline-text-to-mark-chunks.test.ts @@ -671,6 +671,18 @@ describe('image', () => { " `) }) + + it('folds a trailing width comment on an image inside a link label', () => { + expect(parse('[![a](u)](/target)')).toMatchInlineSnapshot(` + " + [0, 1] mdPack(key=link,data={"form":"inline","href":"/target","title":""},revealInFocus=true) + mdLinkText(href=/target) + mdMark + [1, 30] mdPack(key=link,data={"form":"inline","href":"/target","title":""},revealInFocus=true) + mdLinkText(href=/target) + mdPack(key=image) + mdImage(src=u,alt=a,width=320) + [30, 32] mdPack(key=link,data={"form":"inline","href":"/target","title":""},revealInFocus=true) + mdMark + [32, 39] mdPack(key=link,data={"form":"inline","href":"/target","title":""},revealInFocus=true) + mdLinkUri + [39, 40] mdPack(key=link,data={"form":"inline","href":"/target","title":""},revealInFocus=true) + mdMark + " + `) + }) }) describe('wiki embed', () => { diff --git a/packages/core/src/extensions/inline-text-to-mark-chunks.ts b/packages/core/src/extensions/inline-text-to-mark-chunks.ts index c742e159..248023ea 100644 --- a/packages/core/src/extensions/inline-text-to-mark-chunks.ts +++ b/packages/core/src/extensions/inline-text-to-mark-chunks.ts @@ -564,59 +564,87 @@ function walkResolvedLink( const base = [...parentMarks, pack] let pos = node.from - for (const child of node.children) { + for (let index = 0; index < node.children.length; index++) { + const child = node.children[index] + // A previous child may have consumed this one (e.g. an image folding its + // trailing magic comment), so anything fully behind `pos` is done. + if (child.to <= pos) continue if (child.from > pos) { const childMarks = inLabel(pos) ? [...base, linkTextMark] : base emit(out, pos, child.from, childMarks) } const baseForChild = inLabel(child.from) ? [...base, linkTextMark] : base - // A wikilink in the label needs its own source/view walk, not the generic - // per-child mark mapping. - if (child.type === LEZER_NODE_IDS.Wikilink) { - walkWikilink(child, baseForChild, text, marks, out) - pos = child.to - continue - } - if (child.type === LEZER_NODE_IDS.WikiEmbed) { - walkWikiEmbed(child, baseForChild, text, marks, out, options) - pos = child.to - continue - } - if (child.type === LEZER_NODE_IDS.Image) { - walkImage(child, baseForChild, text, marks, out, options, context) - pos = child.to - continue - } - if (isReference && child.type === LEZER_NODE_IDS.LinkLabel) { - emit(out, child.from, child.to, [...baseForChild, marks.mdMark.create()]) - pos = child.to - continue - } - // An autolink inside the label is plain label text: the outer link owns - // the href, and the muted `mdLinkUri` styling belongs to the destination. - if (child.type === LEZER_NODE_IDS.URL && inLabel(child.from)) { - emit(out, child.from, child.to, baseForChild) - pos = child.to - continue - } - const maybeMarkName = MARK_NAME_BY_TYPE_ID.get(child.type) - const childMarks = maybeMarkName - ? [...baseForChild, marks[maybeMarkName].create()] - : baseForChild - if (child.children.length === 0) { - emit(out, child.from, child.to, childMarks) - } else { - // A link label cannot contain another `[label](url)` link, but custom - // atom syntax inside the label still uses the host resolvers. - walk(child.children, childMarks, child.from, child.to, text, marks, out, options, context) - } - pos = child.to + pos = walkResolvedLinkChild( + child, + node.children[index + 1], + baseForChild, + isReference, + inLabel, + text, + marks, + out, + options, + context, + ) } if (pos < node.to) { emit(out, pos, node.to, base) } } +/** + * Walk one child of a resolved link's inline content; returns the new source + * position. + */ +function walkResolvedLinkChild( + child: InlineElement, + next: InlineElement | undefined, + baseForChild: readonly Mark[], + isReference: boolean, + inLabel: (pos: number) => boolean, + text: string, + marks: TypedMarkBuilders, + out: MarkChunk[], + options: InlineMarkOptions | undefined, + context: InlineMarkContext | undefined, +): number { + // A wikilink in the label needs its own source/view walk, not the generic + // per-child mark mapping. + if (child.type === LEZER_NODE_IDS.Wikilink) { + walkWikilink(child, baseForChild, text, marks, out) + return child.to + } + if (child.type === LEZER_NODE_IDS.WikiEmbed) { + walkWikiEmbed(child, baseForChild, text, marks, out, options) + return child.to + } + if (child.type === LEZER_NODE_IDS.Image) { + const trailing = takeMagicComment(child, next, text) + walkImage(child, baseForChild, text, marks, out, options, context, trailing) + return trailing ? trailing.to : child.to + } + if (isReference && child.type === LEZER_NODE_IDS.LinkLabel) { + emit(out, child.from, child.to, [...baseForChild, marks.mdMark.create()]) + return child.to + } + // An autolink inside the label is plain label text: the outer link owns the + // href, and the muted `mdLinkUri` styling belongs to the destination. + if (child.type === LEZER_NODE_IDS.URL && inLabel(child.from)) { + emit(out, child.from, child.to, baseForChild) + return child.to + } + const maybeMarkName = MARK_NAME_BY_TYPE_ID.get(child.type) + const childMarks = maybeMarkName ? [...baseForChild, marks[maybeMarkName].create()] : baseForChild + if (child.children.length === 0) { + emit(out, child.from, child.to, childMarks) + } else { + // A link label cannot contain another `[label](url)` link, but custom + // atom syntax inside the label still uses the host resolvers. + walk(child.children, childMarks, child.from, child.to, text, marks, out, options, context) + } + return child.to +} + interface AdjacentMagicComment { magic: MagicComment to: number From 7a293b883f0dcf78560581824ffd9ca6eaa63820 Mon Sep 17 00:00:00 2001 From: ocavue Date: Mon, 10 Aug 2026 15:08:53 +1000 Subject: [PATCH 2/6] docs: fix stale `hasInlineDestination` reference in `inline.test.ts` --- packages/markdown/src/inline.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/markdown/src/inline.test.ts b/packages/markdown/src/inline.test.ts index 20cd674a..4fb5fa40 100644 --- a/packages/markdown/src/inline.test.ts +++ b/packages/markdown/src/inline.test.ts @@ -85,7 +85,7 @@ describe('link', () => { // signature in the tree: only two LinkMark children (`[` and `]`); a // reference label, when present, is a single LinkLabel node, never more // LinkMarks. Inline links carry at least three LinkMarks (`[`, `]`, `(`). - // `hasInlineDestination` in `inline-text-to-mark-chunks.ts` relies on this. + // `resolveLink` in `inline-text-to-mark-chunks.ts` relies on this. it('parses a shortcut reference link with two LinkMarks', () => { expect(parse('[foo]')).toMatchInlineSnapshot(` From 4ad6a06133b3cebf7ab3e791a4fe490ed3115e80 Mon Sep 17 00:00:00 2001 From: ocavue Date: Mon, 10 Aug 2026 15:09:01 +1000 Subject: [PATCH 3/6] test: pin the parse tree of a comment after a linked image --- packages/markdown/src/inline.test.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/markdown/src/inline.test.ts b/packages/markdown/src/inline.test.ts index 4fb5fa40..10b54c59 100644 --- a/packages/markdown/src/inline.test.ts +++ b/packages/markdown/src/inline.test.ts @@ -227,6 +227,26 @@ describe('image', () => { " `) }) + + it('parses a comment after a linked image as a direct child of the Link', () => { + expect(parse('[![a](u)](/target)')).toMatchInlineSnapshot(` + " + Link [0, 40] "[![a](u)](/target)" + LinkMark [0, 1] "[" + Image [1, 8] "![a](u)" + LinkMark [1, 3] "![" + LinkMark [4, 5] "]" + LinkMark [5, 6] "(" + URL [6, 7] "u" + LinkMark [7, 8] ")" + Comment [8, 30] "" + LinkMark [30, 31] "]" + LinkMark [31, 32] "(" + URL [32, 39] "/target" + LinkMark [39, 40] ")" + " + `) + }) }) describe('highlight', () => { From 2bbe4588de7809a987708ebeccc870d234bad2dd Mon Sep 17 00:00:00 2001 From: ocavue Date: Mon, 10 Aug 2026 15:09:02 +1000 Subject: [PATCH 4/6] fix: fold a stacked run of size comments behind an image --- .../inline-text-to-mark-chunks.test.ts | 30 +++ .../extensions/inline-text-to-mark-chunks.ts | 179 ++++++++++-------- .../core/src/extensions/magic-comment.test.ts | 6 + packages/core/src/extensions/magic-comment.ts | 8 +- 4 files changed, 139 insertions(+), 84 deletions(-) diff --git a/packages/core/src/extensions/inline-text-to-mark-chunks.test.ts b/packages/core/src/extensions/inline-text-to-mark-chunks.test.ts index baa4ba33..48b966c4 100644 --- a/packages/core/src/extensions/inline-text-to-mark-chunks.test.ts +++ b/packages/core/src/extensions/inline-text-to-mark-chunks.test.ts @@ -683,6 +683,36 @@ describe('image', () => { " `) }) + + it('folds a stacked run of size comments, first data winning', () => { + expect(parse('![a](u)')).toMatchInlineSnapshot(` + " + [0, 51] mdPack(key=image) + mdImage(src=u,alt=a,width=100) + " + `) + }) + + it('folds a stacked run of size comments inside a link label', () => { + expect(parse('[![a](u)](/target)')) + .toMatchInlineSnapshot(` + " + [0, 1] mdPack(key=link,data={"form":"inline","href":"/target","title":""},revealInFocus=true) + mdLinkText(href=/target) + mdMark + [1, 52] mdPack(key=link,data={"form":"inline","href":"/target","title":""},revealInFocus=true) + mdLinkText(href=/target) + mdPack(key=image) + mdImage(src=u,alt=a,width=100) + [52, 54] mdPack(key=link,data={"form":"inline","href":"/target","title":""},revealInFocus=true) + mdMark + [54, 61] mdPack(key=link,data={"form":"inline","href":"/target","title":""},revealInFocus=true) + mdLinkUri + [61, 62] mdPack(key=link,data={"form":"inline","href":"/target","title":""},revealInFocus=true) + mdMark + " + `) + }) + + it('stops the fold at a non-metadata comment', () => { + expect(parse('![a](u)')).toMatchInlineSnapshot(` + " + [0, 29] mdPack(key=image) + mdImage(src=u,alt=a,width=100) + [29, 42] + " + `) + }) }) describe('wiki embed', () => { diff --git a/packages/core/src/extensions/inline-text-to-mark-chunks.ts b/packages/core/src/extensions/inline-text-to-mark-chunks.ts index 248023ea..83c13e77 100644 --- a/packages/core/src/extensions/inline-text-to-mark-chunks.ts +++ b/packages/core/src/extensions/inline-text-to-mark-chunks.ts @@ -202,16 +202,15 @@ function walk( let pos = rangeStart for (let index = 0; index < nodes.length; index++) { const node = nodes[index] + // A previous child may have consumed this one (e.g. an image folding its + // trailing magic comments), so anything fully behind `pos` is done. + if (node.to <= pos) continue if (node.from > pos) { emit(out, pos, node.from, parentMarks) } - // An image may fold the next sibling into its own range, so it is the one - // node type the loop resolves itself. - if (node.type === LEZER_NODE_IDS.Image) { - const trailing = takeMagicComment(node, nodes[index + 1], text) - walkImage(node, parentMarks, text, marks, out, options, context, trailing) - if (trailing) index++ // skip the folded comment - pos = trailing ? trailing.to : node.to + const atomEnd = walkAtomChild(nodes, index, parentMarks, text, marks, out, options, context) + if (atomEnd != null) { + pos = atomEnd continue } walkNode(node, parentMarks, text, marks, out, options, context) @@ -234,10 +233,6 @@ function walkNode( switch (node.type) { case LEZER_NODE_IDS.Link: return walkLink(node, parentMarks, text, marks, out, options, context) - case LEZER_NODE_IDS.Wikilink: - return walkWikilink(node, parentMarks, text, marks, out) - case LEZER_NODE_IDS.WikiEmbed: - return walkWikiEmbed(node, parentMarks, text, marks, out, options) case LEZER_NODE_IDS.InlineMath: return walkMath(node, parentMarks, text, marks, out) case LEZER_NODE_IDS.Autolink: @@ -249,6 +244,42 @@ function walkNode( } } +/** + * Walk `nodes[index]` when it is a source-backed atom (wikilink, wiki embed, + * or image); returns the source position after everything the atom consumed, + * or undefined for any other node type. An image also consumes the magic + * comments chained behind it, so callers must skip children ending at or + * before the returned position. Shared by `walk` and `walkResolvedLink` so an + * atom behaves the same at the top level and inside a link label. + */ +function walkAtomChild( + nodes: readonly InlineElement[], + index: number, + parentMarks: readonly Mark[], + text: string, + marks: TypedMarkBuilders, + out: MarkChunk[], + options: InlineMarkOptions | undefined, + context: InlineMarkContext | undefined, +): number | undefined { + const node = nodes[index] + switch (node.type) { + case LEZER_NODE_IDS.Wikilink: + walkWikilink(node, parentMarks, text, marks, out) + return node.to + case LEZER_NODE_IDS.WikiEmbed: + walkWikiEmbed(node, parentMarks, text, marks, out, options) + return node.to + case LEZER_NODE_IDS.Image: { + const trailing = takeMagicComments(nodes, index, text) + walkImage(node, parentMarks, text, marks, out, options, context, trailing) + return trailing ? trailing.to : node.to + } + default: + return undefined + } +} + /** * A node with no source-backed atom of its own: it contributes its `mdPack` and * syntax marks, then recurses into its children. @@ -567,105 +598,91 @@ function walkResolvedLink( for (let index = 0; index < node.children.length; index++) { const child = node.children[index] // A previous child may have consumed this one (e.g. an image folding its - // trailing magic comment), so anything fully behind `pos` is done. + // trailing magic comments), so anything fully behind `pos` is done. if (child.to <= pos) continue if (child.from > pos) { const childMarks = inLabel(pos) ? [...base, linkTextMark] : base emit(out, pos, child.from, childMarks) } const baseForChild = inLabel(child.from) ? [...base, linkTextMark] : base - pos = walkResolvedLinkChild( - child, - node.children[index + 1], + const atomEnd = walkAtomChild( + node.children, + index, baseForChild, - isReference, - inLabel, text, marks, out, options, context, ) + if (atomEnd != null) { + pos = atomEnd + continue + } + if (isReference && child.type === LEZER_NODE_IDS.LinkLabel) { + emit(out, child.from, child.to, [...baseForChild, marks.mdMark.create()]) + pos = child.to + continue + } + // An autolink inside the label is plain label text: the outer link owns + // the href, and the muted `mdLinkUri` styling belongs to the destination. + if (child.type === LEZER_NODE_IDS.URL && inLabel(child.from)) { + emit(out, child.from, child.to, baseForChild) + pos = child.to + continue + } + const maybeMarkName = MARK_NAME_BY_TYPE_ID.get(child.type) + const childMarks = maybeMarkName + ? [...baseForChild, marks[maybeMarkName].create()] + : baseForChild + if (child.children.length === 0) { + emit(out, child.from, child.to, childMarks) + } else { + // A link label cannot contain another `[label](url)` link, but custom + // atom syntax inside the label still uses the host resolvers. + walk(child.children, childMarks, child.from, child.to, text, marks, out, options, context) + } + pos = child.to } if (pos < node.to) { emit(out, pos, node.to, base) } } -/** - * Walk one child of a resolved link's inline content; returns the new source - * position. - */ -function walkResolvedLinkChild( - child: InlineElement, - next: InlineElement | undefined, - baseForChild: readonly Mark[], - isReference: boolean, - inLabel: (pos: number) => boolean, - text: string, - marks: TypedMarkBuilders, - out: MarkChunk[], - options: InlineMarkOptions | undefined, - context: InlineMarkContext | undefined, -): number { - // A wikilink in the label needs its own source/view walk, not the generic - // per-child mark mapping. - if (child.type === LEZER_NODE_IDS.Wikilink) { - walkWikilink(child, baseForChild, text, marks, out) - return child.to - } - if (child.type === LEZER_NODE_IDS.WikiEmbed) { - walkWikiEmbed(child, baseForChild, text, marks, out, options) - return child.to - } - if (child.type === LEZER_NODE_IDS.Image) { - const trailing = takeMagicComment(child, next, text) - walkImage(child, baseForChild, text, marks, out, options, context, trailing) - return trailing ? trailing.to : child.to - } - if (isReference && child.type === LEZER_NODE_IDS.LinkLabel) { - emit(out, child.from, child.to, [...baseForChild, marks.mdMark.create()]) - return child.to - } - // An autolink inside the label is plain label text: the outer link owns the - // href, and the muted `mdLinkUri` styling belongs to the destination. - if (child.type === LEZER_NODE_IDS.URL && inLabel(child.from)) { - emit(out, child.from, child.to, baseForChild) - return child.to - } - const maybeMarkName = MARK_NAME_BY_TYPE_ID.get(child.type) - const childMarks = maybeMarkName ? [...baseForChild, marks[maybeMarkName].create()] : baseForChild - if (child.children.length === 0) { - emit(out, child.from, child.to, childMarks) - } else { - // A link label cannot contain another `[label](url)` link, but custom - // atom syntax inside the label still uses the host resolvers. - walk(child.children, childMarks, child.from, child.to, text, marks, out, options, context) - } - return child.to -} - -interface AdjacentMagicComment { +interface FoldedMagicComments { magic: MagicComment to: number } -// A magic comment sitting immediately after `image`, or undefined. -function takeMagicComment( - image: InlineElement, - next: InlineElement | undefined, +/** + * The run of magic comments chained immediately behind `nodes[index]` (an + * image), or undefined when no magic comment directly abuts it. The first + * comment's data wins: a rewrite of an unfolded image inserted the fresh + * comment right at the image's end, so in a stacked run left is newest. + */ +function takeMagicComments( + nodes: readonly InlineElement[], + index: number, text: string, -): AdjacentMagicComment | undefined { - if (!next || next.type !== LEZER_NODE_IDS.Comment || next.from !== image.to) return undefined - const magic = parseMagicComment(text.slice(next.from, next.to)) +): FoldedMagicComments | undefined { + let magic: MagicComment | undefined + let to = nodes[index].to + for (let i = index + 1; i < nodes.length; i++) { + const next = nodes[i] + if (next.type !== LEZER_NODE_IDS.Comment || next.from !== to) break + const parsed = parseMagicComment(text.slice(next.from, next.to)) + if (!parsed) break + magic ??= parsed + to = next.to + } if (!magic) return undefined - return { magic, to: next.to } + return { magic, to } } /** * Special walker for a direct image `![alt](url)`. * - * A `trailing` magic comment immediately after the image (e.g. + * A `trailing` run of magic comments immediately after the image (e.g. * ``) is folded into the mark range so it round-trips as * source while supplying the image's `width`. */ @@ -677,7 +694,7 @@ function walkImage( out: MarkChunk[], options: InlineMarkOptions | undefined, context: InlineMarkContext | undefined, - trailing?: AdjacentMagicComment, + trailing?: FoldedMagicComments, ): void { const parts = scanLinkParts(node) const resolution = resolveLink(parts, text, context) diff --git a/packages/core/src/extensions/magic-comment.test.ts b/packages/core/src/extensions/magic-comment.test.ts index 7f5c5e8a..d4338403 100644 --- a/packages/core/src/extensions/magic-comment.test.ts +++ b/packages/core/src/extensions/magic-comment.test.ts @@ -40,4 +40,10 @@ describe('formatMagicComment / stripMagicComment', () => { expect(stripMagicComment('![a](u)')).toBe('![a](u)') expect(stripMagicComment('![a](u)')).toBe('![a](u)') }) + + it('strips a whole stacked run of trailing comments', () => { + expect(stripMagicComment('![a](u)')).toBe( + '![a](u)', + ) + }) }) diff --git a/packages/core/src/extensions/magic-comment.ts b/packages/core/src/extensions/magic-comment.ts index a7d0b067..33513e9a 100644 --- a/packages/core/src/extensions/magic-comment.ts +++ b/packages/core/src/extensions/magic-comment.ts @@ -16,8 +16,10 @@ export interface MagicComment { // A whole inline comment carrying a JSON object: ``. const MAGIC_COMMENT_RE = /^$/ -// Same, anchored to the end of a string, for stripping a trailing comment. -const TRAILING_MAGIC_COMMENT_RE = /$/ +// Same, anchored to the end of a string, for stripping the whole trailing +// run: a rewrite of an image whose comment had not folded could stack a +// second comment behind the first. +const TRAILING_MAGIC_COMMENT_RE = /(?:)+$/ /** * Read the metadata out of a `` comment, or `undefined` when the @@ -58,7 +60,7 @@ export function formatMagicComment(magic: MagicComment): string { } /** - * Drop a trailing magic comment from the source text. + * Drop the trailing run of magic comments from the source text. */ export function stripMagicComment(source: string): string { return source.replace(TRAILING_MAGIC_COMMENT_RE, '') From a89e31ca10fad8faecfc2947061531f2a9640a0f Mon Sep 17 00:00:00 2001 From: ocavue Date: Mon, 10 Aug 2026 15:09:02 +1000 Subject: [PATCH 5/6] test: cover linked image resize round-trips --- packages/core/src/extensions/image.test.ts | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/packages/core/src/extensions/image.test.ts b/packages/core/src/extensions/image.test.ts index 8457cca3..244f9880 100644 --- a/packages/core/src/extensions/image.test.ts +++ b/packages/core/src/extensions/image.test.ts @@ -379,6 +379,47 @@ describe('image resize', () => { await expect.element(resizable).toHaveAttribute('data-width', '320') }) + it('applies a persisted width to a linked image', async () => { + using fixture = setupResize('[![cat](u)](/target)') + void fixture + await expect.element(resizable).toHaveAttribute('data-width', '200') + }) + + it('writes the size comment inside the link label when a linked image is resized', async () => { + using fixture = setupResize('[![cat](u)](/target)') + await expect.element(resizable).toBeInTheDocument() + endResize(320) + await vi.waitFor(() => { + expect(fixture.doc.textContent).toBe( + '[![cat](u)](/target)', + ) + }) + await expect.element(resizable).toHaveAttribute('data-width', '320') + }) + + it('replaces the size comment of a linked image when resized again', async () => { + using fixture = setupResize('[![cat](u)](/target)') + await expect.element(resizable).toHaveAttribute('data-width', '100') + endResize(320) + await vi.waitFor(() => { + expect(fixture.doc.textContent).toBe( + '[![cat](u)](/target)', + ) + }) + }) + + // Stacked comments are what a formerly buggy rewrite left behind: each + // resize inserted a fresh comment before the unfolded old one. The first + // comment's size applies, and one resize collapses the run. + it('collapses a stacked run of size comments when resized', async () => { + using fixture = setupResize('![cat](u)') + await expect.element(resizable).toHaveAttribute('data-width', '300') + endResize(320) + await vi.waitFor(() => { + expect(fixture.doc.textContent).toBe('![cat](u)') + }) + }) + it('keeps the same preview DOM when resized', async () => { using fixture = setupResize('![cat](u)') void fixture From 23c5ee89714e679b60de22565ac9c407bfee8131 Mon Sep 17 00:00:00 2001 From: ocavue Date: Mon, 10 Aug 2026 15:09:02 +1000 Subject: [PATCH 6/6] test: cover a linked image size comment in `MarkdownView` --- packages/react/src/components/markdown-view.test.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/react/src/components/markdown-view.test.tsx b/packages/react/src/components/markdown-view.test.tsx index efd3bf97..c881be09 100644 --- a/packages/react/src/components/markdown-view.test.tsx +++ b/packages/react/src/components/markdown-view.test.tsx @@ -54,6 +54,12 @@ describe('MarkdownView', () => { await expect.element(img).toHaveAttribute('alt', 'cat') }) + it('applies a size comment to an image inside a link', async () => { + await renderView('[![cat](https://example.com/cat.png)](/target)') + const img = view.getByTestId('image-preview').locate('img') + await expect.element(img).toHaveStyle({ width: '320px' }) + }) + it('renders a resolved wiki image with its alias and width', async () => { await renderView('![[assets/cat.png|120]]', { resolveWikiEmbed: () => ({ kind: 'image' }),