mediawiki-extensions-Visual.../modules/ve/test/ve.test.js
Timo Tijhof c8ed44fb07 Refactor ve.getHash: Stabilize cross-browser differences; + unit tests
* Replaces c8b4a28936

* Use Object() casting to detect objects instead of .constructor
  (or instanceof). Both .constructor and instanceof compare by reference
  the type "Object" which means if the object comes from another window
  (where there is a different "Object" and "Object.prototype") it will
  drop out of the system and go freewack.

  Theory: If a variable casted to an object returns true when strictly compared
  to the original, the input must be an object.

  Which is true. It doesn't change the inheritance, it doesn't make it inherit
  from this window's Object if the object is from another window's object. All it
  does is cast to an object if not an object already.
  So e.g. "Object(5) !== 5" because 5 is a primitive value as opposed to an instance
  of Number.
  And contrary to "typeof", it doesn't return true for "null".

* .constructor also has the problem that it only works this way if the
  input is a plain object. e.g. a simple construtor function that creates
  an object also get in the wrong side of the if/else case since it is
  an instance of Object, but not directly (rather indirectly via another
  constructor).

* Added unit tests for basic getHash usage, as well as regression tests
  against the above two mentioned problems (these tests fail before this commit).

* While at it, also improved other utilities a bit.
 - Use hasOwnProperty instead of casting to boolean
   when checking for presence of native support.
   Thanks to Douglas Crockford for that tip.
 - Fix documentation for ve.getHash: Parameter is not named "obj".
 - Add Object-check to ve.getObjectKeys per ES5 Object.keys spec (to match native behavior)
 - Add Object-check to ve.getObjectValues to match ve.getObjectKeys
 - Improved performance of ve.getObjectKeys shim. Tried several potential optimizations
   and compared with jsperf. Using a "static" reference to hasOwn improves performance
   (by not having to look it up 4 scopes up and 3 property levels deep).
   Also using [.length] instead of .push() shared off a few ms.
 - Added unit tests for ve.getObjectValues

Change-Id: If24d09405321f201c67f7df75d332bb1171c8a36
2012-08-27 00:14:02 +02:00

179 lines
3.4 KiB
JavaScript

/**
* VisualEditor Base method tests.
*
* @copyright 2011-2012 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
QUnit.module( 've' );
/* Tests */
QUnit.test( 'getHash: Basic usage', 5, function ( assert ) {
var tmp, hash, objects, example;
objects = {};
objects['a-z literal'] = {
a: 1,
b: 1,
c: 1
};
objects['z-a literal'] = {
c: 1,
b: 1,
a: 1
};
tmp = {};
objects['a-z augmented'] = tmp;
tmp.a = 1;
tmp.b = 1;
tmp.c = 1;
tmp = {};
objects['z-a augmented'] = tmp;
tmp.c = 1;
tmp.b = 1;
tmp.a = 1;
hash = '{"a":1,"b":1,"c":1}';
$.each( objects, function ( key, val ) {
assert.equal(
ve.getHash( val ),
hash,
'Similar enough objects have the same hash, regardless of "property order"'
);
});
// .. and that something completely different is in face different
// (just incase getHash is broken and always returns the same)
assert.notEqual(
ve.getHash( { a: 2, b: 2 } ),
hash,
'A different object has a different hash'
);
} );
QUnit.test( 'getHash: Complex usage', 4, function ( assert ) {
var obj, hash, frame;
obj = {
a: 1,
b: 1,
c: 1,
// Nested array
d: ['x', 'y', 'z'],
e: {
a: 2,
b: 2,
c: 2
}
};
assert.equal(
ve.getHash( obj ),
'{"a":1,"b":1,"c":1,"d":["x","y","z"],"e":{"a":2,"b":2,"c":2}}',
'Object with nested array and circular reference'
);
// Include a circular reference
obj.f = obj;
assert.throws( function () {
ve.getHash( obj );
}, 'Throw exceptions for objects with cirular refences ' );
function Foo() {
this.a = 1;
this.c = 3;
this.b = 2;
}
hash = '{"a":1,"b":2,"c":3}';
assert.equal(
ve.getHash( new Foo() ),
hash,
// This was previously broken when we used .constructor === Object
// ve.getHash.keySortReplacer, because although instances of Foo
// do inherit from Object (( new Foo() ) instanceof Object === true),
// direct comparison would return false.
'Treat objects constructed by a function as well'
);
frame = document.createElement( 'frame' );
frame.src = 'about:blank';
$( '#qunit-fixture' ).append( frame );
obj = new frame.contentWindow.Object();
obj.c = 3;
obj.b = 2;
obj.a = 1;
assert.equal(
ve.getHash( obj ),
hash,
// This was previously broken when we used comparison with "Object" in
// ve.getHash.keySortReplacer, because they are an instance of the other
// window's "Object".
'Treat objects constructed by a another window as well'
);
} );
QUnit.test( 'getObjectValues', 6, function ( assert ) {
var tmp;
assert.deepEqual(
ve.getObjectValues( { a: 1, b: 2, c: 3, foo: 'bar' } ),
[ 1, 2, 3, 'bar' ],
'Simple object with numbers and strings as values'
);
assert.deepEqual(
ve.getObjectValues( [ 1, 2, 3, 'bar' ] ),
[ 1, 2, 3, 'bar' ],
'Simple array with numbers and strings as values'
);
tmp = function () {
this.isTest = true;
return this;
};
tmp.a = 'foo';
tmp.b = 'bar';
assert.deepEqual(
ve.getObjectValues( tmp ),
['foo', 'bar'],
'Function with static members'
);
assert.throws(
function () {
ve.getObjectValues( 'hello' );
},
TypeError,
'Throw exception for non-object (string)'
);
assert.throws(
function () {
ve.getObjectValues( 123 );
},
TypeError,
'Throw exception for non-object (number)'
);
assert.throws(
function () {
ve.getObjectValues( null );
},
TypeError,
'Throw exception for non-object (null)'
);
} );