assume-enzyme
Version:
enzyme plugin for the assume assertion framework
350 lines (296 loc) • 9.57 kB
JavaScript
;
var toString = require('react-element-to-jsx-string');
var beautify = require('html/lib/html');
var cheerio = require('cheerio');
var enzyme = require('enzyme');
//
// The `react-element-to-jsx-string` is converted from an ES6 module to ES5 so
// it has a weird .default syntax.
//
toString = toString.default || toString;
/**
* Expose the Assume plugin interface.
*
* @param {Assume} assume The assume instance.
* @param {Object} util Utilities provided by assume.
* @public
*/
module.exports = function plugin(assume, util) {
var hasOwn = Object.prototype.hasOwnProperty;
var ShallowWrapper = enzyme.ShallowWrapper;
var ReactWrapper = enzyme.ReactWrapper;
var format = util.format;
//
// Introduce a new flag.
//
assume.flags._anywhere = 'anywhere, somewhere';
/**
* Helper function to check if a given value is an enzyme instance.
*
* @param {Enzyme} value Possible wrapper.
* @returns {Boolean} If it's a wrapper.
* @private
*/
function isEnzyme(value) {
return value instanceof ShallowWrapper || value instanceof ReactWrapper;
}
/**
* Transform a wrapper to HTML we could walk through.
*
* @param {Enzyme} value Possible wrapper.
* @returns {Cheerio} Transformed HTML.
* @private
*/
function html(value) {
return cheerio(value.html());
}
/**
* Transform a wrapper of children in to a proper array.
*
* @param {Enzyme} wrapper Wrapper
* @private
*/
function toArray(wrapper) {
var result = new Array(wrapper.length);
wrapper.forEach(function each(node, index){
result[index] = node;
});
return result;
}
/**
* Clean up the HTML like output of the enzyme debug command so it's more
* human readable in assertion messages.
*
* @param {Enzyme} value Enzyme instance
* @returns {String} Component.
* @private
*/
function debug(value) {
return beautify.prettyPrint(value.debug(), {
indent_size: 2
});
}
/**
* Check if anything in the tree matches this.
*
* @param {Enzyme} value Wrapper we want to iterate over.
* @param {Function} fn Iterator.
* @returns {Boolean} Did we found anything that matched.
* @private
*/
function anywhere(value, fn) {
var children = toArray(value.children());
var found = false;
while (children.length) {
var node = children.shift();
found = fn(node);
if (found) break;
Array.prototype.push.apply(children, toArray(node.children()));
}
return found;
}
/**
* Check if an object has a given set of keys / values.
*
* @param {Object} value Object that needs property checking
* @param {Array|Object} what keys or object that it should include.
* @private
*/
function properties(value, what) {
var passed = true;
if (util.type(what) === 'array') {
util.each(what, function each(key) {
passed = hasOwn.call(value, key);
if (!passed) return false;
});
} else {
var keys = [];
for (var key in what) {
keys.push(key);
}
util.each(keys, function each(key) {
passed = util.deep(value[key], what[key]);
if (!passed) return false;
});
}
return passed;
}
/**
* Assert that our given value is an enzyme wrapper.
*
* @param {String} msg Reason of assertion failure.
* @returns {Assume} The assume instance for chaining.
* @public
*/
assume.add('enzyme', function enzymes(msg) {
var value = this.value
, expect = format('%j to @ be an enzyme instance', value);
return this.test(isEnzyme(value), msg, expect);
});
/**
* Assert if the wrapper has a given className.
*
* @param {String} str Name of the className it should contain.
* @param {String} msg Reason of assertion failure.
* @returns {Assume} The assume instance for chaining.
* @public
*/
assume.add('className, classNames', function className(str, msg) {
var value = this.value
, found = value.hasClass(str)
, expect = format('`%s` to @ have class %s', value.props().className, str);
if (!this._anywhere || found) return this.test(found, msg, expect);
found = anywhere(value, function iterate(node) {
return node.hasClass(str);
});
return this.test(found, msg, expect);
});
/**
* Assert if the wrapper contains a given component/node.
*
* @param {String} component The component or node it should have.
* @param {String} msg Reason of assertion failure.
* @returns {Assume} The assume instance for chaining.
* @public
*/
assume.add('contain, contains', function contain(component, msg) {
if (!isEnzyme(this.value)) {
return this.clone(this.value).includes(component, msg);
}
var value = this.value
, found = value.contains(component)
, expect = format('%s to @ contain %s', debug(value), toString(component));
if (!this._anywhere || found) return this.test(found, msg, expect);
found = anywhere(value, function iterate(node) {
return node.contains(component);
});
return this.test(found, msg, expect);
});
/**
* Assert that an element has a given tag name.
*
* @param {String} name Tag name it should have.
* @param {String} msg Reason of assertion failure.
* @returns {Assume} The assume instance for chaining.
* @public
*/
assume.add('tagName', function tagName(name, msg) {
var value = html(this.value)[0].name
, expect = format('%s to @ have tag name %s', value, name);
return this.test(value === name, msg, expect);
});
/**
* Assert that the given component is checked.
*
* @param {String} msg Reason of assertion failure.
* @returns {Assume} The assume instance for chaining.
* @public
*/
assume.add('checked', function checked(msg) {
var value = html(this.value).is(':checked')
, expect = format('%s component to @ be checked', debug(this.value));
return this.test(value, msg, expect);
});
/**
* Assert that the given component is disabled.
*
* @param {String} msg Reason of assertion failure.
* @returns {Assume} The assume instance for chaining.
* @public
*/
assume.add('disabled', function disabled(msg) {
var value = html(this.value).is(':disabled')
, expect = format('%s component to @ be disabled', debug(this.value));
return this.test(value, msg, expect);
});
/**
* Assert that the given component has a ref with the given name.
*
* @param {String} name Name of the reference that should be on the component.
* @param {String} msg Reason of assertion failure.
* @returns {Assume} The assume instance for chaining.
* @public
*/
assume.add('ref', function ref(name, msg) {
var value = (this.value.instance().refs || {})[name]
, expect = format('%s component to @ ref %s', debug(this.value), name);
return this.test(!!value, msg, expect);
});
/**
* Assert props.
*
* @param {Array|Object} what Keys, or key/value we want to include.
* @param {String} msg Reason of assertion failure.
* @returns {Assume} The assume instance for chaining.
* @public
*/
assume.add('props', function props(what, msg) {
var value = this.value
, expect
, passed;
//
// Hack: When calling .props() on a root node, it doesn't return the props.
// So we need to check if we need to get the instance props or enzyme's
// props.
//
if (value.root == value) {
value = value.instance().props;
} else {
value = value.props();
}
expect = format('%j to @ include props %j', value, what);
passed = properties(value, what);
return this.test(passed, msg, expect);
});
/**
* Assert state.
*
* @param {Array|Object} what Keys, or key/value we want to include.
* @param {String} msg Reason of assertion failure.
* @returns {Assume} The assume instance for chaining.
* @public
*/
assume.add('state', function state(what, msg) {
var value = this.value.state()
, expect = format('%j to @ include state %j', value, what)
, passed = properties(value, what);
return this.test(passed, msg, expect);
});
/**
* Assert that the HTML output of a component includes a given string.
*
* @param {String} str HTML string the representation should include.
* @param {String} msg Reason of assertion failure.
* @returns {Assume} The assume instance for chaining.
* @public
*/
assume.add('html', function htmls(str, msg) {
var outer = value.html().replace(/\sdata-reactid+="[^"]+"/g, '');
return this.clone(value).includes(str, msg);
});
/**
* Assert that a component or child is empty.
*
* @param {String} msg Reason of assertion failure.
* @returns {Assume} The assume instance for chaining.
* @public
*/
assume.add('blank, empty', function blank(msg) {
var value = this.value
, expect = format('%s to @ be empty', debug(value));
return this.test(value.children().length === 0, msg, expect);
});
/**
* Assert that a given wrapper has the component name.
*
* @param {String} what Name of the component.
* @param {String} msg Reason of assertion failure.
* @returns {Assume} The assume instance for chaining.
* @public
*/
assume.add('name', function type(what, msg) {
var value = this.value.name()
, expect = format('Component name %s to be type %s', value, what);
return this.test(value === what, msg, expect);
});
};