1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 // Equality function based on isEqual in
8 // http://underscorejs.org
9 // (c) 2009-2013 Jeremy Ashkenas,
11 // and Investigative Reporters & Editors
12 // Underscore may be freely distributed under the MIT license.
14 function has(obj
, key
) {
15 return obj
.hasOwnProperty(key
);
17 function isFunction(obj
) {
18 return typeof obj
=== 'function';
20 function isArrayBufferClass(className
) {
21 return className
== '[object ArrayBuffer]' ||
22 className
.match(/\[object \w+\d+(Clamped)?Array\]/);
24 // Internal recursive comparison function for `isEqual`.
25 function eq(a
, b
, aStack
, bStack
) {
26 // Identical objects are equal. `0 === -0`, but they aren't identical.
27 // See the Harmony `egal` proposal:
28 // http://wiki.ecmascript.org/doku.php?id=harmony:egal.
30 return a
!== 0 || 1 / a
== 1 / b
;
31 // A strict comparison is necessary because `null == undefined`.
32 if (a
== null || b
== null)
34 // Compare `[[Class]]` names.
35 var className
= toString
.call(a
);
36 if (className
!= toString
.call(b
))
39 // Strings, numbers, dates, and booleans are compared by value.
40 case '[object String]':
41 // Primitives and their corresponding object wrappers are equivalent;
42 // thus, `"5"` is equivalent to `new String("5")`.
43 return a
== String(b
);
44 case '[object Number]':
45 // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is
46 // performed for other numeric values.
47 return a
!= +a
? b
!= +b
: (a
== 0 ? 1 / a
== 1 / b
: a
== +b
);
49 case '[object Boolean]':
50 // Coerce dates and booleans to numeric primitive values. Dates are
51 // compared by their millisecond representations. Note that invalid
52 // dates with millisecond representations of `NaN` are not equivalent.
54 // RegExps are compared by their source patterns and flags.
55 case '[object RegExp]':
56 return a
.source
== b
.source
&&
57 a
.global
== b
.global
&&
58 a
.multiline
== b
.multiline
&&
59 a
.ignoreCase
== b
.ignoreCase
;
61 if (typeof a
!= 'object' || typeof b
!= 'object')
63 // Assume equality for cyclic structures. The algorithm for detecting
64 // cyclic structures is adapted from ES 5.1 section 15.12.3, abstract
66 var length
= aStack
.length
;
68 // Linear search. Performance is inversely proportional to the number of
69 // unique nested structures.
70 if (aStack
[length
] == a
)
71 return bStack
[length
] == b
;
73 // Objects with different constructors are not equivalent, but `Object`s
74 // from different frames are.
75 var aCtor
= a
.constructor, bCtor
= b
.constructor;
76 if (aCtor
!== bCtor
&& !(isFunction(aCtor
) && (aCtor
instanceof aCtor
) &&
77 isFunction(bCtor
) && (bCtor
instanceof bCtor
))
78 && ('constructor' in a
&& 'constructor' in b
)) {
81 // Add the first object to the stack of traversed objects.
84 var size
= 0, result
= true;
85 // Recursively compare Maps, objects and arrays.
86 if (className
== '[object Array]' || isArrayBufferClass(className
)) {
87 // Compare array lengths to determine if a deep comparison is necessary.
89 result
= size
== b
.length
;
91 // Deep compare the contents, ignoring non-numeric properties.
93 if (!(result
= eq(a
[size
], b
[size
], aStack
, bStack
)))
97 } else if (className
== '[object Map]') {
98 result
= a
.size
== b
.size
;
100 var entries
= a
.entries();
101 for (var e
= entries
.next(); result
&& !e
.done
; e
= entries
.next()) {
102 var key
= e
.value
[0];
103 var value
= e
.value
[1];
104 result
= b
.has(key
) && eq(value
, b
.get(key
), aStack
, bStack
);
108 // Deep compare objects.
111 // Count the expected number of properties.
113 // Deep compare each member.
114 if (!(result
= has(b
, key
) && eq(a
[key
], b
[key
], aStack
, bStack
)))
118 // Ensure that both objects contain the same number of properties.
121 if (has(b
, key
) && !(size
--))
127 // Remove the first object from the stack of traversed objects.
133 function describe(subjects
) {
134 var descriptions
= [];
135 Object
.getOwnPropertyNames(subjects
).forEach(function(name
) {
136 if (name
=== "Description")
137 descriptions
.push(subjects
[name
]);
139 descriptions
.push(name
+ ": " + JSON
.stringify(subjects
[name
]));
141 return descriptions
.join(" ");
146 predicates
.toBe = function(actual
, expected
) {
148 "result": actual
=== expected
,
149 "message": describe({
151 "Expected": expected
,
156 predicates
.toEqual = function(actual
, expected
) {
158 "result": eq(actual
, expected
, [], []),
159 "message": describe({
161 "Expected": expected
,
166 predicates
.toBeDefined = function(actual
) {
168 "result": typeof actual
!== "undefined",
169 "message": describe({
171 "Description": "Expected a defined value",
176 predicates
.toBeUndefined = function(actual
) {
177 // Recall: undefined is just a global variable. :)
179 "result": typeof actual
=== "undefined",
180 "message": describe({
182 "Description": "Expected an undefined value",
187 predicates
.toBeNull = function(actual
) {
188 // Recall: typeof null === "object".
190 "result": actual
=== null,
191 "message": describe({
198 predicates
.toBeTruthy = function(actual
) {
201 "message": describe({
203 "Description": "Expected a truthy value",
208 predicates
.toBeFalsy = function(actual
) {
211 "message": describe({
213 "Description": "Expected a falsy value",
218 predicates
.toContain = function(actual
, element
) {
220 "result": (function () {
221 for (var i
= 0; i
< actual
.length
; ++i
) {
222 if (eq(actual
[i
], element
, [], []))
227 "message": describe({
234 predicates
.toBeLessThan = function(actual
, reference
) {
236 "result": actual
< reference
,
237 "message": describe({
239 "Reference": reference
,
244 predicates
.toBeGreaterThan = function(actual
, reference
) {
246 "result": actual
> reference
,
247 "message": describe({
249 "Reference": reference
,
254 predicates
.toThrow = function(actual
) {
256 "result": (function () {
257 if (!isFunction(actual
))
266 "message": "Expected function to throw",
270 function negate(predicate
) {
272 var outcome
= predicate
.apply(null, arguments
);
273 outcome
.result
= !outcome
.result
;
278 function check(predicate
) {
280 var outcome
= predicate
.apply(null, arguments
);
283 throw outcome
.message
;
287 function Condition(actual
) {
289 Object
.getOwnPropertyNames(predicates
).forEach(function(name
) {
290 var bound
= predicates
[name
].bind(null, actual
);
291 this[name
] = check(bound
);
292 this.not
[name
] = check(negate(bound
));
296 return function(actual
) {
297 return new Condition(actual
);