feat(chai-dom-equals): improve chai-dom-equals error reporting (#111)

This commit is contained in:
Lars den Bakker
2018-12-29 13:50:34 +01:00
committed by GitHub
parent e185a07960
commit 2017cba84f
13 changed files with 193 additions and 499 deletions

View File

@@ -30,8 +30,7 @@ chai.use(chaiDomEquals);
```js
it('has the following dom', async () => {
const el = await fixture(`<div><!-- comment --><h1>${'Hey'} </h1> </div>`);
expect(el).dom.to.equal('<div><!-- comment --><h1>Hey </h1> </div>');
expect(el).dom.to.semantically.equal('<div><h1>Hey</h1></div>');
expect(el).dom.to.equal('<div><h1>Hey</h1></div>');
});
```
@@ -49,7 +48,35 @@ it('has the following shadow dom', async () => {
}
});
const el = await fixture(`<${tag}></${tag}>`);
expect(el).shadowDom.to.equal('<p> shadow content</p> <!-- comment --> <slot></slot>');
expect(el).shadowDom.to.semantically.equal('<p>shadow content</p><slot>');
expect(el).shadowDom.to.equal('<p>shadow content</p><slot>');
});
```
## Literal matching
By default dom is diffed 'semantically'. Differences in whitespace, newlines, attributes/class order are ignored and style, script and commend nodes are removed.
If you want to match literally instead you can use some of the provided utilities to handle diffing on browsers with the shadow dom polyfill:
```javascript
import { getOuterHtml, getCleanedShadowDom } from '@open-wc/chai-dom-equals';
it('literally equals', () => {
const tag = defineCE(class extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.shadowRoot.innerHTML = '<p> shadow content</p> <!-- comment --> <slot></slot>';
}
});
const el = await fixture(`<${tag}></${tag}>`);
const outerHTML = getOuterHtml(el);
const innerHTML = getCleanedShadowDom(el);
expect(outerHTML).to.equal(`<${tag}></${tag}>`);
expect(innerHTML).to.equal('<p> shadow content</p> <!-- comment --> <slot></slot>');
});
```

View File

@@ -1,4 +1,4 @@
import { getSemanticDomDiff } from '@open-wc/semantic-dom-diff';
import { getDiffableSemanticHTML } from '@open-wc/semantic-dom-diff';
/**
* el.outerHTML is not polyfilled so we need to recreate the tag + attributes and
@@ -50,6 +50,8 @@ export const chaiDomEquals = (chai, utils) => {
});
// can not be an arrow function as it gets rebound
// TODO: this is here for backwards compatibility, removal will be
// a breaking change
chai.Assertion.addProperty('semantically', function shadowDom() {
new chai.Assertion(this._obj.nodeType).to.equal(1);
utils.flag(this, 'semantically', true);
@@ -58,60 +60,34 @@ export const chaiDomEquals = (chai, utils) => {
// can not be an arrow function as it gets rebound
// prettier-ignore
const domEquals = _super => function handleDom(value, ...args) {
if (!utils.flag(this, 'semantically') && utils.flag(this, 'dom')) {
const expected = getOuterHtml(this._obj);
this.assert(
value === expected,
'expected dom #{exp} to equal #{act}',
'expected dom #{exp} to not equal #{act}',
expected,
value,
);
} else if (!utils.flag(this, 'semantically') && utils.flag(this, 'shadowDom')) {
const expected = getCleanedShadowDom(this._obj);
this.assert(
value === expected,
'expected shadow dom #{exp} to equal #{act}',
'expected shadow dom #{exp} to not equal #{act}',
expected,
value,
);
} else if (utils.flag(this, 'semantically') && utils.flag(this, 'dom')) {
const result = getSemanticDomDiff(value, getOuterHtml(this._obj));
const message = result ? result.message : '';
const path = result ? result.path : '';
const normalizedHTML = result ? result.normalizedRightHTML : '';
this.assert(
result === null,
() => {
/* eslint-disable no-console */
console.log('Snapshot changed, want to accept the change?');
console.log('Updated Snapshot:');
console.log('');
console.log(normalizedHTML);
/* eslint-enable no-console */
return `expected dom to be semantically equal\n- diff found: ${message}\n- in path: ${path}`;
},
'expected dom to not be semantically equal',
);
} else if (utils.flag(this, 'semantically') && utils.flag(this, 'shadowDom')) {
const result = getSemanticDomDiff(value, getCleanedShadowDom(this._obj));
const message = result ? result.message : '';
const path = result ? result.path : '';
const normalizedHTML = result ? result.normalizedRightHTML : '';
this.assert(
result === null,
() => {
/* eslint-disable no-console */
console.log('Snapshot changed, want to accept the change?');
console.log('Updated Snapshot:');
console.log('');
console.log(normalizedHTML);
/* eslint-enable no-console */
return `expected shadow dom to be semantically equal\n- diff found: ${message}\n- in path: ${path}`;
},
'expected shadow dom to not be semantically equal',
);
if (utils.flag(this, 'dom')) {
const expectedHTML = getDiffableSemanticHTML(value);
const actualHTML = getDiffableSemanticHTML(getOuterHtml(this._obj));
// use chai's built-in string comparison, log the updated snapshot on error
try {
new chai.Assertion(actualHTML).to.equal(expectedHTML);
} catch (error) {
console.log('Snapshot changed, want to accept the change:');
console.log('');
console.log(actualHTML);
throw error;
}
} else if (utils.flag(this, 'shadowDom')) {
const expectedHTML = getDiffableSemanticHTML(value);
const actualHTML = getDiffableSemanticHTML(getCleanedShadowDom(this._obj));
// use chai's built-in string comparison, log the updated snapshot on error
try {
new chai.Assertion(actualHTML).to.equal(expectedHTML);
} catch (error) {
console.log('Snapshot changed, want to accept the change:');
console.log('');
console.log(actualHTML);
throw error;
}
} else {
_super.apply(this, [value, ...args]);
}

View File

@@ -152,19 +152,19 @@ describe('getCleanedShadowDom', () => {
});
describe('dom', () => {
it('can strictly compare dom nodes', async () => {
it('can compare dom nodes', async () => {
const el = await fixture(`<div><!-- comment --><h1>${'Hey'} </h1> </div>`);
expect(el).dom.to.equal('<div><!-- comment --><h1>Hey </h1> </div>');
expect(el).dom.to.equal('<div><h1>Hey</h1></div>');
});
it('can semmantically compare dom nodes', async () => {
it('handles .semantically as backwards compatibility', async () => {
const el = await fixture(`<div><!-- comment --><h1>${'Hey'} </h1> </div>`);
expect(el).dom.to.semantically.equal('<div><h1>Hey</h1></div>');
});
});
describe('shadowDom', () => {
it('can strict compare shadow dom nodes', async () => {
it('can compare shadow dom nodes', async () => {
const tag = defineCE(
class extends HTMLElement {
constructor() {
@@ -178,12 +178,11 @@ describe('shadowDom', () => {
},
);
const el = await fixture(`<${tag}><span> light content </span></${tag}>`);
expect(el).dom.to.semantically.equal(`<${tag}><span>light content</span></${tag}>`);
expect(el).shadowDom.to.equal('<p> shadow content</p> <!-- comment --> <slot></slot>');
expect(el).shadowDom.to.semantically.equal('<p>shadow content</p><slot>');
expect(el).dom.to.equal(`<${tag}><span>light content</span></${tag}>`);
expect(el).shadowDom.to.equal('<p>shadow content</p><slot>');
});
it('can semmantically compare shadow dom nodes', async () => {
it('supports .semantically as backwards compatibility', async () => {
const tag = defineCE(
class extends HTMLElement {
constructor() {

View File

@@ -1 +1 @@
export { getSemanticDomDiff, getAST } from './src/get-dom-diff';
export { getDiffableSemanticHTML, getAST } from './src/get-dom-diff';

View File

@@ -1 +1 @@
export { getSemanticDomDiff } from './src/get-dom-diff.js';
export { getDiffableSemanticHTML } from './src/get-dom-diff.js';

View File

@@ -17,9 +17,9 @@
"test:es5:watch": "karma start karma.es5.config.js --auto-watch=true --single-run=false"
},
"dependencies": {
"@bundled-es-modules/deep-diff": "^1.0.2-rc.1",
"@bundled-es-modules/parse5": "^5.1.0",
"@types/parse5": "^5.0.0"
"@types/parse5": "^5.0.0",
"diffable-html": "^3.0.0"
},
"devDependencies": {
"@bundled-es-modules/chai": "^4.2.0",

View File

@@ -1,39 +0,0 @@
import { isParentNode, isElement } from './parse5-utils.js';
/**
* @param {ASTNode | ASTNode[]} root
* @param {string[]} path
*/
export function findDiffedObject(root, path) {
let node = root;
for (let i = 0; i < path.length; i += 1) {
const step = path[i];
if (Array.isArray(node)) {
const intStep = parseFloat(step);
if (Number.isInteger(intStep)) {
node = node[intStep];
} else {
throw new Error(`Non-integer step: ${step} for array node.`);
}
} else if (step === 'childNodes') {
if (isParentNode(node)) {
node = node.childNodes;
} else {
throw new Error('Cannot read childNodes from non-parent node.');
}
} else if (step === 'attrs') {
if (isElement(node)) {
node = node.attrs;
} else {
throw new Error('Cannot read attributes from non-element node.');
}
} else {
// For all other steps we don't walk further
break;
}
}
return node;
}

View File

@@ -1,70 +0,0 @@
import { isElement, isTextNode } from './parse5-utils.js';
export const isAttribute = arg => arg && 'name' in arg && 'value' in arg;
const { isArray } = Array;
/**
* @param {ASTNode | Attribute} arg
* @returns {string} the human readable diff identifier
*/
function getIdentifier(arg) {
if (isTextNode(arg)) {
return `text "${arg.value}"`;
}
if (isElement(arg)) {
return `tag <${arg.tagName}>`;
}
if (isAttribute(arg)) {
return arg.value ? `attribute [${arg.name}="${arg.value}"]` : `attribute [${arg.name}]`;
}
throw new Error(`Unknown arg: ${arg}`);
}
/**
* Asserts that the diff is an array diff, returns type assertions to remove optional props.
* @returns {boolean}
*/
function isArrayDiff(diff) {
return diff.kind === 'A' && !!diff.item && typeof diff.index === 'number';
}
const messageTemplates = {
// New diff
N: (diff, lhs, rhs) => `${getIdentifier(rhs)} has been added`,
// Edit diff
E: (diff, lhs, rhs) => `${getIdentifier(lhs)} was changed to ${getIdentifier(rhs)}`,
// Delete diff
D: (diff, lhs) => `${getIdentifier(lhs)} has been removed`,
};
/**
* Generates a human understandable message for a HTML diff.
*
* @param {object} diff The diff
* @param {object | object[]} lhs The left hand side diffed object.
* Can be a HTML ASTNode or an Attribute.
* @param {object | object[]} rhs The left hand side diffed object.
* Can be a HTML ASTNode or an Attribute.
*
* @returns the message
*/
export function getDiffMessage(diff, lhs, rhs) {
// Array diff
if (isArray(lhs) || isArray(rhs)) {
if (!isArrayDiff(diff) || !isArray(lhs) || !isArray(rhs)) {
throw new Error('Not all arguments are array diffs');
}
return getDiffMessage(diff.item, lhs[diff.index], rhs[diff.index]);
}
// Non-array diff
if (diff.kind in messageTemplates) {
return messageTemplates[diff.kind](diff, lhs, rhs);
}
throw new Error(`Unknown diff kind: ${diff.kind}`);
}

View File

@@ -1,67 +0,0 @@
import { isParentNode, isElement } from './parse5-utils.js';
/**
* @param {*} node The AST Node
* @returns {string | null} the AST node name
*/
function getNodeName(node) {
if (!isElement(node)) {
return null;
}
if (node.attrs) {
const idAttr = node.attrs.find(attr => attr.name === 'id');
if (idAttr) {
return `${node.nodeName}#${idAttr.value}`;
}
const classAttr = node.attrs.find(attr => attr.name === 'class');
if (classAttr) {
return `${node.nodeName}.${classAttr.value.split(' ').join('.')}`;
}
}
return node.nodeName;
}
/**
* @param {ASTNode | ASTNode[]} root the root to walk from
* @param {string[]} path the full path to the dom element
* @returns {string[]} the human readable path to a dom element
*/
export function getDiffPath(root, path) {
const names = [];
let node = root;
for (let i = 0; i < path.length; i += 1) {
const step = path[i];
if (Array.isArray(node)) {
const intStep = parseFloat(step);
if (Number.isInteger(intStep)) {
node = node[intStep];
} else {
throw new Error(`Non-integer step: ${step} for array node.`);
}
} else if (step === 'childNodes') {
if (isParentNode(node)) {
node = node.childNodes;
} else {
throw new Error('Cannot read childNodes from non-parent node.');
}
} else {
// Break loop if we end up at a type of path section we don't want
// walk further into
break;
}
if (!Array.isArray(node)) {
const name = getNodeName(node);
if (name) {
names.push(name);
}
}
}
return names.join(' > ');
}

