konacha-chai-matchers 0.2.0 → 0.3.0

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