From 56878dafa31967fc430f9d4dcc38466792edf1a6 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Tue, 10 Mar 2015 14:46:49 +0100 Subject: [PATCH 1/7] Support inspecting and diffing HTMLDocuments. --- lib/index.js | 112 ++++++++++++++++++++++++++--------------- test/unexpected-dom.js | 45 ++++++++++++----- 2 files changed, 104 insertions(+), 53 deletions(-) diff --git a/lib/index.js b/lib/index.js index 96115759..58cb8435 100644 --- a/lib/index.js +++ b/lib/index.js @@ -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) { @@ -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('', 'html'); + }, + equal: function (a, b) { + return a.toString() === b.toString(); + }, + diff: function (actual, expected, output, diff) { + var d = diff('', ''); + 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; } }); @@ -236,47 +306,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) { diff --git a/test/unexpected-dom.js b/test/unexpected-dom.js index 31ed67c3..73631aab 100644 --- a/test/unexpected-dom.js +++ b/test/unexpected-dom.js @@ -34,6 +34,22 @@ describe('unexpected-dom', function () { }); }); + it('should inspect a document correctly', function () { + expect( + jsdom.jsdom(' '), + 'to inspect as', + ' ' + ); + }); + + it('should inspect a document with nodes around the documentElement correctly', function () { + expect( + jsdom.jsdom(' '), + 'to inspect as', + ' ' + ); + }); + it('should inspect an attribute-less element correctly', function () { expect('
', 'to inspect as itself'); }); @@ -244,8 +260,14 @@ describe('unexpected-dom', function () { return jsdom.jsdom('' + str + '').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 () { @@ -338,20 +360,17 @@ describe('unexpected-dom', function () { ''); }); - it('should produce a nested diff', function () { + it('should diff documents with stuff around the documentElement', function () { expect( - '
foo
', + jsdom.jsdom(''), 'diffed with', - '
foo
', + jsdom.jsdom(''), 'to equal', - '
\n' + - ' foo\n' + - ' \n' + - ' -\n' + - ' +\n' + - ' \n' + - ' \n' + - '
'); + '\n' + + ' // should be removed\n' + + '\n' + + ' // should be removed' + ); }); }); }); From 2772b6aacffd0293905a175641d0ef62d45e9b92 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Tue, 10 Mar 2015 14:47:05 +0100 Subject: [PATCH 2/7] queried for: Error out if the selector matches no elements. --- lib/index.js | 18 +++++++++++++++++- test/unexpected-dom.js | 10 ++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/index.js b/lib/index.js index 58cb8435..7e33102a 100644 --- a/lib/index.js +++ b/lib/index.js @@ -350,7 +350,23 @@ 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'; + output.error('The selector').sp().jsString(value).sp().error('yielded no results'); + } + } + this.shift(expect, queryResult, 1); }); } }; diff --git a/test/unexpected-dom.js b/test/unexpected-dom.js index 73631aab..b1280ac4 100644 --- a/test/unexpected-dom.js +++ b/test/unexpected-dom.js @@ -253,6 +253,16 @@ describe('unexpected-dom', function () { var document = jsdom.jsdom('
'); expect(document, 'queried for first', 'div', 'to have attributes', { id: 'foo' }); }); + + it('should error out if the selector matches no elements', function () { + var document = jsdom.jsdom('
'); + expect(function () { + expect(document.body, 'queried for first', '.blabla', 'to have attributes', { id: 'foo' }); + }, 'to throw error', + "expected
queried for first '.blabla', 'to have attributes', { id: 'foo' }\n" + + ' The selector .blabla yielded no results' + ); + }); }); describe('diffing', function () { From 9f546353e74b023698b5329cef717be71cb5fbc1 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Tue, 10 Mar 2015 14:52:29 +0100 Subject: [PATCH 3/7] Fixed a stupid bug and a jshint error. --- lib/index.js | 4 +++- test/unexpected-dom.js | 14 ++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/index.js b/lib/index.js index 7e33102a..3b48b4f0 100644 --- a/lib/index.js +++ b/lib/index.js @@ -363,7 +363,9 @@ module.exports = { queryResult = subject.querySelectorAll(value); if (queryResult.length === 0) { this.errorMode = 'nested'; - output.error('The selector').sp().jsString(value).sp().error('yielded no results'); + expect.fail(function (output) { + output.error('The selector').sp().jsString(value).sp().error('yielded no results'); + }); } } this.shift(expect, queryResult, 1); diff --git a/test/unexpected-dom.js b/test/unexpected-dom.js index b1280ac4..acf6217c 100644 --- a/test/unexpected-dom.js +++ b/test/unexpected-dom.js @@ -254,12 +254,22 @@ describe('unexpected-dom', function () { expect(document, 'queried for first', 'div', 'to have attributes', { id: 'foo' }); }); - it('should error out if the selector matches no elements', function () { + it('should error out if the selector matches no elements, first flag set', function () { var document = jsdom.jsdom('
'); expect(function () { expect(document.body, 'queried for first', '.blabla', 'to have attributes', { id: 'foo' }); }, 'to throw error', - "expected
queried for first '.blabla', 'to have attributes', { id: 'foo' }\n" + + 'expected
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('
'); + expect(function () { + expect(document.body, 'queried for', '.blabla', 'to have attributes', { id: 'foo' }); + }, 'to throw error', + "expected
queried for '.blabla', 'to have attributes', { id: 'foo' }\n" + ' The selector .blabla yielded no results' ); }); From ae36f404a216b3ebaf10ad0bb2f346657d8ce52d Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Tue, 10 Mar 2015 21:22:44 +0100 Subject: [PATCH 4/7] Fixed jshint complaint. --- test/unexpected-dom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unexpected-dom.js b/test/unexpected-dom.js index acf6217c..c3835bbd 100644 --- a/test/unexpected-dom.js +++ b/test/unexpected-dom.js @@ -269,7 +269,7 @@ describe('unexpected-dom', function () { expect(function () { expect(document.body, 'queried for', '.blabla', 'to have attributes', { id: 'foo' }); }, 'to throw error', - "expected
queried for '.blabla', 'to have attributes', { id: 'foo' }\n" + + 'expected
queried for \'.blabla\', \'to have attributes\', { id: \'foo\' }\n' + ' The selector .blabla yielded no results' ); }); From 196b7575f5b43565c9402685c06a4e37a8e8a403 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Tue, 10 Mar 2015 21:23:01 +0100 Subject: [PATCH 5/7] Improved test descriptions. --- test/unexpected-dom.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unexpected-dom.js b/test/unexpected-dom.js index c3835bbd..edda6692 100644 --- a/test/unexpected-dom.js +++ b/test/unexpected-dom.js @@ -343,7 +343,7 @@ describe('unexpected-dom', function () { ''); }); - it('should produce a nested diff', function () { + it('should produce a nested diff when the outer elements are identical', function () { expect( '
foofoo
', 'diffed with', @@ -361,7 +361,7 @@ describe('unexpected-dom', function () { ''); }); - 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( '
foofoo
', 'diffed with', From 69a55bbb418bcc3aa85619707c8d6427abbcc16b Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Tue, 10 Mar 2015 21:30:51 +0100 Subject: [PATCH 6/7] Remember to mark end tags up as HTML. --- lib/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/index.js b/lib/index.js index 3b48b4f0..ec9e1805 100644 --- a/lib/index.js +++ b/lib/index.js @@ -281,7 +281,7 @@ module.exports = { }); } } - output.code(stringifyEndTag(element)); + output.code(stringifyEndTag(element), 'html'); return output; }, diffLimit: 512, From d34e5a9ab70f7cb32eac0a05173dd5a32898e042 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Tue, 10 Mar 2015 21:44:34 +0100 Subject: [PATCH 7/7] Mark up contents of