konacha-chai-matchers 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+ }());