diff --git a/modules/ve/test/ve.test.js b/modules/ve/test/ve.test.js index b7668f11a3..e9b06f5422 100644 --- a/modules/ve/test/ve.test.js +++ b/modules/ve/test/ve.test.js @@ -429,3 +429,60 @@ QUnit.test( 'getOpeningHtmlTag', 5, function ( assert ) { 'escaping of attribute values' ); } ); + +QUnit.test( 'getProp', 9, function ( assert ) { + var obj = { + 'foo': 3, + 'bar': { + 'baz': null, + 'quux': { + 'whee': 'yay' + } + } + }; + assert.deepEqual( + ve.getProp( obj, 'foo' ), + 3, + 'single key' + ); + assert.deepEqual( + ve.getProp( obj, 'bar' ), + { 'baz': null, 'quux': { 'whee': 'yay' } }, + 'singe key, returns object' + ); + assert.deepEqual( + ve.getProp( obj, 'bar', 'baz' ), + null, + 'two keys, returns null' + ); + assert.deepEqual( + ve.getProp( obj, 'bar', 'quux', 'whee' ), + 'yay', + 'three keys' + ); + assert.deepEqual( + ve.getProp( obj, 'x' ), + undefined, + 'missing property returns undefined' + ); + assert.deepEqual( + ve.getProp( obj, 'foo', 'bar' ), + undefined, + 'missing 2nd-level property returns undefined' + ); + assert.deepEqual( + ve.getProp( obj, 'foo', 'bar', 'baz', 'quux', 'whee' ), + undefined, + 'multiple missing properties don\'t cause an error' + ); + assert.deepEqual( + ve.getProp( obj, 'bar', 'baz', 'quux' ), + undefined, + 'accessing property of null returns undefined, doesn\'t cause an error' + ); + assert.deepEqual( + ve.getProp( obj, 'bar', 'baz', 'quux', 'whee', 'yay' ), + undefined, + 'accessing multiple properties of null' + ); +} ); diff --git a/modules/ve/ve.js b/modules/ve/ve.js index 02a85dd44d..40fe28fdb1 100644 --- a/modules/ve/ve.js +++ b/modules/ve/ve.js @@ -551,6 +551,30 @@ ve.batchSplice( dst, offset, 0, src ); }; + /** + * Get a deeply nested property of an object using variadic arguments, protecting against + * undefined property errors. + * + * quux = 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 + * @returns obj[arguments[1]][arguments[2]].... or undefined + */ + ve.getProp = function ( obj /*, keys ... */ ) { + var 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; + }; + /** * Logs data to the console. *