1 // ==========================================================================
  2 // Project:   SproutCore Costello - Property Observing Library
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 /*globals CoreTest */
  9 
 10 /**
 11   Tests for equality any JavaScript type and structure without unexpected 
 12   results.
 13 
 14   Discussions and reference: http://philrathe.com/articles/equiv
 15   Test suites: http://philrathe.com/tests/equiv
 16   Author: Philippe Rathé <prathe@gmail.com>
 17 */
 18 CoreTest.equiv = function () {
 19 
 20     var innerEquiv; // the real equiv function
 21     var callers = []; // stack to decide between skip/abort functions
 22 
 23     // Determine what is o.
 24     function hoozit(o) {
 25         if (typeof o === "string") {
 26             return "string";
 27 
 28         } else if (typeof o === "boolean") {
 29             return "boolean";
 30 
 31         } else if (typeof o === "number") {
 32 
 33             if (isNaN(o)) {
 34                 return "nan";
 35             } else {
 36                 return "number";
 37             }
 38 
 39         } else if (typeof o === "undefined") {
 40             return "undefined";
 41 
 42         // consider: typeof null === object
 43         } else if (o === null) {
 44             return "null";
 45 
 46         // consider: typeof [] === object
 47         } else if (o instanceof Array) {
 48             return "array";
 49         
 50         // consider: typeof new Date() === object
 51         } else if (o instanceof Date) {
 52             return "date";
 53 
 54         // consider: /./ instanceof Object;
 55         //           /./ instanceof RegExp;
 56         //          typeof /./ === "function"; // => false in IE and Opera,
 57         //                                          true in FF and Safari
 58         } else if (o instanceof RegExp) {
 59             return "regexp";
 60 
 61         } else if (typeof o === "object") {
 62             return "object";
 63 
 64         } else if (o instanceof Function) {
 65             return "function";
 66         }
 67     }
 68 
 69     // Call the o related callback with the given arguments.
 70     function bindCallbacks(o, callbacks, args) {
 71         var prop = hoozit(o);
 72         if (prop) {
 73             if (hoozit(callbacks[prop]) === "function") {
 74                 return callbacks[prop].apply(callbacks, args);
 75             } else {
 76                 return callbacks[prop]; // or undefined
 77             }
 78         }
 79     }
 80 
 81     var callbacks = function () {
 82 
 83         // for string, boolean, number and null
 84         function useStrictEquality(b, a) {
 85             return a === b;
 86         }
 87 
 88         return {
 89             "string": useStrictEquality,
 90             "boolean": useStrictEquality,
 91             "number": useStrictEquality,
 92             "null": useStrictEquality,
 93             "undefined": useStrictEquality,
 94 
 95             "nan": function (b) {
 96                 return isNaN(b);
 97             },
 98 
 99             "date": function (b, a) {
100                 return hoozit(b) === "date" && a.valueOf() === b.valueOf();
101             },
102 
103             "regexp": function (b, a) {
104                 return hoozit(b) === "regexp" &&
105                     a.source === b.source && // the regex itself
106                     a.global === b.global && // and its modifiers (gmi) ...
107                     a.ignoreCase === b.ignoreCase &&
108                     a.multiline === b.multiline;
109             },
110 
111             // - skip when the property is a method of an instance (OOP)
112             // - abort otherwise,
113             //   initial === would have catch identical references anyway
114             "function": function () {
115                 var caller = callers[callers.length - 1];
116                 return caller !== Object &&
117                         typeof caller !== "undefined";
118             },
119 
120             "array": function (b, a) {
121                 var i;
122                 var len;
123 
124                 // b could be an object literal here
125                 if ( ! (hoozit(b) === "array")) {
126                     return false;
127                 }
128 
129                 len = a.length;
130                 if (len !== b.length) { // safe and faster
131                     return false;
132                 }
133                 for (i = 0; i < len; i++) {
134                     if( ! innerEquiv(a[i], b[i])) {
135                         return false;
136                     }
137                 }
138                 return true;
139             },
140 
141             "object": function (b, a) {
142                 var i;
143                 var eq = true; // unless we can proove it
144                 var aProperties = [], bProperties = []; // collection of strings
145                 if (b===a) return true;
146                 
147                 // comparing constructors is more strict than using instanceof
148                 if ( a.constructor !== b.constructor) {
149                     return false;
150                 }
151 
152                 // stack constructor before traversing properties
153                 callers.push(a.constructor);
154 
155                 for (i in a) { // be strict: don't ensures hasOwnProperty and go deep
156 
157                     aProperties.push(i); // collect a's properties
158 
159                     if ( ! innerEquiv(a[i], b[i])) {
160                         eq = false;
161                     }
162                 }
163 
164                 callers.pop(); // unstack, we are done
165 
166                 for (i in b) {
167                     bProperties.push(i); // collect b's properties
168                 }
169 
170                 // Ensures identical properties name
171                 return eq && innerEquiv(aProperties.sort(), bProperties.sort());
172             }
173         };
174     }();
175 
176     innerEquiv = function () { // can take multiple arguments
177         var args = Array.prototype.slice.apply(arguments);
178         if (args.length < 2) {
179             return true; // end transition
180         }
181 
182         return (function (a, b) {
183             if (a === b) {
184                 return true; // catch the most you can
185 
186             } else if (typeof a !== typeof b || a === null || b === null || typeof a === "undefined" || typeof b === "undefined") {
187                 return false; // don't lose time with error prone cases
188 
189             } else if (b && b.isEqual && b.isEqual instanceof Function) {
190               return b.isEqual(a);
191               
192             } else {
193                 return bindCallbacks(a, callbacks, [b, a]);
194             }
195 
196         // apply transition with (1..n) arguments
197         })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1));
198     };
199 
200     return innerEquiv;
201 }(); // equiv
202