From b38f0fc9cb52710be192f65563abe5089c37d20f Mon Sep 17 00:00:00 2001 From: Simon Haines Date: Sat, 25 Jul 2026 17:15:43 +0100 Subject: [PATCH 1/3] test: image attributes dropped when a title is present The stringify handler for images returns early on the title branch and never appends the attribute block, so `![alt](src "title"){width="200"}` round-trips to `![alt](src "title")`, silently dropping width, class, and every other attribute. The parser is fine (the tree keeps `width`); only rendering is affected. Attributes-only and title-only images round-trip correctly, so the two regression cases fail while the two controls pass. Repro: pnpm --filter comark vitest run test/img-attributes.test.ts Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/comark/test/img-attributes.test.ts | 34 +++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 packages/comark/test/img-attributes.test.ts diff --git a/packages/comark/test/img-attributes.test.ts b/packages/comark/test/img-attributes.test.ts new file mode 100644 index 00000000..c40fafe6 --- /dev/null +++ b/packages/comark/test/img-attributes.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from 'vitest' +import { parse } from 'comark' +import { renderMarkdown } from 'comark/render' + +async function roundTrip(src: string): Promise { + const tree = await parse(`${src}\n`) + return (await renderMarkdown(tree, {})).trim() +} + +describe('image: attributes are preserved alongside a title', () => { + it('keeps attributes when the image has no title', async () => { + expect(await roundTrip('![alt](img.png){width="200"}')).toBe( + '![alt](img.png){width="200"}', + ) + }) + + it('keeps the title when the image has no attributes', async () => { + expect(await roundTrip('![alt](img.png "A title")')).toBe( + '![alt](img.png "A title")', + ) + }) + + it('keeps width when the image also has a title', async () => { + expect(await roundTrip('![alt](img.png "A title"){width="200"}')).toBe( + '![alt](img.png "A title"){width="200"}', + ) + }) + + it('keeps class and width when the image also has a title', async () => { + expect( + await roundTrip('![alt](img.png "A title"){.rounded-asymmetric width="200"}'), + ).toBe('![alt](img.png "A title"){.rounded-asymmetric width="200"}') + }) +}) From 4f4d58db510f97520474e05bbf4f4e2154cd376a Mon Sep 17 00:00:00 2001 From: Simon Haines Date: Sat, 25 Jul 2026 17:15:43 +0100 Subject: [PATCH 2/3] fix: preserve image attributes when a title is present The image stringify handler returned early on the title branch and never appended the attribute block, so `![alt](src "title"){width="200"}` round-tripped to `![alt](src "title")`, dropping width, class, and every other attribute. The parser already keeps them (the tree carries `width`); only rendering was lossy. Append the attribute block on both branches. Fixes the failing cases added in the previous commit. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/comark/src/internal/stringify/handlers/img.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/comark/src/internal/stringify/handlers/img.ts b/packages/comark/src/internal/stringify/handlers/img.ts index b1254878..7a8b33e6 100644 --- a/packages/comark/src/internal/stringify/handlers/img.ts +++ b/packages/comark/src/internal/stringify/handlers/img.ts @@ -8,5 +8,6 @@ export function img(node: ComarkElement, _state: State) { const attrsString = Object.keys(rest).length > 0 ? comarkAttributes(rest) : '' - return title ? `![${alt}](${src} "${title}")` : `![${alt}](${src})${attrsString}` + const link = title ? `![${alt}](${src} "${title}")` : `![${alt}](${src})` + return `${link}${attrsString}` } From 0e940144571e689be047b13dd5b893340e25ee11 Mon Sep 17 00:00:00 2001 From: Farnabaz Date: Mon, 27 Jul 2026 10:05:50 +0200 Subject: [PATCH 3/3] Update img-attributes.test.ts --- packages/comark/test/img-attributes.test.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/comark/test/img-attributes.test.ts b/packages/comark/test/img-attributes.test.ts index c40fafe6..a7374c0e 100644 --- a/packages/comark/test/img-attributes.test.ts +++ b/packages/comark/test/img-attributes.test.ts @@ -9,26 +9,20 @@ async function roundTrip(src: string): Promise { describe('image: attributes are preserved alongside a title', () => { it('keeps attributes when the image has no title', async () => { - expect(await roundTrip('![alt](img.png){width="200"}')).toBe( - '![alt](img.png){width="200"}', - ) + expect(await roundTrip('![alt](img.png){width="200"}')).toBe('![alt](img.png){width="200"}') }) it('keeps the title when the image has no attributes', async () => { - expect(await roundTrip('![alt](img.png "A title")')).toBe( - '![alt](img.png "A title")', - ) + expect(await roundTrip('![alt](img.png "A title")')).toBe('![alt](img.png "A title")') }) it('keeps width when the image also has a title', async () => { - expect(await roundTrip('![alt](img.png "A title"){width="200"}')).toBe( - '![alt](img.png "A title"){width="200"}', - ) + expect(await roundTrip('![alt](img.png "A title"){width="200"}')).toBe('![alt](img.png "A title"){width="200"}') }) it('keeps class and width when the image also has a title', async () => { - expect( - await roundTrip('![alt](img.png "A title"){.rounded-asymmetric width="200"}'), - ).toBe('![alt](img.png "A title"){.rounded-asymmetric width="200"}') + expect(await roundTrip('![alt](img.png "A title"){.rounded-asymmetric width="200"}')).toBe( + '![alt](img.png "A title"){.rounded-asymmetric width="200"}' + ) }) })