Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

To satisfy against DOMElement/string #29

Merged
merged 14 commits into from
Oct 1, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 121 additions & 52 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,51 @@
var arrayChanges = require('array-changes');
var matchesSelector = require('./matchesSelector');

function parseHtml(str, isFragment, assertionNameForErrorMessage) {
if (isFragment) {
str = '<html><head></head><body>' + str + '</body></html>';
}
var htmlDocument;
if (typeof DOMParser !== 'undefined') {
htmlDocument = new DOMParser().parseFromString(str, 'text/html');
} else if (typeof document !== 'undefined' && document.implementation && document.implementation.createHTMLDocument) {
htmlDocument = document.implementation.createHTMLDocument('');
htmlDocument.open();
htmlDocument.write(str);
htmlDocument.close();
} else {
try {
htmlDocument = require('jsdom').jsdom(str);
} catch (err) {
throw new Error('unexpected-dom' + (assertionNameForErrorMessage ? ' (' + assertionNameForErrorMessage + ')' : '') + ': Running outside a browser, but could not find the `jsdom` module. Please npm install jsdom to make this work.');
}
}
if (isFragment) {
var body = htmlDocument.body;
var documentFragment = htmlDocument.createDocumentFragment();
if (body) {
for (var i = 0 ; i < body.childNodes.length ; i += 1) {
documentFragment.appendChild(body.childNodes[i].cloneNode(true));
}
}
return documentFragment;
} else {
return htmlDocument;
}
}

function parseXml(str, assertionNameForErrorMessage) {
if (typeof DOMParser !== 'undefined') {
return new DOMParser().parseFromString(str, 'text/xml');
} else {
try {
return require('jsdom').jsdom(str, { parsingMode: 'xml' });
} catch (err) {
throw new Error('unexpected-dom' + (assertionNameForErrorMessage ? ' (' + assertionNameForErrorMessage + ')' : '') + ': Running outside a browser (or in a browser without DOMParser), but could not find the `jsdom` module. Please npm install jsdom to make this work.');
}
}
}

