Bug Tracker

Changeset 5943 for trunk/qunit

Show
Ignore:
Timestamp:
11/14/08 03:02:35 (2 months ago)
Author:
prathe
Message:

qunit: same: equiv - completely refactored in the testrunner.

Files:
1 modified

Legend:

Unmodified
Added
Removed
  • trunk/qunit/testrunner.js

    r5920 r5943  
    1212 
    1313(function($) { 
     14 
     15// Tests for equality any JavaScript type and structure without unexpected results. 
     16// Discussions and reference: http://philrathe.com/articles/equiv 
     17// Test suites: http://philrathe.com/tests/equiv 
     18// Author: Philippe Rathé <prathe@gmail.com> 
     19var equiv = function () { 
     20 
     21    var innerEquiv; // the real equiv function 
     22    var callers = []; // stack to decide between skip/abort functions 
     23 
     24    // Determine what is o. 
     25    function hoozit(o) { 
     26        if (typeof o === "string") { 
     27            return "string"; 
     28 
     29        } else if (typeof o === "boolean") { 
     30            return "boolean"; 
     31 
     32        } else if (typeof o === "number") { 
     33 
     34            if (isNaN(o)) { 
     35                return "nan"; 
     36            } else { 
     37                return "number"; 
     38            } 
     39 
     40        } else if (typeof o === "undefined") { 
     41            return "undefined"; 
     42 
     43        // consider: typeof null === object 
     44        } else if (o === null) { 
     45            return "null"; 
     46 
     47        // consider: typeof [] === object 
     48        } else if (o instanceof Array) { 
     49            return "array"; 
     50         
     51        // consider: typeof new Date() === object 
     52        } else if (o instanceof Date) { 
     53            return "date"; 
     54 
     55        // consider: /./ instanceof Object; 
     56        //           /./ instanceof RegExp; 
     57        //          typeof /./ === "function"; // => false in IE and Opera, 
     58        //                                          true in FF and Safari 
     59        } else if (o instanceof RegExp) { 
     60            return "regexp"; 
     61 
     62        } else if (typeof o === "object") { 
     63            return "object"; 
     64 
     65        } else if (o instanceof Function) { 
     66            return "function"; 
     67        } 
     68    } 
     69 
     70    // Call the o related callback with the given arguments. 
     71    function bindCallbacks(o, callbacks, args) { 
     72        var prop = hoozit(o); 
     73        if (prop) { 
     74            if (hoozit(callbacks[prop]) === "function") { 
     75                return callbacks[prop].apply(callbacks, args); 
     76            } else { 
     77                return callbacks[prop]; // or undefined 
     78            } 
     79        } 
     80    } 
     81 
     82    var callbacks = function () { 
     83 
     84        // for string, boolean, number and null 
     85        function useStrictEquality(b, a) { 
     86            return a === b; 
     87        } 
     88 
     89        return { 
     90            "string": useStrictEquality, 
     91            "boolean": useStrictEquality, 
     92            "number": useStrictEquality, 
     93            "null": useStrictEquality, 
     94            "undefined": useStrictEquality, 
     95 
     96            "nan": function (b) { 
     97                return isNaN(b); 
     98            }, 
     99 
     100            "date": function (b, a) { 
     101                return hoozit(b) === "date" && a.valueOf() === b.valueOf(); 
     102            }, 
     103 
     104            "regexp": function (b, a) { 
     105                return hoozit(b) === "regexp" && 
     106                    a.source === b.source && // the regex itself 
     107                    a.global === b.global && // and its modifers (gmi) ... 
     108                    a.ignoreCase === b.ignoreCase && 
     109                    a.multiline === b.multiline; 
     110            }, 
     111 
     112            // - skip when the property is a method of an instance (OOP) 
     113            // - abort otherwise, 
     114            //   initial === would have catch identical references anyway 
     115            "function": function () { 
     116                var caller = callers[callers.length - 1]; 
     117                return caller !== Object && 
     118                        typeof caller !== "undefined"; 
     119            }, 
     120 
     121            "array": function (b, a) { 
     122                var i; 
     123                var len; 
     124 
     125                // b could be an object literal here 
     126                if ( ! (hoozit(b) === "array")) { 
     127                    return false; 
     128                } 
     129 
     130                len = a.length; 
     131                if (len !== b.length) { // safe and faster 
     132                    return false; 
     133                } 
     134                for (i = 0; i < len; i++) { 
     135                    if( ! innerEquiv(a[i], b[i])) { 
     136                        return false; 
     137                    } 
     138                } 
     139                return true; 
     140            }, 
     141 
     142            "object": function (b, a) { 
     143                var i; 
     144                var eq = true; // unless we can proove it 
     145                var aProperties = [], bProperties = []; // collection of strings 
     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 { 
     190                return bindCallbacks(a, callbacks, [b, a]); 
     191            } 
     192 
     193        // apply transition with (1..n) arguments 
     194        })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1)); 
     195    }; 
     196 
     197    return innerEquiv; 
     198}(); // equiv 
    14199     
    15200var config = { 
     
    385570} 
    386571 
    387 // Test for equality any JavaScript type. 
    388 // Discussions and reference: http://philrathe.com/articles/equiv 
    389 // Test suites: http://philrathe.com/tests/equiv 
    390 // Author: Philippe Rathé <prathe@gmail.com> 
    391  
    392 // About passing arguments: 
    393 //      when < 2   : return true 
    394 //      when 2     : return true if 1st equals 2nd 
    395 //      when > 2   : return true 1st equals 2nd, and 2nd equals 3rd, 
    396 //                      and 3rd equals 4th ... ans so on (by transition) 
    397 // 
    398  
    399 // Stack of constructors that enables us to skip or abort on functions value. 
    400 var objectCallerConstructorStack = []; 
    401 function equiv() { 
    402     // we will need to use the array splice method, 
    403     // so we need to convert arguments into a true array. 
    404     var args = Array.prototype.slice.apply(arguments); 
    405     var a, b; // compares a and b (1st and 2nd argument) 
    406     var i; // for iterating over objects convenience 
    407     var len; // for iterating array's length memoization 
    408     // Memoized objects' properties. 
    409     // Allow us to test for equivalence only in one way, 
    410     // then to compare properties to make sure no one is missing. 
    411     var aProperties, bProperties; 
    412  
    413  
    414     if (args.length < 2) { 
    415         return true; // nothing to compare together 
    416     } 
    417  
    418     a = args[0]; 
    419     b = args[1]; 
    420  
    421     return (function (a, b) { 
    422  
    423         // Try to optimize the algo speed if ever both a and b are references 
    424         // pointing to the same object (e..g. only for functions, arrays and objects) 
    425         if (a === b) { 
    426             return true; 
    427         } 
    428          
    429         // Must test for the null value before testing for the type of an object, 
    430         // because null is also an object. Trapping null or undefined values here 
    431         // is a good to avoid to much validations further in the code. 
    432         if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined") { 
    433             return false; // Anyway (a === b) would have already caught it. 
    434         } 
    435  
    436         // Don't lose time and prevent further wrong type manipulation or unexpected results. 
    437         if (typeof a !== typeof b) { 
    438             return false; 
    439         } 
    440  
    441         // Must test if it's an array before testing for the type of an object, 
    442         // because an array is also an object. 
    443         if (a instanceof Array) { 
    444  
    445             // Make sure b is also an array. 
    446             // At this point b can be an object. 
    447             // Prevent further arrays operation on object. 
    448             if ( ! (b instanceof Array)) { 
    449                 return false; 
    450             } 
    451  
    452             len = a.length; 
    453             if (len !== b.length) { // safe and faster 
    454                 return false; 
    455             } 
    456             for (i = 0; i < len; i++) { 
    457                 if( ! equiv(a[i], b[i])) { 
    458                     return false; 
    459                 } 
    460             } 
    461             return true; 
    462         } 
    463          
    464         // NOTE: 
    465         //      Must test if it's a date before testing if it is an object, 
    466         //      because a date is also an object. 
    467         //      We don't need to check if b is also an instance of Date because 
    468         //      the "different type check" sooner in the code would have caught it and 
    469         //      return false by evaluating ("object" !== "number") (or conversely) to true. 
    470         if (a instanceof Date) { 
    471             return a.valueOf() === b.valueOf(); 
    472         } 
    473  
    474         // NOTE: 
    475         //      Using toSource() to compare regexp only works in FF. It was working as well as the above. 
    476         //      Must verify the constructor of a regexp before verifying it is an object. 
    477         //      Notice those confusing true statements: 
    478         //          var a = /./; 
    479         //          a instanceof Object; // => true 
    480         //          a instanceof RegExp; // => true 
    481         //          typeof a === "function"; // => false in IE and Opera, true in FF and Safari 
    482         // 
    483         // There is 3 possible modifier for regular expressions (g, m and i) 
    484         // The source property only returns the regular expression string without the modifier. 
    485         if (a instanceof RegExp) { 
    486             return b instanceof RegExp && 
    487                 a.source === b.source && 
    488                 a.global === b.global && 
    489                 a.ignoreCase === b.ignoreCase && 
    490                 a.multiline === b.multiline; 
    491         } 
    492  
    493         // typeof on some types returns sometimes unexpected type that we deals with. 
    494         // We could have used instanceof instead of typeof but it comes with 
    495         // other problems as mentioned above when testing for the RegExp constructor. 
    496         // We explicitely don't use hasOwnProperty when iterating on the properties of 
    497         // an object to allow a deeper equivalence (e.g. for instances particularly) 
    498         if (typeof a === "object") { 
    499             // Different constructors means a and b can't be equivalent instances. 
    500             // We don't have any choice of comparing them in both ways because of inheritance 
    501             // otherwise {} could be mistaken with [] 
    502             // NOTE this: 
    503             //      var a = []; 
    504             //      a instanceof Array;  // true 
    505             //      a instanceof Object; // true 
    506             if ( a.constructor !== b.constructor) { 
    507                 return false; 
    508             } 
    509  
    510             // Stack constructors before iterating over its properties 
    511             objectCallerConstructorStack.push(a.constructor); 
    512  
    513             aProperties = []; 
    514             bProperties = []; 
    515  
    516             // NOTE: 
    517             //      We only need to compare propertie's equivalence in one way 
    518             //      and ensures that all properties of a and b are the same. 
    519  
    520             // Verify a's properties with b's properties equivalence. 
    521             // Stack a's properties 
    522              
    523             for (i in a) { 
    524                 aProperties.push(i); 
    525                 if (!equiv(a[i], b[i])) { 
    526                     // Unstack current constructor when finished with the object 
    527                     objectCallerConstructorStack.pop(); 
    528                     return false; 
    529                 } 
    530             } 
    531  
    532             // Stack also b's properties 
    533             for (i in b) { 
    534                 bProperties.push(i); 
    535             } 
    536  
    537             // Unstack current constructor when finished with the object 
    538             objectCallerConstructorStack.pop(); 
    539  
    540             // Ensures properties's name in both ways. 
    541             // Must sort them because they may not be always in the same order! 
    542             return equiv(aProperties.sort(), bProperties.sort()); 
    543         } 
    544  
    545         // NOTE: 
    546         //      Using the typeof operator will also catch a RegExp instance. 
    547         //      Take no risk. 
    548         if (a instanceof Function) { 
    549             // BEHAVIOR when comparing functions. 
    550             //      Being here means that a and b were were not the same references. 
    551             //      At this level, functions won't be compared at all. 
    552             //      - skip when they are properties of instances (not instantiates from the Object construtor) 
    553             //      - abort otherwise 
    554             // 
    555             //      To Determine if a function is anonymous we use a regexp on the function sources. 
    556             var currentObjectCallerConstructor = 
    557                     objectCallerConstructorStack[objectCallerConstructorStack.length - 1]; 
    558             return currentObjectCallerConstructor !== Object && 
    559                     typeof currentObjectCallerConstructor !== "undefined"; 
    560         } 
    561  
    562         // NaN is a number in JavaScript. 
    563         // Because this statement is false: 0/0 === 0/0, 
    564         // we must use the isNaN function which is the only way to know a number is NaN. 
    565         // Note that it isn't the case with the Infinity number, and that 1/0 === 2/0. 
    566         if (typeof a === "number" && isNaN(a)) { 
    567             return isNaN(b); 
    568         } 
    569  
    570         // Compares Number, String or Boolean 
    571         // Because sooner we have already tried: 
    572         //          a === b (then return if it is the case) 
    573         //            and 
    574         //  typeof a !== typeof b (then return false if it is the case) 
    575         // at this time a and b should be of the same type, but hold different values. 
    576         // We can safely return false instead of returning a === b 
    577         return false; 
    578  
    579     }(a, b)) && equiv.apply(this, args.splice(1, args.length -1)); // apply transition with (1..n) arguments 
    580 } 
    581  
    582572})(jQuery); 
    583573