View File

@@ -1,11 +1,4 @@
import { DocumentFragment } from 'parse5';
interface DiffResult {
message: String;
path: String;
normalizedLeftHTML: String;
normalizedRightHTML: String;
}
export function getAST(value, config): DocumentFragment
export function getSemanticDomDiff(leftHTML, rightHTML, config): DiffResult
export function getDiffableSemanticHTML(html: string): string

View File

@@ -1,38 +1,8 @@
import { parseFragment, serialize } from '@bundled-es-modules/parse5';
import { deepDiff } from '@bundled-es-modules/deep-diff';
import toDiffableHtml from 'diffable-html';
import { sanitizeHtmlString } from './sanitize-html-string.js';
import { normalizeAST } from './normalize-ast.js';
import { getDiffMessage } from './get-diff-message.js';
import { findDiffedObject } from './find-diffed-object.js';
import { getDiffPath } from './get-diff-path.js';
/**
* @typedef {object} DiffResult
* @param {string} message
* @param {string} path
*/
/**
* Creates the DiffResult for two AST trees.
*
* @param {ASTNode} leftTree the left tree AST
* @param {ASTNode} rightTree the right tree AST
* @param {Object} diff the semantic difference between the two trees
*
* @returns {DiffResult} the diff result containing the human readable semantic difference
*/
function createDiffResult(leftTree, rightTree, diff) {
const leftDiffObject = findDiffedObject(leftTree, diff.path);
const rightDiffObject = findDiffedObject(rightTree, diff.path);
return {
message: getDiffMessage(diff, leftDiffObject, rightDiffObject),
path: getDiffPath(leftTree, diff.path),
normalizedLeftHTML: serialize(leftTree),
normalizedRightHTML: serialize(rightTree),
};
}
export function getAST(value, config = {}) {
const ast = parseFragment(sanitizeHtmlString(value));
@@ -41,29 +11,18 @@ export function getAST(value, config = {}) {
}
/**
* Parses two HTML trees, and generates the semantic difference between the two trees.
* The HTML is diffed semantically, not literally. This means that changes in attribute
* and class order and whitespace/newlines are ignored. Also, script and style
* tags ignored.
* Parses HTML and returns HTML in a formatted and diffable format which is semantically
* equal to the input, but with some transformations:
* - whitespace and newlines in and around tags are normalized
* - classes and attributes are ordered
* - script, style and comments are removed
*
* @param leftHTML the left HTML tree
* @param rightHTML the right HTML tree
* @returns {DiffResult | null} the diff result, or undefined if no diffs were found
* @param {string} html
* @returns {string}
*/
export function getSemanticDomDiff(leftHTML, rightHTML, config = {}) {
const leftTree = getAST(leftHTML);
const rightTree = getAST(rightHTML);
normalizeAST(leftTree, config.ignoredTags);
normalizeAST(rightTree, config.ignoredTags);
// parentNode causes a circular reference, so ignore them.
const ignore = (path, key) => key === 'parentNode';
const diffs = deepDiff(leftTree, rightTree, ignore);
if (!diffs || !diffs.length) {
return null;
}
return createDiffResult(leftTree, rightTree, diffs[0]);
export function getDiffableSemanticHTML(html) {
const ast = getAST(html);
normalizeAST(ast, []);
const serialized = serialize(ast);
return toDiffableHtml(serialized);
}

View File

@@ -1,6 +1,2 @@
export const isNode = arg => arg && 'nodeName' in arg;
export const isElement = arg => arg && 'tagName' in arg;
export const isParentNode = arg => arg && 'childNodes' in arg;
export const isTextNode = arg => arg && arg.nodeName === '#text';
export const isCommentNode = arg => arg && arg.nodeName === '#comment';
export const isDocumentFragment = arg => arg && arg.nodeName === '#document-fragment';

View File

@@ -1,93 +1,81 @@
import { expect } from '@bundled-es-modules/chai';
import { getSemanticDomDiff } from '../index.js';
import { getDiffableSemanticHTML } from '../index.js';
import largeTemplate from './large-template';
describe('getSemanticDomDiff()', () => {
describe('diffs', () => {
describe('element', () => {
it('changed element', () => {
const diff = getSemanticDomDiff('<div></div>', '<span></span>');
const htmlA = getDiffableSemanticHTML('<div></div>');
const htmlB = getDiffableSemanticHTML('<span></span>');
expect(diff.message).to.equal('tag <div> was changed to tag <span>');
expect(diff.path).to.equal('div');
expect(htmlA).to.not.equal(htmlB);
});
it('added element', () => {
const diff = getSemanticDomDiff('<div></div>', '<div></div><div></div>');
const htmlA = getDiffableSemanticHTML('<div></div>');
const htmlB = getDiffableSemanticHTML('<div></div><div></div>');
expect(diff.message).to.equal('tag <div> has been added');
expect(diff.path).to.equal('');
expect(htmlA).to.not.equal(htmlB);
});
it('removed element', () => {
const diff = getSemanticDomDiff('<div></div><div></div>', '<div></div>');
const htmlA = getDiffableSemanticHTML('<div></div><div></div>');
const htmlB = getDiffableSemanticHTML('<div></div>');
expect(diff.message).to.equal('tag <div> has been removed');
expect(diff.path).to.equal('');
expect(htmlA).to.not.equal(htmlB);
});
});
describe('attributes', () => {
it('changed attribute', () => {
const diff = getSemanticDomDiff('<div foo="bar"></div>', '<div foo="baz"></div>');
const htmlA = getDiffableSemanticHTML('<div foo="bar"></div>');
const htmlB = getDiffableSemanticHTML('<div foo="baz"></div>');
expect(diff.message).to.equal('attribute [foo="bar"] was changed to attribute [foo="baz"]');
expect(diff.path).to.equal('div');
expect(htmlA).to.not.equal(htmlB);
});
it('added attribute', () => {
const diff = getSemanticDomDiff('<div></div>', '<div foo="bar"></div>');
const htmlA = getDiffableSemanticHTML('<div></div>');
const htmlB = getDiffableSemanticHTML('<div foo="bar"></div>');
expect(diff.message).to.equal('attribute [foo="bar"] has been added');
expect(diff.path).to.equal('div');
expect(htmlA).to.not.equal(htmlB);
});
it('removed attribute', () => {
const diff = getSemanticDomDiff('<div foo="bar"></div>', '<div></div>');
const htmlA = getDiffableSemanticHTML('<div foo="bar"></div>');
const htmlB = getDiffableSemanticHTML('<div></div>');
expect(diff.message).to.equal('attribute [foo="bar"] has been removed');
expect(diff.path).to.equal('div');
expect(htmlA).to.not.equal(htmlB);
});
});
describe('text', () => {
it('changed text', () => {
const diff = getSemanticDomDiff('<div>foo</div>', '<div>bar</div>');
const htmlA = getDiffableSemanticHTML('<div>foo</div>');
const htmlB = getDiffableSemanticHTML('<div>bar</div>');
expect(diff.message).to.equal('text "foo" was changed to text "bar"');
expect(diff.path).to.equal('div');
expect(htmlA).to.not.equal(htmlB);
});
it('removed text', () => {
const diff = getSemanticDomDiff('<div>foo</div>', '<div></div>');
const htmlA = getDiffableSemanticHTML('<div>foo</div>');
const htmlB = getDiffableSemanticHTML('<div></div>');
expect(diff.message).to.equal('text "foo" has been removed');
expect(diff.path).to.equal('div');
expect(htmlA).to.not.equal(htmlB);
});
it('added text', () => {
const diff = getSemanticDomDiff('<div></div>', '<div>foo</div>');
const htmlA = getDiffableSemanticHTML('<div></div>');
const htmlB = getDiffableSemanticHTML('<div>foo</div>');
expect(diff.message).to.equal('text "foo" has been added');
expect(diff.path).to.equal('div');
});
});
describe('multiple diffs', () => {
it('returns the first diff', () => {
const diff = getSemanticDomDiff(
'<div>foo</div><div foo="bar"></div>',
'<div>bar</div><span foo="baz"></span>',
);
expect(diff.message).to.equal('tag <div> was changed to tag <span>');
expect(diff.path).to.equal('div');
expect(htmlA).to.not.equal(htmlB);
});
});
describe('deep changes', () => {
it('element changes', () => {
const a = `
const htmlA = getDiffableSemanticHTML(`
<div>
<div id="foo">
<div>
@@ -99,8 +87,9 @@ describe('getSemanticDomDiff()', () => {
</div>
<div></div>
</div>
`;
const b = `
`);
const htmlB = getDiffableSemanticHTML(`
<div>
<div id="foo">
<div>
@@ -112,15 +101,13 @@ describe('getSemanticDomDiff()', () => {
</div>
<div></div>
</div>
`;
const diff = getSemanticDomDiff(a, b);
`);
expect(diff.message).to.equal('tag <div> was changed to tag <span>');
expect(diff.path).to.equal('div > div#foo > div > div.foo > div.bar.baz.foo');
expect(htmlA).to.not.equal(htmlB);
});
it('attribute changes', () => {
const a = `
const htmlA = getDiffableSemanticHTML(`
<div>
<div id="foo">
<div>
@@ -132,8 +119,9 @@ describe('getSemanticDomDiff()', () => {
</div>
<div></div>
</div>
`;
const b = `
`);
const htmlB = getDiffableSemanticHTML(`
<div>
<div id="foo">
<div>
@@ -145,11 +133,9 @@ describe('getSemanticDomDiff()', () => {
</div>
<div></div>
</div>
`;
const diff = getSemanticDomDiff(a, b);
`);
expect(diff.message).to.equal('attribute [foo="bar"] has been added');
expect(diff.path).to.equal('div > div#foo > div > div');
expect(htmlA).to.not.equal(htmlB);
});
});
});
@@ -157,184 +143,143 @@ describe('getSemanticDomDiff()', () => {
describe('equality', () => {
describe('simple', () => {
it('element', () => {
const diff = getSemanticDomDiff('<div></div>', '<div></div>');
expect(diff).to.equal(null);
const htmlA = getDiffableSemanticHTML('<div></div>');
const htmlB = getDiffableSemanticHTML('<div></div>');
expect(htmlA).to.equal(htmlB);
});
it('attribute', () => {
const diff = getSemanticDomDiff('<div foo="bar"></div>', '<div foo="bar"></div>');
expect(diff).to.equal(null);
const htmlA = getDiffableSemanticHTML('<div foo="bar"></div>');
const htmlB = getDiffableSemanticHTML('<div foo="bar"></div>');
expect(htmlA).to.equal(htmlB);
});
it('text', () => {
const diff = getSemanticDomDiff('<div>foo</div>', '<div>foo</div>');
expect(diff).to.equal(null);
const htmlA = getDiffableSemanticHTML('<div>foo</div>');
const htmlB = getDiffableSemanticHTML('<div>foo</div>');
expect(htmlA).to.equal(htmlB);
});
});
describe('complex', () => {
it('large template', () => {
const diff = getSemanticDomDiff(largeTemplate, largeTemplate);
expect(diff).to.equal(null);
});
it('self closing tags', () => {
const diff = getSemanticDomDiff('<div><br><hr /></div>', '<div><br /><hr></div>');
expect(diff).to.equal(null);
const htmlA = getDiffableSemanticHTML(largeTemplate);
const htmlB = getDiffableSemanticHTML(largeTemplate);
expect(htmlA).to.equal(htmlB);
});
});
describe('ordering', () => {
it('attributes order', () => {
const diff = getSemanticDomDiff(
'<div a="1" b="2" c="3"></div>',
'<div c="3" a="1" b="2"></div>',
);
expect(diff).to.equal(null);
const htmlA = getDiffableSemanticHTML('<div a="1" b="2" c="3"></div>');
const htmlB = getDiffableSemanticHTML('<div c="3" a="1" b="2"></div>');
expect(htmlA).to.equal(htmlB);
});
it('class order', () => {
const diff = getSemanticDomDiff(
'<div class="foo bar"></div>',
'<div class="bar foo"></div>',
);
expect(diff).to.equal(null);
const htmlA = getDiffableSemanticHTML('<div class="foo bar"></div>');
const htmlB = getDiffableSemanticHTML('<div class="bar foo"></div>');
expect(htmlA).to.equal(htmlB);
});
});
describe('whitespace', () => {
it('trailing whitespace in attributes', () => {
const diff = getSemanticDomDiff('<div foo="bar" ></div>', '<div foo="bar"></div>');
expect(diff).to.equal(null);
const htmlA = getDiffableSemanticHTML('<div foo="bar" ></div>');
const htmlB = getDiffableSemanticHTML('<div foo="bar"></div>');
expect(htmlA).to.equal(htmlB);
});
it('trailing whitespace in class', () => {
const diff = getSemanticDomDiff(
'<div class="foo bar "></div>',
'<div class="foo bar "></div>',
);
expect(diff).to.equal(null);
const htmlA = getDiffableSemanticHTML('<div class="foo bar "></div>');
const htmlB = getDiffableSemanticHTML('<div class="foo bar "></div>');
expect(htmlA).to.equal(htmlB);
});
it('whitespace between classes', () => {
const diff = getSemanticDomDiff(
'<div class="foo bar "></div>',
'<div class="foo bar"></div>',
);
expect(diff).to.equal(null);
const htmlA = getDiffableSemanticHTML('<div class="foo bar "></div>');
const htmlB = getDiffableSemanticHTML('<div class="foo bar"></div>');
expect(htmlA).to.equal(htmlB);
});
it('whitespace before and after template', () => {
const diff = getSemanticDomDiff(' <div></div> ', '<div></div>');
expect(diff).to.equal(null);
const htmlA = getDiffableSemanticHTML(' <div></div> ');
const htmlB = getDiffableSemanticHTML('<div></div>');
expect(htmlA).to.equal(htmlB);
});
it('whitespace in between nodes', () => {
const diff = getSemanticDomDiff(
'<div> </div> foo <div> </div>',
'<div></div>foo<div></div>',
);
expect(diff).to.equal(null);
const htmlA = getDiffableSemanticHTML('<div> </div> foo <div> </div>');
const htmlB = getDiffableSemanticHTML('<div></div>foo<div></div>');
expect(htmlA).to.equal(htmlB);
});
it('whitespace around text nodes', () => {
const diff = getSemanticDomDiff('<div>foo</div>', '<div> foo </div>');
expect(diff).to.equal(null);
const htmlA = getDiffableSemanticHTML('<div>foo</div>');
const htmlB = getDiffableSemanticHTML('<div> foo </div>');
expect(htmlA).to.equal(htmlB);
});
});
describe('tabs', () => {
it('tabs before and after template', () => {
const diff = getSemanticDomDiff('\t\t<div></div>\t', '<div></div>');
expect(diff).to.equal(null);
const htmlA = getDiffableSemanticHTML('\t\t<div></div>\t');
const htmlB = getDiffableSemanticHTML('<div></div>');
expect(htmlA).to.equal(htmlB);
});
it('tabs in between nodes', () => {
const diff = getSemanticDomDiff(
'<div>\t<div></div>\t \t \t</div>',
'<div><div></div></div>',
);
expect(diff).to.equal(null);
const htmlA = getDiffableSemanticHTML('<div>\t<div></div>\t \t \t</div>');
const htmlB = getDiffableSemanticHTML('<div><div></div></div>');
expect(htmlA).to.equal(htmlB);
});
it('tabs around text nodes', () => {
const diff = getSemanticDomDiff('<div>foo</div>', '<div>\tfoo\t</div>');
expect(diff).to.equal(null);
const htmlA = getDiffableSemanticHTML('<div>foo</div>');
const htmlB = getDiffableSemanticHTML('<div>\tfoo\t</div>');
expect(htmlA).to.equal(htmlB);
});
});
describe('newlines', () => {
it('newlines before and after template', () => {
const diff = getSemanticDomDiff('\n\n<div></div>\n', '<div></div>');
expect(diff).to.equal(null);
const htmlA = getDiffableSemanticHTML('\n\n<div></div>\n');
const htmlB = getDiffableSemanticHTML('<div></div>');
expect(htmlA).to.equal(htmlB);
});
it('newlines in between nodes', () => {
const diff = getSemanticDomDiff(
'<div>\n<div></div>\n \n \n</div>',
'<div><div></div></div>',
);
expect(diff).to.equal(null);
const htmlA = getDiffableSemanticHTML('<div>\n<div></div>\n \n \n</div>');
const htmlB = getDiffableSemanticHTML('<div><div></div></div>');
expect(htmlA).to.equal(htmlB);
});
it('newlines around text nodes', () => {
const diff = getSemanticDomDiff('<div>foo</div>', '<div>\n\n\nfoo\n</div>');
expect(diff).to.equal(null);
const htmlA = getDiffableSemanticHTML('<div>foo</div>');
const htmlB = getDiffableSemanticHTML('<div>\n\n\nfoo\n</div>');
expect(htmlA).to.equal(htmlB);
});
});
describe('filtered nodes', () => {
it('comments', () => {
const diff = getSemanticDomDiff('<div>foo<!-- comment --></div>', '<div>foo</div>');
expect(diff).to.equal(null);
const htmlA = getDiffableSemanticHTML('<div>foo<!-- comment --></div>');
const htmlB = getDiffableSemanticHTML('<div>foo</div>');
expect(htmlA).to.equal(htmlB);
});
it('styles', () => {
const diff = getSemanticDomDiff(
const htmlA = getDiffableSemanticHTML(
'<div>foo<style> .foo { color: blue; } </style></div>',
'<div>foo</div>',
);
expect(diff).to.equal(null);
const htmlB = getDiffableSemanticHTML('<div>foo</div>');
expect(htmlA).to.equal(htmlB);
});
it('script', () => {
const diff = getSemanticDomDiff(
'<div>foo<script>console.log("foo");</script></div>',
'<div>foo</div>',
);
expect(diff).to.equal(null);
});
it('ignored tags', () => {
const diff = getSemanticDomDiff(
'<div><span>foo</span></div>',
'<div><span>bar</span></div>',
{ ignoredTags: ['span'] },
);
expect(diff).to.equal(null);
const htmlA = getDiffableSemanticHTML('<div>foo<script>console.log("foo");</script></div>');
const htmlB = getDiffableSemanticHTML('<div>foo</div>');
expect(htmlA).to.equal(htmlB);
});
});
@@ -354,35 +299,10 @@ bar
</div>
<div></div>
`;
const diff = getSemanticDomDiff(a, b);
expect(diff).to.equal(null);
const htmlA = getDiffableSemanticHTML(a);
const htmlB = getDiffableSemanticHTML(b);
expect(htmlA).to.equal(htmlB);
});
});
});
describe('values', () => {
it('handles strings', () => {
const diff = getSemanticDomDiff('<div></div>', '<span></span>');
expect(diff.message).to.equal('tag <div> was changed to tag <span>');
});
});
describe('diff tree', () => {
it('returns the left and right side normalized trees', () => {
const diff = getSemanticDomDiff(
`
<div id="foo"> <div> <div class="foo bar "> <div>
</div> </div>
`,
'<span></span>',
);
expect(diff.normalizedLeftHTML).to.equal(
'<div id="foo"><div><div class="bar foo"><div></div></div></div></div>',
);
expect(diff.normalizedRightHTML).to.equal('<span></span>');
});
});
});