1 | /*!
|
2 | * chai-jq
|
3 | * -------
|
4 | * An alternate jQuery assertion library for Chai.
|
5 | */
|
6 | (function () {
|
7 | var root = this;
|
8 |
|
9 | /*!
|
10 | * Chai jQuery plugin implementation.
|
11 | */
|
12 | function chaiJq(chai, utils) {
|
13 | ;
|
14 |
|
15 | // ------------------------------------------------------------------------
|
16 | // Variables
|
17 | // ------------------------------------------------------------------------
|
18 | var flag = utils.flag,
|
19 | toString = Object.prototype.toString;
|
20 |
|
21 | // ------------------------------------------------------------------------
|
22 | // Helpers
|
23 | // ------------------------------------------------------------------------
|
24 | /*!
|
25 | * Give a more useful element name.
|
26 | */
|
27 | var _elName = function ($el) {
|
28 | // Detect if completely empty.
|
29 | if (!$el || $el.length === 0) {
|
30 | return "<EMPTY OBJECT>";
|
31 | }
|
32 |
|
33 | var name = "",
|
34 | id = $el.attr("id"),
|
35 | cls = $el.attr("class") || "";
|
36 |
|
37 | // Try CSS selector id.
|
38 | if (id) {
|
39 | name += "#" + id;
|
40 | }
|
41 | if (cls) {
|
42 | name += "." + cls.split(" ").join(".");
|
43 | }
|
44 | if (name) {
|
45 | return "'" + name + "'";
|
46 | }
|
47 |
|
48 | // Give up.
|
49 | return $el;
|
50 | };
|
51 |
|
52 | // ------------------------------------------------------------------------
|
53 | // Type Inference
|
54 | //
|
55 | // (Inspired by Underscore)
|
56 | // ------------------------------------------------------------------------
|
57 | var _isRegExp = function (val) {
|
58 | return toString.call(val) === "[object RegExp]";
|
59 | };
|
60 |
|
61 | // ------------------------------------------------------------------------
|
62 | // Comparisons
|
63 | // ------------------------------------------------------------------------
|
64 | var _equals = function (exp, act) {
|
65 | return exp === act;
|
66 | };
|
67 |
|
68 | var _contains = function (exp, act) {
|
69 | return act.indexOf(exp) !== -1;
|
70 | };
|
71 |
|
72 | var _exists = function (exp, act) {
|
73 | return act !== undefined;
|
74 | };
|
75 |
|
76 | var _regExpMatch = function (expRe, act) {
|
77 | return expRe.exec(act);
|
78 | };
|
79 |
|
80 | // ------------------------------------------------------------------------
|
81 | // Assertions (Internal)
|
82 | // ------------------------------------------------------------------------
|
83 | /*!
|
84 | * Wrap assert function and add properties.
|
85 | */
|
86 | var _jqAssert = function (fn) {
|
87 | return function (exp, msg) {
|
88 | // Set properties.
|
89 | this._$el = flag(this, "object");
|
90 | this._name = _elName(this._$el);
|
91 |
|
92 | // Flag message.
|
93 | if (msg) {
|
94 | flag(this, "message", msg);
|
95 | }
|
96 |
|
97 | // Invoke assertion function.
|
98 | fn.apply(this, arguments);
|
99 | };
|
100 | };
|
101 |
|
102 | /*!
|
103 | * Base for the boolean is("selector") method call.
|
104 | *
|
105 | * @see http://api.jquery.com/is/]
|
106 | *
|
107 | * @param {String} selector jQuery selector to match against
|
108 | */
|
109 | var _isMethod = function (jqSelector) {
|
110 | // Make selector human readable.
|
111 | var selectorDesc = jqSelector.replace(/:/g, "");
|
112 |
|
113 | // Return decorated assert.
|
114 | return _jqAssert(function () {
|
115 | this.assert(
|
116 | this._$el.is(jqSelector),
|
117 | "expected " + this._name + " to be " + selectorDesc,
|
118 | "expected " + this._name + " to not be " + selectorDesc
|
119 | );
|
120 | });
|
121 | };
|
122 |
|
123 | /*!
|
124 | * Abstract base for a "containable" method call.
|
125 | *
|
126 | * @param {String} jQuery method name.
|
127 | * @param {Object} opts options
|
128 | * @param {String} opts.hasArg takes argument for method
|
129 | * @param {String} opts.isProperty switch assert context to property if no
|
130 | * expected val
|
131 | * @param {String} opts.hasContains is "contains" applicable
|
132 | * @param {String} opts.altGet alternate function to get value if none
|
133 | */
|
134 | var _containMethod = function (jqMeth, opts) {
|
135 | // Unpack options.
|
136 | opts = opts || /* istanbul ignore next */ {};
|
137 | opts.hasArg = !!opts.hasArg;
|
138 | opts.isProperty = !!opts.isProperty;
|
139 | opts.hasContains = !!opts.hasContains;
|
140 | opts.defaultAct = undefined;
|
141 |
|
142 | // Return decorated assert.
|
143 | return _jqAssert(function () {
|
144 | // Arguments.
|
145 | var exp = arguments[opts.hasArg ? 1 : 0],
|
146 | arg = opts.hasArg ? arguments[0] : undefined,
|
147 |
|
148 | // Switch context to property / check mere presence.
|
149 | noExp = arguments.length === (opts.hasArg ? 1 : 0),
|
150 | isProp = opts.isProperty && noExp,
|
151 |
|
152 | // Method.
|
153 | act = (opts.hasArg ? this._$el[jqMeth](arg) : this._$el[jqMeth]()),
|
154 | meth = opts.hasArg ? jqMeth + "('" + arg + "')" : jqMeth,
|
155 |
|
156 | // Assertion type.
|
157 | contains = !isProp && opts.hasContains && flag(this, "contains"),
|
158 | have = contains ? "contain" : "have",
|
159 | comp = _equals;
|
160 |
|
161 | // Set comparison.
|
162 | if (isProp) {
|
163 | comp = _exists;
|
164 | } else if (contains) {
|
165 | comp = _contains;
|
166 | }
|
167 |
|
168 | // Second chance getter.
|
169 | if (opts.altGet && !act) {
|
170 | act = opts.altGet(this._$el, arg);
|
171 | }
|
172 |
|
173 | // Default actual value on undefined.
|
174 | if (typeof act === "undefined") {
|
175 | act = opts.defaultAct;
|
176 | }
|
177 |
|
178 | // Same context assertion.
|
179 | this.assert(
|
180 | comp(exp, act),
|
181 | "expected " + this._name + " to " + have + " " + meth +
|
182 | (isProp ? "" : " #{exp} but found #{act}"),
|
183 | "expected " + this._name + " not to " + have + " " + meth +
|
184 | (isProp ? "" : " #{exp}"),
|
185 | exp,
|
186 | act
|
187 | );
|
188 |
|
189 | // Change context if property and not negated.
|
190 | if (isProp && !flag(this, "negate")) {
|
191 | flag(this, "object", act);
|
192 | }
|
193 | });
|
194 | };
|
195 |
|
196 | // ------------------------------------------------------------------------
|
197 | // API
|
198 | // ------------------------------------------------------------------------
|
199 |
|
200 | /**
|
201 | * Asserts that the element is visible.
|
202 | *
|
203 | * *Node.js/JsDom Note*: JsDom does not currently infer zero-sized or
|
204 | * hidden parent elements as hidden / visible appropriately.
|
205 | *
|
206 | * ```js
|
207 | * expect($("<div> </div>"))
|
208 | * .to.be.$visible;
|
209 | * ```
|
210 | *
|
211 | * @see http://api.jquery.com/visible-selector/
|
212 | *
|
213 | * @api public
|
214 | */
|
215 | var $visible = _isMethod(":visible");
|
216 |
|
217 | chai.Assertion.addProperty("$visible", $visible);
|
218 |
|
219 | /**
|
220 | * Asserts that the element is hidden.
|
221 | *
|
222 | * *Node.js/JsDom Note*: JsDom does not currently infer zero-sized or
|
223 | * hidden parent elements as hidden / visible appropriately.
|
224 | *
|
225 | * ```js
|
226 | * expect($("<div style=\"display: none\" />"))
|
227 | * .to.be.$hidden;
|
228 | * ```
|
229 | *
|
230 | * @see http://api.jquery.com/hidden-selector/
|
231 | *
|
232 | * @api public
|
233 | */
|
234 | var $hidden = _isMethod(":hidden");
|
235 |
|
236 | chai.Assertion.addProperty("$hidden", $hidden);
|
237 |
|
238 | /**
|
239 | * Asserts that the element value matches a string or regular expression.
|
240 | *
|
241 | * ```js
|
242 | * expect($("<input value='foo' />"))
|
243 | * .to.have.$val("foo").and
|
244 | * .to.have.$val(/^foo/);
|
245 | * ```
|
246 | *
|
247 | * @see http://api.jquery.com/val/
|
248 | *
|
249 | * @param {String|RegExp} expected value
|
250 | * @param {String} message failure message (_optional_)
|
251 | * @api public
|
252 | */
|
253 | var $val = _jqAssert(function (exp) {
|
254 | // Manually check empty elements for `.val` call b/c ie9 can otherwise
|
255 | // report `Unspecified error.` at least in Sauce Labs.
|
256 | var act = this._$el && this._$el.length > 0 ? this._$el.val() : undefined,
|
257 | comp = _isRegExp(exp) ? _regExpMatch : _equals;
|
258 |
|
259 | this.assert(
|
260 | comp(exp, act),
|
261 | "expected " + this._name + " to have val #{exp} but found #{act}",
|
262 | "expected " + this._name + " not to have val #{exp}",
|
263 | exp,
|
264 | typeof act === "undefined" ? "undefined" : act
|
265 | );
|
266 | });
|
267 |
|
268 | chai.Assertion.addMethod("$val", $val);
|
269 |
|
270 | /**
|
271 | * Asserts that the element has a class match.
|
272 | *
|
273 | * ```js
|
274 | * expect($("<div class='foo bar' />"))
|
275 | * .to.have.$class("foo").and
|
276 | * .to.have.$class("bar");
|
277 | * ```
|
278 | *
|
279 | * @see http://api.jquery.com/hasClass/
|
280 | *
|
281 | * @param {String} expected class name
|
282 | * @param {String} message failure message (_optional_)
|
283 | * @api public
|
284 | */
|
285 | var $class = _jqAssert(function (exp) {
|
286 | var act = this._$el.attr("class") || "";
|
287 |
|
288 | this.assert(
|
289 | this._$el.hasClass(exp),
|
290 | "expected " + this._name + " to have class #{exp} but found #{act}",
|
291 | "expected " + this._name + " not to have class #{exp}",
|
292 | exp,
|
293 | act
|
294 | );
|
295 | });
|
296 |
|
297 | chai.Assertion.addMethod("$class", $class);
|
298 |
|
299 | /**
|
300 | * Asserts that the target has exactly the given named attribute, or
|
301 | * asserts the target contains a subset of the attribute when using the
|
302 | * `include` or `contain` modifiers.
|
303 | *
|
304 | * ```js
|
305 | * expect($("<div id=\"hi\" foo=\"bar time\" />"))
|
306 | * .to.have.$attr("id", "hi").and
|
307 | * .to.contain.$attr("foo", "bar");
|
308 | * ```
|
309 | *
|
310 | * Changes context to attribute string *value* when no expected value is
|
311 | * provided:
|
312 | *
|
313 | * ```js
|
314 | * expect($("<div id=\"hi\" foo=\"bar time\" />"))
|
315 | * .to.have.$attr("foo").and
|
316 | * .to.equal("bar time").and
|
317 | * .to.match(/^b/);
|
318 | * ```
|
319 | *
|
320 | * @see http://api.jquery.com/attr/
|
321 | *
|
322 | * @param {String} name attribute name
|
323 | * @param {String} expected attribute content (_optional_)
|
324 | * @param {String} message failure message (_optional_)
|
325 | * @returns current object or attribute string value
|
326 | * @api public
|
327 | */
|
328 | var $attr = _containMethod("attr", {
|
329 | hasArg: true,
|
330 | hasContains: true,
|
331 | isProperty: true
|
332 | });
|
333 |
|
334 | chai.Assertion.addMethod("$attr", $attr);
|
335 |
|
336 | /**
|
337 | * Asserts that the target has exactly the given named
|
338 | * data-attribute, or asserts the target contains a subset
|
339 | * of the data-attribute when using the
|
340 | * `include` or `contain` modifiers.
|
341 | *
|
342 | * ```js
|
343 | * expect($("<div data-id=\"hi\" data-foo=\"bar time\" />"))
|
344 | * .to.have.$data("id", "hi").and
|
345 | * .to.contain.$data("foo", "bar");
|
346 | * ```
|
347 | *
|
348 | * Changes context to data-attribute string *value* when no
|
349 | * expected value is provided:
|
350 | *
|
351 | * ```js
|
352 | * expect($("<div data-id=\"hi\" data-foo=\"bar time\" />"))
|
353 | * .to.have.$data("foo").and
|
354 | * .to.equal("bar time").and
|
355 | * .to.match(/^b/);
|
356 | * ```
|
357 | *
|
358 | * @see http://api.jquery.com/data/
|
359 | *
|
360 | * @param {String} name data-attribute name
|
361 | * @param {String} expected data-attribute content (_optional_)
|
362 | * @param {String} message failure message (_optional_)
|
363 | * @returns current object or attribute string value
|
364 | * @api public
|
365 | */
|
366 | var $data = _containMethod("data", {
|
367 | hasArg: true,
|
368 | hasContains: true,
|
369 | isProperty: true
|
370 | });
|
371 |
|
372 | chai.Assertion.addMethod("$data", $data);
|
373 |
|
374 | /**
|
375 | * Asserts that the target has exactly the given named property.
|
376 | *
|
377 | * ```js
|
378 | * expect($("<input type=\"checkbox\" checked=\"checked\" />"))
|
379 | * .to.have.$prop("checked", true).and
|
380 | * .to.have.$prop("type", "checkbox");
|
381 | * ```
|
382 | *
|
383 | * Changes context to property string *value* when no expected value is
|
384 | * provided:
|
385 | *
|
386 | * ```js
|
387 | * expect($("<input type=\"checkbox\" checked=\"checked\" />"))
|
388 | * .to.have.$prop("type").and
|
389 | * .to.equal("checkbox").and
|
390 | * .to.match(/^c.*x$/);
|
391 | * ```
|
392 | *
|
393 | * @see http://api.jquery.com/prop/
|
394 | *
|
395 | * @param {String} name property name
|
396 | * @param {Object} expected property value (_optional_)
|
397 | * @param {String} message failure message (_optional_)
|
398 | * @returns current object or property string value
|
399 | * @api public
|
400 | */
|
401 | var $prop = _containMethod("prop", {
|
402 | hasArg: true,
|
403 | isProperty: true
|
404 | });
|
405 |
|
406 | chai.Assertion.addMethod("$prop", $prop);
|
407 |
|
408 | /**
|
409 | * Asserts that the target has exactly the given HTML, or
|
410 | * asserts the target contains a subset of the HTML when using the
|
411 | * `include` or `contain` modifiers.
|
412 | *
|
413 | * ```js
|
414 | * expect($("<div><span>foo</span></div>"))
|
415 | * .to.have.$html("<span>foo</span>").and
|
416 | * .to.contain.$html("foo");
|
417 | * ```
|
418 | *
|
419 | * @see http://api.jquery.com/html/
|
420 | *
|
421 | * @param {String} expected HTML content
|
422 | * @param {String} message failure message (_optional_)
|
423 | * @api public
|
424 | */
|
425 | var $html = _containMethod("html", {
|
426 | hasContains: true
|
427 | });
|
428 |
|
429 | chai.Assertion.addMethod("$html", $html);
|
430 |
|
431 | /**
|
432 | * Asserts that the target has exactly the given text, or
|
433 | * asserts the target contains a subset of the text when using the
|
434 | * `include` or `contain` modifiers.
|
435 | *
|
436 | * ```js
|
437 | * expect($("<div><span>foo</span> bar</div>"))
|
438 | * .to.have.$text("foo bar").and
|
439 | * .to.contain.$text("foo");
|
440 | * ```
|
441 | *
|
442 | * @see http://api.jquery.com/text/
|
443 | *
|
444 | * @name $text
|
445 | * @param {String} expected text content
|
446 | * @param {String} message failure message (_optional_)
|
447 | * @api public
|
448 | */
|
449 | var $text = _containMethod("text", {
|
450 | hasContains: true
|
451 | });
|
452 |
|
453 | chai.Assertion.addMethod("$text", $text);
|
454 |
|
455 | /**
|
456 | * Asserts that the target has exactly the given CSS property, or
|
457 | * asserts the target contains a subset of the CSS when using the
|
458 | * `include` or `contain` modifiers.
|
459 | *
|
460 | * *Node.js/JsDom Note*: Computed CSS properties are not correctly
|
461 | * inferred as of JsDom v0.8.8. Explicit ones should get matched exactly.
|
462 | *
|
463 | * *Browser Note*: Explicit CSS properties are sometimes not matched
|
464 | * (in contrast to Node.js), so the plugin performs an extra check against
|
465 | * explicit `style` properties for a match. May still have other wonky
|
466 | * corner cases.
|
467 | *
|
468 | * *PhantomJS Note*: PhantomJS also is fairly wonky and unpredictable with
|
469 | * respect to CSS / styles, especially those that come from CSS classes
|
470 | * and not explicity `style` attributes.
|
471 | *
|
472 | * ```js
|
473 | * expect($("<div style=\"width: 50px; border: 1px dotted black;\" />"))
|
474 | * .to.have.$css("width", "50px").and
|
475 | * .to.have.$css("border-top-style", "dotted");
|
476 | * ```
|
477 | *
|
478 | * @see http://api.jquery.com/css/
|
479 | *
|
480 | * @name $css
|
481 | * @param {String} expected CSS property content
|
482 | * @param {String} message failure message (_optional_)
|
483 | * @api public
|
484 | */
|
485 | var $css = _containMethod("css", {
|
486 | hasArg: true,
|
487 | hasContains: true,
|
488 |
|
489 | // Alternate Getter: If no match, go for explicit property.
|
490 | altGet: function ($el, prop) { return $el.prop("style")[prop]; }
|
491 | });
|
492 |
|
493 | chai.Assertion.addMethod("$css", $css);
|
494 | }
|
495 |
|
496 | /* istanbul ignore next */
|
497 | /*!
|
498 | * Wrap AMD, etc. using boilerplate.
|
499 | */
|
500 | function wrap(plugin) {
|
501 | ;
|
502 | /* global module:false, define:false */
|
503 |
|
504 | if (typeof require === "function" &&
|
505 | typeof exports === "object" &&
|
506 | typeof module === "object") {
|
507 | // NodeJS
|
508 | module.exports = plugin;
|
509 |
|
510 | } else if (typeof define === "function" && define.amd) {
|
511 | // AMD: Assumes importing `chai` and `jquery`. Returns a function to
|
512 | // inject with `chai.use()`.
|
513 | //
|
514 | // See: https://github.com/chaijs/chai-jquery/issues/27
|
515 | define(["jquery"], function ($) {
|
516 | return function (chai, utils) {
|
517 | return plugin(chai, utils, $);
|
518 | };
|
519 | });
|
520 |
|
521 | } else {
|
522 | // Other environment (usually <script> tag): plug in to global chai
|
523 | // instance directly.
|
524 | root.chai.use(function (chai, utils) {
|
525 | return plugin(chai, utils, root.jQuery);
|
526 | });
|
527 | }
|
528 | }
|
529 |
|
530 | // Hook it all together.
|
531 | wrap(chaiJq);
|
532 | }());
|