Skip to content

Commit

Permalink
Merge pull request #7 from papandreou/moreStuff
Browse files Browse the repository at this point in the history
More stuff
  • Loading branch information
Munter committed Mar 10, 2015
2 parents 837c93c + d34e5a9 commit d9b177e
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 59 deletions.
148 changes: 104 additions & 44 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,44 @@ function stringifyEndTag(element) {
}
}

function diffNodeLists(actual, expected, output, diff, inspect, equal) {
var changes = arrayChanges(Array.prototype.slice.call(actual), Array.prototype.slice.call(expected), equal, function (a, b) {
// Figure out whether a and b are "struturally similar" so they can be diffed inline.
return (
a.nodeType === 1 && b.nodeType === 1 &&
a.nodeName === b.nodeName
);
});

changes.forEach(function (diffItem, index) {
output.i().block(function () {
var type = diffItem.type;
if (type === 'insert') {
this.annotationBlock(function () {
this.error('missing ').block(inspect(diffItem.value));
});
} else if (type === 'remove') {
this.block(inspect(diffItem.value).sp().error('// should be removed'));
} else if (type === 'equal') {
this.block(inspect(diffItem.value));
} else {
var valueDiff = diff(diffItem.value, diffItem.expected);
if (valueDiff && valueDiff.inline) {
this.block(valueDiff.diff);
} else if (valueDiff) {
this.block(inspect(diffItem.value).sp()).annotationBlock(function () {
this.shouldEqualError(diffItem.expected, inspect).nl().append(valueDiff.diff);
});
} else {
this.block(inspect(diffItem.value).sp()).annotationBlock(function () {
this.shouldEqualError(diffItem.expected, inspect);
});
}
}
}).nl(index < changes.length - 1 ? 1 : 0);
});
}

module.exports = {
name: 'unexpected-dom',
installInto: function (expect) {
Expand Down Expand Up @@ -151,11 +189,43 @@ module.exports = {
}
});

expect.addType({
name: 'HTMLDocType',
base: 'DOMNode',
identify: function (obj) {
return obj && obj.nodeType === 10 && 'publicId' in obj;
},
inspect: function (doctype, depth, output, inspect) {
output.code('<!DOCTYPE ' + doctype.name + '>', 'html');
},
equal: function (a, b) {
return a.toString() === b.toString();
},
diff: function (actual, expected, output, diff) {
var d = diff('<!DOCTYPE ' + actual.name + '>', '<!DOCTYPE ' + expected.name + '>');
d.inline = true;
return d;
}
});

expect.addType({
name: 'HTMLDocument',
base: 'DOMNode',
identify: function (obj) {
return obj && obj.nodeType === 9 && obj.documentElement && obj.implementation;
},
inspect: function (document, depth, output, inspect) {
for (var i = 0 ; i < document.childNodes.length ; i += 1) {
output.append(inspect(document.childNodes[i]));
}
},
diff: function (actual, expected, output, diff, inspect, equal) {
var result = {
inline: true,
diff: output
};
diffNodeLists(actual.childNodes, expected.childNodes, output, diff, inspect, equal);
return result;
}
});

Expand All @@ -182,8 +252,18 @@ module.exports = {
});

var inspectedChildren = [];
for (var i = 0 ; i < element.childNodes.length ; i += 1) {
inspectedChildren.push(inspect(element.childNodes[i]));
if (elementName === 'script') {
var type = element.getAttribute('type');
if (!type || /javascript/.test(type)) {
type = 'javascript';
}
inspectedChildren.push(output.clone().code(element.textContent, type));
} else if (elementName === 'style') {
inspectedChildren.push(output.clone().code(element.textContent, element.getAttribute('type') || 'text/css'));
} else {
for (var i = 0 ; i < element.childNodes.length ; i += 1) {
inspectedChildren.push(inspect(element.childNodes[i]));
}
}

var width = 0;
Expand Down Expand Up @@ -211,7 +291,7 @@ module.exports = {
});
}
}
output.code(stringifyEndTag(element));
output.code(stringifyEndTag(element), 'html');
return output;
},
diffLimit: 512,
Expand All @@ -236,47 +316,9 @@ module.exports = {
}

if (!emptyElements) {
var changes = arrayChanges(Array.prototype.slice.call(actual.childNodes), Array.prototype.slice.call(expected.childNodes), equal, function (a, b) {
// Figure out whether a and b are "struturally similar" so they can be diffed inline.
// TODO: Consider similarity of the child nodes

return (
a.nodeType === 1 && b.nodeType === 1 &&
a.nodeName === b.nodeName
);
});
output.nl().indentLines();

changes.forEach(function (diffItem, index) {
output.i().block(function () {
var type = diffItem.type;
// var last = !!diffItem.last;

if (type === 'insert') {
this.annotationBlock(function () {
this.error('missing ').block(inspect(diffItem.value));
});
} else if (type === 'remove') {
this.block(inspect(diffItem.value).sp().error('// should be removed'));
} else if (type === 'equal') {
this.block(inspect(diffItem.value));
} else {
var valueDiff = diff(diffItem.value, diffItem.expected);
if (valueDiff && valueDiff.inline) {
this.block(valueDiff.diff);
} else if (valueDiff) {
this.block(inspect(diffItem.value).sp()).annotationBlock(function () {
this.shouldEqualError(diffItem.expected, inspect).nl().append(valueDiff.diff);
});
} else {
this.block(inspect(diffItem.value).sp()).annotationBlock(function () {
this.shouldEqualError(diffItem.expected, inspect);
});
}
}
}).nl();
});
output.outdentLines();
diffNodeLists(actual.childNodes, expected.childNodes, output, diff, inspect, equal);
output.nl().outdentLines();
}

