Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Changes to attributes of structural elements are treated as modifications (`vdd-
#### Options

- `addedClass: string = 'vdd-added'` The class used for annotating content additions.
- `ignoreAttributes: boolean = false` If `true`, then attribute names and values are ignored when comparing nodes.
- `modifiedClass: string = 'vdd-modified'` The class used for annotating content modifications.
- `removedClass: string = 'vdd-removed'` The class used for annotating content removals.
- `skipModified: boolean = false` If `true`, then formatting changes are NOT wrapped in `<ins class="vdd-modified">` and modified structural elements are NOT annotated with the `vdd-modified` class.
Expand Down
3 changes: 3 additions & 0 deletions src/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ describe('simple options', () => {
expect(config.modifiedClass).toBe('vdd-modified')
expect(config.removedClass).toBe('vdd-removed')
expect(config.skipModified).toBe(false)
expect(config.ignoreAttributes).toBe(false)
})
test('override', () => {
const customDiffText = (
Expand All @@ -150,12 +151,14 @@ describe('simple options', () => {
const config = optionsToConfig({
addedClass: 'ADDED',
diffText: customDiffText,
ignoreAttributes: true,
modifiedClass: 'MODIFIED',
removedClass: 'REMOVED',
skipModified: true,
})
expect(config.addedClass).toBe('ADDED')
expect(config.diffText).toBe(customDiffText)
expect(config.ignoreAttributes).toBe(true)
expect(config.modifiedClass).toBe('MODIFIED')
expect(config.removedClass).toBe('REMOVED')
expect(config.skipModified).toBe(true)
Expand Down
9 changes: 9 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ export interface Options {
* Default is `'vdd-modified'`.
*/
modifiedClass?: string
/**
* If `true`, differences in attribute names and values are ignored when comparing nodes:
* the first node's attributes are discarded in favor of the second node's attributes.
* Default is `false`.
*/
ignoreAttributes?: boolean
/**
* The class name to use to mark up removed content.
* Default is `'vdd-removed'`.
Expand Down Expand Up @@ -59,6 +65,7 @@ export interface Options {

export interface Config extends Options, DomIteratorOptions {
readonly addedClass: string
readonly ignoreAttributes: boolean
readonly modifiedClass: string
readonly removedClass: string
readonly skipModified: boolean
Expand Down Expand Up @@ -104,6 +111,7 @@ export function optionsToConfig({
addedClass = 'vdd-added',
modifiedClass = 'vdd-modified',
removedClass = 'vdd-removed',
ignoreAttributes = false,
skipModified = false,
skipChildren,
skipSelf,
Expand All @@ -112,6 +120,7 @@ export function optionsToConfig({
return {
addedClass,
diffText,
ignoreAttributes,
modifiedClass,
removedClass,
skipModified,
Expand Down
71 changes: 71 additions & 0 deletions src/diff.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,23 @@ test.each<[string, Node, Node, string, Options | undefined]>([
'<img src="image.png" class="vdd-removed"><img src="image.jpg" class="vdd-added">',
undefined,
],
[
'different images with ignored attributes',
(() => {
const img = document.createElement('IMG')
img.setAttribute('src', 'image.png')
return img
})(),
(() => {
const img = document.createElement('IMG')
img.setAttribute('src', 'image.jpg')
return img
})(),
'<img src="image.jpg">',
{
ignoreAttributes: true,
},
],
[
'complex identical content',
htmlToFragment(
Expand Down Expand Up @@ -359,20 +376,47 @@ test.each<[string, Node, Node, string, Options | undefined]>([
'<div><img src="image.png" class="vdd-removed"><img src="image.jpg" class="vdd-added"></div>',
undefined,
],
[
'differing image src being ignored',
htmlToFragment('<div><img src="image.png"></div>'),
htmlToFragment('<div><img src="image.jpg"></div>'),
'<div><img src="image.jpg"></div>',
{
ignoreAttributes: true,
},
],
[
'differing paragraph attribute - the same text diff',
htmlToFragment('<p data-value="old">test</p>'),
htmlToFragment('<p data-value="new">test</p>'),
'<p data-value="new" class="vdd-modified">test</p>',
undefined,
],
[
'differing paragraph attribute being ignored - the same text diff',
htmlToFragment('<p data-value="old">test</p>'),
htmlToFragment('<p data-value="new">test</p>'),
'<p data-value="new">test</p>',
{
ignoreAttributes: true,
},
],
[
'differing paragraph attribute - different text diff',
htmlToFragment('<p data-value="old">test</p>'),
htmlToFragment('<p data-value="new">hello</p>'),
'<p data-value="new" class="vdd-modified"><del class="vdd-removed">test</del><ins class="vdd-added">hello</ins></p>',
undefined,
],
[
'differing paragraph attribute being ignored - different text diff',
htmlToFragment('<p data-value="old">test</p>'),
htmlToFragment('<p data-value="new">hello</p>'),
'<p data-value="new"><del class="vdd-removed">test</del><ins class="vdd-added">hello</ins></p>',
{
ignoreAttributes: true,
},
],
[
'multiple spaces between words',
htmlToFragment('prefix suffix'),
Expand Down Expand Up @@ -918,6 +962,33 @@ test.each<[string, Node, Node, string, Options | undefined]>([
'<table><thead class="vdd-removed"><tr><th>1111</th><th>2222</th><th>3333</th></tr></thead><tbody><tr><th>first column</th><th class="vdd-removed">second column</th><th>last column</th></tr></tbody></table>',
undefined,
],
[
'ignore different attribute names',
htmlToFragment('<span data-a="a">foo</span>'),
htmlToFragment('<span data-b="b">foo</span>'),
'<span data-b="b">foo</span>',
{
ignoreAttributes: true,
},
],
[
'ignore different attribute values',
htmlToFragment('<span data-a="a">foo</span>'),
htmlToFragment('<span data-a="b">foo</span>'),
'<span data-a="b">foo</span>',
{
ignoreAttributes: true,
},
],
[
'real-life case for ignoring attributes in markdown-generated headings',
htmlToFragment('<h1 id="heading">heading</h1>'),
htmlToFragment('<h1 id="heading-2">heading 2</h1>'),
'<h1 id="heading-2">heading<ins class="vdd-added"> 2</ins></h1>',
{
ignoreAttributes: true,
},
],
])(
'%s',
(
Expand Down
14 changes: 11 additions & 3 deletions src/diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export function visualDomDiff(
const {
addedClass,
diffText,
ignoreAttributes,
modifiedClass,
removedClass,
skipSelf,
Expand Down Expand Up @@ -174,14 +175,21 @@ export function visualDomDiff(
modifiedNodes.add(node)
} else {
for (let i = 0; i < length; ++i) {
if (!areNodesEqual(oldFormatting[i], newFormatting[i])) {
if (
!areNodesEqual(
oldFormatting[i],
newFormatting[i],
false,
ignoreAttributes,
)
) {
modifiedNodes.add(node)
break
}
}
}
} else {
if (!areNodesEqual(oldNode, newNode)) {
if (!areNodesEqual(oldNode, newNode, false, ignoreAttributes)) {
modifiedNodes.add(node)
}

Expand Down Expand Up @@ -352,7 +360,7 @@ export function visualDomDiff(
nodeNameOverride(newNode.nodeName) &&
!skipChildren(oldNode) &&
!skipChildren(newNode)) ||
areNodesEqual(oldNode, newNode))
areNodesEqual(oldNode, newNode, false, ignoreAttributes))
) {
appendCommonChild(
isText(newNode)
Expand Down
20 changes: 20 additions & 0 deletions src/util.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,26 @@ describe.each<[string, (() => string[]) | undefined]>([
false,
)
})
test('elements with different attribute names being ignored', () => {
expect(
areNodesEqual(
span,
differentAttributeNamesSpan,
true,
true,
),
).toBe(true)
})
test('elements with different attribute values being ignored', () => {
expect(
areNodesEqual(
span,
differentAttributeValuesSpan,
true,
true,
),
).toBe(true)
})
test('elements with different childNodes', () => {
expect(areNodesEqual(span, differentChildNodesSpan)).toBe(true)
})
Expand Down
3 changes: 2 additions & 1 deletion src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export function areNodesEqual(
node1: Node,
node2: Node,
deep: boolean = false,
ignoreAttributes = false,
): boolean {
if (node1 === node2) {
return true
Expand All @@ -100,7 +101,7 @@ export function areNodesEqual(
if (node1.data !== (node2 as typeof node1).data) {
return false
}
} else if (isElement(node1)) {
} else if (isElement(node1) && !ignoreAttributes) {
const attributeNames1 = getAttributeNames(node1).sort()
const attributeNames2 = getAttributeNames(node2 as typeof node1).sort()

Expand Down