From 5ec8a257825651afe5e04029b4c4e38aaee580ca Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Wed, 6 May 2015 20:40:35 +0200 Subject: [PATCH 01/13] Update magicpen-prism to 2.0.0. --- lib/index.js | 24 +++++++++++++----------- package.json | 2 +- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/index.js b/lib/index.js index f3482963..aded2f89 100644 --- a/lib/index.js +++ b/lib/index.js @@ -77,7 +77,7 @@ function isVoidElement(elementName) { } function writeAttributeToMagicPen(output, attributeName, value) { - output['prism:attr-name'](attributeName); + output.prismAttrName(attributeName); if (!isBooleanAttribute(attributeName)) { if (attributeName === 'class') { value = value.join(' '); @@ -86,9 +86,10 @@ function writeAttributeToMagicPen(output, attributeName, value) { return cssProp + ': ' + value[cssProp]; }).join('; '); } - output['prism:punctuation']('="'); - output['prism:attr-value'](entitify(value)); - output['prism:punctuation']('"'); + output + .prismPunctuation('="') + .prismAttrValue(entitify(value)) + .prismPunctuation('"'); } } @@ -357,11 +358,12 @@ module.exports = { if (conflictingElement) { var canContinueLine = true; - output['prism:punctuation']('<'); - output['prism:tag'](actual.nodeName.toLowerCase()); + output + .prismPunctuation('<') + .prismTag(actual.nodeName.toLowerCase()); if (actual.nodeName.toLowerCase() !== expected.nodeName.toLowerCase()) { output.sp().annotationBlock(function () { - this.error('should be').sp()['prism:tag'](expected.nodeName.toLowerCase()); + this.error('should be').sp().prismTag(expected.nodeName.toLowerCase()); }).nl(); canContinueLine = false; } @@ -395,7 +397,7 @@ module.exports = { }).nl(); canContinueLine = false; }); - output['prism:punctuation']('>'); + output.prismPunctuation('>'); } else { output.code(stringifyStartTag(actual), 'html'); } @@ -519,7 +521,7 @@ module.exports = { return expect.promise.settle(promiseByKey).then(function () { expect.fail({ diff: function (output, diff, inspect, equal) { - output['prism:punctuation']('<')['prism:tag'](subject.nodeName.toLowerCase()); + output.prismPunctuation('<').prismTag(subject.nodeName.toLowerCase()); var canContinueLine = true; Object.keys(attrs).forEach(function (attributeName) { var lowerCaseAttributeName = attributeName.toLowerCase(); @@ -555,7 +557,7 @@ module.exports = { this .error('missing') .sp() - ['prism:attr-name'](attributeName, 'html'); + .prismAttrName(attributeName, 'html'); if (expectedValueByAttributeName[attributeName] !== true) { this .sp() @@ -569,7 +571,7 @@ module.exports = { canContinueLine = false; } }); - output['prism:punctuation']('>'); + output.prismPunctuation('>'); return { inline: true, diff: output diff --git a/package.json b/package.json index ae5c3d2e..909e9f83 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "jsdom": "^0.11.1", "jshint": "^2.6.3", "magicpen": "^4.3.5", - "magicpen-prism": "^1.1.1", + "magicpen-prism": "2.0.0", "mocha": "^2.1.0", "mocha-lcov-reporter": "0.0.2", "sinon": "1.14.1", From ad633f8e303160394d0b0b0022c0f59927e4ae7e Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Wed, 6 May 2015 20:42:56 +0200 Subject: [PATCH 02/13] Upgrade magicpen-prism to a "real" dependency. I started relying on the MagicPen styles it exposes when implementing the attribute diffs without realizing that it was only a dev dependency. --- lib/index.js | 1 + package.json | 4 ++-- test/unexpected-dom.js | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/index.js b/lib/index.js index aded2f89..12bc9e7e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -170,6 +170,7 @@ function diffNodeLists(actual, expected, output, diff, inspect, equal) { module.exports = { name: 'unexpected-dom', installInto: function (expect) { + expect.output.installPlugin(require('magicpen-prism')); var topLevelExpect = expect; expect.addType({ name: 'DOMNode', diff --git a/package.json b/package.json index 909e9f83..74424d1c 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,6 @@ "jsdom": "^0.11.1", "jshint": "^2.6.3", "magicpen": "^4.3.5", - "magicpen-prism": "2.0.0", "mocha": "^2.1.0", "mocha-lcov-reporter": "0.0.2", "sinon": "1.14.1", @@ -47,6 +46,7 @@ }, "dependencies": { "array-changes": "1.0.1", - "extend": "^2.0.0" + "extend": "^2.0.0", + "magicpen-prism": "2.0.0" } } diff --git a/test/unexpected-dom.js b/test/unexpected-dom.js index 158234a8..f609e15b 100644 --- a/test/unexpected-dom.js +++ b/test/unexpected-dom.js @@ -5,7 +5,6 @@ var unexpected = require('unexpected'), jsdom = require('jsdom'); var expect = unexpected.clone().installPlugin(require('unexpected-sinon')).installPlugin(unexpectedDom); -expect.output.installPlugin(require('magicpen-prism')); expect.addAssertion('to inspect as [itself]', function (expect, subject, value) { var originalSubject = subject; From d0f77bcf54be42e4b70dd72a58064bd7d2f7f64e Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Wed, 6 May 2015 21:54:02 +0200 Subject: [PATCH 03/13] Update unexpected to 7.0.5. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 74424d1c..76720bc5 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "mocha-lcov-reporter": "0.0.2", "sinon": "1.14.1", "uglifyjs": "^2.4.10", - "unexpected": "^7.0.1", + "unexpected": "7.0.5", "unexpected-sinon": "5.1.2" }, "dependencies": { From 83dbee28614c861dbabbaf5c6f8c9ffbc66e07d1 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Wed, 6 May 2015 21:27:11 +0200 Subject: [PATCH 04/13] HTMLElement: Partly implemented 'to satisfy'. --- lib/index.js | 169 ++++++++++++++++++++++++----------------- test/unexpected-dom.js | 48 ++++++++++++ 2 files changed, 148 insertions(+), 69 deletions(-) diff --git a/lib/index.js b/lib/index.js index 12bc9e7e..588173b5 100644 --- a/lib/index.js +++ b/lib/index.js @@ -435,37 +435,82 @@ module.exports = { } }); - expect.addAssertion('HTMLElement', 'to [only] have (attribute|attributes)', function (expect, subject, value) { + expect.addAssertion('HTMLElement', 'to satisfy', function (expect, subject, value) { var flags = this.flags; + var promiseByKey = { + nodeName: expect.promise(function () { + if (value && typeof value.nodeName !== 'undefined') { + return expect(subject.nodeName.toLowerCase(), 'to satisfy', value.nodeName); + } + }), + attributes: {} + }; + + var onlyAttributes = value && value.onlyAttributes; var attrs = getAttributes(subject); + var expectedAttributes = value && value.attributes; + var expectedAttributeNames = []; - if (typeof value === 'string') { - value = Array.prototype.slice.call(arguments, 2); - } - var expectedValueByAttributeName = {}; - if (Array.isArray(value)) { - value.forEach(function (attributeName) { - expectedValueByAttributeName[attributeName] = true; - }); - } else if (value && typeof value === 'object') { - expectedValueByAttributeName = value; - } else { - throw new Error('to have attributes: Argument must be a string, an array, or an object'); - } - var expectedValueByLowerCasedAttributeName = {}, - expectedAttributeNames = []; - Object.keys(expectedValueByAttributeName).forEach(function (attributeName) { - var lowerCasedAttributeName = attributeName.toLowerCase(); - expectedAttributeNames.push(lowerCasedAttributeName); - if (expectedValueByLowerCasedAttributeName.hasOwnProperty(lowerCasedAttributeName)) { - throw new Error('Duplicate expected attribute with different casing: ' + attributeName); + if (typeof expectedAttributes !== 'undefined') { + if (typeof expectedAttributes === 'string') { + expectedAttributes = [expectedAttributes]; } - expectedValueByLowerCasedAttributeName[lowerCasedAttributeName] = expectedValueByAttributeName[attributeName]; - }); - expectedValueByAttributeName = expectedValueByLowerCasedAttributeName; + var expectedValueByAttributeName = {}; + if (Array.isArray(expectedAttributes)) { + expectedAttributes.forEach(function (attributeName) { + expectedValueByAttributeName[attributeName] = true; + }); + } else if (expectedAttributes && typeof expectedAttributes === 'object') { + expectedValueByAttributeName = expectedAttributes; + } + var expectedValueByLowerCasedAttributeName = {}; + Object.keys(expectedValueByAttributeName).forEach(function (attributeName) { + var lowerCasedAttributeName = attributeName.toLowerCase(); + expectedAttributeNames.push(lowerCasedAttributeName); + if (expectedValueByLowerCasedAttributeName.hasOwnProperty(lowerCasedAttributeName)) { + throw new Error('Duplicate expected attribute with different casing: ' + attributeName); + } + expectedValueByLowerCasedAttributeName[lowerCasedAttributeName] = expectedValueByAttributeName[attributeName]; + }); + expectedValueByAttributeName = expectedValueByLowerCasedAttributeName; + + expectedAttributeNames.forEach(function (attributeName) { + var attributeValue = subject.getAttribute(attributeName); + var expectedAttributeValue = expectedValueByAttributeName[attributeName]; + promiseByKey.attributes[attributeName] = expect.promise(function () { + if (attributeName === 'class' && (typeof expectedAttributeValue === 'string' || Array.isArray(expectedAttributeValue))) { + var actualClasses = getClassNamesFromAttributeValue(attributeValue); + var expectedClasses = expectedAttributeValue; + if (typeof expectedClasses === 'string') { + expectedClasses = getClassNamesFromAttributeValue(expectedAttributeValue); + } + if (onlyAttributes) { + return topLevelExpect(actualClasses.sort(), 'to equal', expectedClasses.sort()); + } else { + return topLevelExpect.apply(topLevelExpect, [actualClasses, 'to contain'].concat(expectedClasses)); + } + } else if (attributeName === 'style') { + var expectedStyleObj; + if (typeof expectedValueByAttributeName.style === 'string') { + expectedStyleObj = styleStringToObject(expectedValueByAttributeName.style); + } else { + expectedStyleObj = expectedValueByAttributeName.style; + } - var promiseByKey = { - presence: expect.promise(function () { + if (onlyAttributes) { + return topLevelExpect(attrs.style, 'to exhaustively satisfy', expectedStyleObj); + } else { + return topLevelExpect(attrs.style, 'to satisfy', expectedStyleObj); + } + } else if (expectedAttributeValue === true) { + expect(subject.hasAttribute(attributeName), 'to be true'); + } else { + return topLevelExpect(attributeValue, 'to satisfy', expectedAttributeValue); + } + }); + }); + + promiseByKey.attributePresence = expect.promise(function () { var attributeNamesExpectedToBeDefined = []; expectedAttributeNames.forEach(function (attributeName) { if (typeof expectedValueByAttributeName[attributeName] === 'undefined') { @@ -475,61 +520,36 @@ module.exports = { expect(attrs, 'to have key', attributeName); } }); - if (flags.only) { + if (onlyAttributes) { expect(Object.keys(attrs).sort(), 'to equal', attributeNamesExpectedToBeDefined.sort()); } - }), - attributes: {} - }; - - expectedAttributeNames.forEach(function (attributeName) { - var attributeValue = subject.getAttribute(attributeName); - var expectedAttributeValue = expectedValueByAttributeName[attributeName]; - promiseByKey.attributes[attributeName] = expect.promise(function () { - if (attributeName === 'class' && (typeof expectedAttributeValue === 'string' || Array.isArray(expectedAttributeValue))) { - var actualClasses = getClassNamesFromAttributeValue(attributeValue); - var expectedClasses = expectedAttributeValue; - if (typeof expectedClasses === 'string') { - expectedClasses = getClassNamesFromAttributeValue(expectedAttributeValue); - } - if (flags.only) { - return topLevelExpect(actualClasses.sort(), 'to equal', expectedClasses.sort()); - } else { - return topLevelExpect.apply(topLevelExpect, [actualClasses, 'to contain'].concat(expectedClasses)); - } - } else if (attributeName === 'style') { - var expectedStyleObj; - if (typeof expectedValueByAttributeName.style === 'string') { - expectedStyleObj = styleStringToObject(expectedValueByAttributeName.style); - } else { - expectedStyleObj = expectedValueByAttributeName.style; - } - - if (flags.only) { - return topLevelExpect(attrs.style, 'to exhaustively satisfy', expectedStyleObj); - } else { - return topLevelExpect(attrs.style, 'to satisfy', expectedStyleObj); - } - } else if (expectedAttributeValue === true) { - expect(subject.hasAttribute(attributeName), 'to be true'); - } else { - return topLevelExpect(attributeValue, 'to satisfy', expectedAttributeValue); - } }); - }); + } return expect.promise.all(promiseByKey).caught(function () { return expect.promise.settle(promiseByKey).then(function () { expect.fail({ diff: function (output, diff, inspect, equal) { - output.prismPunctuation('<').prismTag(subject.nodeName.toLowerCase()); + output + .prismPunctuation('<') + .prismTag(subject.nodeName.toLowerCase()); var canContinueLine = true; + if (promiseByKey.nodeName.isRejected()) { + var nodeNameError = promiseByKey.nodeName.reason(); + output.sp().annotationBlock(function () { + this + .error((nodeNameError && nodeNameError.label) || 'should satisfy') // v8: err.getLabel() + .sp() + .append(inspect(value.nodeName)); + }).nl(); + canContinueLine = false; + } Object.keys(attrs).forEach(function (attributeName) { var lowerCaseAttributeName = attributeName.toLowerCase(); var promise = promiseByKey.attributes[lowerCaseAttributeName]; output.sp(canContinueLine ? 1 : 2 + subject.nodeName.length); writeAttributeToMagicPen(output, attributeName, attrs[attributeName]); - if ((promise && promise.isFulfilled()) || (!promise && (!flags.only || expectedAttributeNames.indexOf(lowerCaseAttributeName) !== -1))) { + if ((promise && promise.isFulfilled()) || (!promise && (!onlyAttributes || expectedAttributeNames.indexOf(lowerCaseAttributeName) !== -1))) { canContinueLine = true; } else { output @@ -538,7 +558,7 @@ module.exports = { if (promise) { this.append(promise.reason().output); // v8: getErrorMessage } else { - // flags.only === true + // onlyAttributes === true this.error('should be removed'); } }) @@ -583,6 +603,17 @@ module.exports = { }); }); + expect.addAssertion('HTMLElement', 'to [only] have (attribute|attributes)', function (expect, subject, value) { + if (typeof value === 'string') { + if (arguments.length > 3) { + value = Array.prototype.slice.call(arguments, 2); + } + } else if (!value || typeof value !== 'object') { + throw new Error('to have attributes: Argument must be a string, an array, or an object'); + } + return expect(subject, 'to satisfy', { attributes: value, onlyAttributes: this.flags.only }); + }); + expect.addAssertion('HTMLElement', 'to have [no] (child|children)', function (expect, subject, query, cmp) { if (this.flags.no) { this.errorMode = 'nested'; @@ -617,7 +648,7 @@ module.exports = { }); } } - this.shift(expect, queryResult, 1); + return this.shift(expect, queryResult, 1); }); expect.addAssertion('string', 'when parsed as (html|HTML)', function (expect, subject) { diff --git a/test/unexpected-dom.js b/test/unexpected-dom.js index f609e15b..7000ad00 100644 --- a/test/unexpected-dom.js +++ b/test/unexpected-dom.js @@ -673,6 +673,54 @@ describe('unexpected-dom', function () { }); }); + describe('to satisfy', function () { + describe('with a nodeName assertion', function () { + it('should succeed', function () { + body.innerHTML = '
'; + expect(body.firstChild, 'to satisfy', { nodeName: /^d/ }); + }); + + it('should fail with a diff', function () { + body.innerHTML = '
'; + expect(function () { + expect(body.firstChild, 'to satisfy', { nodeName: /^sp/ }); + }, 'to throw', + 'expected
to satisfy { nodeName: /^sp/ }\n' + + '\n' + + '
' + ); + }); + }); + + it('should fail with a diff', function () { + body.innerHTML = '
foobar
hey
'; + expect(function () { + expect(body, 'queried for', 'div', 'to satisfy', { + 1: { attributes: { foo: 'bar' } } + }); + }, 'to throw', + 'expected\n' + + '\n' + + '
foobar
\n' + + '
hey
\n' + + '\n' + + 'queried for \'div\' to satisfy { 1: { attributes: { foo: \'bar\' } } }\n' + + ' expected NodeList[
foobar
,
hey
]\n' + + ' to satisfy { 1: { attributes: { foo: \'bar\' } } }\n' + + '\n' + + ' NodeList({\n' + + ' 0:
...
,\n' + + ' 1:
\n' + + ' })' + ); + }); + }); + describe('queried for', function () { it('should work with HTMLDocument', function () { var document = jsdom.jsdom('
'); From c866362c5af96598290f9300e83249514740d562 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Wed, 6 May 2015 22:28:31 +0200 Subject: [PATCH 05/13] to satisfy: nodeName => name The nodeName property is in uppercase according to the spec, and I want to avoid confusion. --- lib/index.js | 14 +++++++------- test/unexpected-dom.js | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/index.js b/lib/index.js index 588173b5..141cba9c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -438,9 +438,9 @@ module.exports = { expect.addAssertion('HTMLElement', 'to satisfy', function (expect, subject, value) { var flags = this.flags; var promiseByKey = { - nodeName: expect.promise(function () { - if (value && typeof value.nodeName !== 'undefined') { - return expect(subject.nodeName.toLowerCase(), 'to satisfy', value.nodeName); + name: expect.promise(function () { + if (value && typeof value.name !== 'undefined') { + return expect(subject.nodeName.toLowerCase(), 'to satisfy', value.name); } }), attributes: {} @@ -534,13 +534,13 @@ module.exports = { .prismPunctuation('<') .prismTag(subject.nodeName.toLowerCase()); var canContinueLine = true; - if (promiseByKey.nodeName.isRejected()) { - var nodeNameError = promiseByKey.nodeName.reason(); + if (promiseByKey.name.isRejected()) { + var nameError = promiseByKey.name.reason(); output.sp().annotationBlock(function () { this - .error((nodeNameError && nodeNameError.label) || 'should satisfy') // v8: err.getLabel() + .error((nameError && nameError.label) || 'should satisfy') // v8: err.getLabel() .sp() - .append(inspect(value.nodeName)); + .append(inspect(value.name)); }).nl(); canContinueLine = false; } diff --git a/test/unexpected-dom.js b/test/unexpected-dom.js index 7000ad00..f85f05ba 100644 --- a/test/unexpected-dom.js +++ b/test/unexpected-dom.js @@ -674,18 +674,18 @@ describe('unexpected-dom', function () { }); describe('to satisfy', function () { - describe('with a nodeName assertion', function () { + describe('with a name assertion', function () { it('should succeed', function () { body.innerHTML = '
'; - expect(body.firstChild, 'to satisfy', { nodeName: /^d/ }); + expect(body.firstChild, 'to satisfy', { name: /^d/ }); }); it('should fail with a diff', function () { body.innerHTML = '
'; expect(function () { - expect(body.firstChild, 'to satisfy', { nodeName: /^sp/ }); + expect(body.firstChild, 'to satisfy', { name: /^sp/ }); }, 'to throw', - 'expected
to satisfy { nodeName: /^sp/ }\n' + + 'expected
to satisfy { name: /^sp/ }\n' + '\n' + '
' From 93db162f6bdb7d44890614d405e4f408706b9252 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Wed, 6 May 2015 23:12:20 +0200 Subject: [PATCH 06/13] DOMTextNode: Implemented 'to satisfy' assertion. --- lib/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/index.js b/lib/index.js index 141cba9c..77f0aff3 100644 --- a/lib/index.js +++ b/lib/index.js @@ -435,6 +435,10 @@ module.exports = { } }); + expect.addAssertion('DOMTextNode', 'to satisfy', function (expect, subject, value) { + return expect(subject.nodeValue, 'to satisfy', value); + }); + expect.addAssertion('HTMLElement', 'to satisfy', function (expect, subject, value) { var flags = this.flags; var promiseByKey = { From 1231731fa9179b2e1ec6af01f30e357754726735 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Wed, 6 May 2015 23:13:10 +0200 Subject: [PATCH 07/13] HTMLElement to satisfy: Added support for asserting on children. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also ínclude the children and the end tag in the diff. --- lib/index.js | 143 +++++++++++++++++++++++++---------------- test/unexpected-dom.js | 57 +++++++++++----- 2 files changed, 130 insertions(+), 70 deletions(-) diff --git a/lib/index.js b/lib/index.js index 77f0aff3..a3c03ec0 100644 --- a/lib/index.js +++ b/lib/index.js @@ -440,11 +440,15 @@ module.exports = { }); expect.addAssertion('HTMLElement', 'to satisfy', function (expect, subject, value) { - var flags = this.flags; var promiseByKey = { name: expect.promise(function () { if (value && typeof value.name !== 'undefined') { - return expect(subject.nodeName.toLowerCase(), 'to satisfy', value.name); + return topLevelExpect(subject.nodeName.toLowerCase(), 'to satisfy', value.name); + } + }), + children: expect.promise(function () { + if (typeof value.children !== 'undefined') { + return topLevelExpect(subject.childNodes, 'to satisfy', value.children); } }), attributes: {} @@ -534,69 +538,98 @@ module.exports = { return expect.promise.settle(promiseByKey).then(function () { expect.fail({ diff: function (output, diff, inspect, equal) { - output - .prismPunctuation('<') - .prismTag(subject.nodeName.toLowerCase()); - var canContinueLine = true; - if (promiseByKey.name.isRejected()) { - var nameError = promiseByKey.name.reason(); - output.sp().annotationBlock(function () { - this - .error((nameError && nameError.label) || 'should satisfy') // v8: err.getLabel() - .sp() - .append(inspect(value.name)); - }).nl(); - canContinueLine = false; - } - Object.keys(attrs).forEach(function (attributeName) { - var lowerCaseAttributeName = attributeName.toLowerCase(); - var promise = promiseByKey.attributes[lowerCaseAttributeName]; - output.sp(canContinueLine ? 1 : 2 + subject.nodeName.length); - writeAttributeToMagicPen(output, attributeName, attrs[attributeName]); - if ((promise && promise.isFulfilled()) || (!promise && (!onlyAttributes || expectedAttributeNames.indexOf(lowerCaseAttributeName) !== -1))) { - canContinueLine = true; - } else { - output - .sp() - .annotationBlock(function () { - if (promise) { - this.append(promise.reason().output); // v8: getErrorMessage - } else { - // onlyAttributes === true - this.error('should be removed'); - } - }) - .nl(); + output.block(function () { + var output = this; + output + .prismPunctuation('<') + .prismTag(subject.nodeName.toLowerCase()); + var canContinueLine = true; + if (promiseByKey.name.isRejected()) { + var nameError = promiseByKey.name.reason(); + output.sp().annotationBlock(function () { + this + .error((nameError && nameError.label) || 'should satisfy') // v8: err.getLabel() + .sp() + .append(inspect(value.name)); + }).nl(); canContinueLine = false; } - }); - expectedAttributeNames.forEach(function (attributeName) { - if (!subject.hasAttribute(attributeName)) { - var promise = promiseByKey.attributes[attributeName]; - if (!promise || promise.isRejected()) { - var err = promise && promise.reason(); + Object.keys(attrs).forEach(function (attributeName) { + var lowerCaseAttributeName = attributeName.toLowerCase(); + var promise = promiseByKey.attributes[lowerCaseAttributeName]; + output.sp(canContinueLine ? 1 : 2 + subject.nodeName.length); + writeAttributeToMagicPen(output, attributeName, attrs[attributeName]); + if ((promise && promise.isFulfilled()) || (!promise && (!onlyAttributes || expectedAttributeNames.indexOf(lowerCaseAttributeName) !== -1))) { + canContinueLine = true; + } else { output - .nl() - .sp(2 + subject.nodeName.length) + .sp() .annotationBlock(function () { - this - .error('missing') - .sp() - .prismAttrName(attributeName, 'html'); - if (expectedValueByAttributeName[attributeName] !== true) { - this - .sp() - .error((err && err.label) || 'should satisfy') // v8: err.getLabel() - .sp() - .append(inspect(expectedValueByAttributeName[attributeName])); + if (promise) { + this.append(promise.reason().output); // v8: getErrorMessage + } else { + // onlyAttributes === true + this.error('should be removed'); } }) .nl(); + canContinueLine = false; + } + }); + expectedAttributeNames.forEach(function (attributeName) { + if (!subject.hasAttribute(attributeName)) { + var promise = promiseByKey.attributes[attributeName]; + if (!promise || promise.isRejected()) { + var err = promise && promise.reason(); + output + .nl() + .sp(2 + subject.nodeName.length) + .annotationBlock(function () { + this + .error('missing') + .sp() + .prismAttrName(attributeName, 'html'); + if (expectedValueByAttributeName[attributeName] !== true) { + this + .sp() + .error((err && err.label) || 'should satisfy') // v8: err.getLabel() + .sp() + .append(inspect(expectedValueByAttributeName[attributeName])); + } + }) + .nl(); + } + canContinueLine = false; + } + }); + output.prismPunctuation('>'); + var childrenError = promiseByKey.children.isRejected() && promiseByKey.children.reason(); + var childrenDiff = childrenError && childrenError.createDiff && childrenError.createDiff(output.clone(), diff, inspect, equal); + if (childrenError) { + output + .nl() + .indentLines() + .i().block(function () { + for (var i = 0 ; i < subject.childNodes.length ; i += 1) { + this.append(inspect(subject.childNodes[i])).nl(); + } + }); + if (childrenError) { + output.sp().annotationBlock(function () { // v8: childrenError.getErrorMessage() + this.append(childrenError.output); + if (childrenDiff && childrenDiff.diff) { + this.nl(2).append(childrenDiff.diff); + } + }); + } + output.nl(); + } else { + for (var i = 0 ; i < subject.childNodes.length ; i += 1) { + this.append(inspect(subject.childNodes[i])); } - canContinueLine = false; } + output.code(stringifyEndTag(subject), 'html'); }); - output.prismPunctuation('>'); return { inline: true, diff: output diff --git a/test/unexpected-dom.js b/test/unexpected-dom.js index f85f05ba..fb69b078 100644 --- a/test/unexpected-dom.js +++ b/test/unexpected-dom.js @@ -213,7 +213,7 @@ describe('unexpected-dom', function () { 'expected to have class \'quux\'\n' + '\n' + '' ); }); }); @@ -232,7 +232,7 @@ describe('unexpected-dom', function () { 'expected to have classes [ \'quux\', \'bar\' ]\n' + '\n' + '' ); }); }); @@ -257,7 +257,7 @@ describe('unexpected-dom', function () { ' // \'bar\', // should be removed\n' + ' // \'quux\'\n' + ' // ]\n' + - ' data-info="baz" disabled>' + ' data-info="baz" disabled>Press me' ); }); }); @@ -283,7 +283,7 @@ describe('unexpected-dom', function () { ' // \'foo\', // should be removed\n' + ' // \'quux\'\n' + ' // ]\n' + - ' data-info="baz" disabled>' + ' data-info="baz" disabled>Press me' ); }); }); @@ -310,7 +310,7 @@ describe('unexpected-dom', function () { '' ); }); @@ -331,7 +331,7 @@ describe('unexpected-dom', function () { '\n' + '' ); }); }); @@ -355,7 +355,7 @@ describe('unexpected-dom', function () { '' ); }); @@ -376,7 +376,7 @@ describe('unexpected-dom', function () { '\n' + '' ); }); }); @@ -446,7 +446,7 @@ describe('unexpected-dom', function () { 'expected to have attributes { class: \'foo bar baz\' }\n' + '\n' + '' + '>' ); }); @@ -688,7 +688,33 @@ describe('unexpected-dom', function () { 'expected
to satisfy { name: /^sp/ }\n' + '\n' + '
' + ' foo="bar">
' + ); + }); + }); + + describe('with a children assertion', function () { + it('should succeed', function () { + body.innerHTML = '
hey
'; + expect(body.firstChild, 'to satisfy', { children: [ 'hey' ] }); + }); + + it('should fail with a diff', function () { + body.innerHTML = '
hey
'; + expect(function () { + expect(body.firstChild, 'to satisfy', { children: [ 'there' ] }); + }, 'to throw', + 'expected
hey
to satisfy { children: [ \'there\' ] }\n' + + '\n' + + '
\n' + + ' hey // expected NodeList[ hey ] to satisfy [ \'there\' ]\n' + + ' //\n' + + ' // [\n' + + ' // hey // should equal \'there\'\n' + + ' // // -hey\n' + + ' // // +there\n' + + ' // ]\n' + + '
' ); }); }); @@ -711,11 +737,12 @@ describe('unexpected-dom', function () { '\n' + ' NodeList({\n' + ' 0:
...
,\n' + - ' 1:
\n' + + ' 1:\n' + + '
hey
\n' + ' })' ); }); From 74865cfe0810226cbc98e83138d37663288c8525 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Wed, 6 May 2015 23:21:39 +0200 Subject: [PATCH 08/13] HTMLElement to satisfy: Throw an error if an unsupported option is used. --- lib/index.js | 9 +++++++++ test/unexpected-dom.js | 7 +++++++ 2 files changed, 16 insertions(+) diff --git a/lib/index.js b/lib/index.js index a3c03ec0..6bee144e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -440,6 +440,15 @@ module.exports = { }); expect.addAssertion('HTMLElement', 'to satisfy', function (expect, subject, value) { + if (value && typeof value === 'object') { + var unsupportedOptions = Object.keys(value).filter(function (key) { + return key !== 'attributes' && key !== 'name' && key !== 'children' && key !== 'onlyAttributes'; + }); + if (unsupportedOptions.length > 0) { + throw new Error('Unsupported option' + (unsupportedOptions.length === 1 ? '' : 's') + ': ' + unsupportedOptions.join(', ')); + } + } + var promiseByKey = { name: expect.promise(function () { if (value && typeof value.name !== 'undefined') { diff --git a/test/unexpected-dom.js b/test/unexpected-dom.js index fb69b078..4a54ae23 100644 --- a/test/unexpected-dom.js +++ b/test/unexpected-dom.js @@ -674,6 +674,13 @@ describe('unexpected-dom', function () { }); describe('to satisfy', function () { + it('should fail if an unsupported property is passed in the value', function () { + body.innerHTML = '
'; + expect(function () { + expect(body.firstChild, 'to satisfy', { foo: 'bar' }); + }, 'to throw', 'Unsupported option: foo'); + }); + describe('with a name assertion', function () { it('should succeed', function () { body.innerHTML = '
'; From 54c83e08db31b4beab20b8f2f4ccd60dece69e2d Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Fri, 8 May 2015 22:52:17 +0200 Subject: [PATCH 09/13] Update jsdom to 3.1.2. --- package.json | 2 +- test/unexpected-dom.js | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 76720bc5..baa4de83 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "browserify": "^9.0.3", "coveralls": "^2.11.2", "istanbul": "^0.3.6", - "jsdom": "^0.11.1", + "jsdom": "^3.1.2", "jshint": "^2.6.3", "magicpen": "^4.3.5", "mocha": "^2.1.0", diff --git a/test/unexpected-dom.js b/test/unexpected-dom.js index 4a54ae23..dd728e83 100644 --- a/test/unexpected-dom.js +++ b/test/unexpected-dom.js @@ -794,12 +794,12 @@ describe('unexpected-dom', function () { }); it('should fail array checks with useful nested error message', function () { - var document = jsdom.jsdom('
'); + var document = jsdom.jsdom('
'); expect(function () { expect(document, 'queried for', 'div', 'to have length', 1); }, 'to throw', - 'expected ......... queried for \'div\' to have length 1\n' + + 'expected ......... queried for \'div\' to have length 1\n' + ' expected NodeList[
,
,
] to have length 1\n' + ' expected 3 to be 1' ); @@ -914,13 +914,13 @@ describe('unexpected-dom', function () { it('should diff documents with stuff around the documentElement', function () { expect( - jsdom.jsdom(''), + jsdom.jsdom(' '), 'diffed with', - jsdom.jsdom(''), + jsdom.jsdom(' '), 'to equal', '\n' + ' // should be removed\n' + - '\n' + + ' \n' + ' // should be removed' ); }); From 5803c514b3b1f2c2c64b93dc3f94976106d5c2c7 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Fri, 8 May 2015 22:52:40 +0200 Subject: [PATCH 10/13] Fixed typo. --- 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 dd728e83..d511a72e 100644 --- a/test/unexpected-dom.js +++ b/test/unexpected-dom.js @@ -930,7 +930,7 @@ describe('unexpected-dom', function () { var htmlSrc = 'foo'; it('should parse a string as a complete HTML document', function () { expect(htmlSrc, 'when parsed as HTML', - expect.it('to be a', 'HTMLDocument') + expect.it('to be an', 'HTMLDocument') .and('to equal', jsdom.jsdom(htmlSrc)) .and('queried for first', 'body', 'to have attributes', { class: 'bar' }) ); From 854d41b11b15cd80e8c070bd9f18d7cb2d576aac Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Sat, 9 May 2015 00:36:53 +0200 Subject: [PATCH 11/13] Support XML documents. --- lib/index.js | 84 +++++++++++++++++++++++++++++++----------- package.json | 2 +- test/unexpected-dom.js | 72 +++++++++++++++++++++++++++++++++++- 3 files changed, 133 insertions(+), 25 deletions(-) diff --git a/lib/index.js b/lib/index.js index 6bee144e..484b6fe4 100644 --- a/lib/index.js +++ b/lib/index.js @@ -41,6 +41,7 @@ function getClassNamesFromAttributeValue(attributeValue) { } function getAttributes(element) { + var isHtml = element.ownerDocument.contentType === 'text/html'; var attrs = element.attributes; var result = {}; @@ -50,7 +51,7 @@ function getAttributes(element) { } else if (attrs[i].name === 'style') { result[attrs[i].name] = styleStringToObject(attrs[i].value); } else { - result[attrs[i].name] = isBooleanAttribute(attrs[i].name) ? true : (attrs[i].value || ''); + result[attrs[i].name] = isHtml && isBooleanAttribute(attrs[i].name) ? true : (attrs[i].value || ''); } } @@ -76,9 +77,9 @@ function isVoidElement(elementName) { return (/(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)/i).test(elementName); } -function writeAttributeToMagicPen(output, attributeName, value) { +function writeAttributeToMagicPen(output, attributeName, value, isHtml) { output.prismAttrName(attributeName); - if (!isBooleanAttribute(attributeName)) { + if (!isHtml || !isBooleanAttribute(attributeName)) { if (attributeName === 'class') { value = value.join(' '); } else if (attributeName === 'style') { @@ -108,7 +109,7 @@ function stringifyAttribute(attributeName, value) { } function stringifyStartTag(element) { - var elementName = element.nodeName.toLowerCase(); + var elementName = element.ownerDocument.contentType === 'text/html' ? element.nodeName.toLowerCase() : element.nodeName; var str = '<' + elementName; var attrs = getCanonicalAttributes(element); @@ -121,8 +122,9 @@ function stringifyStartTag(element) { } function stringifyEndTag(element) { - var elementName = element.nodeName.toLowerCase(); - if (isVoidElement(elementName) && element.childNodes.length === 0) { + var isHtml = element.ownerDocument.contentType === 'text/html'; + var elementName = isHtml ? element.nodeName.toLowerCase() : element.nodeName; + if (isHtml && isVoidElement(elementName) && element.childNodes.length === 0) { return ''; } else { return ''; @@ -267,14 +269,14 @@ module.exports = { }); expect.addType({ - name: 'HTMLDocument', + name: 'DOMDocument', base: 'DOMNode', identify: function (obj) { return obj && typeof obj.nodeType === 'number' && 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])); + output.append(inspect(document.childNodes[i], depth - 1)); } }, diff: function (actual, expected, output, diff, inspect, equal) { @@ -288,7 +290,29 @@ module.exports = { }); expect.addType({ - name: 'HTMLElement', + name: 'HTMLDocument', + base: 'DOMDocument', + identify: function (obj) { + return this.baseType.identify(obj) && obj.contentType === 'text/html'; + } + }); + + expect.addType({ + name: 'XMLDocument', + base: 'DOMDocument', + identify: function (obj) { + return this.baseType.identify(obj) && /^(?:application|text)\/xml|\+xml\b/.test(obj.contentType); + }, + inspect: function (document, depth, output, inspect) { + output.code('', 'xml'); + for (var i = 0 ; i < document.childNodes.length ; i += 1) { + output.append(inspect(document.childNodes[i], depth - 1)); + } + } + }); + + expect.addType({ + name: 'DOMElement', base: 'DOMNode', identify: function (obj) { return obj && typeof obj.nodeType === 'number' && obj.nodeType === 1 && obj.nodeName && obj.attributes; @@ -344,6 +368,7 @@ module.exports = { }, diffLimit: 512, diff: function (actual, expected, output, diff, inspect, equal) { + var isHtml = actual.ownerDocument.contentType === 'text/html'; var result = { diff: output, inline: true @@ -372,7 +397,7 @@ module.exports = { var expectedAttributes = getAttributes(expected); Object.keys(actualAttributes).forEach(function (attributeName) { output.sp(canContinueLine ? 1 : 2 + actual.nodeName.length); - writeAttributeToMagicPen(output, attributeName, actualAttributes[attributeName]); + writeAttributeToMagicPen(output, attributeName, actualAttributes[attributeName], isHtml); if (attributeName in expectedAttributes) { if (actualAttributes[attributeName] === expectedAttributes[attributeName]) { canContinueLine = true; @@ -394,7 +419,7 @@ module.exports = { output.sp(canContinueLine ? 1 : 2 + actual.nodeName.length); output.annotationBlock(function () { this.error('missing').sp(); - writeAttributeToMagicPen(this, attributeName, expectedAttributes[attributeName]); + writeAttributeToMagicPen(this, attributeName, expectedAttributes[attributeName], isHtml); }).nl(); canContinueLine = false; }); @@ -414,7 +439,7 @@ module.exports = { } }); - expect.addAssertion('HTMLElement', 'to [only] have (class|classes)', function (expect, subject, value) { + expect.addAssertion('DOMElement', 'to [only] have (class|classes)', function (expect, subject, value) { var flags = this.flags; if (flags.only) { return expect(subject, 'to have attributes', { @@ -439,7 +464,8 @@ module.exports = { return expect(subject.nodeValue, 'to satisfy', value); }); - expect.addAssertion('HTMLElement', 'to satisfy', function (expect, subject, value) { + expect.addAssertion('DOMElement', 'to satisfy', function (expect, subject, value) { + var isHtml = subject.ownerDocument.contentType === 'text/html'; if (value && typeof value === 'object') { var unsupportedOptions = Object.keys(value).filter(function (key) { return key !== 'attributes' && key !== 'name' && key !== 'children' && key !== 'onlyAttributes'; @@ -452,7 +478,7 @@ module.exports = { var promiseByKey = { name: expect.promise(function () { if (value && typeof value.name !== 'undefined') { - return topLevelExpect(subject.nodeName.toLowerCase(), 'to satisfy', value.name); + return topLevelExpect(isHtml ? subject.nodeName.toLowerCase() : subject.nodeName, 'to satisfy', value.name); } }), children: expect.promise(function () { @@ -551,7 +577,7 @@ module.exports = { var output = this; output .prismPunctuation('<') - .prismTag(subject.nodeName.toLowerCase()); + .prismTag(isHtml ? subject.nodeName.toLowerCase() : subject.nodeName); var canContinueLine = true; if (promiseByKey.name.isRejected()) { var nameError = promiseByKey.name.reason(); @@ -567,8 +593,8 @@ module.exports = { var lowerCaseAttributeName = attributeName.toLowerCase(); var promise = promiseByKey.attributes[lowerCaseAttributeName]; output.sp(canContinueLine ? 1 : 2 + subject.nodeName.length); - writeAttributeToMagicPen(output, attributeName, attrs[attributeName]); - if ((promise && promise.isFulfilled()) || (!promise && (!onlyAttributes || expectedAttributeNames.indexOf(lowerCaseAttributeName) !== -1))) { + writeAttributeToMagicPen(output, attributeName, attrs[attributeName], isHtml); + if ((promise && promise.isFulfilled()) || (!promise && (!onlyAttributes || expectedAttributeNames.indexOf(attributeName) !== -1))) { canContinueLine = true; } else { output @@ -649,7 +675,7 @@ module.exports = { }); }); - expect.addAssertion('HTMLElement', 'to [only] have (attribute|attributes)', function (expect, subject, value) { + expect.addAssertion('DOMElement', 'to [only] have (attribute|attributes)', function (expect, subject, value) { if (typeof value === 'string') { if (arguments.length > 3) { value = Array.prototype.slice.call(arguments, 2); @@ -660,7 +686,7 @@ module.exports = { return expect(subject, 'to satisfy', { attributes: value, onlyAttributes: this.flags.only }); }); - expect.addAssertion('HTMLElement', 'to have [no] (child|children)', function (expect, subject, query, cmp) { + expect.addAssertion('DOMElement', 'to have [no] (child|children)', function (expect, subject, query, cmp) { if (this.flags.no) { this.errorMode = 'nested'; return expect(Array.prototype.slice.call(subject.childNodes), 'to be an empty array'); @@ -670,11 +696,11 @@ module.exports = { } }); - expect.addAssertion('HTMLElement', 'to have text', function (expect, subject, value) { + expect.addAssertion('DOMElement', 'to have text', function (expect, subject, value) { return expect(subject.textContent, 'to satisfy', value); }); - expect.addAssertion(['HTMLDocument', 'HTMLElement'], 'queried for [first]', function (expect, subject, value) { + expect.addAssertion(['DOMDocument', 'DOMElement'], 'queried for [first]', function (expect, subject, value) { var queryResult; this.errorMode = 'nested'; @@ -710,10 +736,24 @@ module.exports = { try { htmlDocument = require('jsdom').jsdom(subject); } catch (err) { - throw new Error('The assertion `when parsed as html` was run outside a browser, but could not find the `jsdom` module. Please npm install jsdom to make this work.'); + throw new Error('The assertion `' + this.testDescription + '` was run outside a browser, but could not find the `jsdom` module. Please npm install jsdom to make this work.'); } } return this.shift(expect, htmlDocument, 0); }); + + expect.addAssertion('string', 'when parsed as (xml|XML)', function (expect, subject) { + var xmlDocument; + if (typeof DOMParser !== 'undefined') { + xmlDocument = new DOMParser().parseFromString(subject, 'text/xml'); + } else { + try { + xmlDocument = require('jsdom').jsdom(subject, { parsingMode: 'xml' }); + } catch (err) { + throw new Error('The assertion `' + this.testDescription + '` was outside a browser (or in a browser without DOMParser), but could not find the `jsdom` module. Please npm install jsdom to make this work.'); + } + } + return this.shift(expect, xmlDocument, 0); + }); } }; diff --git a/package.json b/package.json index baa4de83..bf50655e 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,6 @@ "dependencies": { "array-changes": "1.0.1", "extend": "^2.0.0", - "magicpen-prism": "2.0.0" + "magicpen-prism": "2.1.0" } } diff --git a/test/unexpected-dom.js b/test/unexpected-dom.js index d511a72e..c20a915f 100644 --- a/test/unexpected-dom.js +++ b/test/unexpected-dom.js @@ -46,14 +46,23 @@ describe('unexpected-dom', function () { }); }); - it('should inspect a document correctly', function () { + it('should inspect an HTML document correctly', function () { expect( - jsdom.jsdom(' '), + jsdom.jsdom(' '), 'to inspect as', ' ' ); }); + it('should inspect an XML document correctly', function () { + expect( + 'abc', + 'when parsed as XML', + 'to inspect as', + 'abc' + ); + }); + it('should inspect a document with nodes around the documentElement correctly', function () { expect( jsdom.jsdom(' '), @@ -698,6 +707,25 @@ describe('unexpected-dom', function () { ' foo="bar">
' ); }); + + describe('in an XML document with a mixed case node name', function () { + var xmlDoc = jsdom.jsdom('', { parsingMode: 'xml' }); + + it('should succeed', function () { + expect(xmlDoc.firstChild, 'to satisfy', { name: 'fooBar' }); + }); + + it('should fail with a diff', function () { + expect(function () { + expect(xmlDoc.firstChild, 'to satisfy', { name: 'fooBarQuux' }); + }, 'to throw', + 'expected to satisfy { name: \'fooBarQuux\' }\n' + + '\n' + + '' + ); + }); + }); }); describe('with a children assertion', function () { @@ -1002,4 +1030,44 @@ describe('unexpected-dom', function () { }); }); }); + + describe('when parsed as XML', function () { + var xmlSrc = 'foo'; + it('should parse a string as a complete XML document', function () { + expect(xmlSrc, 'when parsed as XML', + expect.it('to be an', 'XMLDocument') + .and('to equal', jsdom.jsdom(xmlSrc, { parsingMode: 'xml' })) + .and('queried for first', 'fooBar', 'to have attributes', { yes: 'sir' }) + ); + }); + + describe('when the DOMParser global is available', function () { + var originalDOMParser, + DOMParserSpy, + parseFromStringSpy; + + beforeEach(function () { + originalDOMParser = global.DOMParser; + global.DOMParser = DOMParserSpy = sinon.spy(function () { + return { + parseFromString: parseFromStringSpy = sinon.spy(function (xmlString, contentType) { + return jsdom.jsdom(xmlString, { parsingMode: 'xml' }); + }) + }; + }); + }); + afterEach(function () { + global.DOMParser = originalDOMParser; + }); + + it('should use DOMParser to parse the document', function () { + expect(xmlSrc, 'when parsed as XML', 'queried for first', 'fooBar', 'to have text', 'foo'); + expect(DOMParserSpy, 'was called once'); + expect(DOMParserSpy, 'was called with'); + expect(DOMParserSpy.calledWithNew(), 'to be true'); + expect(parseFromStringSpy, 'was called once'); + expect(parseFromStringSpy, 'was called with', xmlSrc, 'text/xml'); + }); + }); + }); }); From 443c0ac980c97e9838513c25cd82e611a7d53531 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Sat, 9 May 2015 00:39:25 +0200 Subject: [PATCH 12/13] Oh, turns out attribute names are case sensitive, even in HTML documents. --- lib/index.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/index.js b/lib/index.js index 484b6fe4..421fdd76 100644 --- a/lib/index.js +++ b/lib/index.js @@ -506,16 +506,9 @@ module.exports = { } else if (expectedAttributes && typeof expectedAttributes === 'object') { expectedValueByAttributeName = expectedAttributes; } - var expectedValueByLowerCasedAttributeName = {}; Object.keys(expectedValueByAttributeName).forEach(function (attributeName) { - var lowerCasedAttributeName = attributeName.toLowerCase(); - expectedAttributeNames.push(lowerCasedAttributeName); - if (expectedValueByLowerCasedAttributeName.hasOwnProperty(lowerCasedAttributeName)) { - throw new Error('Duplicate expected attribute with different casing: ' + attributeName); - } - expectedValueByLowerCasedAttributeName[lowerCasedAttributeName] = expectedValueByAttributeName[attributeName]; + expectedAttributeNames.push(attributeName); }); - expectedValueByAttributeName = expectedValueByLowerCasedAttributeName; expectedAttributeNames.forEach(function (attributeName) { var attributeValue = subject.getAttribute(attributeName); @@ -590,8 +583,7 @@ module.exports = { canContinueLine = false; } Object.keys(attrs).forEach(function (attributeName) { - var lowerCaseAttributeName = attributeName.toLowerCase(); - var promise = promiseByKey.attributes[lowerCaseAttributeName]; + var promise = promiseByKey.attributes[attributeName]; output.sp(canContinueLine ? 1 : 2 + subject.nodeName.length); writeAttributeToMagicPen(output, attributeName, attrs[attributeName], isHtml); if ((promise && promise.isFulfilled()) || (!promise && (!onlyAttributes || expectedAttributeNames.indexOf(attributeName) !== -1))) { From 5e4cf0041cbbaa36d45966c6289f3c76d26de987 Mon Sep 17 00:00:00 2001 From: Andreas Lind Date: Sat, 9 May 2015 01:43:09 +0200 Subject: [PATCH 13/13] Change the error mode of the when parsed as... adverbial assertions to 'nested' so the output isn't swallowed. --- lib/index.js | 2 ++ test/unexpected-dom.js | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/lib/index.js b/lib/index.js index 421fdd76..5af2e5d8 100644 --- a/lib/index.js +++ b/lib/index.js @@ -716,6 +716,7 @@ module.exports = { }); expect.addAssertion('string', 'when parsed as (html|HTML)', function (expect, subject) { + this.errorMode = 'nested'; var htmlDocument; if (typeof DOMParser !== 'undefined') { htmlDocument = new DOMParser().parseFromString(subject, 'text/html'); @@ -735,6 +736,7 @@ module.exports = { }); expect.addAssertion('string', 'when parsed as (xml|XML)', function (expect, subject) { + this.errorMode = 'nested'; var xmlDocument; if (typeof DOMParser !== 'undefined') { xmlDocument = new DOMParser().parseFromString(subject, 'text/xml'); diff --git a/test/unexpected-dom.js b/test/unexpected-dom.js index c20a915f..2dc6ba60 100644 --- a/test/unexpected-dom.js +++ b/test/unexpected-dom.js @@ -964,6 +964,22 @@ describe('unexpected-dom', function () { ); }); + it('should fail when the next assertion fails', function () { + expect(function () { + expect(htmlSrc, 'when parsed as HTML', 'queried for first', 'body', 'to have attributes', { class: 'quux' }); + }, 'to throw', + 'expected \'foo\'\n' + + 'when parsed as HTML queried for first \'body\', \'to have attributes\', { class: \'quux\' }\n' + + ' expected ...\n' + + ' queried for first \'body\' to have attributes { class: \'quux\' }\n' + + ' expected foo to have attributes { class: \'quux\' }\n' + + '\n' + + ' foo' + ); + }); + + describe('when the DOMParser global is available', function () { var originalDOMParser, DOMParserSpy,