fix: handle html tags in markdown comparison (#9838)

Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>
This commit is contained in:
Alexander Onnikov
2025-09-11 18:41:27 +07:00
committed by GitHub
parent 113e72e3af
commit 7b87ca4047
3 changed files with 78 additions and 19 deletions
@@ -36,8 +36,8 @@ expect.extend({
return {
message: () =>
pass
? `Expected markdown strings NOT to be equal:\n Received: ${received}\n Expected: ${expected}`
: `Expected markdown strings to be equal:\n Received: ${received}\n Expected: ${expected}`,
? `Expected markdown strings NOT to be equal:\n Received:\n${received}\n Expected:\n${expected}`
: `Expected markdown strings to be equal:\n Received:\n${received}\n Expected:\n${expected}`,
pass
}
}
@@ -1033,6 +1033,15 @@ describe('markdownToMarkup -> markupToMarkdown', () => {
name: 'Image',
markdown: '<img width="320" height="160" src="http://example.com/image" alt="image">'
},
{
name: 'Images',
markdown: `
<img width="250" height="330" src="https://github.com/user-attachments/assets/f348e016-3f7d-45b1-b8a0-9098e9961885" alt="Screenshot 2025-09-11 at 15 42 40" />
<img width="250" height="230" alt="Screenshot 2025-09-11 at 15 43 42" src="https://github.com/user-attachments/assets/4502eba1-1f55-44df-b691-c4d3d3d3d67d" >
<img src="https://github.com/user-attachments/assets/e21431a3-2062-4b0b-9c8f-d06c92ede741" alt="Screenshot 2025-09-11 at 15 43 50" width="250" height="210" >`
},
{
name: 'Image with multiline alt',
markdown: '![link0\\\n\\\nline1](http://example.com/image.png)'
+67 -16
View File
@@ -49,20 +49,71 @@ export function calcSørensenDiceCoefficient (a: string, b: string): number {
* Perform markdown diff/comparison to understand do we have a major differences.
*/
export function isMarkdownsEquals (source1: string, source2: string): boolean {
const normalizeLineEndings = (str: string): string => str.replace(/\r?\n/g, '\n')
const excludeBlankLines = (str: string): string =>
str
.split('\n')
.map((it) => it.trimEnd())
.filter((it) => it.length > 0)
.join('\n')
const norm1 = normalizeLineEndings(source1 ?? '')
const lines1 = excludeBlankLines(norm1)
const norm2 = normalizeLineEndings(source2 ?? '')
const lines2 = excludeBlankLines(norm2)
return lines1 === lines2
const normalized1 = normalizeMarkdown(source1)
const normalized2 = normalizeMarkdown(source2)
return normalized1 === normalized2
}
export function normalizeMarkdown (source: string): string {
const tagRegex = /<(\w+)([^>]*?)(\/?)>/g
const attrRegex = /(\w+)(?:=(?:"([^"]*)"|'([^']*)'|([^\s>]+)))?/g
// Normalize line endings to LF
source = source.replace(/\r?\n/g, '\n')
// Remove extra blank lines
source = source
.split('\n')
.map((it) => it.trimEnd())
.filter((it) => it.length > 0)
.join('\n')
// Normalize HTML tags
source = source.replace(tagRegex, (match, tagName, attributes) => {
const attrs: Record<string, string> = {}
let attrMatch = attrRegex.exec(attributes)
while (attrMatch !== null) {
const attrName = attrMatch[1]
const attrValue = attrMatch[2] ?? attrMatch[3] ?? attrMatch[4] ?? ''
attrs[attrName] = attrValue
attrMatch = attrRegex.exec(attributes)
}
// Sort attributes by name for consistent order
const sortedAttrs = Object.keys(attrs)
.sort()
.map((key) => {
const value = attrs[key]
return value !== '' ? `${key}="${value}"` : key
})
.join(' ')
// Normalize to self-closing format for void elements
const voidElements = [
'img',
'br',
'hr',
'input',
'meta',
'area',
'base',
'col',
'embed',
'link',
'param',
'source',
'track',
'wbr'
]
const isVoidElement = voidElements.includes(tagName.toLowerCase())
if (sortedAttrs !== '') {
return isVoidElement ? `<${tagName} ${sortedAttrs} />` : `<${tagName} ${sortedAttrs}>`
} else {
return isVoidElement ? `<${tagName} />` : `<${tagName}>`
}
})
return source
}
-1
View File
@@ -204,7 +204,6 @@ export const storeNodes: Record<string, NodeProcessor> = {
)
} else {
if (attrs.width != null || attrs.height != null) {
// state.write(`<img width="446" alt="{alt}" src="{src}">`)
state.write(
'<img' +
(attrs.width != null ? ` width="${state.esc(`${attrs.width}`)}"` : '') +