/*! * chai-jq * ------- * An alternate jQuery assertion library for Chai. */ (function () { var root = this; /*! * Chai jQuery plugin implementation. */ function chaiJq(chai, utils) { "use strict"; // ------------------------------------------------------------------------ // Variables // ------------------------------------------------------------------------ var flag = utils.flag, toString = Object.prototype.toString; // ------------------------------------------------------------------------ // Helpers // ------------------------------------------------------------------------ /*! * Give a more useful element name. */ var _elName = function ($el) { var name = "", id = $el.attr("id"), cls = $el.attr("class") || ""; // Try CSS selector id. if (id) { name += "#" + id; } if (cls) { name += "." + cls.split(" ").join("."); } if (name) { return "'" + name + "'"; } // Give up. return $el; }; // ------------------------------------------------------------------------ // Type Inference // // (Inspired by Underscore) // ------------------------------------------------------------------------ var _isRegExp = function (val) { return toString.call(val) === "[object RegExp]"; }; // ------------------------------------------------------------------------ // Comparisons // ------------------------------------------------------------------------ var _equals = function (exp, act) { return exp === act; }; var _contains = function (exp, act) { return act.indexOf(exp) !== -1; }; var _exists = function (exp, act) { return act !== undefined; }; var _regExpMatch = function (expRe, act) { return expRe.exec(act); }; // ------------------------------------------------------------------------ // Assertions (Internal) // ------------------------------------------------------------------------ /*! * Wrap assert function and add properties. */ var _jqAssert = function (fn) { return function (exp, msg) { // Set properties. this._$el = flag(this, "object"); this._name = _elName(this._$el); // Flag message. if (msg) { flag(this, "message", msg); } // Invoke assertion function. fn.apply(this, arguments); }; }; /*! * Base for the boolean is("selector") method call. * * @see http://api.jquery.com/is/] * * @param {String} selector jQuery selector to match against */ var _isMethod = function (jqSelector) { // Make selector human readable. var selectorDesc = jqSelector.replace(/:/g, ""); // Return decorated assert. return _jqAssert(function () { this.assert( this._$el.is(jqSelector), "expected " + this._name + " to be " + selectorDesc, "expected " + this._name + " to not be " + selectorDesc ); }); }; /*! * Abstract base for a "containable" method call. * * @param {String} jQuery method name. * @param {Object} opts options * @param {String} opts.hasArg takes argument for method * @param {String} opts.isProperty switch assert context to property if no * expected val * @param {String} opts.hasContains is "contains" applicable * @param {String} opts.altGet alternate function to get value if none */ var _containMethod = function (jqMeth, opts) { // Unpack options. opts || (opts = {}); opts.hasArg = !!opts.hasArg; opts.isProperty = !!opts.isProperty; opts.hasContains = !!opts.hasContains; opts.defaultAct = undefined; // Return decorated assert. return _jqAssert(function () { // Arguments. var exp = arguments[opts.hasArg ? 1 : 0], arg = opts.hasArg ? arguments[0] : undefined, // Switch context to property / check mere presence. noExp = arguments.length === (opts.hasArg ? 1 : 0), isProp = opts.isProperty && noExp, // Method. act = (opts.hasArg ? this._$el[jqMeth](arg) : this._$el[jqMeth]()), meth = opts.hasArg ? jqMeth + "('" + arg + "')" : jqMeth, // Assertion type. contains = !isProp && opts.hasContains && flag(this, "contains"), have = contains ? "contain" : "have", comp = _equals; // Set comparison. if (isProp) { comp = _exists; } else if (contains) { comp = _contains; } // Second chance getter. if (opts.altGet && !act) { act = opts.altGet(this._$el, arg); } // Default actual value on undefined. if (typeof act === "undefined") { act = opts.defaultAct; } // Same context assertion. this.assert( comp(exp, act), "expected " + this._name + " to " + have + " " + meth + (isProp ? "" : " #{exp} but found #{act}"), "expected " + this._name + " not to " + have + " " + meth + (isProp ? "" : " #{exp}"), exp, act ); // Change context if property and not negated. if (isProp && !flag(this, "negate")) { flag(this, "object", act); } }); }; // ------------------------------------------------------------------------ // API // ------------------------------------------------------------------------ /** * Asserts that the element is visible. * * *Node.js/JsDom Note*: JsDom does not currently infer zero-sized or * hidden parent elements as hidden / visible appropriately. * * ```js * expect($("
 
")) * .to.be.$visible; * ``` * * @see http://api.jquery.com/visible-selector/ * * @api public */ var $visible = _isMethod(":visible"); chai.Assertion.addProperty("$visible", $visible); /** * Asserts that the element is hidden. * * *Node.js/JsDom Note*: JsDom does not currently infer zero-sized or * hidden parent elements as hidden / visible appropriately. * * ```js * expect($("
")) * .to.be.$hidden; * ``` * * @see http://api.jquery.com/hidden-selector/ * * @api public */ var $hidden = _isMethod(":hidden"); chai.Assertion.addProperty("$hidden", $hidden); /** * Asserts that the element value matches a string or regular expression. * * ```js * expect($("")) * .to.have.$val("foo").and * .to.have.$val(/^foo/); * ``` * * @see http://api.jquery.com/val/ * * @param {String|RegExp} expected value * @param {String} message failure message (_optional_) * @api public */ var $val = _jqAssert(function (exp) { var act = this._$el.val(), comp = _isRegExp(exp) ? _regExpMatch : _equals; this.assert( comp(exp, act), "expected " + this._name + " to have val #{exp} but found #{act}", "expected " + this._name + " not to have val #{exp}", exp, typeof act === "undefined" ? "undefined" : act ); }); chai.Assertion.addMethod("$val", $val); /** * Asserts that the element has a class match. * * ```js * expect($("
")) * .to.have.$class("foo").and * .to.have.$class("bar"); * ``` * * @see http://api.jquery.com/hasClass/ * * @param {String} expected class name * @param {String} message failure message (_optional_) * @api public */ var $class = _jqAssert(function (exp) { var act = this._$el.attr("class") || ""; this.assert( this._$el.hasClass(exp), "expected " + this._name + " to have class #{exp} but found #{act}", "expected " + this._name + " not to have class #{exp}", exp, act ); }); chai.Assertion.addMethod("$class", $class); /** * Asserts that the target has exactly the given named attribute, or * asserts the target contains a subset of the attribute when using the * `include` or `contain` modifiers. * * ```js * expect($("
")) * .to.have.$attr("id", "hi").and * .to.contain.$attr("foo", "bar"); * ``` * * Changes context to attribute string *value* when no expected value is * provided: * * ```js * expect($("
")) * .to.have.$attr("foo").and * .to.equal("bar time").and * .to.match(/^b/); * ``` * * @see http://api.jquery.com/attr/ * * @param {String} name attribute name * @param {String} expected attribute content (_optional_) * @param {String} message failure message (_optional_) * @returns current object or attribute string value * @api public */ var $attr = _containMethod("attr", { hasArg: true, hasContains: true, isProperty: true }); chai.Assertion.addMethod("$attr", $attr); /** * Asserts that the target has exactly the given named * data-attribute, or asserts the target contains a subset * of the data-attribute when using the * `include` or `contain` modifiers. * * ```js * expect($("
")) * .to.have.$data("id", "hi").and * .to.contain.$data("foo", "bar"); * ``` * * Changes context to data-attribute string *value* when no * expected value is provided: * * ```js * expect($("
")) * .to.have.$data("foo").and * .to.equal("bar time").and * .to.match(/^b/); * ``` * * @see http://api.jquery.com/data/ * * @param {String} name data-attribute name * @param {String} expected data-attribute content (_optional_) * @param {String} message failure message (_optional_) * @returns current object or attribute string value * @api public */ var $data = _containMethod("data", { hasArg: true, hasContains: true, isProperty: true }); chai.Assertion.addMethod("$data", $data); /** * Asserts that the target has exactly the given named property. * * ```js * expect($("")) * .to.have.$prop("checked", true).and * .to.have.$prop("type", "checkbox"); * ``` * * Changes context to property string *value* when no expected value is * provided: * * ```js * expect($("")) * .to.have.$prop("type").and * .to.equal("checkbox").and * .to.match(/^c.*x$/); * ``` * * @see http://api.jquery.com/prop/ * * @param {String} name property name * @param {Object} expected property value (_optional_) * @param {String} message failure message (_optional_) * @returns current object or property string value * @api public */ var $prop = _containMethod("prop", { hasArg: true, isProperty: true }); chai.Assertion.addMethod("$prop", $prop); /** * Asserts that the target has exactly the given HTML, or * asserts the target contains a subset of the HTML when using the * `include` or `contain` modifiers. * * ```js * expect($("
foo
")) * .to.have.$html("foo").and * .to.contain.$html("foo"); * ``` * * @see http://api.jquery.com/html/ * * @param {String} expected HTML content * @param {String} message failure message (_optional_) * @api public */ var $html = _containMethod("html", { hasContains: true }); chai.Assertion.addMethod("$html", $html); /** * Asserts that the target has exactly the given text, or * asserts the target contains a subset of the text when using the * `include` or `contain` modifiers. * * ```js * expect($("
foo bar
")) * .to.have.$text("foo bar").and * .to.contain.$text("foo"); * ``` * * @see http://api.jquery.com/text/ * * @name $text * @param {String} expected text content * @param {String} message failure message (_optional_) * @api public */ var $text = _containMethod("text", { hasContains: true }); chai.Assertion.addMethod("$text", $text); /** * Asserts that the target has exactly the given CSS property, or * asserts the target contains a subset of the CSS when using the * `include` or `contain` modifiers. * * *Node.js/JsDom Note*: Computed CSS properties are not correctly * inferred as of JsDom v0.8.8. Explicit ones should get matched exactly. * * *Browser Note*: Explicit CSS properties are sometimes not matched * (in contrast to Node.js), so the plugin performs an extra check against * explicit `style` properties for a match. May still have other wonky * corner cases. * * *PhantomJS Note*: PhantomJS also is fairly wonky and unpredictable with * respect to CSS / styles, especially those that come from CSS classes * and not explicity `style` attributes. * * ```js * expect($("
")) * .to.have.$css("width", "50px").and * .to.have.$css("border-top-style", "dotted"); * ``` * * @see http://api.jquery.com/css/ * * @name $css * @param {String} expected CSS property content * @param {String} message failure message (_optional_) * @api public */ var $css = _containMethod("css", { hasArg: true, hasContains: true, // Alternate Getter: If no match, go for explicit property. altGet: function ($el, prop) { return $el.prop("style")[prop]; } }); chai.Assertion.addMethod("$css", $css); } /*! * Wrap AMD, etc. using boilerplate. */ function wrap(plugin) { "use strict"; /* global module:false, define:false */ if (typeof require === "function" && typeof exports === "object" && typeof module === "object") { // NodeJS module.exports = plugin; } else if (typeof define === "function" && define.amd) { // AMD: Assumes importing `chai` and `jquery`. Returns a function to // inject with `chai.use()`. // // See: https://github.com/chaijs/chai-jquery/issues/27 define(["jquery"], function ($) { return function (chai, utils) { return plugin(chai, utils, $); }; }); } else { // Other environment (usually