// From html-minifier
var enumeratedAttributeValues = {
draggable: ['true', 'false'] // defaults to 'auto'
Expand Down Expand Up @@ -257,7 +302,7 @@ module.exports = {
// Fake type to make it possible to build 'to satisfy' diffs to be rendered inline:
expect.addType({
name: 'attachedDOMNodeList',
base: 'DOMNodeList',
base: 'array-like',
prefix: function (output) { return output; },
suffix: function (output) { return output; },
delimiter: function (output) { return output; },
Expand Down Expand Up @@ -330,12 +375,20 @@ module.exports = {

expect.addType({
name: 'DOMDocumentFragment',
base: 'DOMDocument',
base: 'DOMNode',
identify: function (obj) {
return obj && obj.nodeType === 11; // In jsdom, documentFragment.toString() does not return [object DocumentFragment]
},
inspect: function (documentFragment, depth, output, inspect) {
output.text('DocumentFragment[').append(inspect(documentFragment.childNodes, depth)).text(']');
},
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 Down Expand Up @@ -492,12 +545,68 @@ module.exports = {
}
});

expect.addAssertion('DOMTextNode', 'to satisfy', function (expect, subject, value) {
return expect(subject.nodeValue, 'to satisfy', value);
expect.addAssertion('DOMTextNode', 'to [exhaustively] satisfy', function (expect, subject, value) {
return expect(subject.nodeValue, 'to satisfy', expect.findTypeOf(value).name === 'DOMTextNode' ? value.nodeValue : value);
});

function convertDOMNodeToSatisfySpec(node, isHtml) {
if (node.nodeType === 1) {
// DOMElement
var result = {
name: isHtml ? node.nodeName.toLowerCase() : node.nodeName,
attributes: {}
};
for (var i = 0; i < node.attributes.length ; i += 1) {
result.attributes[node.attributes[i].name] = isHtml && isBooleanAttribute(node.attributes[i].name) ? true : (node.attributes[i].value || '');
}
result.children = Array.prototype.map.call(node.childNodes, function (childNode) {
return convertDOMNodeToSatisfySpec(childNode, isHtml);
});
return result;
} else if (node.nodeType === 3) {
// DOMTextNode
return node.nodeValue;
} else {
throw new Error('to satisfy: Node type ' + node.nodeType + ' is not yet supported in the value');
}
}

expect.addAssertion('DOMDocumentFragment', 'to [exhaustively] satisfy', function (expect, subject, value) {
var isHtml = subject.ownerDocument.contentType === 'text/html';
var valueType = expect.findTypeOf(value);
if (valueType.name === 'string') {
var originalValue = value;
this.argsOutput = function (output) {
output.code(originalValue, isHtml ? 'html' : 'xml');
};
value = isHtml ? parseHtml(value, true, this.testDescription) : parseXml(value, this.testDescription);
}

if (value && value.nodeType === 11) {
value = Array.prototype.map.call(value.childNodes, function (childNode) {
return convertDOMNodeToSatisfySpec(childNode, isHtml);
});
}

return expect(subject.childNodes, 'to [exhaustively] satisfy', value);
});

expect.addAssertion('DOMElement', 'to satisfy', function (expect, subject, value) {
expect.addAssertion('DOMElement', 'to [exhaustively] satisfy', function (expect, subject, value) {
var isHtml = subject.ownerDocument.contentType === 'text/html';
if (typeof value === 'string') {
var documentFragment = isHtml ? parseHtml(value, true, this.testDescription) : parseXml(value, this.testDescription);
if (documentFragment.childNodes.length !== 1) {
throw new Error('HTMLElement to satisfy string: Only a single node is supported');
}
var originalValue = value;
value = documentFragment.childNodes[0];
this.argsOutput = function (output) {
output.code(originalValue, isHtml ? 'html' : 'xml');
};
}
if (expect.findTypeOf(value).name === 'DOMElement') {
value = convertDOMNodeToSatisfySpec(value, isHtml);
}
if (value && typeof value === 'object') {
var unsupportedOptions = Object.keys(value).filter(function (key) {
return key !== 'attributes' && key !== 'name' && key !== 'children' && key !== 'onlyAttributes' && key !== 'textContent';
Expand All @@ -518,20 +627,20 @@ module.exports = {
if (typeof value.textContent !== 'undefined') {
throw new Error('The children and textContent properties are not supported together');
}
var subjectFoo = [];
var nodeListForDiffing = [];
for (var i = 0 ; i < subject.childNodes.length ; i += 1) {
subjectFoo.push(subject.childNodes[i]);
nodeListForDiffing.push(subject.childNodes[i]);
}
subjectFoo._isAttachedDOMNodeList = true;
return topLevelExpect(subjectFoo, 'to satisfy', value.children);
nodeListForDiffing._isAttachedDOMNodeList = true;
return topLevelExpect(nodeListForDiffing, 'to satisfy', value.children);
} else if (typeof value.textContent !== 'undefined') {
return topLevelExpect(subject.textContent, 'to satisfy', value.textContent);
}
}),
attributes: {}
};

var onlyAttributes = value && value.onlyAttributes;
var onlyAttributes = value && value.onlyAttributes || this.flags.exhaustively;
var attrs = getAttributes(subject);
var expectedAttributes = value && value.attributes;
var expectedAttributeNames = [];
Expand Down Expand Up @@ -771,52 +880,12 @@ module.exports = {

expect.addAssertion('string', 'when parsed as (html|HTML) [fragment]', function (expect, subject) {
this.errorMode = 'nested';
var htmlSource = subject;
if (this.flags.fragment) {
htmlSource = '<html><head></head><body>' + htmlSource + '</body></html>';
}
var htmlDocument;
if (typeof DOMParser !== 'undefined') {
htmlDocument = new DOMParser().parseFromString(htmlSource, 'text/html');
} else if (typeof document !== 'undefined' && document.implementation && document.implementation.createHTMLDocument) {
htmlDocument = document.implementation.createHTMLDocument('');
htmlDocument.open();
htmlDocument.write(htmlSource);
htmlDocument.close();
} else {
try {
htmlDocument = require('jsdom').jsdom(htmlSource);
} catch (err) {
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.');
}
}
if (this.flags.fragment) {
var body = htmlDocument.body;
var documentFragment = htmlDocument.createDocumentFragment();
if (body) {
for (var i = 0 ; i < body.childNodes.length ; i += 1) {
documentFragment.appendChild(body.childNodes[i].cloneNode(true));
}
}
return this.shift(expect, documentFragment, 0);
} else {
return this.shift(expect, htmlDocument, 0);
}
return this.shift(expect, parseHtml(subject, this.flags.fragment, this.testDescription), 0);
});

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');
} 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);
return this.shift(expect, parseXml(subject, this.testDescription), 0);
});
}
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"mocha-lcov-reporter": "0.0.2",
"sinon": "^1.15.4",
"uglifyjs": "^2.4.10",
"unexpected": "9.10.0",
"unexpected": "9.16.0",
"unexpected-sinon": "6.4.1"
},
"dependencies": {
Expand Down
Loading