Catrope has uploaded a new change for review.
https://gerrit.wikimedia.org/r/172939
Change subject: Add getProp(), setProp() and isInstanceOfAny()
......................................................................
Add getProp(), setProp() and isInstanceOfAny()
Moved from VisualEditor core.
Also copied get/setProp() tests, and added tests for
isInstanceOfAny() which VE didn't have.
Change-Id: If99eb7732e6d63c09f75d2207936a94ac98c539f
---
M src/core.js
M tests/unit/core.test.js
2 files changed, 234 insertions(+), 0 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/oojs/core refs/changes/39/172939/1
diff --git a/src/core.js b/src/core.js
index 1d3c9e4..bee01d1 100644
--- a/src/core.js
+++ b/src/core.js
@@ -145,6 +145,80 @@
/* Object Methods */
/**
+ * Checks if an object is an instance of one or more classes.
+ *
+ * @param {Object} subject Object to check
+ * @param {Function[]} classes Classes to compare with
+ * @returns {boolean} Object inherits from one or more of the classes
+ */
+oo.isInstanceOfAny = function ( subject, classes ) {
+ var i = classes.length;
+
+ while ( classes[--i] ) {
+ if ( subject instanceof classes[i] ) {
+ return true;
+ }
+ }
+ return false;
+};
+
+/**
+ * Get a deeply nested property of an object using variadic arguments,
protecting against
+ * undefined property errors.
+ *
+ * `quux = oo.getProp( obj, 'foo', 'bar', 'baz' );` is equivalent to `quux =
obj.foo.bar.baz;`
+ * except that the former protects against JS errors if one of the
intermediate properties
+ * is undefined. Instead of throwing an error, this function will return
undefined in
+ * that case.
+ *
+ * @param {Object} obj
+ * @param {Mixed...} [keys]
+ * @returns obj[arguments[1]][arguments[2]].... or undefined
+ */
+oo.getProp = function ( obj ) {
+ var i, retval = obj;
+ for ( i = 1; i < arguments.length; i++ ) {
+ if ( retval === undefined || retval === null ) {
+ // Trying to access a property of undefined or null
causes an error
+ return undefined;
+ }
+ retval = retval[arguments[i]];
+ }
+ return retval;
+};
+
+/**
+ * Set a deeply nested property of an object using variadic arguments,
protecting against
+ * undefined property errors.
+ *
+ * `oo.setProp( obj, 'foo', 'bar', 'baz' );` is equivalent to `obj.foo.bar =
baz;` except that
+ * the former protects against JS errors if one of the intermediate properties
is
+ * undefined. Instead of throwing an error, undefined intermediate properties
will be
+ * initialized to an empty object. If an intermediate property is null, or if
obj itself
+ * is undefined or null, this function will silently abort.
+ *
+ * @param {Object} obj
+ * @param {Mixed...} [keys]
+ * @param {Mixed} [value]
+ */
+oo.setProp = function ( obj ) {
+ var i, prop = obj;
+ if ( Object( obj ) !== obj ) {
+ return;
+ }
+ for ( i = 1; i < arguments.length - 2; i++ ) {
+ if ( prop[arguments[i]] === undefined ) {
+ prop[arguments[i]] = {};
+ }
+ if ( prop[arguments[i]] === null || typeof prop[arguments[i]]
!== 'object' ) {
+ return;
+ }
+ prop = prop[arguments[i]];
+ }
+ prop[arguments[arguments.length - 2]] = arguments[arguments.length - 1];
+};
+
+/**
* Create a new object that is an instance of the same
* constructor as the input, inherits from the same object
* and contains the same own properties.
diff --git a/tests/unit/core.test.js b/tests/unit/core.test.js
index 253a58e..61f5c6d 100644
--- a/tests/unit/core.test.js
+++ b/tests/unit/core.test.js
@@ -189,6 +189,166 @@
assert.strictEqual( quux.isBar(), 'method of Bar', 'method
works as expected' );
} );
+ QUnit.test( 'isInstanceOfAny', 7, function ( assert ) {
+ function Foo() {}
+ oo.initClass( Foo );
+
+ function Bar() {}
+ oo.initClass( Bar );
+
+ function SpecialFoo() {}
+ oo.inheritClass( SpecialFoo, Foo );
+
+ function VerySpecialFoo() {}
+ oo.inheritClass( VerySpecialFoo, SpecialFoo );
+
+ assert.strictEqual(
+ oo.isInstanceOfAny( new Foo(), [ Foo ] ),
+ true,
+ 'Foo is an instance of Foo'
+ );
+
+ assert.strictEqual(
+ oo.isInstanceOfAny( new SpecialFoo(), [ Foo ] ),
+ true,
+ 'SpecialFoo is an instance of Foo'
+ );
+
+ assert.strictEqual(
+ oo.isInstanceOfAny( new SpecialFoo(), [ Bar ] ),
+ false,
+ 'SpecialFoo is not an instance of Bar'
+ );
+
+ assert.strictEqual(
+ oo.isInstanceOfAny( new SpecialFoo(), [ Bar, Foo ] ),
+ true,
+ 'SpecialFoo is an instance of Bar or Foo'
+ );
+
+ assert.strictEqual(
+ oo.isInstanceOfAny( new VerySpecialFoo(), [ Bar, Foo ]
),
+ true,
+ 'VerySpecialFoo is an instance of Bar or Foo'
+ );
+
+ assert.strictEqual(
+ oo.isInstanceOfAny( new VerySpecialFoo(), [ Foo,
SpecialFoo ] ),
+ true,
+ 'VerySpecialFoo is an instance of Foo or SpecialFoo'
+ );
+
+ assert.strictEqual(
+ oo.isInstanceOfAny( new VerySpecialFoo(), [] ),
+ false,
+ 'VerySpecialFoo is not an instance of nothing'
+ );
+ } );
+
+ function testGetSetProp( type, obj ) {
+ QUnit.test( 'getProp( ' + type + ' )', 9, function ( assert ) {
+ assert.deepEqual(
+ oo.getProp( obj, 'foo' ),
+ 3,
+ 'single key'
+ );
+ assert.deepEqual(
+ oo.getProp( obj, 'bar' ),
+ { baz: null, quux: { whee: 'yay' } },
+ 'single key, returns object'
+ );
+ assert.deepEqual(
+ oo.getProp( obj, 'bar', 'baz' ),
+ null,
+ 'two keys, returns null'
+ );
+ assert.deepEqual(
+ oo.getProp( obj, 'bar', 'quux', 'whee' ),
+ 'yay',
+ 'three keys'
+ );
+ assert.deepEqual(
+ oo.getProp( obj, 'x' ),
+ undefined,
+ 'missing property returns undefined'
+ );
+ assert.deepEqual(
+ oo.getProp( obj, 'foo', 'bar' ),
+ undefined,
+ 'missing 2nd-level property returns undefined'
+ );
+ assert.deepEqual(
+ oo.getProp( obj, 'foo', 'bar', 'baz', 'quux',
'whee' ),
+ undefined,
+ 'multiple missing properties don\'t cause an
error'
+ );
+ assert.deepEqual(
+ oo.getProp( obj, 'bar', 'baz', 'quux' ),
+ undefined,
+ 'accessing property of null returns undefined,
doesn\'t cause an error'
+ );
+ assert.deepEqual(
+ oo.getProp( obj, 'bar', 'baz', 'quux', 'whee',
'yay' ),
+ undefined,
+ 'accessing multiple properties of null'
+ );
+ } );
+
+ QUnit.test( 'setProp( ' + type + ' )', 7, function ( assert ) {
+ oo.setProp( obj, 'foo', 4 );
+ assert.deepEqual( 4, obj.foo, 'setting an existing key
with depth 1' );
+
+ oo.setProp( obj, 'test', 'TEST' );
+ assert.deepEqual( 'TEST', obj.test, 'setting a new key
with depth 1' );
+
+ oo.setProp( obj, 'bar', 'quux', 'whee', 'YAY' );
+ assert.deepEqual( 'YAY', obj.bar.quux.whee, 'setting an
existing key with depth 3' );
+
+ oo.setProp( obj, 'bar', 'a', 'b', 'c' );
+ assert.deepEqual( 'c', obj.bar.a.b, 'setting two new
keys within an existing key' );
+
+ oo.setProp( obj, 'a', 'b', 'c', 'd', 'e', 'f' );
+ assert.deepEqual( 'f', obj.a.b.c.d.e, 'setting new keys
with depth 5' );
+
+ oo.setProp( obj, 'bar', 'baz', 'whee', 'wheee',
'wheeee' );
+ assert.deepEqual( null, obj.bar.baz, 'descending into
null fails silently' );
+
+ oo.setProp( obj, 'foo', 'bar', 'baz', 5 );
+ assert.deepEqual( undefined, obj.foo.bar, 'descending
into a non-object fails silently' );
+ } );
+ }
+
+ var plainObj, funcObj, arrObj;
+ plainObj = {
+ foo: 3,
+ bar: {
+ baz: null,
+ quux: {
+ whee: 'yay'
+ }
+ }
+ };
+ funcObj = function abc( d ) { return d; };
+ funcObj.foo = 3;
+ funcObj.bar = {
+ baz: null,
+ quux: {
+ whee: 'yay'
+ }
+ };
+ arrObj = [ 'a', 'b', 'c' ];
+ arrObj.foo = 3;
+ arrObj.bar = {
+ baz: null,
+ quux: {
+ whee: 'yay'
+ }
+ };
+
+ testGetSetProp( 'Object', plainObj );
+ testGetSetProp( 'Function', funcObj );
+ testGetSetProp( 'Array', arrObj );
+
QUnit.test( 'cloneObject', 4, function ( assert ) {
var myfoo, myfooClone, expected;
--
To view, visit https://gerrit.wikimedia.org/r/172939
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: If99eb7732e6d63c09f75d2207936a94ac98c539f
Gerrit-PatchSet: 1
Gerrit-Project: oojs/core
Gerrit-Branch: master
Gerrit-Owner: Catrope <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits