Roll src/third_party/WebKit 9f7fb92:f103b33 (svn 202621:202622)
[chromium-blink-merge.git] / gin / test / expect.js
blob597b5b1d5e32bb456109f10cea141fb480451b93
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.
5 define(function() {
6   // Equality function based on isEqual in
7   // Underscore.js 1.5.2
8   // http://underscorejs.org
9   // (c) 2009-2013 Jeremy Ashkenas,
10   //               DocumentCloud,
11   //               and Investigative Reporters & Editors
12   // Underscore may be freely distributed under the MIT license.
13   //
14   function has(obj, key) {
15     return obj.hasOwnProperty(key);
16   }
17   function isFunction(obj) {
18     return typeof obj === 'function';
19   }
20   function isArrayBufferClass(className) {
21     return className == '[object ArrayBuffer]' ||
22         className.match(/\[object \w+\d+(Clamped)?Array\]/);
23   }
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.
29     if (a === b)
30       return a !== 0 || 1 / a == 1 / b;
31     // A strict comparison is necessary because `null == undefined`.
32     if (a == null || b == null)
33       return a === b;
34     // Compare `[[Class]]` names.
35     var className = toString.call(a);
36     if (className != toString.call(b))
37       return false;
38     switch (className) {
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);
48       case '[object Date]':
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.
53         return +a == +b;
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;
60     }
61     if (typeof a != 'object' || typeof b != 'object')
62       return false;
63     // Assume equality for cyclic structures. The algorithm for detecting
64     // cyclic structures is adapted from ES 5.1 section 15.12.3, abstract
65     // operation `JO`.
66     var length = aStack.length;
67     while (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;
72     }
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)) {
79       return false;
80     }
81     // Add the first object to the stack of traversed objects.
82     aStack.push(a);
83     bStack.push(b);
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.
88       size = a.length;
89       result = size == b.length;
90       if (result) {
91         // Deep compare the contents, ignoring non-numeric properties.
92         while (size--) {
93           if (!(result = eq(a[size], b[size], aStack, bStack)))
94             break;
95         }
96       }
97     } else if (className == '[object Map]') {
98       result = a.size == b.size;
99       if (result) {
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);
105         }
106       }
107     } else {
108       // Deep compare objects.
109       for (var key in a) {
110         if (has(a, key)) {
111           // Count the expected number of properties.
112           size++;
113           // Deep compare each member.
114           if (!(result = has(b, key) && eq(a[key], b[key], aStack, bStack)))
115             break;
116         }
117       }
118       // Ensure that both objects contain the same number of properties.
119       if (result) {
120         for (key in b) {
121           if (has(b, key) && !(size--))
122             break;
123         }
124         result = !size;
125       }
126     }
127     // Remove the first object from the stack of traversed objects.
128     aStack.pop();
129     bStack.pop();
130     return result;
131   };
133   function describe(subjects) {
134     var descriptions = [];
135     Object.getOwnPropertyNames(subjects).forEach(function(name) {
136       if (name === "Description")
137         descriptions.push(subjects[name]);
138       else
139         descriptions.push(name + ": " + JSON.stringify(subjects[name]));
140     });
141     return descriptions.join(" ");
142   }
144   var predicates = {};
146   predicates.toBe = function(actual, expected) {
147     return {
148       "result": actual === expected,
149       "message": describe({
150         "Actual": actual,
151         "Expected": expected,
152       }),
153     };
154   };
156   predicates.toEqual = function(actual, expected) {
157     return {
158       "result": eq(actual, expected, [], []),
159       "message": describe({
160         "Actual": actual,
161         "Expected": expected,
162       }),
163     };
164   };
166   predicates.toBeDefined = function(actual) {
167     return {
168       "result": typeof actual !== "undefined",
169       "message": describe({
170         "Actual": actual,
171         "Description": "Expected a defined value",
172       }),
173     };
174   };
176   predicates.toBeUndefined = function(actual) {
177     // Recall: undefined is just a global variable. :)
178     return {
179       "result": typeof actual === "undefined",
180       "message": describe({
181         "Actual": actual,
182         "Description": "Expected an undefined value",
183       }),
184     };
185   };
187   predicates.toBeNull = function(actual) {
188     // Recall: typeof null === "object".
189     return {
190       "result": actual === null,
191       "message": describe({
192         "Actual": actual,
193         "Expected": null,
194       }),
195     };
196   };
198   predicates.toBeTruthy = function(actual) {
199     return {
200       "result": !!actual,
201       "message": describe({
202         "Actual": actual,
203         "Description": "Expected a truthy value",
204       }),
205     };
206   };
208   predicates.toBeFalsy = function(actual) {
209     return {
210       "result": !!!actual,
211       "message": describe({
212         "Actual": actual,
213         "Description": "Expected a falsy value",
214       }),
215     };
216   };
218   predicates.toContain = function(actual, element) {
219     return {
220       "result": (function () {
221         for (var i = 0; i < actual.length; ++i) {
222           if (eq(actual[i], element, [], []))
223             return true;
224         }
225         return false;
226       })(),
227       "message": describe({
228         "Actual": actual,
229         "Element": element,
230       }),
231     };
232   };
234   predicates.toBeLessThan = function(actual, reference) {
235     return {
236       "result": actual < reference,
237       "message": describe({
238         "Actual": actual,
239         "Reference": reference,
240       }),
241     };
242   };
244   predicates.toBeGreaterThan = function(actual, reference) {
245     return {
246       "result": actual > reference,
247       "message": describe({
248         "Actual": actual,
249         "Reference": reference,
250       }),
251     };
252   };
254   predicates.toThrow = function(actual) {
255     return {
256       "result": (function () {
257         if (!isFunction(actual))
258           throw new TypeError;
259         try {
260           actual();
261         } catch (ex) {
262           return true;
263         }
264         return false;
265       })(),
266       "message": "Expected function to throw",
267     };
268   }
270   function negate(predicate) {
271     return function() {
272       var outcome = predicate.apply(null, arguments);
273       outcome.result = !outcome.result;
274       return outcome;
275     }
276   }
278   function check(predicate) {
279     return function() {
280       var outcome = predicate.apply(null, arguments);
281       if (outcome.result)
282         return;
283       throw outcome.message;
284     };
285   }
287   function Condition(actual) {
288     this.not = {};
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));
293     }, this);
294   }
296   return function(actual) {
297     return new Condition(actual);
298   };