if (emptyElements && conflictingElement) {
Expand Down Expand Up @@ -318,7 +360,25 @@ module.exports = {
});

expect.addAssertion(['HTMLDocument', 'HTMLElement'], 'queried for [first]', function (expect, subject, value) {
this.shift(expect, this.flags.first ? subject.querySelector(value) : subject.querySelectorAll(value), 1);
var queryResult;
if (this.flags.first) {
queryResult = subject.querySelector(value);
if (!queryResult) {
this.errorMode = 'nested';
expect.fail(function (output) {
output.error('The selector').sp().jsString(value).sp().error('yielded no results');
});
}
} else {
queryResult = subject.querySelectorAll(value);
if (queryResult.length === 0) {
this.errorMode = 'nested';
expect.fail(function (output) {
output.error('The selector').sp().jsString(value).sp().error('yielded no results');
});
}
}
this.shift(expect, queryResult, 1);
});
}
};
69 changes: 54 additions & 15 deletions test/unexpected-dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,22 @@ describe('unexpected-dom', function () {
});
});

it('should inspect a document correctly', function () {
expect(
jsdom.jsdom('<!DOCTYPE html><html><head></head><body></body></html>'),
'to inspect as',
'<!DOCTYPE html><html><head></head><body></body></html>'
);
});

it('should inspect a document with nodes around the documentElement correctly', function () {
expect(
jsdom.jsdom('<!DOCTYPE html><!--foo--><html><head></head><body></body></html><!--bar-->'),
'to inspect as',
'<!DOCTYPE html><!--foo--><html><head></head><body></body></html><!--bar-->'
);
});

it('should inspect an attribute-less element correctly', function () {
expect('<div></div>', 'to inspect as itself');
});
Expand Down Expand Up @@ -237,15 +253,41 @@ describe('unexpected-dom', function () {
var document = jsdom.jsdom('<!DOCTYPE html><html><body><div id="foo"></div></body></html>');
expect(document, 'queried for first', 'div', 'to have attributes', { id: 'foo' });
});

it('should error out if the selector matches no elements, first flag set', function () {
var document = jsdom.jsdom('<!DOCTYPE html><html><body><div id="foo"></div></body></html>');
expect(function () {
expect(document.body, 'queried for first', '.blabla', 'to have attributes', { id: 'foo' });
}, 'to throw error',
'expected <body><div id="foo"></div></body> queried for first \'.blabla\', \'to have attributes\', { id: \'foo\' }\n' +
' The selector .blabla yielded no results'
);
});

it('should error out if the selector matches no elements, first flag not set', function () {
var document = jsdom.jsdom('<!DOCTYPE html><html><body><div id="foo"></div></body></html>');
expect(function () {
expect(document.body, 'queried for', '.blabla', 'to have attributes', { id: 'foo' });
}, 'to throw error',
'expected <body><div id="foo"></div></body> queried for \'.blabla\', \'to have attributes\', { id: \'foo\' }\n' +
' The selector .blabla yielded no results'
);
});
});

describe('diffing', function () {
function parseHtmlElement(str) {
return jsdom.jsdom('<!DOCTYPE html><html><body>' + str + '</body></html>').body.firstChild;
}

expect.addAssertion('string', 'diffed with', function (expect, subject, value) {
this.shift(expect, expect.diff(parseHtmlElement(subject), parseHtmlElement(value)).diff.toString(), 1);
expect.addAssertion(['string', 'DOMNode'], 'diffed with', function (expect, subject, value) {
if (typeof subject === 'string') {
subject = parseHtmlElement(subject);
}
if (typeof value === 'string') {
value = parseHtmlElement(value);
}
this.shift(expect, expect.diff(subject, value).diff.toString(), 1);
});

it('should work with HTMLElement', function () {
Expand Down Expand Up @@ -301,7 +343,7 @@ describe('unexpected-dom', function () {
'</div>');
});

it('should produce a nested diff', function () {
it('should produce a nested diff when the outer elements are identical', function () {
expect(
'<div>foo<span><span>foo</span></span><!--bar--></div>',
'diffed with',
Expand All @@ -319,7 +361,7 @@ describe('unexpected-dom', function () {
'</div>');
});

it('should produce a nested diff', function () {
it('should produce a nested diff when when the outer element has a different set of attributes', function () {
expect(
'<div>foo<span id="foo" class="bar"><span>foo</span></span><!--bar--></div>',
'diffed with',
Expand All @@ -338,20 +380,17 @@ describe('unexpected-dom', function () {
'</div>');
});

it('should produce a nested diff', function () {
it('should diff documents with stuff around the documentElement', function () {
expect(
'<div>foo<span><i></i></span><!--bar--></div>',
jsdom.jsdom('<!DOCTYPE html><!--foo--><html><body></body></html><!--bar-->'),
'diffed with',
'<div>foo<span><span></span></span><!--bar--></div>',
jsdom.jsdom('<!DOCTYPE html><html><body></body></html>'),
'to equal',
'<div>\n' +
' foo\n' +
' <span>\n' +
' -<i></i>\n' +
' +<span></span>\n' +
' </span>\n' +
' <!--bar-->\n' +
'</div>');
'<!DOCTYPE html>\n' +
'<!--foo--> // should be removed\n' +
'<html><body></body></html>\n' +
'<!--bar--> // should be removed'
);
});
});
});

0 comments on commit d9b177e

Please sign in to comment.