Files.app: Handle 'Invalid Date' on drawing file list
[chromium-blink-merge.git] / gin / test / expect.js
blobb5e0f216e7890e73c96f09c9033274f0c23879f9
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 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 {
98       // Deep compare objects.
99       for (var key in a) {
100         if (has(a, key)) {
101           // Count the expected number of properties.
102           size++;
103           // Deep compare each member.
104           if (!(result = has(b, key) && eq(a[key], b[key], aStack, bStack)))
105             break;
106         }
107       }
108       // Ensure that both objects contain the same number of properties.
109       if (result) {
110         for (key in b) {
111           if (has(b, key) && !(size--))
112             break;
113         }
114         result = !size;
115       }
116     }
117     // Remove the first object from the stack of traversed objects.
118     aStack.pop();
119     bStack.pop();
120     return result;
121   };
123   function describe(subjects) {
124     var descriptions = [];
125     Object.getOwnPropertyNames(subjects).forEach(function(name) {
126       if (name === "Description")
127         descriptions.push(subjects[name]);
128       else
129         descriptions.push(name + ": " + JSON.stringify(subjects[name]));
130     });
131     return descriptions.join(" ");
132   }
134   var predicates = {};
136   predicates.toBe = function(actual, expected) {
137     return {
138       "result": actual === expected,
139       "message": describe({
140         "Actual": actual,
141         "Expected": expected,
142       }),
143     };
144   };
146   predicates.toEqual = function(actual, expected) {
147     return {
148       "result": eq(actual, expected, [], []),
149       "message": describe({
150         "Actual": actual,
151         "Expected": expected,
152       }),
153     };
154   };
156   predicates.toBeDefined = function(actual) {
157     return {
158       "result": typeof actual !== "undefined",
159       "message": describe({
160         "Actual": actual,
161         "Description": "Expected a defined value",
162       }),
163     };
164   };
166   predicates.toBeUndefined = function(actual) {
167     // Recall: undefined is just a global variable. :)
168     return {
169       "result": typeof actual === "undefined",
170       "message": describe({
171         "Actual": actual,
172         "Description": "Expected an undefined value",
173       }),
174     };
175   };
177   predicates.toBeNull = function(actual) {
178     // Recall: typeof null === "object".
179     return {
180       "result": actual === null,
181       "message": describe({
182         "Actual": actual,
183         "Expected": null,
184       }),
185     };
186   };
188   predicates.toBeTruthy = function(actual) {
189     return {
190       "result": !!actual,
191       "message": describe({
192         "Actual": actual,
193         "Description": "Expected a truthy value",
194       }),
195     };
196   };
198   predicates.toBeFalsy = function(actual) {
199     return {
200       "result": !!!actual,
201       "message": describe({
202         "Actual": actual,
203         "Description": "Expected a falsy value",
204       }),
205     };
206   };
208   predicates.toContain = function(actual, element) {
209     return {
210       "result": (function () {
211         for (var i = 0; i < actual.length; ++i) {
212           if (eq(actual[i], element, [], []))
213             return true;
214         }
215         return false;
216       })(),
217       "message": describe({
218         "Actual": actual,
219         "Element": element,
220       }),
221     };
222   };
224   predicates.toBeLessThan = function(actual, reference) {
225     return {
226       "result": actual < reference,
227       "message": describe({
228         "Actual": actual,
229         "Reference": reference,
230       }),
231     };
232   };
234   predicates.toBeGreaterThan = function(actual, reference) {
235     return {
236       "result": actual > reference,
237       "message": describe({
238         "Actual": actual,
239         "Reference": reference,
240       }),
241     };
242   };
244   predicates.toThrow = function(actual) {
245     return {
246       "result": (function () {
247         if (!isFunction(actual))
248           throw new TypeError;
249         try {
250           actual();
251         } catch (ex) {
252           return true;
253         }
254         return false;
255       })(),
256       "message": "Expected function to throw",
257     };
258   }
260   function negate(predicate) {
261     return function() {
262       var outcome = predicate.apply(null, arguments);
263       outcome.result = !outcome.result;
264       return outcome;
265     }
266   }
268   function check(predicate) {
269     return function() {
270       var outcome = predicate.apply(null, arguments);
271       if (outcome.result)
272         return;
273       throw outcome.message;
274     };
275   }
277   function Condition(actual) {
278     this.not = {};
279     Object.getOwnPropertyNames(predicates).forEach(function(name) {
280       var bound = predicates[name].bind(null, actual);
281       this[name] = check(bound);
282       this.not[name] = check(negate(bound));
283     }, this);
284   }
286   return function(actual) {
287     return new Condition(actual);
288   };