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