From 1bba35418ac7a4e14f134eb858945f8540e9f133 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Sat, 7 Jun 2014 21:42:22 -0400 Subject: [PATCH] Added Global prototype This will clean up the code for providing global check and alternatives. --- lib/util.js | 3 + lib/util/Global.js | 120 +++++++++++++++++++++++++++++++++ test/Util/GlobalTest.js | 143 ++++++++++++++++++++++++++++++++++++++++ test/Util/IndexTest.js | 41 ++++++++++++ 4 files changed, 307 insertions(+) create mode 100644 lib/util/Global.js create mode 100644 test/Util/GlobalTest.js create mode 100644 test/Util/IndexTest.js diff --git a/lib/util.js b/lib/util.js index 5627118..de59fb5 100644 --- a/lib/util.js +++ b/lib/util.js @@ -52,6 +52,9 @@ var can_define_prop = ( function() } )(); +exports.Global = require( './util/Global' ); + + /** * Freezes an object if freezing is supported * diff --git a/lib/util/Global.js b/lib/util/Global.js new file mode 100644 index 0000000..597d47e --- /dev/null +++ b/lib/util/Global.js @@ -0,0 +1,120 @@ +/** + * Global scope handling + * + * Copyright (C) 2014 Free Software Foundation, Inc. + * + * This file is part of GNU ease.js. + * + * ease.js is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +// retrieve global scope; works with ES5 strict mode +(0,eval)( 'var _the_global=this' ); + +// prototype to allow us to augment the global scope for our own purposes +// without polluting the global scope +function _G() {} +_G.prototype = _the_global; + + +/** + * Provides access to and augmentation of global variables + * + * This provides a static method to consistently provide access to the + * object representing the global scope, regardless of environment. Through + * instantiation, its API permits augmenting a local object whose prototype + * is the global scope, providing alternatives to variables that do not + * exist. + */ +function Global() +{ + // allows omitting `new` keyword, consistent with ease.js style + if ( !( this instanceof Global ) ) + { + return new Global(); + } + + // allows us to extend the global object without actually polluting the + // global scope + this._global = new _G(); +} + + +/** + * Provides consistent access to the global scope through all ECMAScript + * versions, for any root variable name, and works with ES5 strict mode. + * + * As an example, Node.js exposes the variable `root` to represent global + * scope, but browsers expose `window`. Further, ES5 strict mode will provde + * an error when checking whether `typeof SomeGlobalVar === 'undefined'`. + * + * @return {Object} global object + */ +Global.expose = function() +{ + return _the_global; +}; + + +Global.prototype = { + /** + * Provide a value for the provided global variable name if it is not + * defined + * + * A function returning the value to assign to NAME should be provided, + * ensuring that the alternative is never even evaluated unless it is + * needed. + * + * The global scope will not be polluted with this alternative; + * consequently, you must access the value using the `get` method. + * + * @param {string} name global variable name + * @param {function()} f function returning value to assign + * + * @return {Global} self + */ + provideAlt: function( name, f ) + { + if ( typeof this._global[ name ] !== 'undefined' ) + { + return; + } + + this._global[ name ] = f(); + return this; + }, + + + /** + * Retrieve global value or provided alternative + * + * This will take into account values provided via `provideAlt`; if no + * alternative was provided, the request will be deleagated to the + * global variable NAME, which may or may not be undefined. + * + * No error will be thrown if NAME is not globally defined. + * + * @param {string} name global variable name + * + * @return {*} value associated with global variable NAME or + * its provided alternative + */ + get: function( name ) + { + return this._global[ name ]; + }, +}; + +module.exports = Global; + diff --git a/test/Util/GlobalTest.js b/test/Util/GlobalTest.js new file mode 100644 index 0000000..1b350ef --- /dev/null +++ b/test/Util/GlobalTest.js @@ -0,0 +1,143 @@ +/** + * Tests global scope handling + * + * Copyright (C) 2014 Free Software Foundation, Inc. + * + * This file is part of GNU ease.js. + * + * ease.js is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +var _global = this; + +require( 'common' ).testCase( +{ + caseSetUp: function() + { + this.Sut = this.require( 'util/Global' ); + this.gobj = this.Sut.expose(); + this.uniq = '___$$easejs$globaltest$$'; + }, + + + /** + * Check common environments and ensure that the returned object is + * strictly equal to the global object for that environment. For + * environments that we do *not* know about, just check for a common + * object that must exist in ES3 and above. + */ + 'Global object represents environment global object': function() + { + switch ( true ) + { + // browser + case _global.window: + this.assertStrictEqual( this.gobj, _global.window ); + break; + + // Node.js + case _global.root: + this.assertStrictEqual( this.gobj, _global.root ); + break; + + // something else; we'll just check for something that should + // exist in >=ES3 + default: + this.assertStrictEqual( this.gobj.Array, Array ); + } + }, + + + /** + * Since ease.js makes use of ECMAScript features when they are + * available, it must also find a way to gracefully degrade to support + * less fortunate environments; the ability to define alternative + * definitions is key to that. + */ + 'Providing alternative will set value if name does not exist': + function() + { + var sut = this.Sut(); + + var field = this.uniq, + value = { _: 'easejsOK' }; + + sut.provideAlt( field, function() { return value; } ); + this.assertStrictEqual( sut.get( field ), value ); + }, + + + /** + * It is also important that our own definitions do not pollute the + * global scope; reasons for this are not just to be polite, but also + * because other code/libraries may provide their own definitions that + * we would not want to interfere with. (Indeed, we'd also want to use + * those definitions, if they already exist before provideAlt is + * called.) + */ + 'Providing alternative will not pollute the global scope': function() + { + this.Sut().provideAlt( this.uniq, function() { return {} } ); + this.assertEqual( this.gobj[ this.uniq ], undefined ); + }, + + + /** + * Our alternatives are unneeded if the object we are providing an + * alternative for is already defined. + */ + 'Providing alternative will not modify global if name exists': + function() + { + var sut = this.Sut(); + + // a field that must exist in ES3+ + var field = 'Array', + orig = this.gobj[ field ]; + + sut.provideAlt( field, function() { return {}; } ); + this.assertStrictEqual( sut.get( field ), orig ); + }, + + + /** + * Once an alternative is defined, it shall be treated as though the + * value were defined globally; providing additional alternatives should + * therefore have no effect. + */ + 'Providing alternative twice will not modify first alternative': + function() + { + var sut = this.Sut(); + field = this.uniq, + expected = { _: 'easejsOK' }; + + // first should provide alternative, second should do nothing + sut.provideAlt( field, function() { return expected; } ); + sut.provideAlt( field, function() { return 'oops'; } ); + + this.assertStrictEqual( sut.get( field ), expected ); + }, + + + 'provideAlt returns self for method chaining': function() + { + var sut = this.Sut(); + + this.assertStrictEqual( sut, + sut.provideAlt( 'foo', function() {} ) + ); + }, +} ); + diff --git a/test/Util/IndexTest.js b/test/Util/IndexTest.js new file mode 100644 index 0000000..d719a56 --- /dev/null +++ b/test/Util/IndexTest.js @@ -0,0 +1,41 @@ +/** + * Tests utility module entry point + * + * Copyright (C) 2014 Free Software Foundation, Inc. + * + * This file is part of GNU ease.js. + * + * ease.js is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * N.B. Despite this saying that it tests the index (i.e. entry point), this + * is not yet the case; it will be in the future, though. + */ + +require( 'common' ).testCase( +{ + caseSetUp: function() + { + this.Sut = this.require( 'util' ); + }, + + + 'Exposes Global prototype': function() + { + this.assertStrictEqual( + this.Sut.Global, + this.require( 'util/Global' ) + ); + }, +} ); +