diff options
Diffstat (limited to 'contexts/data/lib/closure-library/closure/goog/labs')
50 files changed, 6462 insertions, 1429 deletions
diff --git a/contexts/data/lib/closure-library/closure/goog/labs/.svn/all-wcprops b/contexts/data/lib/closure-library/closure/goog/labs/.svn/all-wcprops deleted file mode 100644 index 9418569..0000000 --- a/contexts/data/lib/closure-library/closure/goog/labs/.svn/all-wcprops +++ /dev/null @@ -1,5 +0,0 @@ -K 25 -svn:wc:ra_dav:version-url -V 42 -/svn/!svn/ver/1453/trunk/closure/goog/labs -END diff --git a/contexts/data/lib/closure-library/closure/goog/labs/.svn/entries b/contexts/data/lib/closure-library/closure/goog/labs/.svn/entries deleted file mode 100644 index 3935db5..0000000 --- a/contexts/data/lib/closure-library/closure/goog/labs/.svn/entries +++ /dev/null @@ -1,31 +0,0 @@ -10 - -dir -1494 -http://closure-library.googlecode.com/svn/trunk/closure/goog/labs -http://closure-library.googlecode.com/svn - - - -2011-12-07T18:41:01.000000Z -1453 -pupius@google.com - - - - - - - - - - - - - - -0b95b8e8-c90f-11de-9d4f-f947ee5921c8 - -net -dir - diff --git a/contexts/data/lib/closure-library/closure/goog/labs/mock/mock.js b/contexts/data/lib/closure-library/closure/goog/labs/mock/mock.js new file mode 100644 index 0000000..fa6b86c --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/mock/mock.js @@ -0,0 +1,350 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Provides a mocking framework in Closure to make unit tests easy + * to write and understand. The methods provided here can be used to replace + * implementations of existing objects with 'mock' objects to abstract out + * external services and dependencies thereby isolating the code under test. + * Apart from mocking, methods are also provided to just monitor calls to an + * object (spying) and returning specific values for some or all the inputs to + * methods (stubbing). + * + */ + + +goog.provide('goog.labs.mock'); + +goog.require('goog.array'); +goog.require('goog.debug.Error'); +goog.require('goog.functions'); + + +/** + * Mocks a given object or class. + * + * @param {!Object} objectOrClass An instance or a constructor of a class to be + * mocked. + * + * @return {!Object} The mocked object. + */ +goog.labs.mock = function(objectOrClass) { + // Go over properties of 'objectOrClass' and create a MockManager to + // be used for stubbing out calls to methods. + var mockedObject = new goog.labs.mock.MockManager_(objectOrClass); + return mockedObject.getMockedObject(); +}; + + +/** + * This array contains the name of the functions that are part of the base + * Object prototype. + * Basically a copy of goog.object.PROTOTYPE_FIELDS_. + * @const + * @type {!Array.<string>} + * @private + */ +goog.labs.mock.PROTOTYPE_FIELDS_ = [ + 'constructor', + 'hasOwnProperty', + 'isPrototypeOf', + 'propertyIsEnumerable', + 'toLocaleString', + 'toString', + 'valueOf' +]; + + + +/** + * Sets up mock for the given object (or class), stubbing out all the defined + * methods. By default, all stubs return {@code undefined}, though stubs can be + * later defined using {@code goog.labs.mock.when}. + * + * @param {!Object|!Function} objOrClass The object to set up the mock for. + * + * @constructor + * @private + */ +goog.labs.mock.MockManager_ = function(objOrClass) { + /** + * Proxies the methods for the mocked object or class to execute the stubs. + * @type {!Object} + * @private + * TODO(user): make instanceof work. + */ + this.mockedObject_ = {}; + + /** + * Holds the stub bindings established so far. + * @private + */ + this.methodBindings_ = []; + + /** + * Proxies the calls to establish the first step of the stub bindings (object + * and method name) + * @private + */ + this.binderProxy_ = {}; + + var obj; + if (goog.isFunction(objOrClass)) { + // Create a temporary subclass with a no-op constructor so that we can + // create an instance and determine what methods it has. + /** @constructor */ + function tempCtor() {}; + goog.inherits(tempCtor, objOrClass); + obj = new tempCtor(); + } else { + obj = objOrClass; + } + + var enumerableProperties = goog.object.getKeys(obj); + // The non enumerable properties are added due to the fact that IE8 does not + // enumerate any of the prototype Object functions even when overriden and + // mocking these is sometimes needed. + for (var i = 0; i < goog.labs.mock.PROTOTYPE_FIELDS_.length; i++) { + var prop = goog.labs.mock.PROTOTYPE_FIELDS_[i]; + if (!goog.array.contains(enumerableProperties, prop)) { + enumerableProperties.push(prop); + } + } + + // Adds the properties to the mock, creating a proxy stub for each method on + // the instance. + for (var i = 0; i < enumerableProperties.length; i++) { + var prop = enumerableProperties[i]; + if (goog.isFunction(obj[prop])) { + this.mockedObject_[prop] = goog.bind(this.executeStub_, this, prop); + this.binderProxy_[prop] = goog.bind(this.handleMockCall_, this, prop); + } + } + + this.mockedObject_.$binderProxy = this.binderProxy_; +}; + + +/** + * Handles the first step in creating a stub, returning a stub-binder that + * is later used to bind a stub for a method. + * + * @param {string} methodName The name of the method being bound. + * @param {...} var_args The arguments to the method. + * + * @return {!goog.labs.mock.StubBinder_} The stub binder. + * @private + */ +goog.labs.mock.MockManager_.prototype.handleMockCall_ = + function(methodName, var_args) { + var args = goog.array.slice(arguments, 1); + return new goog.labs.mock.StubBinder_(this, methodName, args); +}; + + +/** + * Returns the mock object. This should have a stubbed method for each method + * on the object being mocked. + * + * @return {!Object} The mock object. + */ +goog.labs.mock.MockManager_.prototype.getMockedObject = function() { + return this.mockedObject_; +}; + + +/** + * Adds a binding for the method name and arguments to be stubbed. + * + * @param {string} methodName The name of the stubbed method. + * @param {!Array} args The arguments passed to the method. + * @param {!Function} func The stub function. + * + */ +goog.labs.mock.MockManager_.prototype.addBinding = + function(methodName, args, func) { + var binding = new goog.labs.mock.MethodBinding_(methodName, args, func); + this.methodBindings_.push(binding); +}; + + +/** + * Returns a stub, if defined, for the method and arguments passed in as + * parameters. + * + * @param {string} methodName The name of the stubbed method. + * @param {Array} args The arguments passed to the method. + * + * @return {!Function|undefined} The stub function or undefined. + * @private + */ +goog.labs.mock.MockManager_.prototype.findBinding_ = + function(methodName, args) { + var stub = goog.array.find(this.methodBindings_, function(binding) { + return binding.matches(methodName, args); + }); + return stub && stub.getStub(); +}; + + +/** + * Looks up the list of stubs defined on the mock object and executes the + * function associated with that stub. + * + * @param {string} methodName The name of the method to execute. + * @param {...} var_args The arguments passed to the method. + * + * @return {*} Value returned by the stub function. + * @private + */ +goog.labs.mock.MockManager_.prototype.executeStub_ = + function(methodName, var_args) { + var args = goog.array.slice(arguments, 1); + var func = this.findBinding_(methodName, args); + if (func) { + return func.apply(null, args); + } +}; + + + +/** + * The stub binder is the object that helps define the stubs by binding + * method name to the stub method. + * + * @param {!goog.labs.mock.MockManager_} mockManager The mock manager. + * @param {string} name The method name. + * @param {!Array} args The other arguments to the method. + * + * @constructor + * @private + */ +goog.labs.mock.StubBinder_ = function(mockManager, name, args) { + /** + * The mock manager instance. + * @type {!goog.labs.mock.MockManager_} + * @private + */ + this.mockManager_ = mockManager; + + /** + * Holds the name of the method to be bound. + * @type {string} + * @private + */ + this.name_ = name; + + /** + * Holds the arguments for the method. + * @type {!Array} + * @private + */ + this.args_ = args; +}; + + +/** + * Defines the stub to be called for the method name and arguments bound + * earlier. + * TODO(user): Add support for the 'Answer' interface. + * + * @param {!Function} func The stub. + */ +goog.labs.mock.StubBinder_.prototype.then = function(func) { + this.mockManager_.addBinding(this.name_, this.args_, func); +}; + + +/** + * Defines the stub to return a specific value for a method name and arguments. + * + * @param {*} value The value to return. + */ +goog.labs.mock.StubBinder_.prototype.thenReturn = function(value) { + this.mockManager_.addBinding(this.name_, this.args_, + goog.functions.constant(value)); +}; + + +/** + * Facilitates (and is the first step in) setting up stubs. Obtains an object + * on which, the method to be mocked is called to create a stub. Sample usage: + * + * var mockObj = goog.labs.mock(objectBeingMocked); + * goog.labs.mock.when(mockObj).getFoo(3).thenReturn(4); + * + * @param {!Object} mockObject The mocked object. + * + * @return {!goog.labs.mock.StubBinder_} The property binder. + */ +goog.labs.mock.when = function(mockObject) { + return mockObject.$binderProxy; +}; + + + +/** + * Represents a binding between a method name, args and a stub. + * + * @param {string} methodName The name of the method being stubbed. + * @param {!Array} args The arguments passed to the method. + * @param {!Function} stub The stub function to be called for the given method. + * @constructor + * @private + */ +goog.labs.mock.MethodBinding_ = function(methodName, args, stub) { + /** + * The name of the method being stubbed. + * @type {string} + * @private + */ + this.methodName_ = methodName; + + /** + * The arguments for the method being stubbed. + * @type {!Array} + * @private + */ + this.args_ = args; + + /** + * The stub function. + * @type {!Function} + * @private + */ + this.stub_ = stub; +}; + + +/** + * @return {!Function} The stub to be executed. + */ +goog.labs.mock.MethodBinding_.prototype.getStub = function() { + return this.stub_; +}; + + +/** + * Determines whether the given args match the stored args_. Used to determine + * which stub to invoke for a method. + * + * @param {string} methodName The name of the method being stubbed. + * @param {!Array} args An array of arguments. + * @return {boolean} If it matches the stored arguments. + */ +goog.labs.mock.MethodBinding_.prototype.matches = function(methodName, args) { + //TODO(user): More elaborate argument matching. + return this.methodName_ == methodName && + goog.array.equals(args, this.args_); +}; diff --git a/contexts/data/lib/closure-library/closure/goog/labs/mock/mock_test.html b/contexts/data/lib/closure-library/closure/goog/labs/mock/mock_test.html new file mode 100644 index 0000000..93a3984 --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/mock/mock_test.html @@ -0,0 +1,115 @@ +<!DOCTYPE html> +<html> +<!-- +Copyright 2012 The Closure Library Authors. All Rights Reserved. + +Use of this source code is governed by the Apache License, Version 2.0. +See the COPYING file for details. +--> +<head> +<title>Closure Unit Tests - goog.labs.mock</title> +<script src="../../base.js"></script> +<script> + +goog.require('goog.labs.mock'); +goog.require('goog.testing.jsunit'); + +</script> +</head> +<body> +<script> + +var ParentClass = function() {}; +ParentClass.prototype.method1 = function() {}; +ParentClass.prototype.x = 1; + +var ChildClass = function() {}; +goog.inherits(ChildClass, ParentClass); +ChildClass.prototype.method2 = function() {}; +ChildClass.prototype.y = 2; + +function testParentClass() { + var parentMock = goog.labs.mock(ParentClass); + + assertNotUndefined(parentMock.method1); + assertUndefined(parentMock.method1()); + assertUndefined(parentMock.method2); + assertUndefined(parentMock.x); + assertUndefined(parentMock.y); +} + +function testChildClass() { + var childMock = goog.labs.mock(ChildClass); + + assertNotUndefined(childMock.method1); + assertUndefined(childMock.method1()); + assertNotUndefined(childMock.method2); + assertUndefined(childMock.method2()); + assertUndefined(childMock.x); + assertUndefined(childMock.y); +} + +function testParentClassInstance() { + var parentMock = goog.labs.mock(new ParentClass()); + + assertNotUndefined(parentMock.method1); + assertUndefined(parentMock.method1()); + assertUndefined(parentMock.method2); + assertUndefined(parentMock.x); + assertUndefined(parentMock.y); +} + +function testChildClassInstance() { + var childMock = goog.labs.mock(new ChildClass()); + + assertNotUndefined(childMock.method1); + assertUndefined(childMock.method1()); + assertNotUndefined(childMock.method2); + assertUndefined(childMock.method2()); + assertUndefined(childMock.x); + assertUndefined(childMock.y); +} + +function testNonEnumerableProperties() { + var mockObject = goog.labs.mock({}); + assertNotUndefined(mockObject.toString); + goog.labs.mock.when(mockObject).toString().then(function() { + return 'toString'; + }); + assertEquals('toString', mockObject.toString()); +} + +function testBasicStubbing() { + var obj = { + method1: function(i) { + return 2 * i; + }, + method2: function(i, str) { + return str; + }, + method3: function(x) { + return x; + } + }; + + var mockObj = goog.labs.mock(obj); + goog.labs.mock.when(mockObj).method1(2).then(function(i) {return i;}); + + assertEquals(4, obj.method1(2)); + assertEquals(2, mockObj.method1(2)); + assertUndefined(mockObj.method1(4)); + + goog.labs.mock.when(mockObj).method2(1, 'hi').then(function(i) {return 'oh'}); + assertEquals('hi', obj.method2(1, 'hi')); + assertEquals('oh', mockObj.method2(1, 'hi')); + assertUndefined(mockObj.method2(3, 'foo')); + + goog.labs.mock.when(mockObj).method3(4).thenReturn(10); + assertEquals(4, obj.method3(4)); + assertEquals(10, mockObj.method3(4)); + assertUndefined(mockObj.method3(5)); +} + +</script> +</body> +</html> diff --git a/contexts/data/lib/closure-library/closure/goog/labs/net/.svn/all-wcprops b/contexts/data/lib/closure-library/closure/goog/labs/net/.svn/all-wcprops deleted file mode 100644 index 2b312b0..0000000 --- a/contexts/data/lib/closure-library/closure/goog/labs/net/.svn/all-wcprops +++ /dev/null @@ -1,17 +0,0 @@ -K 25 -svn:wc:ra_dav:version-url -V 46 -/svn/!svn/ver/1453/trunk/closure/goog/labs/net -END -xhr_test.html -K 25 -svn:wc:ra_dav:version-url -V 60 -/svn/!svn/ver/1453/trunk/closure/goog/labs/net/xhr_test.html -END -xhr.js -K 25 -svn:wc:ra_dav:version-url -V 53 -/svn/!svn/ver/1453/trunk/closure/goog/labs/net/xhr.js -END diff --git a/contexts/data/lib/closure-library/closure/goog/labs/net/.svn/entries b/contexts/data/lib/closure-library/closure/goog/labs/net/.svn/entries deleted file mode 100644 index 671edc4..0000000 --- a/contexts/data/lib/closure-library/closure/goog/labs/net/.svn/entries +++ /dev/null @@ -1,96 +0,0 @@ -10 - -dir -1494 -http://closure-library.googlecode.com/svn/trunk/closure/goog/labs/net -http://closure-library.googlecode.com/svn - - - -2011-12-07T18:41:01.000000Z -1453 -pupius@google.com - - - - - - - - - - - - - - -0b95b8e8-c90f-11de-9d4f-f947ee5921c8 - -xhr_test.html -file - - - - -2011-12-23T22:42:29.432342Z -08f19869b6b3067562fcb9bc509c0eb8 -2011-12-07T18:41:01.000000Z -1453 -pupius@google.com -has-props - - - - - - - - - - - - - - - - - - - - -12561 - -xhr.js -file - - - - -2011-12-23T22:42:29.432342Z -77170937aa4671abae08f58b0efd3239 -2011-12-07T18:41:01.000000Z -1453 -pupius@google.com -has-props - - - - - - - - - - - - - - - - - - - - -13736 - diff --git a/contexts/data/lib/closure-library/closure/goog/labs/net/.svn/prop-base/xhr.js.svn-base b/contexts/data/lib/closure-library/closure/goog/labs/net/.svn/prop-base/xhr.js.svn-base deleted file mode 100644 index 530636b..0000000 --- a/contexts/data/lib/closure-library/closure/goog/labs/net/.svn/prop-base/xhr.js.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:mime-type -V 15 -text/javascript -END diff --git a/contexts/data/lib/closure-library/closure/goog/labs/net/.svn/prop-base/xhr_test.html.svn-base b/contexts/data/lib/closure-library/closure/goog/labs/net/.svn/prop-base/xhr_test.html.svn-base deleted file mode 100644 index d356868..0000000 --- a/contexts/data/lib/closure-library/closure/goog/labs/net/.svn/prop-base/xhr_test.html.svn-base +++ /dev/null @@ -1,5 +0,0 @@ -K 13 -svn:mime-type -V 9 -text/html -END diff --git a/contexts/data/lib/closure-library/closure/goog/labs/net/.svn/text-base/xhr.js.svn-base b/contexts/data/lib/closure-library/closure/goog/labs/net/.svn/text-base/xhr.js.svn-base deleted file mode 100644 index e2135d2..0000000 --- a/contexts/data/lib/closure-library/closure/goog/labs/net/.svn/text-base/xhr.js.svn-base +++ /dev/null @@ -1,423 +0,0 @@ -// Copyright 2011 The Closure Library Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - -/** - * @fileoverview Offered as an alternative to XhrIo as a way for making requests - * via XMLHttpRequest. Instead of mirroring the XHR interface and exposing - * events, deferreds are used as a way to pass a "promise" of the response to - * interested parties. - * - */ - -goog.provide('goog.labs.net.xhr'); -goog.provide('goog.labs.net.xhr.Error'); -goog.provide('goog.labs.net.xhr.HttpError'); -goog.provide('goog.labs.net.xhr.TimeoutError'); - -goog.require('goog.async.Deferred'); -goog.require('goog.debug.Error'); -goog.require('goog.json'); -goog.require('goog.net.HttpStatus'); -goog.require('goog.net.XmlHttp'); -goog.require('goog.string'); -goog.require('goog.uri.utils'); - - - -goog.scope(function() { - var _ = goog.labs.net.xhr; - var Deferred = goog.async.Deferred; - var HttpStatus = goog.net.HttpStatus; - - - /** - * Configuration options for an XMLHttpRequest. - * - headers: map of header key/value pairs. - * - timeoutMs: number of milliseconds after which the request will be timed - * out by the client. Default is to allow the browser to handle timeouts. - * - withCredentials: whether user credentials are to be included in a - * cross-origin request. See: - * http://dev.w3.org/2006/webapi/XMLHttpRequest-2/#the-withcredentials-attribute - * - mimeType: allows the caller to override the content-type and charset for - * the request, which is useful when requesting binary data. See: - * http://dev.w3.org/2006/webapi/XMLHttpRequest-2/#dom-xmlhttprequest-overridemimetype - * - xssiPrefix: Prefix used for protecting against XSSI attacks, which should - * be removed before parsing the response as JSON. - * - * @typedef {{ - * headers: (Object.<string>|undefined), - * timeoutMs: (number|undefined), - * withCredentials: (boolean|undefined), - * mimeType: (string|undefined), - * xssiPrefix: (string|undefined) - * }} - */ - _.Options; - - - /** - * Defines the types that are allowed as post data. - * @typedef {(ArrayBuffer|Blob|Document|FormData|null|string|undefined)} - */ - _.PostData; - - - /** - * The Content-Type HTTP header name. - * @type {string} - */ - _.CONTENT_TYPE_HEADER = 'Content-Type'; - - - /** - * The Content-Type HTTP header value for a url-encoded form. - * @type {string} - */ - _.FORM_CONTENT_TYPE = 'application/x-www-form-urlencoded;charset=utf-8'; - - - /** - * Sends a get request, returning a deferred which will be called back with - * the response text once the request completes. - * - * @param {string} url The URL to request. - * @param {_.Options=} opt_options Configuration options for the request. - * @return {!Deferred} A deferred object that will be called back - * with the response text once the request finishes. - */ - _.get = function(url, opt_options) { - return _.send('GET', url, null, opt_options). - addCallback(_.getResponseText_); - }; - - - /** - * Sends a post request, returning a deferred which will be called back with - * the response text once the request completes. - * - * @param {string} url The URL to request. - * @param {_.PostData} data The body of the post request. - * @param {_.Options=} opt_options Configuration options for the request. - * @return {!Deferred} A deferred object that will be called back - * with the response text once the request finishes. - */ - _.post = function(url, data, opt_options) { - return _.send('POST', url, data, opt_options). - addCallback(_.getResponseText_); - }; - - - /** - * Sends a get request, returning a deferred which will be called back with - * the parsed response text once the request completes. - * - * @param {string} url The URL to request. - * @param {_.Options=} opt_options Configuration options for the request. - * @return {!Deferred} A deferred object that will be called back - * with the response JSON once the request finishes. - */ - _.getJson = function(url, opt_options) { - var d = _.send('GET', url, null, opt_options); - _.addJsonParsingCallbacks_(d, opt_options); - return d; - }; - - - /** - * Sends a post request, returning a deferred which will be called back with - * the parsed response text once the request completes. - * - * @param {string} url The URL to request. - * @param {_.PostData} data The body of the post request. - * @param {_.Options=} opt_options Configuration options for the request. - * @return {!Deferred} A deferred object that will be called back - * with the response JSON once the request finishes. - */ - _.postJson = function(url, data, opt_options) { - var d = _.send('POST', url, data, opt_options); - _.addJsonParsingCallbacks_(d, opt_options); - return d; - }; - - - /** - * Sends a request using XMLHttpRequest and returns a deferred. - * - * @param {string} method The HTTP method for the request. - * @param {string} url The URL to request. - * @param {_.PostData} data The body of the post request. - * @param {_.Options=} opt_options Configuration options for the request. - * @return {!Deferred} A deferred object that will be called back - * with the XHR object when the request finishes. - */ - _.send = function(method, url, data, opt_options) { - - // When the deferred is cancelled, we abort the XHR. We want to make sure - // the readystatechange event still fires, so it can do the timeout - // cleanup, however we don't want the callback or errback to be called - // again. Thus the slight ugliness here. If deferreds were pushed into - // makeRequest, this could become a lot cleaner but we want an option for - // people not to include goog.async.Deferred. - - function cancel() { - cancelled = true; - xhr.abort(); - xhr.onreadystatechange = goog.nullFunction; - // TODO(user): We could return an AbortedError if we wanted to. - } - - function callback(data) { - if (!cancelled) { - d.callback(data); - } - } - - function errback(err) { - if (!cancelled) { - d.errback(err); - } - } - - var cancelled = false; - var d = new Deferred(cancel); - var xhr = _.makeRequest(method, url, data, opt_options, callback, errback); - - return d; - }; - - - /** - * Creates a new XMLHttpRequest and initiates a request. - * - * @param {string} method The HTTP method for the request. - * @param {string} url The URL to request. - * @param {_.PostData} data The body of the post request, unless the content - * type is explicitly set in the Options, then it will default to form - * urlencoded. - * @param {_.Options=} opt_options Configuration options for the request. - * @param {function(XMLHttpRequest)=} opt_callback Optional callback to call - * when the request completes. - * @param {function(Error)=} opt_errback Optional callback to call - * when there is an error. - * @return {!XMLHttpRequest} The new XMLHttpRequest. - */ - _.makeRequest = function( - method, url, data, opt_options, opt_callback, opt_errback) { - var options = opt_options || {}; - var callback = opt_callback || goog.nullFunction; - var errback = opt_errback || goog.nullFunction; - var timer; - - var xhr = /** @type {!XMLHttpRequest} */ (goog.net.XmlHttp()); - try { - xhr.open(method, url, true); - } catch (e) { - // XMLHttpRequest.open may throw when 'open' is called, for example, IE7 - // throws "Access Denied" for cross-origin requests. - errback(new _.Error('Error opening XHR: ' + e.message, url, xhr)); - return xhr; - } - - // So sad that IE doesn't support onload and onerror. - xhr.onreadystatechange = function() { - if (xhr.readyState == goog.net.XmlHttp.ReadyState.COMPLETE) { - window.clearTimeout(timer); - if (HttpStatus.isSuccess(xhr.status) || - xhr.status === 0 && !_.isEffectiveSchemeHttp_(url)) { - callback(xhr); - } else { - errback(new _.HttpError(xhr.status, url, xhr)); - } - } - }; - - // Set the headers. - var contentTypeIsSet = false; - if (options.headers) { - for (var key in options.headers) { - xhr.setRequestHeader(key, options.headers[key]); - } - contentTypeIsSet = _.CONTENT_TYPE_HEADER in options.headers; - } - - // If a content type hasn't been set, default to form-urlencoded/UTF8 for - // POSTs. This is because some proxies have been known to reject posts - // without a content-type. - if (method == 'POST' && !contentTypeIsSet) { - xhr.setRequestHeader(_.CONTENT_TYPE_HEADER, _.FORM_CONTENT_TYPE); - } - - // Set whether to pass cookies on cross-domain requests (if applicable). - // @see http://dev.w3.org/2006/webapi/XMLHttpRequest-2/#the-withcredentials-attribute - if (options.withCredentials) { - xhr.withCredentials = options.withCredentials; - } - - // Allow the request to override the mime type, useful for getting binary - // data from the server. e.g. 'text/plain; charset=x-user-defined'. - // @see http://dev.w3.org/2006/webapi/XMLHttpRequest-2/#dom-xmlhttprequest-overridemimetype - if (options.mimeType) { - xhr.overrideMimeType(options.mimeType); - } - - // Handle timeouts, if requested. - if (options.timeoutMs > 0) { - timer = window.setTimeout(function() { - // Clear event listener before aborting so the errback will not be - // called twice. - xhr.onreadystatechange = goog.nullFunction; - xhr.abort(); - errback(new _.TimeoutError(url, xhr)); - }, options.timeoutMs); - } - - // Trigger the send. - try { - xhr.send(data); - } catch (e) { - // XMLHttpRequest.send is known to throw on some versions of FF, for example - // if a cross-origin request is disallowed. - errback(new _.Error('Error sending XHR: ' + e.message, url, xhr)); - } - - return xhr; - }; - - - /** - * @param {string} url The URL to test. - * @return {boolean} Whether the effective scheme is HTTP or HTTPs. - * @private - */ - _.isEffectiveSchemeHttp_ = function(url) { - var scheme = goog.uri.utils.getEffectiveScheme(url); - // NOTE(user): Empty-string is for the case under FF3.5 when the location - // is not defined inside a web worker. - return scheme == 'http' || scheme == 'https' || scheme == ''; - }; - - - /** - * Returns the response text of an XHR object. Intended for use as a callback - * on a deferred. - * - * @param {!XMLHttpRequest} xhr The XHR object. - * @return {string} The response text. - * @private - */ - _.getResponseText_ = function(xhr) { - return xhr.responseText; - }; - - - /** - * Adds the callbacks required for parsing a response as JSON. The chain will - * expect an XMLHttpRequest and will result in a JS object. Parse errors will - * trigger the errback chain. - * - * @param {!Deferred} d The deferred to add callbacks to. - * @param {_.Options|undefined} options The options object. - * @private - */ - _.addJsonParsingCallbacks_ = function(d, options) { - d.addCallback(_.getResponseText_); - if (options && options.xssiPrefix) { - d.addCallback(goog.partial(_.stripXssiPrefix_, options.xssiPrefix)); - } - d.addCallback(goog.json.parse); - }; - - - /** - * Strips the XSSI prefix from the input string. - * - * @param {string} prefix The XSSI prefix. - * @param {string} string The string to strip the prefix from. - * @return {string} The input string without the prefix. - * @private - */ - _.stripXssiPrefix_ = function(prefix, string) { - if (goog.string.startsWith(string, prefix)) { - string = string.substring(prefix.length); - } - return string; - }; - - - - /** - * Generic error that may occur during a request. - * - * @param {string} message The error message. - * @param {string} url The URL that was being requested. - * @param {!XMLHttpRequest} xhr The XMLHttpRequest that failed. - * @extends {goog.debug.Error} - * @constructor - */ - _.Error = function(message, url, xhr) { - goog.base(this, message + ', url=' + url); - - /** - * The URL that was requested. - * @type {string} - */ - this.url = url; - - /** - * The XMLHttpRequest corresponding with the failed request. - * @type {!XMLHttpRequest} - */ - this.xhr = xhr; - }; - goog.inherits(_.Error, goog.debug.Error); - - - - /** - * Class for HTTP errors. - * - * @param {number} status The HTTP status code of the response. - * @param {string} url The URL that was being requested. - * @param {!XMLHttpRequest} xhr The XMLHttpRequest that failed. - * @extends {_.Error} - * @constructor - */ - _.HttpError = function(status, url, xhr) { - goog.base(this, 'Request Failed, status=' + status, url, xhr); - - /** - * The HTTP status code for the error. - * @type {number} - */ - this.status = status; - }; - goog.inherits(_.HttpError, _.Error); - - - - /** - * Class for Timeout errors. - * - * @param {string} url The URL that timed out. - * @param {!XMLHttpRequest} xhr The XMLHttpRequest that failed. - * @extends {_.Error} - * @constructor - */ - _.TimeoutError = function(url, xhr) { - goog.base(this, 'Request timed out', url, xhr); - }; - goog.inherits(_.TimeoutError, _.Error); - -}); diff --git a/contexts/data/lib/closure-library/closure/goog/labs/net/.svn/text-base/xhr_test.html.svn-base b/contexts/data/lib/closure-library/closure/goog/labs/net/.svn/text-base/xhr_test.html.svn-base deleted file mode 100644 index 601e02d..0000000 --- a/contexts/data/lib/closure-library/closure/goog/labs/net/.svn/text-base/xhr_test.html.svn-base +++ /dev/null @@ -1,424 +0,0 @@ -<!DOCTYPE html> -<html> -<!-- -Copyright 2011 The Closure Library Authors. All Rights Reserved. - -Use of this source code is governed by the Apache License, Version 2.0. -See the COPYING file for details. ---> -<!-- ---> -<head> -<meta http-equiv="X-UA-Compatible" content="IE=edge"> -<title>Closure Unit Tests - goog.labs.net.xhr</title> -<script src="../../base.js"></script> -<script> - goog.require('goog.labs.net.xhr'); - goog.require('goog.string'); - goog.require('goog.testing.AsyncTestCase'); - goog.require('goog.testing.MockClock'); - goog.require('goog.testing.jsunit'); - goog.require('goog.userAgent'); -</script> -</head> -<body> -<script> - - function setupStubXMLHttpRequest() { - - mockClock = new goog.testing.MockClock(true); - - var stubXhr = { - sent: false, - aborted: false, - status: 0, - headers: {}, - open: function(method, url, async) { - this.method = method; - this.url = url; - this.async = async; - }, - setRequestHeader: function(key, value) { - this.headers[key] = value; - }, - overrideMimeType: function(mimeType) { - this.mimeType = mimeType; - }, - abort: function() { - this.aborted = true; - this.load(0); - }, - send: function(data) { - this.data = data; - this.sent = true; - }, - load: function(status) { - this.status = status; - this.readyState = 4; - if (this.onreadystatechange) this.onreadystatechange(); - } - }; - - goog.net.XmlHttp = function() { - return stubXhr; - }; - for (var x in originalXmlHttp) { - goog.net.XmlHttp[x] = originalXmlHttp[x]; - } - - return stubXhr; - } - - - var xhr = goog.labs.net.xhr; - var originalXmlHttp = goog.net.XmlHttp; - var mockClock; - - var testCase = new goog.testing.AsyncTestCase(document.title); - testCase.stepTimeout = 5 * 1000; - - testCase.setUpPage = function() { - this.autoDiscoverTests(); - }; - - testCase.tearDown = function() { - if (mockClock) { - mockClock.dispose(); - mockClock = null; - } - goog.net.XmlHttp = originalXmlHttp; - }; - - G_testRunner.initialize(testCase); - - // Many tests don't work on the local file system due to cross-origin - // restrictions in Chrome without --allow-file-access-from-files. - // They will run on the farm or on a Closure Test server. - var shouldRunLocally = goog.userAgent.IE || goog.userAgent.GECKO || - goog.string.startsWith(document.location.href, 'file://'); - - function testSimpleRequest() { - if (shouldRunLocally) return; - - testCase.waitForAsync('simpleRequest'); - xhr.send('GET', 'testdata/xhr_test_text.data'). - addCallback(function(xhr) { - assertEquals('Just some data.', xhr.responseText); - assertEquals(200, xhr.status); - testCase.continueTesting(); - }). - addErrback(fail); - } - - function testGetText() { - if (shouldRunLocally) return; - - testCase.waitForAsync('getText'); - xhr.get('testdata/xhr_test_text.data'). - addCallback(function(content) { - assertEquals('Just some data.', content); - testCase.continueTesting(); - }). - addErrback(fail); - } - - function testGetTextWithJson() { - if (shouldRunLocally) return; - - testCase.waitForAsync('getTextWithJson'); - xhr.get('testdata/xhr_test_json.data'). - addCallback(function(content) { - assertEquals('while(1);\n{"stat":"ok","count":12345}\n', content); - testCase.continueTesting(); - }). - addErrback(fail); - } - - function testPostText() { - if (shouldRunLocally) return; - - testCase.waitForAsync('postText'); - xhr.post('testdata/xhr_test_text.data', 'post-data'). - addCallback(function(content) { - // No good way to test post-data gets transported. - assertEquals('Just some data.', content); - testCase.continueTesting(); - }). - addErrback(fail); - } - - function testGetJson() { - if (shouldRunLocally) return; - - testCase.waitForAsync('getJson'); - xhr.getJson('testdata/xhr_test_json.data', {xssiPrefix: 'while(1);\n'}). - addCallback(function(content) { - assertEquals('ok', content['stat']); - assertEquals(12345, content['count']); - testCase.continueTesting(); - }). - addErrback(fail); - } - - function testSerialRequests() { - if (shouldRunLocally) return; - - var d = xhr.get('testdata/xhr_test_text.data'). - addCallback(function(txt) { - return xhr.getJson( - 'testdata/xhr_test_json.data', {xssiPrefix: 'while(1);\n'}); - }); - - // Data that comes through to callbacks should be from the 2nd request. - d.addCallback(function(json) { - assertEquals('ok', json['stat']); - assertEquals(12345, json['count']); - testCase.continueTesting(); - }); - d.addErrback(fail); - } - - function testParallelRequest() { - if (shouldRunLocally) return; - - var textData, jsonData; - function loadText(response) { - textData = response; - } - function loadJson(response) { - jsonData = response; - } - - var d = goog.async.Deferred.succeed(); - d.awaitDeferred( - xhr.get('testdata/xhr_test_text.data').addCallback(loadText)); - d.awaitDeferred( - xhr.getJson('testdata/xhr_test_json.data', - {xssiPrefix: 'while(1);\n'}).addCallback(loadJson)); - d.addCallback(function() { - assertEquals('Just some data.', textData); - assertEquals('ok', jsonData['stat']); - testCase.continueTesting(); - }); - d.addErrback(fail); - } - - function testBadUrlDetectedAsError() { - if (shouldRunLocally) return; - - testCase.waitForAsync('badUrl'); - xhr.getJson('unknown-file.dat'). - addCallback(fail). - addErrback(function(err) { - assertTrue('Error should be an HTTP error', - err instanceof xhr.HttpError); - assertEquals(404, err.status); - assertNotNull(err.xhr); - testCase.continueTesting(); - }); - } - - function testBadOriginTriggersOnErrorHandler() { - testCase.waitForAsync('badOrigin'); - xhr.get('http://www.google.com'). - addCallback(fail). - addErrback(function(err) { - // In IE this will be a goog.labs.net.xhr.Error since it is thrown - // when calling xhr.open(), other browsers will raise an HttpError. - assertTrue('Error should be an xhr error', - err instanceof xhr.Error); - assertNotNull(err.xhr); - testCase.continueTesting(); - }); - } - - function testAbortRequest() { - if (shouldRunLocally) return; - - var err; - var d = xhr.send('GET', 'test-url', null). - addCallback(fail). - addErrback(function(e) { - err = e; - }); - d.cancel(); - assertTrue(err instanceof goog.async.Deferred.CancelledError); - } - - //============================================================================ - // The following tests are synchronous and use a stubbed out XMLHttpRequest. - //============================================================================ - - function testSendNoOptions() { - var stubXhr = setupStubXMLHttpRequest(); - var called = false; - xhr.send('GET', 'test-url', null). - addCallback(function(xhr) { - called = true; - assertEquals('Objects should be equal', xhr, stubXhr); - }). - addErrback(fail); - - assertTrue('XHR should have been sent', stubXhr.sent); - assertFalse('Callback should not yet have been called', called); - - stubXhr.load(200); - - assertTrue('Callback should have been called', called); - - assertEquals('GET', stubXhr.method); - assertEquals('test-url', stubXhr.url); - } - - function testSendPostSetsDefaultHeader() { - var stubXhr = setupStubXMLHttpRequest(); - xhr.send('POST', 'test-url', null).addErrback(fail); - - stubXhr.load(200); - - assertEquals('POST', stubXhr.method); - assertEquals('test-url', stubXhr.url); - assertEquals('application/x-www-form-urlencoded;charset=utf-8', - stubXhr.headers['Content-Type']); - } - - function testSendPostHeaders() { - var stubXhr = setupStubXMLHttpRequest(); - xhr.send('POST', 'test-url', null, { - headers: {'Content-Type': 'text/plain', 'X-Made-Up': 'FooBar'} - }).addErrback(fail); - - stubXhr.load(200); - - assertEquals('POST', stubXhr.method); - assertEquals('test-url', stubXhr.url); - assertEquals('text/plain', stubXhr.headers['Content-Type']); - assertEquals('FooBar', stubXhr.headers['X-Made-Up']); - } - - function testSendWithCredentials() { - var stubXhr = setupStubXMLHttpRequest(); - xhr.send('POST', 'test-url', null, {withCredentials: true}). - addErrback(fail); - stubXhr.load(200); - assertTrue('XHR should have been sent', stubXhr.sent); - assertTrue(stubXhr.withCredentials); - } - - function testSendWithMimeType() { - var stubXhr = setupStubXMLHttpRequest(); - xhr.send('POST', 'test-url', null, {mimeType: 'text/plain'}). - addErrback(fail); - - stubXhr.load(200); - assertTrue('XHR should have been sent', stubXhr.sent); - assertEquals('text/plain', stubXhr.mimeType); - } - - function testSendWithHttpError() { - var stubXhr = setupStubXMLHttpRequest(); - var err; - xhr.send('POST', 'test-url', null). - addCallback(fail). - addErrback(function(e) { err = e; }); - - stubXhr.load(500); - - assertTrue('XHR should have been sent', stubXhr.sent); - assertTrue(err instanceof xhr.HttpError); - assertEquals(500, err.status); - } - - function testSendWithTimeoutNotHit() { - // TODO(user): This test fails in safari if it is run as part of a batch - // but passes when run on its own. Something strange is going on to do - // with the references to window.clearTimeout inside onreadystatechange and - // the mockclock overrides. - if (goog.userAgent.SAFARI) return; - - var stubXhr = setupStubXMLHttpRequest(); - var err; - xhr.send('POST', 'test-url', null, {timeoutMs: 1500}).addErrback(fail); - assertTrue(mockClock.getTimeoutsMade() > 0); - mockClock.tick(1400); - stubXhr.load(200); - mockClock.tick(200); - assertTrue('XHR should have been sent', stubXhr.sent); - assertFalse('XHR should not have been aborted', stubXhr.aborted); - } - - function testSendWithTimeoutHit() { - var stubXhr = setupStubXMLHttpRequest(); - var err; - xhr.send('POST', 'test-url', null, {timeoutMs: 50}) - .addCallback(fail) - .addErrback(function(e) { err = e; }); - assertTrue(mockClock.getTimeoutsMade() > 0); - mockClock.tick(50); - assertTrue('XHR should have been sent', stubXhr.sent); - assertTrue('XHR should have been aborted', stubXhr.aborted); - assertTrue(err instanceof xhr.TimeoutError); - } - - function testSendWithTimeoutWithoutDeferreds() { - var stubXhr = setupStubXMLHttpRequest(); - var err; - xhr.makeRequest('POST', 'test-url', null, {timeoutMs: 50}, fail, - function(e) { err = e; }); - assertTrue(mockClock.getTimeoutsMade() > 0); - mockClock.tick(50); - assertTrue('XHR should have been sent', stubXhr.sent); - assertTrue('XHR should have been aborted', stubXhr.aborted); - assertTrue(err instanceof xhr.TimeoutError); - } - - function testCancelRequest() { - var stubXhr = setupStubXMLHttpRequest(); - var err; - var d = xhr.send('GET', 'test-url', null, {timeoutMs: 50}). - addCallback(fail). - addErrback(function(e) { - err = e; - }); - d.cancel(); - stubXhr.load(0); // Call load anyway, shoudn't make a difference. - mockClock.tick(100); // Timeout should never be called. - - assertTrue('XHR should have been sent', stubXhr.sent); - assertTrue('XHR should have been aborted', stubXhr.aborted); - assertTrue(err instanceof goog.async.Deferred.CancelledError); - } - - function testGetJson() { - var stubXhr = setupStubXMLHttpRequest(); - var responseData; - xhr.getJson('test-url'). - addCallback(function(data) { - responseData = data; - }). - addErrback(fail); - - stubXhr.responseText = '{"a": 1, "b": 2}'; - stubXhr.load(200); - - assertObjectEquals({a: 1, b: 2}, responseData); - } - - function testGetJsonWithXssiPrefix() { - var stubXhr = setupStubXMLHttpRequest(); - var responseData; - xhr.getJson('test-url', {xssiPrefix: 'while(1);\n'}). - addCallback(function(data) { - responseData = data; - }). - addErrback(fail); - - stubXhr.responseText = 'while(1);\n{"a": 1, "b": 2}'; - stubXhr.load(200); - - assertObjectEquals({a: 1, b: 2}, responseData); - } - -</script> -</body> -</html> diff --git a/contexts/data/lib/closure-library/closure/goog/labs/net/image.js b/contexts/data/lib/closure-library/closure/goog/labs/net/image.js new file mode 100644 index 0000000..1f1973a --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/net/image.js @@ -0,0 +1,93 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Simple image loader, used for preloading. + * @author nnaze@google.com (Nathan Naze) + */ + +goog.provide('goog.labs.net.image'); + +goog.require('goog.events.EventHandler'); +goog.require('goog.events.EventType'); +goog.require('goog.net.EventType'); +goog.require('goog.result.SimpleResult'); +goog.require('goog.userAgent'); + + +/** + * Loads a single image. Useful for preloading images. May be combined with + * goog.result.combine to preload many images. + * + * @param {string} uri URI of the image. + * @param {(Image|function(): !Image)=} opt_image If present, instead of + * creating a new Image instance the function will use the passed Image + * instance or the result of calling the Image factory respectively. This + * can be used to control exactly how Image instances are created, for + * example if they should be created in a particular document element, or + * have fields that will trigger CORS image fetches. + * @return {!goog.result.Result} An asyncronous result that will succeed + * if the image successfully loads or error if the image load fails. + */ +goog.labs.net.image.load = function(uri, opt_image) { + var image; + if (!goog.isDef(opt_image)) { + image = new Image(); + } else if (goog.isFunction(opt_image)) { + image = opt_image(); + } else { + image = opt_image; + } + + // IE's load event on images can be buggy. Instead, we wait for + // readystatechange events and check if readyState is 'complete'. + // See: + // http://msdn.microsoft.com/en-us/library/ie/ms536957(v=vs.85).aspx + // http://msdn.microsoft.com/en-us/library/ie/ms534359(v=vs.85).aspx + var loadEvent = goog.userAgent.IE ? goog.net.EventType.READY_STATE_CHANGE : + goog.events.EventType.LOAD; + + var result = new goog.result.SimpleResult(); + + var handler = new goog.events.EventHandler(); + handler.listen( + image, + [loadEvent, goog.net.EventType.ABORT, goog.net.EventType.ERROR], + function(e) { + + // We only registered listeners for READY_STATE_CHANGE for IE. + // If readyState is now COMPLETE, the image has loaded. + // See related comment above. + if (e.type == goog.net.EventType.READY_STATE_CHANGE && + image.readyState != goog.net.EventType.COMPLETE) { + return; + } + + // At this point, we know whether the image load was successful + // and no longer care about image events. + goog.dispose(handler); + + // Whether the image successfully loaded. + if (e.type == loadEvent) { + result.setValue(image); + } else { + result.setError(); + } + }); + + // Initiate the image request. + image.src = uri; + + return result; +}; diff --git a/contexts/data/lib/closure-library/closure/goog/labs/net/image_test.html b/contexts/data/lib/closure-library/closure/goog/labs/net/image_test.html new file mode 100644 index 0000000..32d529a --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/net/image_test.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html> +<!-- +Copyright 2012 The Closure Library Authors. All Rights Reserved. + +Use of this source code is governed by the Apache License, Version 2.0. +See the COPYING file for details. +--> +<!-- + Author: nnaze@google.com (Nathan Naze) +--> +<head> +<title>Closure Unit Tests - goog.labs.net.image</title> +<script src="../../base.js"></script> +</head> +<body> +<script> +goog.require('goog.labs.net.imageTest'); +</script> +</body> +</html> diff --git a/contexts/data/lib/closure-library/closure/goog/labs/net/image_test.js b/contexts/data/lib/closure-library/closure/goog/labs/net/image_test.js new file mode 100644 index 0000000..57ad109 --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/net/image_test.js @@ -0,0 +1,128 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Unit tests for goog.labs.net.Image. + * + * @author nnaze@google.com (Nathan Naze) + */ + + +/** @suppress {extraRequire} */ +goog.provide('goog.labs.net.imageTest'); + +goog.require('goog.events'); +goog.require('goog.labs.net.image'); +goog.require('goog.result'); +goog.require('goog.result.Result'); +goog.require('goog.string'); +goog.require('goog.testing.AsyncTestCase'); +goog.require('goog.testing.jsunit'); +goog.require('goog.testing.recordFunction'); + +goog.setTestOnly('goog.labs.net.ImageTest'); + +var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall(); + +function testValidImage() { + var url = 'testdata/cleardot.gif'; + + asyncTestCase.waitForAsync('image load'); + + assertEquals(0, goog.events.getTotalListenerCount()); + + var result = goog.labs.net.image.load(url); + + goog.result.waitOnSuccess(result, function(value) { + + assertEquals(goog.result.Result.State.SUCCESS, result.getState()); + + assertEquals('IMG', value.tagName); + assertTrue(goog.string.endsWith(value.src, url)); + assertUndefined(result.getError()); + + assertEquals('Listeners should have been cleaned up.', + 0, goog.events.getTotalListenerCount()); + + asyncTestCase.continueTesting(); + }); +} + +function testInvalidImage() { + + var url = 'testdata/invalid.gif'; // This file does not exist. + + asyncTestCase.waitForAsync('image load'); + + assertEquals(0, goog.events.getTotalListenerCount()); + + var result = goog.labs.net.image.load(url); + + goog.result.wait(result, function(result) { + + assertEquals(goog.result.Result.State.ERROR, result.getState()); + assertUndefined(result.getValue()); + assertUndefined(result.getError()); + + assertEquals('Listeners should have been cleaned up.', + 0, goog.events.getTotalListenerCount()); + + asyncTestCase.continueTesting(); + }); +} + +function testImageFactory() { + var returnedImage = new Image(); + var factory = function() { + return returnedImage; + } + var countedFactory = goog.testing.recordFunction(factory); + + var url = 'testdata/cleardot.gif'; + + asyncTestCase.waitForAsync('image load'); + assertEquals(0, goog.events.getTotalListenerCount()); + var result = goog.labs.net.image.load(url, countedFactory); + + goog.result.waitOnSuccess(result, function(value) { + assertEquals(goog.result.Result.State.SUCCESS, result.getState()); + assertEquals(returnedImage, value); + assertEquals(1, countedFactory.getCallCount()); + assertUndefined(result.getError()); + + assertEquals('Listeners should have been cleaned up.', + 0, goog.events.getTotalListenerCount()); + asyncTestCase.continueTesting(); + }); +} + +function testExistingImage() { + var image = new Image(); + + var url = 'testdata/cleardot.gif'; + + asyncTestCase.waitForAsync('image load'); + assertEquals(0, goog.events.getTotalListenerCount()); + var result = goog.labs.net.image.load(url, image); + + goog.result.waitOnSuccess(result, function(value) { + assertEquals(goog.result.Result.State.SUCCESS, result.getState()); + assertEquals(image, value); + assertUndefined(result.getError()); + + assertEquals('Listeners should have been cleaned up.', + 0, goog.events.getTotalListenerCount()); + asyncTestCase.continueTesting(); + }); +} diff --git a/contexts/data/lib/closure-library/closure/goog/labs/net/testdata/cleardot.gif b/contexts/data/lib/closure-library/closure/goog/labs/net/testdata/cleardot.gif Binary files differnew file mode 100644 index 0000000..1d11fa9 --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/net/testdata/cleardot.gif diff --git a/contexts/data/lib/closure-library/closure/goog/labs/net/testdata/xhr_test_json.data b/contexts/data/lib/closure-library/closure/goog/labs/net/testdata/xhr_test_json.data new file mode 100644 index 0000000..44afb24 --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/net/testdata/xhr_test_json.data @@ -0,0 +1,2 @@ +while(1); +{"stat":"ok","count":12345} diff --git a/contexts/data/lib/closure-library/closure/goog/labs/net/testdata/xhr_test_text.data b/contexts/data/lib/closure-library/closure/goog/labs/net/testdata/xhr_test_text.data new file mode 100644 index 0000000..b7f2f0e --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/net/testdata/xhr_test_text.data @@ -0,0 +1 @@ +Just some data.
\ No newline at end of file diff --git a/contexts/data/lib/closure-library/closure/goog/labs/net/xhr.js b/contexts/data/lib/closure-library/closure/goog/labs/net/xhr.js index e2135d2..224ec74 100644 --- a/contexts/data/lib/closure-library/closure/goog/labs/net/xhr.js +++ b/contexts/data/lib/closure-library/closure/goog/labs/net/xhr.js @@ -16,7 +16,7 @@ /** * @fileoverview Offered as an alternative to XhrIo as a way for making requests * via XMLHttpRequest. Instead of mirroring the XHR interface and exposing - * events, deferreds are used as a way to pass a "promise" of the response to + * events, results are used as a way to pass a "promise" of the response to * interested parties. * */ @@ -26,398 +26,419 @@ goog.provide('goog.labs.net.xhr.Error'); goog.provide('goog.labs.net.xhr.HttpError'); goog.provide('goog.labs.net.xhr.TimeoutError'); -goog.require('goog.async.Deferred'); goog.require('goog.debug.Error'); goog.require('goog.json'); goog.require('goog.net.HttpStatus'); goog.require('goog.net.XmlHttp'); +goog.require('goog.result'); goog.require('goog.string'); goog.require('goog.uri.utils'); goog.scope(function() { - var _ = goog.labs.net.xhr; - var Deferred = goog.async.Deferred; - var HttpStatus = goog.net.HttpStatus; +var _ = goog.labs.net.xhr; +var Result = goog.result.Result; +var SimpleResult = goog.result.SimpleResult; +var Wait = goog.result.wait; +var HttpStatus = goog.net.HttpStatus; - /** - * Configuration options for an XMLHttpRequest. - * - headers: map of header key/value pairs. - * - timeoutMs: number of milliseconds after which the request will be timed - * out by the client. Default is to allow the browser to handle timeouts. - * - withCredentials: whether user credentials are to be included in a - * cross-origin request. See: - * http://dev.w3.org/2006/webapi/XMLHttpRequest-2/#the-withcredentials-attribute - * - mimeType: allows the caller to override the content-type and charset for - * the request, which is useful when requesting binary data. See: - * http://dev.w3.org/2006/webapi/XMLHttpRequest-2/#dom-xmlhttprequest-overridemimetype - * - xssiPrefix: Prefix used for protecting against XSSI attacks, which should - * be removed before parsing the response as JSON. - * - * @typedef {{ - * headers: (Object.<string>|undefined), - * timeoutMs: (number|undefined), - * withCredentials: (boolean|undefined), - * mimeType: (string|undefined), - * xssiPrefix: (string|undefined) - * }} - */ - _.Options; - - - /** - * Defines the types that are allowed as post data. - * @typedef {(ArrayBuffer|Blob|Document|FormData|null|string|undefined)} - */ - _.PostData; +/** + * Configuration options for an XMLHttpRequest. + * - headers: map of header key/value pairs. + * - timeoutMs: number of milliseconds after which the request will be timed + * out by the client. Default is to allow the browser to handle timeouts. + * - withCredentials: whether user credentials are to be included in a + * cross-origin request. See: + * http://dev.w3.org/2006/webapi/XMLHttpRequest-2/#the-withcredentials-attribute + * - mimeType: allows the caller to override the content-type and charset for + * the request, which is useful when requesting binary data. See: + * http://dev.w3.org/2006/webapi/XMLHttpRequest-2/#dom-xmlhttprequest-overridemimetype + * - xssiPrefix: Prefix used for protecting against XSSI attacks, which should + * be removed before parsing the response as JSON. + * + * @typedef {{ + * headers: (Object.<string>|undefined), + * timeoutMs: (number|undefined), + * withCredentials: (boolean|undefined), + * mimeType: (string|undefined), + * xssiPrefix: (string|undefined) + * }} + */ +_.Options; - /** - * The Content-Type HTTP header name. - * @type {string} - */ - _.CONTENT_TYPE_HEADER = 'Content-Type'; +/** + * Defines the types that are allowed as post data. + * @typedef {(ArrayBuffer|Blob|Document|FormData|null|string|undefined)} + */ +_.PostData; - /** - * The Content-Type HTTP header value for a url-encoded form. - * @type {string} - */ - _.FORM_CONTENT_TYPE = 'application/x-www-form-urlencoded;charset=utf-8'; +/** + * The Content-Type HTTP header name. + * @type {string} + */ +_.CONTENT_TYPE_HEADER = 'Content-Type'; - /** - * Sends a get request, returning a deferred which will be called back with - * the response text once the request completes. - * - * @param {string} url The URL to request. - * @param {_.Options=} opt_options Configuration options for the request. - * @return {!Deferred} A deferred object that will be called back - * with the response text once the request finishes. - */ - _.get = function(url, opt_options) { - return _.send('GET', url, null, opt_options). - addCallback(_.getResponseText_); - }; +/** + * The Content-Type HTTP header value for a url-encoded form. + * @type {string} + */ +_.FORM_CONTENT_TYPE = 'application/x-www-form-urlencoded;charset=utf-8'; - /** - * Sends a post request, returning a deferred which will be called back with - * the response text once the request completes. - * - * @param {string} url The URL to request. - * @param {_.PostData} data The body of the post request. - * @param {_.Options=} opt_options Configuration options for the request. - * @return {!Deferred} A deferred object that will be called back - * with the response text once the request finishes. - */ - _.post = function(url, data, opt_options) { - return _.send('POST', url, data, opt_options). - addCallback(_.getResponseText_); - }; +/** + * Sends a get request, returning a transformed result which will be resolved + * with the response text once the request completes. + * + * @param {string} url The URL to request. + * @param {_.Options=} opt_options Configuration options for the request. + * @return {!Result} A result object that will be resolved + * with the response text once the request finishes. + */ +_.get = function(url, opt_options) { + var result = _.send('GET', url, null, opt_options); + var transformedResult = goog.result.transform(result, + _.getResponseText_); + return transformedResult; +}; - /** - * Sends a get request, returning a deferred which will be called back with - * the parsed response text once the request completes. - * - * @param {string} url The URL to request. - * @param {_.Options=} opt_options Configuration options for the request. - * @return {!Deferred} A deferred object that will be called back - * with the response JSON once the request finishes. - */ - _.getJson = function(url, opt_options) { - var d = _.send('GET', url, null, opt_options); - _.addJsonParsingCallbacks_(d, opt_options); - return d; - }; +/** + * Sends a post request, returning a transformed result which will be resolved + * with the response text once the request completes. + * + * @param {string} url The URL to request. + * @param {_.PostData} data The body of the post request. + * @param {_.Options=} opt_options Configuration options for the request. + * @return {!Result} A result object that will be resolved + * with the response text once the request finishes. + */ +_.post = function(url, data, opt_options) { + var result = _.send('POST', url, data, opt_options); + var transformedResult = goog.result.transform(result, + _.getResponseText_); + return transformedResult; +}; - /** - * Sends a post request, returning a deferred which will be called back with - * the parsed response text once the request completes. - * - * @param {string} url The URL to request. - * @param {_.PostData} data The body of the post request. - * @param {_.Options=} opt_options Configuration options for the request. - * @return {!Deferred} A deferred object that will be called back - * with the response JSON once the request finishes. - */ - _.postJson = function(url, data, opt_options) { - var d = _.send('POST', url, data, opt_options); - _.addJsonParsingCallbacks_(d, opt_options); - return d; - }; +/** + * Sends a get request, returning a result which will be resolved with + * the parsed response text once the request completes. + * + * @param {string} url The URL to request. + * @param {_.Options=} opt_options Configuration options for the request. + * @return {!Result} A result object that will be resolved + * with the response JSON once the request finishes. + */ +_.getJson = function(url, opt_options) { + var result = _.send('GET', url, null, opt_options); + var transformedResult = _.addJsonParsingCallbacks_(result, opt_options); + return transformedResult; +}; - /** - * Sends a request using XMLHttpRequest and returns a deferred. - * - * @param {string} method The HTTP method for the request. - * @param {string} url The URL to request. - * @param {_.PostData} data The body of the post request. - * @param {_.Options=} opt_options Configuration options for the request. - * @return {!Deferred} A deferred object that will be called back - * with the XHR object when the request finishes. - */ - _.send = function(method, url, data, opt_options) { +/** + * Sends a post request, returning a result which will be resolved with + * the parsed response text once the request completes. + * + * @param {string} url The URL to request. + * @param {_.PostData} data The body of the post request. + * @param {_.Options=} opt_options Configuration options for the request. + * @return {!Result} A result object that will be resolved + * with the response JSON once the request finishes. + */ +_.postJson = function(url, data, opt_options) { + var result = _.send('POST', url, data, opt_options); + var transformedResult = _.addJsonParsingCallbacks_(result, opt_options); + return transformedResult; +}; - // When the deferred is cancelled, we abort the XHR. We want to make sure - // the readystatechange event still fires, so it can do the timeout - // cleanup, however we don't want the callback or errback to be called - // again. Thus the slight ugliness here. If deferreds were pushed into - // makeRequest, this could become a lot cleaner but we want an option for - // people not to include goog.async.Deferred. - function cancel() { - cancelled = true; +/** + * Sends a request using XMLHttpRequest and returns a result. + * + * @param {string} method The HTTP method for the request. + * @param {string} url The URL to request. + * @param {_.PostData} data The body of the post request. + * @param {_.Options=} opt_options Configuration options for the request. + * @return {!Result} A result object that will be resolved + * with the XHR object as it's value when the request finishes. + */ +_.send = function(method, url, data, opt_options) { + + var result = new SimpleResult(); + + // When the deferred is cancelled, we abort the XHR. We want to make sure + // the readystatechange event still fires, so it can do the timeout + // cleanup, however we don't want the callback or errback to be called + // again. Thus the slight ugliness here. If results were pushed into + // makeRequest, this could become a lot cleaner but we want an option for + // people not to include goog.result.Result. + goog.result.waitOnError(result, function(result) { + if (result.isCanceled()) { xhr.abort(); xhr.onreadystatechange = goog.nullFunction; - // TODO(user): We could return an AbortedError if we wanted to. } + }); - function callback(data) { - if (!cancelled) { - d.callback(data); - } - } + function callback(data) { + result.setValue(data); + } + + function errback(err) { + result.setError(err); + } - function errback(err) { - if (!cancelled) { - d.errback(err); + var xhr = _.makeRequest(method, url, data, opt_options, callback, errback); + + return result; +}; + + +/** + * Creates a new XMLHttpRequest and initiates a request. + * + * @param {string} method The HTTP method for the request. + * @param {string} url The URL to request. + * @param {_.PostData} data The body of the post request, unless the content + * type is explicitly set in the Options, then it will default to form + * urlencoded. + * @param {_.Options=} opt_options Configuration options for the request. + * @param {function(XMLHttpRequest)=} opt_callback Optional callback to call + * when the request completes. + * @param {function(Error)=} opt_errback Optional callback to call + * when there is an error. + * @return {!XMLHttpRequest} The new XMLHttpRequest. + */ +_.makeRequest = function( + method, url, data, opt_options, opt_callback, opt_errback) { + var options = opt_options || {}; + var callback = opt_callback || goog.nullFunction; + var errback = opt_errback || goog.nullFunction; + var timer; + + var xhr = /** @type {!XMLHttpRequest} */ (goog.net.XmlHttp()); + try { + xhr.open(method, url, true); + } catch (e) { + // XMLHttpRequest.open may throw when 'open' is called, for example, IE7 + // throws "Access Denied" for cross-origin requests. + errback(new _.Error('Error opening XHR: ' + e.message, url, xhr)); + return xhr; + } + + // So sad that IE doesn't support onload and onerror. + xhr.onreadystatechange = function() { + if (xhr.readyState == goog.net.XmlHttp.ReadyState.COMPLETE) { + window.clearTimeout(timer); + if (HttpStatus.isSuccess(xhr.status) || + xhr.status === 0 && !_.isEffectiveSchemeHttp_(url)) { + callback(xhr); + } else { + errback(new _.HttpError(xhr.status, url, xhr)); } } + }; - var cancelled = false; - var d = new Deferred(cancel); - var xhr = _.makeRequest(method, url, data, opt_options, callback, errback); + // Set the headers. + var contentTypeIsSet = false; + if (options.headers) { + for (var key in options.headers) { + xhr.setRequestHeader(key, options.headers[key]); + } + contentTypeIsSet = _.CONTENT_TYPE_HEADER in options.headers; + } + + // If a content type hasn't been set, default to form-urlencoded/UTF8 for + // POSTs. This is because some proxies have been known to reject posts + // without a content-type. + if (method == 'POST' && !contentTypeIsSet) { + xhr.setRequestHeader(_.CONTENT_TYPE_HEADER, _.FORM_CONTENT_TYPE); + } + + // Set whether to pass cookies on cross-domain requests (if applicable). + // @see http://dev.w3.org/2006/webapi/XMLHttpRequest-2/#the-withcredentials-attribute + if (options.withCredentials) { + xhr.withCredentials = options.withCredentials; + } + + // Allow the request to override the mime type, useful for getting binary + // data from the server. e.g. 'text/plain; charset=x-user-defined'. + // @see http://dev.w3.org/2006/webapi/XMLHttpRequest-2/#dom-xmlhttprequest-overridemimetype + if (options.mimeType) { + xhr.overrideMimeType(options.mimeType); + } + + // Handle timeouts, if requested. + if (options.timeoutMs > 0) { + timer = window.setTimeout(function() { + // Clear event listener before aborting so the errback will not be + // called twice. + xhr.onreadystatechange = goog.nullFunction; + xhr.abort(); + errback(new _.TimeoutError(url, xhr)); + }, options.timeoutMs); + } - return d; - }; + // Trigger the send. + try { + xhr.send(data); + } catch (e) { + // XMLHttpRequest.send is known to throw on some versions of FF, for example + // if a cross-origin request is disallowed. + errback(new _.Error('Error sending XHR: ' + e.message, url, xhr)); + } + return xhr; +}; - /** - * Creates a new XMLHttpRequest and initiates a request. - * - * @param {string} method The HTTP method for the request. - * @param {string} url The URL to request. - * @param {_.PostData} data The body of the post request, unless the content - * type is explicitly set in the Options, then it will default to form - * urlencoded. - * @param {_.Options=} opt_options Configuration options for the request. - * @param {function(XMLHttpRequest)=} opt_callback Optional callback to call - * when the request completes. - * @param {function(Error)=} opt_errback Optional callback to call - * when there is an error. - * @return {!XMLHttpRequest} The new XMLHttpRequest. - */ - _.makeRequest = function( - method, url, data, opt_options, opt_callback, opt_errback) { - var options = opt_options || {}; - var callback = opt_callback || goog.nullFunction; - var errback = opt_errback || goog.nullFunction; - var timer; - - var xhr = /** @type {!XMLHttpRequest} */ (goog.net.XmlHttp()); - try { - xhr.open(method, url, true); - } catch (e) { - // XMLHttpRequest.open may throw when 'open' is called, for example, IE7 - // throws "Access Denied" for cross-origin requests. - errback(new _.Error('Error opening XHR: ' + e.message, url, xhr)); - return xhr; - } - // So sad that IE doesn't support onload and onerror. - xhr.onreadystatechange = function() { - if (xhr.readyState == goog.net.XmlHttp.ReadyState.COMPLETE) { - window.clearTimeout(timer); - if (HttpStatus.isSuccess(xhr.status) || - xhr.status === 0 && !_.isEffectiveSchemeHttp_(url)) { - callback(xhr); - } else { - errback(new _.HttpError(xhr.status, url, xhr)); - } - } - }; +/** + * @param {string} url The URL to test. + * @return {boolean} Whether the effective scheme is HTTP or HTTPs. + * @private + */ +_.isEffectiveSchemeHttp_ = function(url) { + var scheme = goog.uri.utils.getEffectiveScheme(url); + // NOTE(user): Empty-string is for the case under FF3.5 when the location + // is not defined inside a web worker. + return scheme == 'http' || scheme == 'https' || scheme == ''; +}; - // Set the headers. - var contentTypeIsSet = false; - if (options.headers) { - for (var key in options.headers) { - xhr.setRequestHeader(key, options.headers[key]); - } - contentTypeIsSet = _.CONTENT_TYPE_HEADER in options.headers; - } - // If a content type hasn't been set, default to form-urlencoded/UTF8 for - // POSTs. This is because some proxies have been known to reject posts - // without a content-type. - if (method == 'POST' && !contentTypeIsSet) { - xhr.setRequestHeader(_.CONTENT_TYPE_HEADER, _.FORM_CONTENT_TYPE); - } +/** + * Returns the response text of an XHR object. Intended to be called when + * the result resolves. + * + * @param {!XMLHttpRequest} xhr The XHR object. + * @return {string} The response text. + * @private + */ +_.getResponseText_ = function(xhr) { + return xhr.responseText; +}; - // Set whether to pass cookies on cross-domain requests (if applicable). - // @see http://dev.w3.org/2006/webapi/XMLHttpRequest-2/#the-withcredentials-attribute - if (options.withCredentials) { - xhr.withCredentials = options.withCredentials; - } - // Allow the request to override the mime type, useful for getting binary - // data from the server. e.g. 'text/plain; charset=x-user-defined'. - // @see http://dev.w3.org/2006/webapi/XMLHttpRequest-2/#dom-xmlhttprequest-overridemimetype - if (options.mimeType) { - xhr.overrideMimeType(options.mimeType); - } +/** + * Transforms a result, parsing the JSON in the original result value's + * responseText. The transformed result's value is a javascript object. + * Parse errors resolve the transformed result in an error. + * + * @param {!Result} result The result to wait on. + * @param {_.Options|undefined} options The options object. + * + * @return {!Result} The transformed result. + * @private + */ +_.addJsonParsingCallbacks_ = function(result, options) { + var resultWithResponseText = goog.result.transform(result, + _.getResponseText_); + var prefixStrippedResult = resultWithResponseText; + if (options && options.xssiPrefix) { + prefixStrippedResult = goog.result.transform(resultWithResponseText, + goog.partial(_.stripXssiPrefix_, options.xssiPrefix)); + } - // Handle timeouts, if requested. - if (options.timeoutMs > 0) { - timer = window.setTimeout(function() { - // Clear event listener before aborting so the errback will not be - // called twice. - xhr.onreadystatechange = goog.nullFunction; - xhr.abort(); - errback(new _.TimeoutError(url, xhr)); - }, options.timeoutMs); - } + var jsonParsedResult = goog.result.transform(prefixStrippedResult, + goog.json.parse); + return jsonParsedResult; +}; - // Trigger the send. - try { - xhr.send(data); - } catch (e) { - // XMLHttpRequest.send is known to throw on some versions of FF, for example - // if a cross-origin request is disallowed. - errback(new _.Error('Error sending XHR: ' + e.message, url, xhr)); - } - return xhr; - }; +/** + * Strips the XSSI prefix from the input string. + * + * @param {string} prefix The XSSI prefix. + * @param {string} string The string to strip the prefix from. + * @return {string} The input string without the prefix. + * @private + */ +_.stripXssiPrefix_ = function(prefix, string) { + if (goog.string.startsWith(string, prefix)) { + string = string.substring(prefix.length); + } + return string; +}; - /** - * @param {string} url The URL to test. - * @return {boolean} Whether the effective scheme is HTTP or HTTPs. - * @private - */ - _.isEffectiveSchemeHttp_ = function(url) { - var scheme = goog.uri.utils.getEffectiveScheme(url); - // NOTE(user): Empty-string is for the case under FF3.5 when the location - // is not defined inside a web worker. - return scheme == 'http' || scheme == 'https' || scheme == ''; - }; +/** + * Generic error that may occur during a request. + * + * @param {string} message The error message. + * @param {string} url The URL that was being requested. + * @param {!XMLHttpRequest} xhr The XMLHttpRequest that failed. + * @extends {goog.debug.Error} + * @constructor + */ +_.Error = function(message, url, xhr) { + goog.base(this, message + ', url=' + url); /** - * Returns the response text of an XHR object. Intended for use as a callback - * on a deferred. - * - * @param {!XMLHttpRequest} xhr The XHR object. - * @return {string} The response text. - * @private + * The URL that was requested. + * @type {string} */ - _.getResponseText_ = function(xhr) { - return xhr.responseText; - }; - + this.url = url; /** - * Adds the callbacks required for parsing a response as JSON. The chain will - * expect an XMLHttpRequest and will result in a JS object. Parse errors will - * trigger the errback chain. - * - * @param {!Deferred} d The deferred to add callbacks to. - * @param {_.Options|undefined} options The options object. - * @private + * The XMLHttpRequest corresponding with the failed request. + * @type {!XMLHttpRequest} */ - _.addJsonParsingCallbacks_ = function(d, options) { - d.addCallback(_.getResponseText_); - if (options && options.xssiPrefix) { - d.addCallback(goog.partial(_.stripXssiPrefix_, options.xssiPrefix)); - } - d.addCallback(goog.json.parse); - }; + this.xhr = xhr; +}; +goog.inherits(_.Error, goog.debug.Error); - /** - * Strips the XSSI prefix from the input string. - * - * @param {string} prefix The XSSI prefix. - * @param {string} string The string to strip the prefix from. - * @return {string} The input string without the prefix. - * @private - */ - _.stripXssiPrefix_ = function(prefix, string) { - if (goog.string.startsWith(string, prefix)) { - string = string.substring(prefix.length); - } - return string; - }; +/** @override */ +_.Error.prototype.name = 'XhrError'; +/** + * Class for HTTP errors. + * + * @param {number} status The HTTP status code of the response. + * @param {string} url The URL that was being requested. + * @param {!XMLHttpRequest} xhr The XMLHttpRequest that failed. + * @extends {_.Error} + * @constructor + */ +_.HttpError = function(status, url, xhr) { + goog.base(this, 'Request Failed, status=' + status, url, xhr); + /** - * Generic error that may occur during a request. - * - * @param {string} message The error message. - * @param {string} url The URL that was being requested. - * @param {!XMLHttpRequest} xhr The XMLHttpRequest that failed. - * @extends {goog.debug.Error} - * @constructor + * The HTTP status code for the error. + * @type {number} */ - _.Error = function(message, url, xhr) { - goog.base(this, message + ', url=' + url); - - /** - * The URL that was requested. - * @type {string} - */ - this.url = url; - - /** - * The XMLHttpRequest corresponding with the failed request. - * @type {!XMLHttpRequest} - */ - this.xhr = xhr; - }; - goog.inherits(_.Error, goog.debug.Error); + this.status = status; +}; +goog.inherits(_.HttpError, _.Error); +/** @override */ +_.HttpError.prototype.name = 'XhrHttpError'; - /** - * Class for HTTP errors. - * - * @param {number} status The HTTP status code of the response. - * @param {string} url The URL that was being requested. - * @param {!XMLHttpRequest} xhr The XMLHttpRequest that failed. - * @extends {_.Error} - * @constructor - */ - _.HttpError = function(status, url, xhr) { - goog.base(this, 'Request Failed, status=' + status, url, xhr); - - /** - * The HTTP status code for the error. - * @type {number} - */ - this.status = status; - }; - goog.inherits(_.HttpError, _.Error); +/** + * Class for Timeout errors. + * + * @param {string} url The URL that timed out. + * @param {!XMLHttpRequest} xhr The XMLHttpRequest that failed. + * @extends {_.Error} + * @constructor + */ +_.TimeoutError = function(url, xhr) { + goog.base(this, 'Request timed out', url, xhr); +}; +goog.inherits(_.TimeoutError, _.Error); - /** - * Class for Timeout errors. - * - * @param {string} url The URL that timed out. - * @param {!XMLHttpRequest} xhr The XMLHttpRequest that failed. - * @extends {_.Error} - * @constructor - */ - _.TimeoutError = function(url, xhr) { - goog.base(this, 'Request timed out', url, xhr); - }; - goog.inherits(_.TimeoutError, _.Error); -}); +/** @override */ +_.TimeoutError.prototype.name = 'XhrTimeoutError'; + +}); // goog.scope diff --git a/contexts/data/lib/closure-library/closure/goog/labs/net/xhr_test.html b/contexts/data/lib/closure-library/closure/goog/labs/net/xhr_test.html index 601e02d..992a646 100644 --- a/contexts/data/lib/closure-library/closure/goog/labs/net/xhr_test.html +++ b/contexts/data/lib/closure-library/closure/goog/labs/net/xhr_test.html @@ -14,6 +14,7 @@ See the COPYING file for details. <script src="../../base.js"></script> <script> goog.require('goog.labs.net.xhr'); + goog.require('goog.result'); goog.require('goog.string'); goog.require('goog.testing.AsyncTestCase'); goog.require('goog.testing.MockClock'); @@ -101,50 +102,51 @@ See the COPYING file for details. if (shouldRunLocally) return; testCase.waitForAsync('simpleRequest'); - xhr.send('GET', 'testdata/xhr_test_text.data'). - addCallback(function(xhr) { + var result = xhr.send('GET', 'testdata/xhr_test_text.data'); + goog.result.waitOnSuccess(result, + function(xhr) { assertEquals('Just some data.', xhr.responseText); assertEquals(200, xhr.status); testCase.continueTesting(); - }). - addErrback(fail); + }); + goog.result.waitOnError(result, fail); } function testGetText() { if (shouldRunLocally) return; testCase.waitForAsync('getText'); - xhr.get('testdata/xhr_test_text.data'). - addCallback(function(content) { - assertEquals('Just some data.', content); - testCase.continueTesting(); - }). - addErrback(fail); + var result = xhr.get('testdata/xhr_test_text.data'); + goog.result.waitOnSuccess(result, function(content) { + assertEquals('Just some data.', content); + testCase.continueTesting(); + }); + goog.result.waitOnError(result, fail); } function testGetTextWithJson() { if (shouldRunLocally) return; testCase.waitForAsync('getTextWithJson'); - xhr.get('testdata/xhr_test_json.data'). - addCallback(function(content) { - assertEquals('while(1);\n{"stat":"ok","count":12345}\n', content); - testCase.continueTesting(); - }). - addErrback(fail); + var result = xhr.get('testdata/xhr_test_json.data'); + goog.result.waitOnSuccess(result, function(content) { + assertEquals('while(1);\n{"stat":"ok","count":12345}\n', content); + testCase.continueTesting(); + }); + goog.result.waitOnError(result, fail); } function testPostText() { if (shouldRunLocally) return; testCase.waitForAsync('postText'); - xhr.post('testdata/xhr_test_text.data', 'post-data'). - addCallback(function(content) { - // No good way to test post-data gets transported. - assertEquals('Just some data.', content); - testCase.continueTesting(); - }). - addErrback(fail); + var result = xhr.post('testdata/xhr_test_text.data', 'post-data'); + goog.result.waitOnSuccess(result, function(content) { + // No good way to test post-data gets transported. + assertEquals('Just some data.', content); + testCase.continueTesting(); + }); + goog.result.waitOnError(result, fail); } function testGetJson() { @@ -163,19 +165,19 @@ See the COPYING file for details. function testSerialRequests() { if (shouldRunLocally) return; - var d = xhr.get('testdata/xhr_test_text.data'). - addCallback(function(txt) { + var result = xhr.get('testdata/xhr_test_text.data'); + var chainedResult = goog.result.chain(result, function(result) { return xhr.getJson( 'testdata/xhr_test_json.data', {xssiPrefix: 'while(1);\n'}); }); // Data that comes through to callbacks should be from the 2nd request. - d.addCallback(function(json) { + goog.result.waitOnSuccess(chainedResult, function(json) { assertEquals('ok', json['stat']); assertEquals(12345, json['count']); testCase.continueTesting(); }); - d.addErrback(fail); + goog.result.waitOnError(chainedResult, fail); } function testParallelRequest() { @@ -189,60 +191,66 @@ See the COPYING file for details. jsonData = response; } - var d = goog.async.Deferred.succeed(); - d.awaitDeferred( - xhr.get('testdata/xhr_test_text.data').addCallback(loadText)); - d.awaitDeferred( - xhr.getJson('testdata/xhr_test_json.data', - {xssiPrefix: 'while(1);\n'}).addCallback(loadJson)); - d.addCallback(function() { + var fetchTextResult = xhr.get('testdata/xhr_test_text.data'); + var textDataResult = goog.result.transform(fetchTextResult, loadText); + + var fetchJsonResult = xhr.getJson('testdata/xhr_test_json.data', + {xssiPrefix: 'while(1);\n'}); + var jsonDataResult = goog.result.transform(fetchJsonResult, loadJson); + + var combinedResult = goog.result.combine(textDataResult, + jsonDataResult); + + goog.result.waitOnSuccess(combinedResult, function() { assertEquals('Just some data.', textData); assertEquals('ok', jsonData['stat']); testCase.continueTesting(); }); - d.addErrback(fail); + goog.result.waitOnError(combinedResult, fail); } function testBadUrlDetectedAsError() { if (shouldRunLocally) return; testCase.waitForAsync('badUrl'); - xhr.getJson('unknown-file.dat'). - addCallback(fail). - addErrback(function(err) { - assertTrue('Error should be an HTTP error', - err instanceof xhr.HttpError); - assertEquals(404, err.status); - assertNotNull(err.xhr); - testCase.continueTesting(); - }); + var result = xhr.getJson('unknown-file.dat'); + goog.result.waitOnSuccess(result, fail); + goog.result.waitOnError(result, function(result) { + var err = result.getError(); + assertTrue('Error should be an HTTP error', + err instanceof xhr.HttpError); + assertEquals(404, err.status); + assertNotNull(err.xhr); + testCase.continueTesting(); + }); } function testBadOriginTriggersOnErrorHandler() { testCase.waitForAsync('badOrigin'); - xhr.get('http://www.google.com'). - addCallback(fail). - addErrback(function(err) { - // In IE this will be a goog.labs.net.xhr.Error since it is thrown - // when calling xhr.open(), other browsers will raise an HttpError. - assertTrue('Error should be an xhr error', - err instanceof xhr.Error); - assertNotNull(err.xhr); - testCase.continueTesting(); - }); + var result = xhr.get('http://www.google.com'); + goog.result.waitOnSuccess(result, fail); + goog.result.waitOnError(result, function(result) { + var err = result.getError(); + // In IE this will be a goog.labs.net.xhr.Error since it is thrown + // when calling xhr.open(), other browsers will raise an HttpError. + assertTrue('Error should be an xhr error', + err instanceof xhr.Error); + assertNotNull(err.xhr); + testCase.continueTesting(); + }); } function testAbortRequest() { if (shouldRunLocally) return; var err; - var d = xhr.send('GET', 'test-url', null). - addCallback(fail). - addErrback(function(e) { - err = e; + var result = xhr.send('GET', 'test-url', null); + goog.result.waitOnSuccess(result, fail); + goog.result.waitOnError(result, function(result) { + err = result.getError(); }); - d.cancel(); - assertTrue(err instanceof goog.async.Deferred.CancelledError); + result.cancel(); + assertTrue(err instanceof goog.result.Result.CancelError); } //============================================================================ @@ -252,12 +260,12 @@ See the COPYING file for details. function testSendNoOptions() { var stubXhr = setupStubXMLHttpRequest(); var called = false; - xhr.send('GET', 'test-url', null). - addCallback(function(xhr) { - called = true; - assertEquals('Objects should be equal', xhr, stubXhr); - }). - addErrback(fail); + var result = xhr.send('GET', 'test-url', null); + goog.result.waitOnSuccess(result, function(xhr) { + called = true; + assertEquals('Objects should be equal', xhr, stubXhr); + }); + goog.result.waitOnError(result, fail); assertTrue('XHR should have been sent', stubXhr.sent); assertFalse('Callback should not yet have been called', called); @@ -272,7 +280,8 @@ See the COPYING file for details. function testSendPostSetsDefaultHeader() { var stubXhr = setupStubXMLHttpRequest(); - xhr.send('POST', 'test-url', null).addErrback(fail); + var result = xhr.send('POST', 'test-url', null); + goog.result.waitOnError(result, fail); stubXhr.load(200); @@ -284,9 +293,10 @@ See the COPYING file for details. function testSendPostHeaders() { var stubXhr = setupStubXMLHttpRequest(); - xhr.send('POST', 'test-url', null, { + var result = xhr.send('POST', 'test-url', null, { headers: {'Content-Type': 'text/plain', 'X-Made-Up': 'FooBar'} - }).addErrback(fail); + }); + goog.result.waitOnError(result, fail); stubXhr.load(200); @@ -298,8 +308,8 @@ See the COPYING file for details. function testSendWithCredentials() { var stubXhr = setupStubXMLHttpRequest(); - xhr.send('POST', 'test-url', null, {withCredentials: true}). - addErrback(fail); + var result = xhr.send('POST', 'test-url', null, {withCredentials: true}); + goog.result.waitOnError(result, fail); stubXhr.load(200); assertTrue('XHR should have been sent', stubXhr.sent); assertTrue(stubXhr.withCredentials); @@ -307,8 +317,8 @@ See the COPYING file for details. function testSendWithMimeType() { var stubXhr = setupStubXMLHttpRequest(); - xhr.send('POST', 'test-url', null, {mimeType: 'text/plain'}). - addErrback(fail); + var result = xhr.send('POST', 'test-url', null, {mimeType: 'text/plain'}); + goog.result.waitOnError(result, fail); stubXhr.load(200); assertTrue('XHR should have been sent', stubXhr.sent); @@ -318,9 +328,11 @@ See the COPYING file for details. function testSendWithHttpError() { var stubXhr = setupStubXMLHttpRequest(); var err; - xhr.send('POST', 'test-url', null). - addCallback(fail). - addErrback(function(e) { err = e; }); + var result = xhr.send('POST', 'test-url', null); + goog.result.waitOnSuccess(result, fail); + goog.result.waitOnError(result, function(result) { + err = result.getError(); + }); stubXhr.load(500); @@ -338,7 +350,8 @@ See the COPYING file for details. var stubXhr = setupStubXMLHttpRequest(); var err; - xhr.send('POST', 'test-url', null, {timeoutMs: 1500}).addErrback(fail); + var result = xhr.send('POST', 'test-url', null, {timeoutMs: 1500}); + goog.result.waitOnError(result, fail); assertTrue(mockClock.getTimeoutsMade() > 0); mockClock.tick(1400); stubXhr.load(200); @@ -350,9 +363,9 @@ See the COPYING file for details. function testSendWithTimeoutHit() { var stubXhr = setupStubXMLHttpRequest(); var err; - xhr.send('POST', 'test-url', null, {timeoutMs: 50}) - .addCallback(fail) - .addErrback(function(e) { err = e; }); + var result = xhr.send('POST', 'test-url', null, {timeoutMs: 50}); + goog.result.waitOnSuccess(result, fail); + goog.result.waitOnError(result, function(res) { err = res.getError(); }); assertTrue(mockClock.getTimeoutsMade() > 0); mockClock.tick(50); assertTrue('XHR should have been sent', stubXhr.sent); @@ -375,28 +388,28 @@ See the COPYING file for details. function testCancelRequest() { var stubXhr = setupStubXMLHttpRequest(); var err; - var d = xhr.send('GET', 'test-url', null, {timeoutMs: 50}). - addCallback(fail). - addErrback(function(e) { - err = e; - }); - d.cancel(); + var result = xhr.send('GET', 'test-url', null, {timeoutMs: 50}); + goog.result.waitOnSuccess(result, fail); + goog.result.waitOnError(result, function(result) { + err = result.getError(); + }); + result.cancel(); stubXhr.load(0); // Call load anyway, shoudn't make a difference. mockClock.tick(100); // Timeout should never be called. assertTrue('XHR should have been sent', stubXhr.sent); assertTrue('XHR should have been aborted', stubXhr.aborted); - assertTrue(err instanceof goog.async.Deferred.CancelledError); + assertTrue(err instanceof goog.result.Result.CancelError); } function testGetJson() { var stubXhr = setupStubXMLHttpRequest(); var responseData; - xhr.getJson('test-url'). - addCallback(function(data) { - responseData = data; - }). - addErrback(fail); + var result = xhr.getJson('test-url'); + goog.result.waitOnSuccess(result, function(data) { + responseData = data; + }); + goog.result.waitOnError(result, fail); stubXhr.responseText = '{"a": 1, "b": 2}'; stubXhr.load(200); @@ -407,11 +420,11 @@ See the COPYING file for details. function testGetJsonWithXssiPrefix() { var stubXhr = setupStubXMLHttpRequest(); var responseData; - xhr.getJson('test-url', {xssiPrefix: 'while(1);\n'}). - addCallback(function(data) { - responseData = data; - }). - addErrback(fail); + var result = xhr.getJson('test-url', {xssiPrefix: 'while(1);\n'}); + goog.result.waitOnSuccess(result, function(data) { + responseData = data; + }); + goog.result.waitOnError(result, fail); stubXhr.responseText = 'while(1);\n{"a": 1, "b": 2}'; stubXhr.load(200); diff --git a/contexts/data/lib/closure-library/closure/goog/labs/object/object.js b/contexts/data/lib/closure-library/closure/goog/labs/object/object.js new file mode 100644 index 0000000..e534769 --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/object/object.js @@ -0,0 +1,46 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A labs location for functions destined for Closure's + * {@code goog.object} namespace. + */ + +goog.provide('goog.labs.object'); + + +/** + * Whether two values are not observably distinguishable. This + * correctly detects that 0 is not the same as -0 and two NaNs are + * practically equivalent. + * + * The implementation is as suggested by harmony:egal proposal. + * + * @param {*} v The first value to compare. + * @param {*} v2 The second value to compare. + * @return {boolean} Whether two values are not observably distinguishable. + * @see http://wiki.ecmascript.org/doku.php?id=harmony:egal + */ +goog.labs.object.is = function(v, v2) { + if (v === v2) { + // 0 === -0, but they are not identical. + // We need the cast because the compiler requires that v2 is a + // number (although 1/v2 works with non-number). We cast to ? to + // stop the compiler from type-checking this statement. + return v !== 0 || 1 / v === 1 / /** @type {?} */ (v2); + } + + // NaN is non-reflexive: NaN !== NaN, although they are identical. + return v !== v && v2 !== v2; +}; diff --git a/contexts/data/lib/closure-library/closure/goog/labs/object/object_test.html b/contexts/data/lib/closure-library/closure/goog/labs/object/object_test.html new file mode 100644 index 0000000..d9bd1be --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/object/object_test.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html> +<!-- +Copyright 2012 The Closure Library Authors. All Rights Reserved. + +Use of this source code is governed by the Apache License, Version 2.0. +See the COPYING file for details. +--> +<!-- +--> +<head> +<meta http-equiv="X-UA-Compatible" content="IE=edge"> +<title>Closure Unit Tests - goog.labs.object</title> +<script src="../../base.js"></script> +<script> + goog.require('goog.labs.object'); + goog.require('goog.testing.jsunit'); +</script> +</head> +<body> +<script> +function testIs() { + var object = {}; + assertTrue(goog.labs.object.is(object, object)); + assertFalse(goog.labs.object.is(object, {})); + + assertTrue(goog.labs.object.is(NaN, NaN)); + assertTrue(goog.labs.object.is(0, 0)); + assertTrue(goog.labs.object.is(1, 1)); + assertTrue(goog.labs.object.is(-1, -1)); + assertTrue(goog.labs.object.is(123, 123)); + assertFalse(goog.labs.object.is(0, -0)); + assertFalse(goog.labs.object.is(-0, 0)); + assertFalse(goog.labs.object.is(0, 1)); + + assertTrue(goog.labs.object.is(true, true)); + assertTrue(goog.labs.object.is(false, false)); + assertFalse(goog.labs.object.is(true, false)); + assertFalse(goog.labs.object.is(false, true)); + + assertTrue(goog.labs.object.is('', '')); + assertTrue(goog.labs.object.is('a', 'a')); + assertFalse(goog.labs.object.is('', 'a')); + assertFalse(goog.labs.object.is('a', '')); + assertFalse(goog.labs.object.is('a', 'b')); + + assertFalse(goog.labs.object.is(true, 'true')); + assertFalse(goog.labs.object.is('true', true)); + assertFalse(goog.labs.object.is(false, 'false')); + assertFalse(goog.labs.object.is('false', false)); + assertFalse(goog.labs.object.is(0, '0')); + assertFalse(goog.labs.object.is('0', 0)); +} +</script> +</body> +</html> diff --git a/contexts/data/lib/closure-library/closure/goog/labs/observe/notice.js b/contexts/data/lib/closure-library/closure/goog/labs/observe/notice.js new file mode 100644 index 0000000..157573e --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/observe/notice.js @@ -0,0 +1,63 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Provides a notice object that is used to encapsulates + * information about a particular change/notification on an observable + * object. + */ + +goog.provide('goog.labs.observe.Notice'); + + + +/** + * A notice object encapsulates information about a notification fired + * by an observable. + * @param {!goog.labs.observe.Observable} observable The observable + * object that fires this notice. + * @param {*=} opt_data The optional data associated with this notice. + * @constructor + */ +goog.labs.observe.Notice = function(observable, opt_data) { + /** + * @type {!goog.labs.observe.Observable} + * @private + */ + this.observable_ = observable; + + /** + * @type {*} + * @private + */ + this.data_ = opt_data; +}; + + +/** + * @return {!goog.labs.observe.Observable} The observable object that + * fires this notice. + */ +goog.labs.observe.Notice.prototype.getObservable = function() { + return this.observable_; +}; + + +/** + * @return {*} The optional data associated with this notice. May be + * null/undefined. + */ +goog.labs.observe.Notice.prototype.getData = function() { + return this.data_; +}; diff --git a/contexts/data/lib/closure-library/closure/goog/labs/observe/observable.js b/contexts/data/lib/closure-library/closure/goog/labs/observe/observable.js new file mode 100644 index 0000000..2ca576f --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/observe/observable.js @@ -0,0 +1,77 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Experimental observer-observable API. This is + * intended as super lightweight replacement of + * goog.events.EventTarget when w3c event model bubble/capture + * behavior is not required. + * + * This is similar to {@code goog.pubsub.PubSub} but with different + * intent and naming so that it is more discoverable. The API is + * tighter while allowing for more flexibility offered by the + * interface {@code Observable}. + * + * WARNING: This is still highly experimental. Please contact author + * before using this. + * + */ + +goog.provide('goog.labs.observe.Observable'); + +goog.require('goog.disposable.IDisposable'); + + + +/** + * Interface for an observable object. + * @interface + * @extends {goog.disposable.IDisposable} + */ +goog.labs.observe.Observable = function() {}; + + +/** + * Registers an observer on the observable. + * + * Note that no guarantee is provided on order of execution of the + * observers. For a single notification, one Notice object is reused + * across all invoked observers. + * + * Note that if an observation with the same observer is already + * registered, it will not be registered again. Comparison is done via + * observer's {@code equals} method. + * + * @param {!goog.labs.observe.Observer} observer The observer to add. + * @return {boolean} Whether the observer was successfully added. + */ +goog.labs.observe.Observable.prototype.observe = function(observer) {}; + + +/** + * Unregisters an observer from the observable. The parameter must be + * the same as those passed to {@code observe} method. Comparison is + * done via observer's {@code equals} method. + * @param {!goog.labs.observe.Observer} observer The observer to remove. + * @return {boolean} Whether the observer is removed. + */ +goog.labs.observe.Observable.prototype.unobserve = function(observer) {}; + + +/** + * Notifies observers by invoking them. Optionally, a data object may be + * given to be passed to each observer. + * @param {*=} opt_data An optional data object. + */ +goog.labs.observe.Observable.prototype.notify = function(opt_data) {}; diff --git a/contexts/data/lib/closure-library/closure/goog/labs/observe/observableset.js b/contexts/data/lib/closure-library/closure/goog/labs/observe/observableset.js new file mode 100644 index 0000000..8ce844e --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/observe/observableset.js @@ -0,0 +1,180 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A set of {@code goog.labs.observe.Observable}s that + * allow registering and removing observers to all of the observables + * in the set. + */ + +goog.provide('goog.labs.observe.ObservableSet'); + +goog.require('goog.array'); +goog.require('goog.labs.observe.Observer'); + + + +/** + * Creates a set of observables. + * + * An ObservableSet is a collection of observables. Observers may be + * reigstered and will receive notifications when any of the + * observables notify. This class is meant to simplify management of + * observations on multiple observables of the same nature. + * + * @constructor + */ +goog.labs.observe.ObservableSet = function() { + /** + * The observers registered with this set. + * @type {!Array.<!goog.labs.observe.Observer>} + * @private + */ + this.observers_ = []; + + /** + * The observables in this set. + * @type {!Array.<!goog.labs.observe.Observable>} + * @private + */ + this.observables_ = []; +}; + + +/** + * Adds an observer that observes all observables in the set. If new + * observables are added to or removed from the set, the observer will + * be registered or unregistered accordingly. + * + * The observer will not be added if there is already an equivalent + * observer. + * + * @param {!goog.labs.observe.Observer} observer The observer to invoke. + * @return {boolean} Whether the observer is actually added. + */ +goog.labs.observe.ObservableSet.prototype.addObserver = function(observer) { + // Check whether the observer already exists. + if (goog.array.find(this.observers_, goog.partial( + goog.labs.observe.Observer.equals, observer))) { + return false; + } + + this.observers_.push(observer); + goog.array.forEach(this.observables_, function(o) { + o.observe(observer); + }); + return true; +}; + + +/** + * Removes an observer from the set. The observer will be removed from + * all observables in the set. Does nothing if the observer is not in + * the set. + * @param {!goog.labs.observe.Observer} observer The observer to remove. + * @return {boolean} Whether the observer is actually removed. + */ +goog.labs.observe.ObservableSet.prototype.removeObserver = function(observer) { + // Check that the observer exists before removing. + var removed = goog.array.removeIf(this.observers_, goog.partial( + goog.labs.observe.Observer.equals, observer)); + + if (removed) { + goog.array.forEach(this.observables_, function(o) { + o.unobserve(observer); + }); + } + return removed; +}; + + +/** + * Removes all registered observers. + */ +goog.labs.observe.ObservableSet.prototype.removeAllObservers = function() { + this.unregisterAll_(); + this.observers_.length = 0; +}; + + +/** + * Adds an observable to the set. All previously added and future + * observers will be added to the new observable as well. + * + * The observable will not be added if it is already registered in the + * set. + * + * @param {!goog.labs.observe.Observable} observable The observable to add. + * @return {boolean} Whether the observable is actually added. + */ +goog.labs.observe.ObservableSet.prototype.addObservable = function(observable) { + if (goog.array.contains(this.observables_, observable)) { + return false; + } + + this.observables_.push(observable); + goog.array.forEach(this.observers_, function(observer) { + observable.observe(observer); + }); + return true; +}; + + +/** + * Removes an observable from the set. All observers registered on the + * set will be removed from the observable as well. + * @param {!goog.labs.observe.Observable} observable The observable to remove. + * @return {boolean} Whether the observable is actually removed. + */ +goog.labs.observe.ObservableSet.prototype.removeObservable = function( + observable) { + var removed = goog.array.remove(this.observables_, observable); + if (removed) { + goog.array.forEach(this.observers_, function(observer) { + observable.unobserve(observer); + }); + } + return removed; +}; + + +/** + * Removes all registered observables. + */ +goog.labs.observe.ObservableSet.prototype.removeAllObservables = function() { + this.unregisterAll_(); + this.observables_.length = 0; +}; + + +/** + * Removes all registered observations and observables. + */ +goog.labs.observe.ObservableSet.prototype.removeAll = function() { + this.removeAllObservers(); + this.observables_.length = 0; +}; + + +/** + * Unregisters all registered observers from all registered observables. + * @private + */ +goog.labs.observe.ObservableSet.prototype.unregisterAll_ = function() { + goog.array.forEach(this.observers_, function(observer) { + goog.array.forEach(this.observables_, function(o) { + o.unobserve(observer); + }); + }, this); +}; diff --git a/contexts/data/lib/closure-library/closure/goog/labs/observe/observableset_test.html b/contexts/data/lib/closure-library/closure/goog/labs/observe/observableset_test.html new file mode 100644 index 0000000..fac989d --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/observe/observableset_test.html @@ -0,0 +1,207 @@ +<!DOCTYPE html> +<html> +<!-- +Copyright 2012 The Closure Library Authors. All Rights Reserved. + +Use of this source code is governed by the Apache License, Version 2.0. +See the COPYING file for details. +--> +<!-- +--> +<head> +<meta http-equiv="X-UA-Compatible" content="IE=edge"> +<title>Closure Unit Tests - goog.labs.observe.ObservationSet</title> +<script src="../../base.js"></script> +<script> + goog.require('goog.dispose'); + goog.require('goog.labs.observe.ObservableSet'); + goog.require('goog.labs.observe.Observer'); + goog.require('goog.labs.observe.SimpleObservable'); + goog.require('goog.testing.jsunit'); + goog.require('goog.testing.recordFunction'); +</script> +</head> +<body> +<script> +var observable1; +var observable2; +var observableSet; + + +function setUp() { + observable1 = new goog.labs.observe.SimpleObservable(); + observable2 = new goog.labs.observe.SimpleObservable(); + observableSet = new goog.labs.observe.ObservableSet(); +} + + +function tearDown() { + goog.dispose(observable1); + goog.dispose(observable2); +} + + +function testAddingObservers() { + var observerFn1 = goog.testing.recordFunction(); + var observerFn2 = goog.testing.recordFunction(); + assertTrue(observableSet.addObserver( + goog.labs.observe.Observer.fromFunction(observerFn1))); + assertFalse(observableSet.addObserver( + goog.labs.observe.Observer.fromFunction(observerFn1))); + assertTrue(observableSet.addObserver( + goog.labs.observe.Observer.fromFunction(observerFn2))); + assertFalse(observableSet.addObserver( + goog.labs.observe.Observer.fromFunction(observerFn2))); +} + + +function testAddingObservables() { + assertTrue(observableSet.addObservable(observable1)); + assertFalse(observableSet.addObservable(observable1)); + assertTrue(observableSet.addObservable(observable2)); +} + + +function testRemovingObservers() { + var observerFn = goog.testing.recordFunction(); + observableSet.addObserver( + goog.labs.observe.Observer.fromFunction(observerFn)); + observableSet.addObservable(observable1); + observableSet.addObservable(observable2); + + assertTrue(observableSet.removeObserver( + goog.labs.observe.Observer.fromFunction(observerFn))); + assertFalse(observableSet.removeObserver( + goog.labs.observe.Observer.fromFunction(observerFn))); + + observable1.notify(); + observable2.notify(); + assertEquals(0, observerFn.getCallCount()); +} + + +function testRemovingObservables() { + var observerFn = goog.testing.recordFunction(); + observableSet.addObservable(observable1); + observableSet.addObserver( + goog.labs.observe.Observer.fromFunction(observerFn)); + observableSet.addObserver( + goog.labs.observe.Observer.fromFunction(observerFn)); + + assertTrue(observableSet.removeObservable(observable1)); + assertFalse(observableSet.removeObservable(observable1)); + + observable1.notify(); + observable1.notify(); + assertEquals(0, observerFn.getCallCount()); +} + + +function testAddingObserverBeforeAddingObservable() { + var observerFn = goog.testing.recordFunction(); + observableSet.addObserver( + goog.labs.observe.Observer.fromFunction(observerFn)); + + observableSet.addObservable(observable1); + observableSet.addObservable(observable2); + + observable1.notify(); + assertEquals(1, observerFn.getCallCount()); + observable2.notify(); + assertEquals(2, observerFn.getCallCount()); +} + + +function testAddingObserverAfterAddingObservable() { + var observerFn1 = goog.testing.recordFunction(); + var observerFn2 = goog.testing.recordFunction(); + + observableSet.addObservable(observable1); + + observableSet.addObserver( + goog.labs.observe.Observer.fromFunction(observerFn1)); + observableSet.addObserver( + goog.labs.observe.Observer.fromFunction(observerFn2)); + + observable1.notify(); + assertEquals(1, observerFn1.getCallCount()); + assertEquals(1, observerFn2.getCallCount()); +} + + +function testRemoveAllObservers() { + var observerFn1 = goog.testing.recordFunction(); + var observerFn2 = goog.testing.recordFunction(); + + observableSet.addObservable(observable1); + observableSet.addObservable(observable2); + observableSet.addObserver( + goog.labs.observe.Observer.fromFunction(observerFn1)); + observableSet.addObserver( + goog.labs.observe.Observer.fromFunction(observerFn2)); + + observableSet.removeAllObservers(); + + observable1.notify(); + observable2.notify(); + assertEquals(0, observerFn1.getCallCount()); + + // Ensures that the observables are still around. + observableSet.addObserver( + goog.labs.observe.Observer.fromFunction(observerFn1)); + observable1.notify(); + observable2.notify(); + assertEquals(2, observerFn1.getCallCount()); +} + + +function testRemoveAllObservables() { + var observerFn1 = goog.testing.recordFunction(); + var observerFn2 = goog.testing.recordFunction(); + + observableSet.addObservable(observable1); + observableSet.addObservable(observable2); + observableSet.addObserver( + goog.labs.observe.Observer.fromFunction(observerFn1)); + observableSet.addObserver( + goog.labs.observe.Observer.fromFunction(observerFn2)); + + observableSet.removeAllObservables(); + + observable1.notify(); + observable2.notify(); + assertEquals(0, observerFn1.getCallCount()); + assertEquals(0, observerFn2.getCallCount()); + + // Ensures that the observers are still around. + observableSet.addObservable(observable1); + observable1.notify(); + assertEquals(1, observerFn1.getCallCount()); + assertEquals(1, observerFn2.getCallCount()); +} + + +function testRemoveAll() { + var observerFn1 = goog.testing.recordFunction(); + var observerFn2 = goog.testing.recordFunction(); + + observableSet.addObservable(observable1); + observableSet.addObservable(observable2); + observableSet.addObserver( + goog.labs.observe.Observer.fromFunction(observerFn1)); + observableSet.addObserver( + goog.labs.observe.Observer.fromFunction(observerFn2)); + + observableSet.removeAll(); + + observable1.notify(); + observable2.notify(); + assertEquals(0, observerFn1.getCallCount()); + + assertEquals(0, observableSet.observers_.length); + assertEquals(0, observableSet.observables_.length); +} + +</script> +</body> +</html> diff --git a/contexts/data/lib/closure-library/closure/goog/labs/observe/observationset.js b/contexts/data/lib/closure-library/closure/goog/labs/observe/observationset.js new file mode 100644 index 0000000..892c3c6 --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/observe/observationset.js @@ -0,0 +1,156 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A set of observations. This set provides a convenient + * means of observing many observables at once. + * + * This is similar in purpose to {@code goog.events.EventHandler}. + * + */ + +goog.provide('goog.labs.observe.ObservationSet'); + +goog.require('goog.array'); +goog.require('goog.labs.observe.Observer'); + + + +/** + * A set of observations. An observation is defined by an observable + * and an observer. The set keeps track of observations and + * allows their removal. + * @param {!Object=} opt_defaultScope Optional function scope to use + * when using {@code observeWithFunction} and + * {@code unobserveWithFunction}. + * @constructor + */ +goog.labs.observe.ObservationSet = function(opt_defaultScope) { + /** + * @type {!Array.<!goog.labs.observe.ObservationSet.Observation_>} + * @private + */ + this.storedObservations_ = []; + + /** + * @type {!Object|undefined} + * @private + */ + this.defaultScope_ = opt_defaultScope; +}; + + +/** + * Observes the given observer on the observable. + * @param {!goog.labs.observe.Observable} observable The observable to + * observe on. + * @param {!goog.labs.observe.Observer} observer The observer. + * @return {boolean} True if the observer is successfully registered. + */ +goog.labs.observe.ObservationSet.prototype.observe = function( + observable, observer) { + var success = observable.observe(observer); + if (success) { + this.storedObservations_.push( + new goog.labs.observe.ObservationSet.Observation_( + observable, observer)); + } + return success; +}; + + +/** + * Observes the given function on the observable. + * @param {!goog.labs.observe.Observable} observable The observable to + * observe on. + * @param {function(!goog.labs.observe.Notice)} fn The handler function. + * @param {!Object=} opt_scope Optional scope. + * @return {goog.labs.observe.Observer} The registered observer object. + * If the observer is not successfully registered, this will be null. + */ +goog.labs.observe.ObservationSet.prototype.observeWithFunction = function( + observable, fn, opt_scope) { + var observer = goog.labs.observe.Observer.fromFunction( + fn, opt_scope || this.defaultScope_); + if (this.observe(observable, observer)) { + return observer; + } + return null; +}; + + +/** + * Unobserves the given observer from the observable. + * @param {!goog.labs.observe.Observable} observable The observable to + * unobserve from. + * @param {!goog.labs.observe.Observer} observer The observer. + * @return {boolean} True if the observer is successfully removed. + */ +goog.labs.observe.ObservationSet.prototype.unobserve = function( + observable, observer) { + var removed = goog.array.removeIf( + this.storedObservations_, function(o) { + return o.observable == observable && + goog.labs.observe.Observer.equals(o.observer, observer); + }); + + if (removed) { + observable.unobserve(observer); + } + return removed; +}; + + +/** + * Unobserves the given function from the observable. + * @param {!goog.labs.observe.Observable} observable The observable to + * unobserve from. + * @param {function(!goog.labs.observe.Notice)} fn The handler function. + * @param {!Object=} opt_scope Optional scope. + * @return {boolean} True if the observer is successfully removed. + */ +goog.labs.observe.ObservationSet.prototype.unobserveWithFunction = function( + observable, fn, opt_scope) { + var observer = goog.labs.observe.Observer.fromFunction( + fn, opt_scope || this.defaultScope_); + return this.unobserve(observable, observer); +}; + + +/** + * Removes all observations registered through this set. + */ +goog.labs.observe.ObservationSet.prototype.removeAll = function() { + goog.array.forEach(this.storedObservations_, function(observation) { + var observable = observation.observable; + var observer = observation.observer; + observable.unobserve(observer); + }); +}; + + + +/** + * A representation of an observation, which is defined uniquely by + * the observable and observer. + * @param {!goog.labs.observe.Observable} observable The observable. + * @param {!goog.labs.observe.Observer} observer The observer. + * @constructor + * @private + */ +goog.labs.observe.ObservationSet.Observation_ = function( + observable, observer) { + this.observable = observable; + this.observer = observer; +}; diff --git a/contexts/data/lib/closure-library/closure/goog/labs/observe/observationset_test.html b/contexts/data/lib/closure-library/closure/goog/labs/observe/observationset_test.html new file mode 100644 index 0000000..17e0ebe --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/observe/observationset_test.html @@ -0,0 +1,252 @@ +<!DOCTYPE html> +<html> +<!-- +Copyright 2012 The Closure Library Authors. All Rights Reserved. + +Use of this source code is governed by the Apache License, Version 2.0. +See the COPYING file for details. +--> +<!-- +--> +<head> +<meta http-equiv="X-UA-Compatible" content="IE=edge"> +<title>Closure Unit Tests - goog.labs.observe.ObservationSet</title> +<script src="../../base.js"></script> +<script> + goog.require('goog.dispose'); + goog.require('goog.labs.observe.Observer'); + goog.require('goog.labs.observe.ObservationSet'); + goog.require('goog.labs.observe.SimpleObservable'); + goog.require('goog.testing.jsunit'); + goog.require('goog.testing.recordFunction'); +</script> +</head> +<body> +<script> +var observable; +var observable2; +var observationSet; + + +function setUp() { + observable = new goog.labs.observe.SimpleObservable(); + observable2 = new goog.labs.observe.SimpleObservable(); + observationSet = new goog.labs.observe.ObservationSet(); +} + + +function tearDown() { + goog.dispose(observable); + goog.dispose(observable2); +} + + +function testObserveObservesCorrectly() { + var observerFn = goog.testing.recordFunction(); + assertTrue(observationSet.observe( + observable, goog.labs.observe.Observer.fromFunction(observerFn))); + + observable.notify(); + assertEquals(1, observerFn.getCallCount()); +} + + +function testObserveDoesNotRegisterDuplicateObserver() { + var observerFn = goog.testing.recordFunction(); + assertTrue(observationSet.observe( + observable, goog.labs.observe.Observer.fromFunction(observerFn))); + assertFalse(observationSet.observe( + observable, goog.labs.observe.Observer.fromFunction(observerFn))); + + observable.notify(); + assertEquals(1, observerFn.getCallCount()); +} + + +function testObserveWithMultipleObservers() { + var observerFn = goog.testing.recordFunction(); + var observerFn2 = goog.testing.recordFunction(); + assertTrue(observationSet.observe( + observable, goog.labs.observe.Observer.fromFunction(observerFn))); + assertTrue(observationSet.observe( + observable, goog.labs.observe.Observer.fromFunction(observerFn2))); + + observable.notify(); + assertEquals(1, observerFn.getCallCount()); + assertEquals(1, observerFn2.getCallCount()); +} + + +function testObserveWithFunction() { + var observerFn = goog.testing.recordFunction(); + assertNotNull(observationSet.observeWithFunction(observable, observerFn)); + assertNull(observationSet.observeWithFunction(observable, observerFn)); + assertFalse(observationSet.observe( + observable, goog.labs.observe.Observer.fromFunction(observerFn))); + + observable.notify(); + assertEquals(1, observerFn.getCallCount()); +} + + +function testObserveWithFunctionAndScope() { + var scope = {}; + + var observerFn = goog.testing.recordFunction(function() { + assertEquals(scope, this); + }); + assertNotNull(observationSet.observeWithFunction( + observable, observerFn, scope)); + assertNull(observationSet.observeWithFunction( + observable, observerFn, scope)); + assertFalse(observationSet.observe( + observable, goog.labs.observe.Observer.fromFunction(observerFn, scope))); + + observable.notify(); + assertEquals(1, observerFn.getCallCount()); +} + + +function testObserveWithFunctionWithDefaultScope() { + var scope = {}; + observationSet = new goog.labs.observe.ObservationSet(scope); + + var observerFn = goog.testing.recordFunction(function() { + assertEquals(scope, this); + }); + + assertNotNull(observationSet.observeWithFunction(observable, observerFn)); + assertNull(observationSet.observeWithFunction(observable, observerFn)); + assertNull(observationSet.observeWithFunction(observable, observerFn, scope)); + assertFalse(observationSet.observe( + observable, goog.labs.observe.Observer.fromFunction(observerFn, scope))); + + observable.notify(); + assertEquals(1, observerFn.getCallCount()); + + // This should succeed since we use observe method, defaultScope is + // not used in this case. + assertTrue(observationSet.observe( + observable, goog.labs.observe.Observer.fromFunction(observerFn))); + + // This should succeed since the scope is different. + var scope2 = {}; + assertNotNull(observationSet.observeWithFunction( + observable, observerFn, scope2)); +} + + +function testUnobserveUnobservesCorrectly() { + var observerFn = goog.testing.recordFunction(); + assertTrue(observationSet.observe( + observable, goog.labs.observe.Observer.fromFunction(observerFn))); + assertTrue(observationSet.unobserve( + observable, goog.labs.observe.Observer.fromFunction(observerFn))); + + observable.notify(); + assertEquals(0, observerFn.getCallCount()); +} + + +function testUnobserveDoesNotUnobserveObservationNotMadeByObservationSet() { + var observerFn = goog.testing.recordFunction(); + + // Observation via observable directly. + observable.observe(goog.labs.observe.Observer.fromFunction(observerFn)); + + assertFalse(observationSet.unobserve( + observable, goog.labs.observe.Observer.fromFunction(observerFn))); + observable.notify(); + assertEquals(1, observerFn.getCallCount()); + observerFn.reset(); + + // Observation via another ObservationSet. + var observationSet2 = new goog.labs.observe.ObservationSet(); + observationSet2.observe( + observable2, goog.labs.observe.Observer.fromFunction(observerFn)); + + assertFalse(observationSet.unobserve( + observable2, goog.labs.observe.Observer.fromFunction(observerFn))); + observable2.notify(); + assertEquals(1, observerFn.getCallCount()); + observerFn.reset(); +} + + +function testUnobserveWithFunction() { + var observerFn = goog.testing.recordFunction(); + observationSet.observeWithFunction(observable, observerFn); + assertTrue(observationSet.unobserveWithFunction(observable, observerFn)); + assertNotNull(observationSet.observeWithFunction(observable, observerFn)); + assertTrue(observationSet.unobserve( + observable, goog.labs.observe.Observer.fromFunction(observerFn))); + + observable.notify(); + assertEquals(0, observerFn.getCallCount()); +} + + +function testUnobserveWithFunctionAndScope() { + var scope = {}; + + var observerFn = goog.testing.recordFunction(); + observationSet.observeWithFunction( + observable, observerFn, scope); + assertTrue(observationSet.unobserveWithFunction( + observable, observerFn, scope)); + assertNotNull(observationSet.observeWithFunction( + observable, observerFn, scope)); + assertTrue(observationSet.unobserve( + observable, goog.labs.observe.Observer.fromFunction(observerFn, scope))); + + observable.notify(); + assertEquals(0, observerFn.getCallCount()); +} + + +function testUnobserveWithFunctionWithDefaultScope() { + var scope = {}; + observationSet = new goog.labs.observe.ObservationSet(scope); + + var observerFn = goog.testing.recordFunction(); + observationSet.observeWithFunction(observable, observerFn); + assertTrue(observationSet.unobserveWithFunction(observable, observerFn)); + + assertNotNull(observationSet.observeWithFunction( + observable, observerFn, scope)); + assertTrue(observationSet.unobserveWithFunction(observable, observerFn)); + + assertNotNull(observationSet.observeWithFunction(observable, observerFn)); + assertTrue(observationSet.unobserveWithFunction( + observable, observerFn, scope)); + + observable.notify(); + assertEquals(0, observerFn.getCallCount()); +} + + +function testRemoveAllWithZeroObservation() { + observationSet.removeAll(); +} + + +function testRemoveAll() { + var observerFn = goog.testing.recordFunction(); + var observerFn2 = goog.testing.recordFunction(); + assertTrue(observationSet.observe( + observable, goog.labs.observe.Observer.fromFunction(observerFn))); + assertTrue(observationSet.observe( + observable, goog.labs.observe.Observer.fromFunction(observerFn2))); + assertTrue(observationSet.observe( + observable2, goog.labs.observe.Observer.fromFunction(observerFn))); + observationSet.removeAll(); + + observable.notify(); + observable2.notify(); + + assertEquals(0, observerFn.getCallCount()); + assertEquals(0, observerFn2.getCallCount()); +} +</script> +</body> +</html> diff --git a/contexts/data/lib/closure-library/closure/goog/labs/observe/observer.js b/contexts/data/lib/closure-library/closure/goog/labs/observe/observer.js new file mode 100644 index 0000000..ff04430 --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/observe/observer.js @@ -0,0 +1,100 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Provide definition of an observer. This is meant to + * be used with {@code goog.labs.observe.Observable}. + * + * This file also provides convenient functions to compare and create + * Observer objects. + * + */ + +goog.provide('goog.labs.observe.Observer'); + + + +/** + * A class implementing {@code Observer} may be informed of changes in + * observable object. + * @see {goog.labs.observe.Observable} + * @interface + */ +goog.labs.observe.Observer = function() {}; + + +/** + * Notifies the observer of changes to the observable object. + * @param {!goog.labs.observe.Notice} notice The notice object. + */ +goog.labs.observe.Observer.prototype.notify; + + +/** + * Whether this observer is equal to the given observer. + * @param {!goog.labs.observe.Observer} observer The observer to compare with. + * @return {boolean} Whether the two observers are equal. + */ +goog.labs.observe.Observer.prototype.equals; + + +/** + * @param {!goog.labs.observe.Observer} observer1 Observer to compare. + * @param {!goog.labs.observe.Observer} observer2 Observer to compare. + * @return {boolean} Whether observer1 and observer2 are equal, as + * determined by the first observer1's {@code equals} method. + */ +goog.labs.observe.Observer.equals = function(observer1, observer2) { + return observer1 == observer2 || observer1.equals(observer2); +}; + + +/** + * Creates an observer that calls the given function. + * @param {function(!goog.labs.observe.Notice)} fn Function to be converted. + * @param {!Object=} opt_scope Optional scope to execute the function. + * @return {!goog.labs.observe.Observer} An observer object. + */ +goog.labs.observe.Observer.fromFunction = function(fn, opt_scope) { + return new goog.labs.observe.Observer.FunctionObserver_(fn, opt_scope); +}; + + + +/** + * An observer that calls the given function on {@code notify}. + * @param {function(!goog.labs.observe.Notice)} fn Function to delegate to. + * @param {!Object=} opt_scope Optional scope to execute the function. + * @constructor + * @implements {goog.labs.observe.Observer} + * @private + */ +goog.labs.observe.Observer.FunctionObserver_ = function(fn, opt_scope) { + this.fn_ = fn; + this.scope_ = opt_scope; +}; + + +/** @override */ +goog.labs.observe.Observer.FunctionObserver_.prototype.notify = function( + notice) { + this.fn_.call(this.scope_, notice); +}; + + +/** @override */ +goog.labs.observe.Observer.FunctionObserver_.prototype.equals = function( + observer) { + return this.fn_ === observer.fn_ && this.scope_ === observer.scope_; +}; diff --git a/contexts/data/lib/closure-library/closure/goog/labs/observe/observer_test.html b/contexts/data/lib/closure-library/closure/goog/labs/observe/observer_test.html new file mode 100644 index 0000000..3b3e5d2 --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/observe/observer_test.html @@ -0,0 +1,65 @@ +<!DOCTYPE html> +<html> +<!-- +Copyright 2012 The Closure Library Authors. All Rights Reserved. + +Use of this source code is governed by the Apache License, Version 2.0. +See the COPYING file for details. +--> +<!-- +--> +<head> +<meta http-equiv="X-UA-Compatible" content="IE=edge"> +<title>Closure Unit Tests - goog.labs.observe.Observer</title> +<script src="../../base.js"></script> +<script> + goog.require('goog.labs.observe.Notice'); + goog.require('goog.labs.observe.Observer'); + goog.require('goog.testing.jsunit'); + goog.require('goog.testing.recordFunction'); +</script> +</head> +<body> +<script> +var TYPE = 'testtype'; + + +function testToObserver() { + var expectedScope = this; + var notice = new goog.labs.observe.Notice(null, TYPE); + var fn = goog.testing.recordFunction(function(n) { + assertEquals('Function receives wrong notice object.', notice, n); + assertEquals('Function is executed with wrong scope.', expectedScope, this); + }); + var observer = goog.labs.observe.Observer.fromFunction(fn); + observer.notify(notice); + assertEquals(1, fn.getCallCount()); +} + + +function testToObserverWithScope() { + var expectedScope = {}; + var notice = new goog.labs.observe.Notice(null, TYPE); + var fn = goog.testing.recordFunction(function(n) { + assertEquals('Function receives wrong notice object.', notice, n); + assertEquals('Function is executed with wrong scope.', expectedScope, this); + }); + var observer = goog.labs.observe.Observer.fromFunction(fn, expectedScope); + observer.notify(notice); + assertEquals(1, fn.getCallCount()); +} + + +function testToObserverEquals() { + var fn = function() {}; + assertTrue( + goog.labs.observe.Observer.fromFunction(fn).equals( + goog.labs.observe.Observer.fromFunction(fn))); + + var scope = {}; + assertTrue( + goog.labs.observe.Observer.fromFunction(fn, scope).equals( + goog.labs.observe.Observer.fromFunction(fn, scope))); +} +</script> +</body> diff --git a/contexts/data/lib/closure-library/closure/goog/labs/observe/simpleobservable.js b/contexts/data/lib/closure-library/closure/goog/labs/observe/simpleobservable.js new file mode 100644 index 0000000..acc3a84 --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/observe/simpleobservable.js @@ -0,0 +1,129 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview An implementation of {@code Observable} that can be + * used as base class or composed into another class that wants to + * implement {@code Observable}. + */ + +goog.provide('goog.labs.observe.SimpleObservable'); + +goog.require('goog.Disposable'); +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.labs.observe.Notice'); +goog.require('goog.labs.observe.Observable'); +goog.require('goog.labs.observe.Observer'); +goog.require('goog.object'); + + + +/** + * A simple implementation of {@code goog.labs.observe.Observable} that can + * be used as a standalone observable or as a base class for other + * observable object. + * + * When another class wants to implement observable without extending + * {@code SimpleObservable}, they can create an instance of + * {@code SimpleObservable}, specifying {@code opt_actualObservable}, + * and delegate to the instance. Here is a trivial example: + * + * <pre> + * ClassA = function() { + * goog.base(this); + * this.observable_ = new SimpleObservable(this); + * this.registerDisposable(this.observable_); + * }; + * goog.inherits(ClassA, goog.Disposable); + * + * ClassA.prototype.observe = function(observer) { + * this.observable_.observe(observer); + * }; + * + * ClassA.prototype.unobserve = function(observer) { + * this.observable_.unobserve(observer); + * }; + * + * ClassA.prototype.notify = function(opt_data) { + * this.observable_.notify(opt_data); + * }; + * </pre> + * + * @param {!goog.labs.observe.Observable=} opt_actualObservable + * Optional observable object. Defaults to 'this'. When used as + * base class, the parameter need not be given. It is only useful + * when using this class to implement implement {@code Observable} + * interface on another object, see example above. + * @constructor + * @implements {goog.labs.observe.Observable} + * @extends {goog.Disposable} + */ +goog.labs.observe.SimpleObservable = function(opt_actualObservable) { + goog.base(this); + + /** + * @type {!goog.labs.observe.Observable} + * @private + */ + this.actualObservable_ = opt_actualObservable || this; + + /** + * Observers registered on this object. + * @type {!Array.<!goog.labs.observe.Observer>} + * @private + */ + this.observers_ = []; +}; +goog.inherits(goog.labs.observe.SimpleObservable, goog.Disposable); + + +/** @override */ +goog.labs.observe.SimpleObservable.prototype.observe = function(observer) { + goog.asserts.assert(!this.isDisposed()); + + // Registers the (type, observer) only if it has not been previously + // registered. + var shouldRegisterObserver = !goog.array.some(this.observers_, goog.partial( + goog.labs.observe.Observer.equals, observer)); + if (shouldRegisterObserver) { + this.observers_.push(observer); + } + return shouldRegisterObserver; +}; + + +/** @override */ +goog.labs.observe.SimpleObservable.prototype.unobserve = function(observer) { + goog.asserts.assert(!this.isDisposed()); + return goog.array.removeIf(this.observers_, goog.partial( + goog.labs.observe.Observer.equals, observer)); +}; + + +/** @override */ +goog.labs.observe.SimpleObservable.prototype.notify = function(opt_data) { + goog.asserts.assert(!this.isDisposed()); + var notice = new goog.labs.observe.Notice(this.actualObservable_, opt_data); + goog.array.forEach( + goog.array.clone(this.observers_), function(observer) { + observer.notify(notice); + }); +}; + + +/** @override */ +goog.labs.observe.SimpleObservable.prototype.disposeInternal = function() { + this.observers_.length = 0; +}; diff --git a/contexts/data/lib/closure-library/closure/goog/labs/observe/simpleobservable_test.html b/contexts/data/lib/closure-library/closure/goog/labs/observe/simpleobservable_test.html new file mode 100644 index 0000000..288b859 --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/observe/simpleobservable_test.html @@ -0,0 +1,196 @@ +<!DOCTYPE html> +<html> +<!-- +Copyright 2012 The Closure Library Authors. All Rights Reserved. + +Use of this source code is governed by the Apache License, Version 2.0. +See the COPYING file for details. +--> +<!-- +--> +<head> +<meta http-equiv="X-UA-Compatible" content="IE=edge"> +<title>Closure Unit Tests - goog.labs.observe.SimpleObservable</title> +<script src="../../base.js"></script> +<script> + goog.require('goog.dispose'); + goog.require('goog.labs.observe.Observer'); + goog.require('goog.labs.observe.SimpleObservable'); + goog.require('goog.testing.jsunit'); + goog.require('goog.testing.recordFunction'); +</script> +</head> +<body> +<script> +var observable; + + +function setUp() { + observable = new goog.labs.observe.SimpleObservable(); +} + + +function tearDown() { + goog.dispose(observable); +} + + +function testObserve() { + var observerFn = goog.testing.recordFunction(); + observable.observe(goog.labs.observe.Observer.fromFunction(observerFn)); + observable.notify(); + assertEquals(1, observerFn.getCallCount()); +} + + +function testObserveWithTheSameObserver() { + var observerFn = goog.testing.recordFunction(); + var observer = goog.labs.observe.Observer.fromFunction(observerFn); + + assertTrue(observable.observe(observer)); + assertFalse(observable.observe(observer)); + observable.notify(); + assertEquals(1, observerFn.getCallCount()); + observerFn.reset(); + + // observer.equals(observer2) == true + var observer2 = goog.labs.observe.Observer.fromFunction(observerFn); + assertFalse(observable.observe(observer2)); + observable.notify(); + assertEquals(1, observerFn.getCallCount()); +} + + +function testMultipleObservers() { + var observerFn = goog.testing.recordFunction(); + var observerFn2 = goog.testing.recordFunction(); + observable.observe(goog.labs.observe.Observer.fromFunction(observerFn)); + observable.observe(goog.labs.observe.Observer.fromFunction(observerFn2)); + observable.notify(); + assertEquals(1, observerFn.getCallCount()); + assertEquals(1, observerFn2.getCallCount()); +} + + +function testUnobserve() { + var observerFn = goog.testing.recordFunction(); + var observer = goog.labs.observe.Observer.fromFunction(observerFn); + observable.observe(observer); + assertTrue(observable.unobserve(observer)); + observable.notify(); + assertEquals(0, observerFn.getCallCount()); + assertFalse(observable.unobserve(observer)); +} + + +function testNotice() { + var observerFn = goog.testing.recordFunction(function(notice) { + assertNotNull(notice); + assertEquals(observable, notice.getObservable()); + assertUndefined(notice.getData()); + }); + + observable.observe(goog.labs.observe.Observer.fromFunction(observerFn)); + observable.notify(); + assertEquals(1, observerFn.getCallCount()); +} + + +function testNoticeWithData() { + var data = {foo: 1}; + var observerFn = goog.testing.recordFunction(function(notice) { + assertNotNull(notice); + assertEquals(observable, notice.getObservable()); + assertEquals(data, notice.getData()); + }); + + observable.observe(goog.labs.observe.Observer.fromFunction(observerFn)); + observable.notify(data); + assertEquals(1, observerFn.getCallCount()); +} + + +function testUnobserveWhileObserverIsFiring() { + var observerFn = goog.testing.recordFunction(function() { + observable.unobserve(observer); + assertFalse(observable.unobserve(observer)); + }); + var observer = goog.labs.observe.Observer.fromFunction(observerFn); + var observerFn2 = goog.testing.recordFunction(); + observable.observe(observer); + observable.observe(goog.labs.observe.Observer.fromFunction(observerFn2)); + + observable.notify(); + assertEquals(1, observerFn.getCallCount()); + assertEquals(1, observerFn2.getCallCount()); + + observable.notify(); + assertEquals(1, observerFn.getCallCount()); + assertEquals(2, observerFn2.getCallCount()); +} + + +function testSimpleObservableAsComposition() { + var observable = new ActualObservable(); + + var observerFn = goog.testing.recordFunction(function(notice) { + assertNotNull(notice); + assertEquals(observable, notice.getObservable()); + assertUndefined(notice.getData()); + }); + var observer = goog.labs.observe.Observer.fromFunction(observerFn); + + observable.observe(observer); + observable.notify(); + assertEquals(1, observerFn.getCallCount()); + observerFn.reset(); +} + + +function testDispose() { + var observerFn = function() {}; + var observer = goog.labs.observe.Observer.fromFunction(observerFn); + + observable.dispose(); + assertThrows(function() { + observable.observe(observer); + }); + assertThrows(function() { + observable.notify(); + }); + assertThrows(function() { + observable.observe(observer); + }); +} + + + +/** + * @constructor + * @extends {goog.Disposable} + */ +ActualObservable = function() { + goog.base(this); + this.observable_ = new goog.labs.observe.SimpleObservable(this); + this.registerDisposable(this.observable_); +}; +goog.inherits(ActualObservable, goog.Disposable); + + +ActualObservable.prototype.observe = function(type, observer) { + this.observable_.observe(type, observer); +}; + + +ActualObservable.prototype.unobserve = function(type, observer) { + this.observable_.unobserve(type, observer); +}; + + +ActualObservable.prototype.notify = function(type, opt_data) { + this.observable_.notify(type, opt_data); +}; + +</script> +</body> +</html> diff --git a/contexts/data/lib/closure-library/closure/goog/labs/structs/map.js b/contexts/data/lib/closure-library/closure/goog/labs/structs/map.js new file mode 100644 index 0000000..0c030d8 --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/structs/map.js @@ -0,0 +1,339 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A map data structure that offers a convenient API to + * manipulate a key, value map. The key must be a string. + * + * This implementation also ensure that you can use keys that would + * not be usable using a normal object literal {}. Some examples + * include __proto__ (all newer browsers), toString/hasOwnProperty (IE + * <= 8). + */ + +goog.provide('goog.labs.structs.Map'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.labs.object'); +goog.require('goog.object'); + + + +/** + * Creates a new map. + * @constructor + */ +goog.labs.structs.Map = function() { + // clear() initializes the map to the empty state. + this.clear(); +}; + + +/** + * @type {function(this: Object, string): boolean} + * @private + */ +goog.labs.structs.Map.objectPropertyIsEnumerable_ = + Object.prototype.propertyIsEnumerable; + + +/** + * @type {function(this: Object, string): boolean} + * @private + */ +goog.labs.structs.Map.objectHasOwnProperty_ = + Object.prototype.hasOwnProperty; + + +/** + * Primary backing store of this map. + * @type {!Object} + * @private + */ +goog.labs.structs.Map.prototype.map_; + + +/** + * Secondary backing store for keys. The index corresponds to the + * index for secondaryStoreValues_. + * @type {!Array.<string>} + * @private + */ +goog.labs.structs.Map.prototype.secondaryStoreKeys_; + + +/** + * Secondary backing store for keys. The index corresponds to the + * index for secondaryStoreValues_. + * @type {!Array.<*>} + * @private + */ +goog.labs.structs.Map.prototype.secondaryStoreValues_; + + +/** + * Adds the (key, value) pair, overriding previous entry with the same + * key, if any. + * @param {string} key The key. + * @param {*} value The value. + */ +goog.labs.structs.Map.prototype.set = function(key, value) { + this.assertKeyIsString_(key); + + var newKey = !this.hasKeyInPrimaryStore_(key); + this.map_[key] = value; + + // __proto__ is not settable on object. + if (key == '__proto__' || + // Shadows for built-in properties are not enumerable in IE <= 8 . + (!goog.labs.structs.Map.BrowserFeature.OBJECT_CREATE_SUPPORTED && + !goog.labs.structs.Map.objectPropertyIsEnumerable_.call( + this.map_, key))) { + delete this.map_[key]; + var index = goog.array.indexOf(this.secondaryStoreKeys_, key); + if ((newKey = index < 0)) { + index = this.secondaryStoreKeys_.length; + } + + this.secondaryStoreKeys_[index] = key; + this.secondaryStoreValues_[index] = value; + } + + if (newKey) this.count_++; +}; + + +/** + * Gets the value for the given key. + * @param {string} key The key whose value we want to retrieve. + * @param {*=} opt_default The default value to return if the key does + * not exist in the map, default to undefined. + * @return {*} The value corresponding to the given key, or opt_default + * if the key does not exist in this map. + */ +goog.labs.structs.Map.prototype.get = function(key, opt_default) { + this.assertKeyIsString_(key); + + if (this.hasKeyInPrimaryStore_(key)) { + return this.map_[key]; + } + + var index = goog.array.indexOf(this.secondaryStoreKeys_, key); + return index >= 0 ? this.secondaryStoreValues_[index] : opt_default; +}; + + +/** + * Removes the map entry with the given key. + * @param {string} key The key to remove. + * @return {boolean} True if the entry is removed. + */ +goog.labs.structs.Map.prototype.remove = function(key) { + this.assertKeyIsString_(key); + + if (this.hasKeyInPrimaryStore_(key)) { + this.count_--; + delete this.map_[key]; + return true; + } else { + var index = goog.array.indexOf(this.secondaryStoreKeys_, key); + if (index >= 0) { + this.count_--; + goog.array.removeAt(this.secondaryStoreKeys_, index); + goog.array.removeAt(this.secondaryStoreValues_, index); + return true; + } + } + return false; +}; + + +/** + * Adds the content of the map to this map. If a new entry uses a key + * that already exists in this map, the existing key is replaced. + * @param {!goog.labs.structs.Map} map The map to add. + */ +goog.labs.structs.Map.prototype.addAll = function(map) { + goog.array.forEach(map.getKeys(), function(key) { + this.set(key, map.get(key)); + }, this); +}; + + +/** + * @return {boolean} True if the map is empty. + */ +goog.labs.structs.Map.prototype.isEmpty = function() { + return !this.count_; +}; + + +/** + * @return {number} The number of the entries in this map. + */ +goog.labs.structs.Map.prototype.getCount = function() { + return this.count_; +}; + + +/** + * @param {string} key The key to check. + * @return {boolean} True if the map contains the given key. + */ +goog.labs.structs.Map.prototype.containsKey = function(key) { + this.assertKeyIsString_(key); + return this.hasKeyInPrimaryStore_(key) || + goog.array.contains(this.secondaryStoreKeys_, key); +}; + + +/** + * Whether the map contains the given value. The comparison is done + * using !== comparator. Also returns true if the passed value is NaN + * and a NaN value exists in the map. + * @param {*} value Value to check. + * @return {boolean} True if the map contains the given value. + */ +goog.labs.structs.Map.prototype.containsValue = function(value) { + var found = goog.object.some(this.map_, function(v, k) { + return this.hasKeyInPrimaryStore_(k) && + goog.labs.object.is(v, value); + }, this); + return found || goog.array.contains(this.secondaryStoreValues_, value); +}; + + +/** + * @return {!Array.<string>} An array of all the keys contained in this map. + */ +goog.labs.structs.Map.prototype.getKeys = function() { + var keys; + if (goog.labs.structs.Map.BrowserFeature.OBJECT_KEYS_SUPPORTED) { + keys = goog.array.clone(Object.keys(this.map_)); + } else { + keys = []; + for (var key in this.map_) { + if (goog.labs.structs.Map.objectHasOwnProperty_.call(this.map_, key)) { + keys.push(key); + } + } + } + + goog.array.extend(keys, this.secondaryStoreKeys_); + return keys; +}; + + +/** + * @return {!Array.<*>} An array of all the values contained in this map. + * There may be duplicates. + */ +goog.labs.structs.Map.prototype.getValues = function() { + var values = []; + var keys = this.getKeys(); + for (var i = 0; i < keys.length; i++) { + values.push(this.get(keys[i])); + } + return values; +}; + + +/** + * @return {!Array.<Array>} An array of entries. Each entry is of the + * form [key, value]. Do not rely on consistent ordering of entries. + */ +goog.labs.structs.Map.prototype.getEntries = function() { + var entries = []; + var keys = this.getKeys(); + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + entries.push([key, this.get(key)]); + } + return entries; +}; + + +/** + * Clears the map to the initial state. + */ +goog.labs.structs.Map.prototype.clear = function() { + this.map_ = goog.labs.structs.Map.BrowserFeature.OBJECT_CREATE_SUPPORTED ? + Object.create(null) : {}; + this.secondaryStoreKeys_ = []; + this.secondaryStoreValues_ = []; + this.count_ = 0; +}; + + +/** + * Clones this map. + * @return {!goog.labs.structs.Map} The clone of this map. + */ +goog.labs.structs.Map.prototype.clone = function() { + var map = new goog.labs.structs.Map(); + map.addAll(this); + return map; +}; + + +/** + * @param {string} key The key to check. + * @return {boolean} True if the given key has been added successfully + * to the primary store. + * @private + */ +goog.labs.structs.Map.prototype.hasKeyInPrimaryStore_ = function(key) { + // New browsers that support Object.create do not allow setting of + // __proto__. In other browsers, hasOwnProperty will return true for + // __proto__ for object created with literal {}, so we need to + // special case it. + if (key == '__proto__') { + return false; + } + + if (goog.labs.structs.Map.BrowserFeature.OBJECT_CREATE_SUPPORTED) { + return key in this.map_; + } + + return goog.labs.structs.Map.objectHasOwnProperty_.call(this.map_, key); +}; + + +/** + * Asserts that the given key is a string. + * @param {string} key The key to check. + * @private + */ +goog.labs.structs.Map.prototype.assertKeyIsString_ = function(key) { + goog.asserts.assert(goog.isString(key), 'key must be a string.'); +}; + + +/** + * Browser feature enum necessary for map. + * @enum {boolean} + */ +goog.labs.structs.Map.BrowserFeature = { + // TODO(user): Replace with goog.userAgent detection. + /** + * Whether Object.create method is supported. + */ + OBJECT_CREATE_SUPPORTED: !!Object.create, + + /** + * Whether Object.keys method is supported. + */ + OBJECT_KEYS_SUPPORTED: !!Object.keys +}; diff --git a/contexts/data/lib/closure-library/closure/goog/labs/structs/map_perf.js b/contexts/data/lib/closure-library/closure/goog/labs/structs/map_perf.js new file mode 100644 index 0000000..1bc9d7a --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/structs/map_perf.js @@ -0,0 +1,201 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Performance test for goog.structs.Map and + * goog.labs.structs.Map. To run this test fairly, you would have to + * compile this via JsCompiler (with --export_test_functions), and + * pull the compiled JS into an empty HTML file. + */ + +goog.provide('goog.labs.structs.mapPerf'); + +goog.require('goog.dom'); +goog.require('goog.labs.structs.Map'); +goog.require('goog.structs.Map'); +goog.require('goog.testing.PerformanceTable'); +goog.require('goog.testing.jsunit'); + +goog.scope(function() { +var mapPerf = goog.labs.structs.mapPerf; + + +/** + * @typedef {goog.labs.structs.Map|goog.structs.Map} + */ +mapPerf.MapType; + + +/** + * @type {goog.testing.PerformanceTable} + */ +mapPerf.perfTable; + + +/** + * A key list. This maps loop index to key name to be used during + * benchmark. This ensure that we do not need to pay the cost of + * string concatenation/GC whenever we derive a key from loop index. + * + * This is filled once in setUpPage and then remain unchanged for the + * rest of the test case. + * + * @type {Array} + */ +mapPerf.keyList = []; + + +/** + * Maxium number of keys in keyList (and, by extension, the map under + * test). + * @type {number} + */ +mapPerf.MAX_NUM_KEY = 10000; + + +/** + * Fills the given map with generated key-value pair. + * @param {mapPerf.MapType} map The map to fill. + * @param {number} numKeys The number of key-value pair to fill. + */ +mapPerf.fillMap = function(map, numKeys) { + goog.asserts.assert(numKeys <= mapPerf.MAX_NUM_KEY); + + for (var i = 0; i < numKeys; ++i) { + map.set(mapPerf.keyList[i], i); + } +}; + + +/** + * Primes the given map with deletion of keys. + * @param {mapPerf.MapType} map The map to prime. + * @return {mapPerf.MapType} The primed map (for chaining). + */ +mapPerf.primeMapWithDeletion = function(map) { + for (var i = 0; i < 1000; ++i) { + map.set(mapPerf.keyList[i], i); + } + for (var i = 0; i < 1000; ++i) { + map.remove(mapPerf.keyList[i]); + } + return map; +}; + + +/** + * Runs performance test for Map#get with the given map. + * @param {mapPerf.MapType} map The map to stress. + * @param {string} message Message to be put in performance table. + */ +mapPerf.runPerformanceTestForMapGet = function(map, message) { + mapPerf.fillMap(map, 10000); + + mapPerf.perfTable.run( + function() { + // Creates local alias for map and keyList. + var localMap = map; + var localKeyList = mapPerf.keyList; + + for (var i = 0; i < 500; ++i) { + var sum = 0; + for (var j = 0; j < 10000; ++j) { + sum += localMap.get(localKeyList[j]); + } + } + }, + message); +}; + + +/** + * Runs performance test for Map#set with the given map. + * @param {mapPerf.MapType} map The map to stress. + * @param {string} message Message to be put in performance table. + */ +mapPerf.runPerformanceTestForMapSet = function(map, message) { + mapPerf.perfTable.run( + function() { + // Creates local alias for map and keyList. + var localMap = map; + var localKeyList = mapPerf.keyList; + + for (var i = 0; i < 500; ++i) { + for (var j = 0; j < 10000; ++j) { + localMap.set(localKeyList[i], i); + } + } + }, + message); +}; + + +goog.global['setUpPage'] = function() { + var content = goog.dom.createDom('div'); + goog.dom.insertChildAt(document.body, content, 0); + var ua = navigator.userAgent; + content.innerHTML = + '<h1>Closure Performance Tests - Map</h1>' + + '<p><strong>User-agent: </strong><span id="ua">' + ua + '</span></p>' + + '<div id="perf-table"></div>' + + '<hr>'; + + mapPerf.perfTable = new goog.testing.PerformanceTable( + goog.dom.getElement('perf-table')); + + // Fills keyList. + for (var i = 0; i < mapPerf.MAX_NUM_KEY; ++i) { + mapPerf.keyList.push('k' + i); + } +}; + + +goog.global['testGetFromLabsMap'] = function() { + mapPerf.runPerformanceTestForMapGet( + new goog.labs.structs.Map(), '#get: no previous deletion (Labs)'); +}; + + +goog.global['testGetFromOriginalMap'] = function() { + mapPerf.runPerformanceTestForMapGet( + new goog.structs.Map(), '#get: no previous deletion (Original)'); +}; + + +goog.global['testGetWithPreviousDeletionFromLabsMap'] = function() { + mapPerf.runPerformanceTestForMapGet( + mapPerf.primeMapWithDeletion(new goog.labs.structs.Map()), + '#get: with previous deletion (Labs)'); +}; + + +goog.global['testGetWithPreviousDeletionFromOriginalMap'] = function() { + mapPerf.runPerformanceTestForMapGet( + mapPerf.primeMapWithDeletion(new goog.structs.Map()), + '#get: with previous deletion (Original)'); +}; + + +goog.global['testSetFromLabsMap'] = function() { + mapPerf.runPerformanceTestForMapSet( + new goog.labs.structs.Map(), '#set: no previous deletion (Labs)'); +}; + + +goog.global['testSetFromOriginalMap'] = function() { + mapPerf.runPerformanceTestForMapSet( + new goog.structs.Map(), '#set: no previous deletion (Original)'); +}; + +}); // goog.scope diff --git a/contexts/data/lib/closure-library/closure/goog/labs/structs/map_test.html b/contexts/data/lib/closure-library/closure/goog/labs/structs/map_test.html new file mode 100644 index 0000000..9a4d375 --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/structs/map_test.html @@ -0,0 +1,434 @@ +<!DOCTYPE html> +<html> +<!-- +Copyright 2012 The Closure Library Authors. All Rights Reserved. + +Use of this source code is governed by the Apache License, Version 2.0. +See the COPYING file for details. +--> +<!-- +--> +<head> +<meta http-equiv="X-UA-Compatible" content="IE=edge"> +<title>Closure Unit Tests - goog.labs.structs.Map</title> +<script src="../../base.js"></script> +<script> +goog.require('goog.dispose'); +goog.require('goog.labs.structs.Map'); +goog.require('goog.testing.PropertyReplacer'); +goog.require('goog.testing.jsunit'); +</script> +</head> +<body> +<script> +var map; +var stubs = new goog.testing.PropertyReplacer(); + + +function setUp() { + map = new goog.labs.structs.Map(); +} + + +function testSet() { + var key = 'test'; + var value = 'value'; + map.set(key, value); + assertEquals(value, map.get(key)); +} + + +function testSetsWithSameKey() { + var key = 'test'; + var value = 'value'; + var value2 = 'value2'; + map.set(key, value); + map.set(key, value2); + assertEquals(value2, map.get(key)); +} + + +function testSetWithUndefinedValue() { + var key = 'test'; + map.set(key, undefined); + assertUndefined(map.get(key)); +} + + +function testSetWithUnderUnderProtoUnderUnder() { + var key = '__proto__'; + var value = 'value'; + var value2 = 'value2'; + + map.set(key, value); + assertEquals(value, map.get(key)); + + map.set(key, value2); + assertEquals(value2, map.get(key)); +} + + +function testSetWithBuiltInPropertyShadows() { + var key = 'toString'; + var value = 'value'; + var key2 = 'hasOwnProperty'; + var value2 = 'value2'; + + map.set(key, value); + map.set(key2, value2); + assertEquals(value, map.get(key)); + assertEquals(value2, map.get(key2)); + + map.set(key, value2); + map.set(key2, value); + assertEquals(value2, map.get(key)); + assertEquals(value, map.get(key2)); +} + + +function testGetBeforeSetOfUnderUnderProtoUnderUnder() { + assertUndefined(map.get('__proto__')); +} + + +function testContainsKey() { + assertFalse(map.containsKey('key')); + assertFalse(map.containsKey('__proto__')); + assertFalse(map.containsKey('toString')); + assertFalse(map.containsKey('hasOwnProperty')); + assertFalse(map.containsKey('key2')); + assertFalse(map.containsKey('key3')); + assertFalse(map.containsKey('key4')); + + map.set('key', 'v'); + map.set('__proto__', 'v'); + map.set('toString', 'v'); + map.set('hasOwnProperty', 'v'); + map.set('key2', undefined); + map.set('key3', null); + map.set('key4', ''); + + assertTrue(map.containsKey('key')); + assertTrue(map.containsKey('__proto__')); + assertTrue(map.containsKey('toString')); + assertTrue(map.containsKey('hasOwnProperty')); + assertTrue(map.containsKey('key2')); + assertTrue(map.containsKey('key3')); + assertTrue(map.containsKey('key4')); +} + + +function testContainsValueWithShadowKeys() { + assertFalse(map.containsValue('v2')); + assertFalse(map.containsValue('v3')); + assertFalse(map.containsValue('v4')); + + map.set('__proto__', 'v2'); + map.set('toString', 'v3'); + map.set('hasOwnProperty', 'v4'); + + assertTrue(map.containsValue('v2')); + assertTrue(map.containsValue('v3')); + assertTrue(map.containsValue('v4')); + + assertFalse(map.containsValue(Object.prototype.toString)); + assertFalse(map.containsValue(Object.prototype.hasOwnProperty)); +} + + +function testContainsValueWithNullAndUndefined() { + assertFalse(map.containsValue(undefined)); + assertFalse(map.containsValue(null)); + + map.set('key2', undefined); + map.set('key3', null); + + assertTrue(map.containsValue(undefined)); + assertTrue(map.containsValue(null)); +} + + +function testContainsValueWithNumber() { + assertFalse(map.containsValue(-1)); + assertFalse(map.containsValue(0)); + assertFalse(map.containsValue(1)); + map.set('key', -1); + map.set('key2', 0); + map.set('key3', 1); + assertTrue(map.containsValue(-1)); + assertTrue(map.containsValue(0)); + assertTrue(map.containsValue(1)); +} + + +function testContainsValueWithNaN() { + assertFalse(map.containsValue(NaN)); + map.set('key', NaN); + assertTrue(map.containsValue(NaN)); +} + + +function testContainsValueWithNegativeZero() { + assertFalse(map.containsValue(-0)); + map.set('key', -0); + assertTrue(map.containsValue(-0)); + assertFalse(map.containsValue(0)); + + map.set('key', 0); + assertFalse(map.containsValue(-0)); + assertTrue(map.containsValue(0)); +} + + +function testContainsValueWithStrings() { + assertFalse(map.containsValue('')); + assertFalse(map.containsValue('v')); + map.set('key', ''); + map.set('key2', 'v'); + assertTrue(map.containsValue('')); + assertTrue(map.containsValue('v')); +} + +function testRemove() { + map.set('key', 'v'); + map.set('__proto__', 'v2'); + map.set('toString', 'v3'); + map.set('hasOwnProperty', 'v4'); + map.set('key2', undefined); + map.set('key3', null); + map.set('key4', ''); + + assertFalse(map.remove('key do not exist')); + + assertTrue(map.remove('key')); + assertFalse(map.containsKey('key')); + assertFalse(map.remove('key')); + + assertTrue(map.remove('__proto__')); + assertFalse(map.containsKey('__proto__')); + assertFalse(map.remove('__proto__')); + + assertTrue(map.remove('toString')); + assertFalse(map.containsKey('toString')); + assertFalse(map.remove('toString')); + + assertTrue(map.remove('hasOwnProperty')); + assertFalse(map.containsKey('hasOwnProperty')); + assertFalse(map.remove('hasOwnProperty')); + + assertTrue(map.remove('key2')); + assertFalse(map.containsKey('key2')); + assertFalse(map.remove('key2')); + + assertTrue(map.remove('key3')); + assertFalse(map.containsKey('key3')); + assertFalse(map.remove('key3')); + + assertTrue('', map.remove('key4')); + assertFalse(map.containsKey('key4')); + assertFalse(map.remove('key4')); +} + + +function testGetCountAndIsEmpty() { + assertEquals(0, map.getCount()); + assertTrue(map.isEmpty()); + + map.set('key', 'v'); + assertEquals(1, map.getCount()); + map.set('__proto__', 'v2'); + assertEquals(2, map.getCount()); + map.set('toString', 'v3'); + assertEquals(3, map.getCount()); + map.set('hasOwnProperty', 'v4'); + assertEquals(4, map.getCount()); + + map.set('key', 'a'); + assertEquals(4, map.getCount()); + map.set('__proto__', 'a2'); + assertEquals(4, map.getCount()); + map.set('toString', 'a3'); + assertEquals(4, map.getCount()); + map.set('hasOwnProperty', 'a4'); + assertEquals(4, map.getCount()); + + map.remove('key'); + assertEquals(3, map.getCount()); + map.remove('__proto__'); + assertEquals(2, map.getCount()); + map.remove('toString'); + assertEquals(1, map.getCount()); + map.remove('hasOwnProperty'); + assertEquals(0, map.getCount()); +} + + +function testClear() { + map.set('key', 'v'); + map.set('__proto__', 'v'); + map.set('toString', 'v'); + map.set('hasOwnProperty', 'v'); + map.set('key2', undefined); + map.set('key3', null); + map.set('key4', ''); + + map.clear(); + + assertFalse(map.containsKey('key')); + assertFalse(map.containsKey('__proto__')); + assertFalse(map.containsKey('toString')); + assertFalse(map.containsKey('hasOwnProperty')); + assertFalse(map.containsKey('key2')); + assertFalse(map.containsKey('key3')); + assertFalse(map.containsKey('key4')); +} + + +function testGetEntries() { + map.set('key', 'v'); + map.set('__proto__', 'v'); + map.set('toString', 'v'); + map.set('hasOwnProperty', 'v'); + map.set('key2', undefined); + map.set('key3', null); + map.set('key4', ''); + + var entries = map.getEntries(); + assertEquals(7, entries.length); + assertContainsEntry(['key', 'v'], entries); + assertContainsEntry(['__proto__', 'v'], entries); + assertContainsEntry(['toString', 'v'], entries); + assertContainsEntry(['hasOwnProperty', 'v'], entries); + assertContainsEntry(['key2', undefined], entries); + assertContainsEntry(['key3', null], entries); + assertContainsEntry(['key4', ''], entries); +} + + +function testGetKeys() { + map.set('key', 'v'); + map.set('__proto__', 'v'); + map.set('toString', 'v'); + map.set('hasOwnProperty', 'v'); + map.set('key2', undefined); + map.set('key3', null); + map.set('k4', ''); + + var values = map.getKeys(); + assertSameElements( + ['key', '__proto__', 'toString', 'hasOwnProperty', 'key2', 'key3', 'k4'], + values); +} + + +function testGetValues() { + map.set('key', 'v'); + map.set('__proto__', 'v'); + map.set('toString', 'v'); + map.set('hasOwnProperty', 'v'); + map.set('key2', undefined); + map.set('key3', null); + map.set('key4', ''); + + var values = map.getValues(); + assertSameElements(['v', 'v', 'v', 'v', undefined, null, ''], values); +} + + +function testAddAllToEmptyMap() { + map.set('key', 'v'); + map.set('key2', 'v2'); + map.set('key3', 'v3'); + map.set('key4', 'v4'); + + var map2 = new goog.labs.structs.Map(); + map2.addAll(map); + + assertEquals(4, map2.getCount()); + assertEquals('v', map2.get('key')); + assertEquals('v2', map2.get('key2')); + assertEquals('v3', map2.get('key3')); + assertEquals('v4', map2.get('key4')); +} + + +function testAddAllToNonEmptyMap() { + map.set('key', 'v'); + map.set('key2', 'v2'); + map.set('key3', 'v3'); + map.set('key4', 'v4'); + + var map2 = new goog.labs.structs.Map(); + map2.set('key0', 'o'); + map2.set('key', 'o'); + map2.set('key2', 'o2'); + map2.set('key3', 'o3'); + map2.addAll(map); + + assertEquals(5, map2.getCount()); + assertEquals('o', map2.get('key0')); + assertEquals('v', map2.get('key')); + assertEquals('v2', map2.get('key2')); + assertEquals('v3', map2.get('key3')); + assertEquals('v4', map2.get('key4')); +} + + +function testClone() { + map.set('key', 'v'); + map.set('key2', 'v2'); + map.set('key3', 'v3'); + map.set('key4', 'v4'); + + var map2 = map.clone(); + + assertEquals(4, map2.getCount()); + assertEquals('v', map2.get('key')); + assertEquals('v2', map2.get('key2')); + assertEquals('v3', map2.get('key3')); + assertEquals('v4', map2.get('key4')); +} + + +function testMapWithModifiedObjectPrototype() { + stubs.set(Object.prototype, 'toString', function() {}); + stubs.set(Object.prototype, 'foo', function() {}); + stubs.set(Object.prototype, 'field', 100); + stubs.set(Object.prototype, 'fooKey', function() {}); + + map = new goog.labs.structs.Map(); + map.set('key', 'v'); + map.set('key2', 'v2'); + map.set('fooKey', 'v3'); + + assertTrue(map.containsKey('key')); + assertTrue(map.containsKey('key2')); + assertTrue(map.containsKey('fooKey')); + assertFalse(map.containsKey('toString')); + assertFalse(map.containsKey('foo')); + assertFalse(map.containsKey('field')); + + assertTrue(map.containsValue('v')); + assertTrue(map.containsValue('v2')); + assertTrue(map.containsValue('v3')); + assertFalse(map.containsValue(100)); + + var entries = map.getEntries(); + assertEquals(3, entries.length); + assertContainsEntry(['key', 'v'], entries); + assertContainsEntry(['key2', 'v2'], entries); + assertContainsEntry(['fooKey', 'v3'], entries); +} + + +function assertContainsEntry(entry, entryList) { + for (var i = 0; i < entryList.length; ++i) { + if (entry[0] == entryList[i][0] && entry[1] === entryList[i][1]) { + return; + } + } + fail('Did not find entry: ' + entry + ' in: ' + entryList); +} +</script> +</body> +</html> diff --git a/contexts/data/lib/closure-library/closure/goog/labs/structs/multimap.js b/contexts/data/lib/closure-library/closure/goog/labs/structs/multimap.js new file mode 100644 index 0000000..d7399f9 --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/structs/multimap.js @@ -0,0 +1,279 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A collection similar to + * {@code goog.labs.structs.Map}, but also allows associating multiple + * values with a single key. + * + * This implementation ensures that you can use any string keys. + * + */ + +goog.provide('goog.labs.structs.Multimap'); + +goog.require('goog.array'); +goog.require('goog.labs.object'); +goog.require('goog.labs.structs.Map'); + + + +/** + * Creates a new multimap. + * @constructor + */ +goog.labs.structs.Multimap = function() { + this.clear(); +}; + + +/** + * The backing map. + * @type {!goog.labs.structs.Map} + * @private + */ +goog.labs.structs.Multimap.prototype.map_; + + +/** + * @type {number} + * @private + */ +goog.labs.structs.Multimap.prototype.count_ = 0; + + +/** + * Clears the multimap. + */ +goog.labs.structs.Multimap.prototype.clear = function() { + this.count_ = 0; + this.map_ = new goog.labs.structs.Map(); +}; + + +/** + * Clones this multimap. + * @return {!goog.labs.structs.Multimap} A multimap that contains all + * the mapping this multimap has. + */ +goog.labs.structs.Multimap.prototype.clone = function() { + var map = new goog.labs.structs.Multimap(); + map.addAllFromMultimap(this); + return map; +}; + + +/** + * Adds the given (key, value) pair to the map. The (key, value) pair + * is guaranteed to be added. + * @param {string} key The key to add. + * @param {*} value The value to add. + */ +goog.labs.structs.Multimap.prototype.add = function(key, value) { + var values = this.map_.get(key); + if (!values) { + this.map_.set(key, (values = [])); + } + + values.push(value); + this.count_++; +}; + + +/** + * Stores a collection of values to the given key. Does not replace + * existing (key, value) pairs. + * @param {string} key The key to add. + * @param {!Array.<*>} values The values to add. + */ +goog.labs.structs.Multimap.prototype.addAllValues = function(key, values) { + goog.array.forEach(values, function(v) { + this.add(key, v); + }, this); +}; + + +/** + * Adds the contents of the given map/multimap to this multimap. + * @param {!(goog.labs.structs.Map|goog.labs.structs.Multimap)} map The + * map to add. + */ +goog.labs.structs.Multimap.prototype.addAllFromMultimap = function(map) { + goog.array.forEach(map.getEntries(), function(entry) { + this.add(entry[0], entry[1]); + }, this); +}; + + +/** + * Replaces all the values for the given key with the given values. + * @param {string} key The key whose values are to be replaced. + * @param {!Array.<*>} values The new values. If empty, this is + * equivalent to {@code removaAll(key)}. + */ +goog.labs.structs.Multimap.prototype.replaceValues = function(key, values) { + this.removeAll(key); + this.addAllValues(key, values); +}; + + +/** + * Gets the values correspond to the given key. + * @param {string} key The key to retrieve. + * @return {!Array.<*>} An array of values corresponding to the given + * key. May be empty. Note that the ordering of values are not + * guaranteed to be consistent. + */ +goog.labs.structs.Multimap.prototype.get = function(key) { + var values = /** @type {Array.<string>} */ (this.map_.get(key)); + return values ? goog.array.clone(values) : []; +}; + + +/** + * Removes a single occurrence of (key, value) pair. + * @param {string} key The key to remove. + * @param {*} value The value to remove. + * @return {boolean} Whether any matching (key, value) pair is removed. + */ +goog.labs.structs.Multimap.prototype.remove = function(key, value) { + var values = /** @type {Array.<string>} */ (this.map_.get(key)); + if (!values) { + return false; + } + + var removed = goog.array.removeIf(values, function(v) { + return goog.labs.object.is(value, v); + }); + + if (removed) { + this.count_--; + if (values.length == 0) { + this.map_.remove(key); + } + } + return removed; +}; + + +/** + * Removes all values corresponding to the given key. + * @param {string} key The key whose values are to be removed. + * @return {boolean} Whether any value is removed. + */ +goog.labs.structs.Multimap.prototype.removeAll = function(key) { + // We have to first retrieve the values from the backing map because + // we need to keep track of count (and correctly calculates the + // return value). values may be undefined. + var values = this.map_.get(key); + if (this.map_.remove(key)) { + this.count_ -= values.length; + return true; + } + + return false; +}; + + +/** + * @return {boolean} Whether the multimap is empty. + */ +goog.labs.structs.Multimap.prototype.isEmpty = function() { + return !this.count_; +}; + + +/** + * @return {number} The count of (key, value) pairs in the map. + */ +goog.labs.structs.Multimap.prototype.getCount = function() { + return this.count_; +}; + + +/** + * @param {string} key The key to check. + * @param {string} value The value to check. + * @return {boolean} Whether the (key, value) pair exists in the multimap. + */ +goog.labs.structs.Multimap.prototype.containsEntry = function(key, value) { + var values = /** @type {Array.<string>} */ (this.map_.get(key)); + if (!values) { + return false; + } + + var index = goog.array.findIndex(values, function(v) { + return goog.labs.object.is(v, value); + }); + return index >= 0; +}; + + +/** + * @param {string} key The key to check. + * @return {boolean} Whether the multimap contains at least one (key, + * value) pair with the given key. + */ +goog.labs.structs.Multimap.prototype.containsKey = function(key) { + return this.map_.containsKey(key); +}; + + +/** + * @param {*} value The value to check. + * @return {boolean} Whether the multimap contains at least one (key, + * value) pair with the given value. + */ +goog.labs.structs.Multimap.prototype.containsValue = function(value) { + return goog.array.some(this.map_.getValues(), + function(values) { + return goog.array.some(/** @type {Array} */ (values), function(v) { + return goog.labs.object.is(v, value); + }); + }); +}; + + +/** + * @return {!Array.<string>} An array of unique keys. + */ +goog.labs.structs.Multimap.prototype.getKeys = function() { + return this.map_.getKeys(); +}; + + +/** + * @return {!Array.<*>} An array of values. There may be duplicates. + */ +goog.labs.structs.Multimap.prototype.getValues = function() { + return goog.array.flatten(this.map_.getValues()); +}; + + +/** + * @return {!Array.<!Array>} An array of entries. Each entry is of the + * form [key, value]. + */ +goog.labs.structs.Multimap.prototype.getEntries = function() { + var keys = this.getKeys(); + var entries = []; + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + var values = this.get(key); + for (var j = 0; j < values.length; j++) { + entries.push([key, values[j]]); + } + } + return entries; +}; diff --git a/contexts/data/lib/closure-library/closure/goog/labs/structs/multimap_test.html b/contexts/data/lib/closure-library/closure/goog/labs/structs/multimap_test.html new file mode 100644 index 0000000..38390e7 --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/structs/multimap_test.html @@ -0,0 +1,332 @@ +<!DOCTYPE html> +<html> +<!-- +Copyright 2012 The Closure Library Authors. All Rights Reserved. + +Use of this source code is governed by the Apache License, Version 2.0. +See the COPYING file for details. +--> +<!-- +--> +<head> +<meta http-equiv="X-UA-Compatible" content="IE=edge"> +<title>Closure Unit Tests - goog.labs.structs.Multimap</title> +<script src="../../base.js"></script> +<script> + goog.require('goog.dispose'); + goog.require('goog.labs.structs.Multimap'); + goog.require('goog.testing.jsunit'); +</script> +</head> +<body> +<script> +var map; + + +function setUp() { + map = new goog.labs.structs.Multimap(); +} + + +function testGetCountWithEmptyMultimap() { + assertEquals(0, map.getCount()); + assertTrue(map.isEmpty()); +} + + +function testClone() { + map.add('k', 'v'); + map.addAllValues('k2', ['v', 'v1', 'v2']); + + var map2 = map.clone(); + + assertSameElements(['v'], map.get('k')); + assertSameElements(['v', 'v1', 'v2'], map.get('k2')); +} + + +function testAdd() { + map.add('key', 'v'); + assertEquals(1, map.getCount()); + map.add('key', 'v2'); + assertEquals(2, map.getCount()); + map.add('key', 'v3'); + assertEquals(3, map.getCount()); + + var values = map.get('key'); + assertEquals(3, values.length); + assertContains('v', values); + assertContains('v2', values); + assertContains('v3', values); +} + + +function testAddValues() { + map.addAllValues('key', ['v', 'v2', 'v3']); + assertSameElements(['v', 'v2', 'v3'], map.get('key')); + + map.add('key2', 'a'); + map.addAllValues('key2', ['v', 'v2', 'v3']); + assertSameElements(['a', 'v', 'v2', 'v3'], map.get('key2')); +} + + +function testAddAllWithMultimap() { + map.add('k', 'v'); + map.addAllValues('k2', ['v', 'v1', 'v2']); + + var map2 = new goog.labs.structs.Multimap(); + map2.add('k2', 'v'); + map2.addAllValues('k3', ['a', 'a1', 'a2']); + + map.addAllFromMultimap(map2); + assertSameElements(['v'], map.get('k')); + assertSameElements(['v', 'v1', 'v2', 'v'], map.get('k2')); + assertSameElements(['a', 'a1', 'a2'], map.get('k3')); +} + + +function testAddAllWithMap() { + map.add('k', 'v'); + map.addAllValues('k2', ['v', 'v1', 'v2']); + + var map2 = new goog.labs.structs.Map(); + map2.set('k2', 'v'); + map2.set('k3', 'a'); + + map.addAllFromMultimap(map2); + assertSameElements(['v'], map.get('k')); + assertSameElements(['v', 'v1', 'v2', 'v'], map.get('k2')); + assertSameElements(['a'], map.get('k3')); +} + + +function testReplaceValues() { + map.add('key', 'v'); + map.add('key', 'v2'); + + map.replaceValues('key', [0, 1, 2]); + assertSameElements([0, 1, 2], map.get('key')); + assertEquals(3, map.getCount()); + + map.replaceValues('key', ['v']); + assertSameElements(['v'], map.get('key')); + assertEquals(1, map.getCount()); + + map.replaceValues('key', []); + assertSameElements([], map.get('key')); + assertEquals(0, map.getCount()); +} + + +function testRemove() { + map.add('key', 'v'); + map.add('key', 'v2'); + map.add('key', 'v3'); + + assertTrue(map.remove('key', 'v')); + var values = map.get('key'); + assertEquals(2, map.getCount()); + assertEquals(2, values.length); + assertContains('v2', values); + assertContains('v3', values); + assertFalse(map.remove('key', 'v')); + + assertTrue(map.remove('key', 'v2')); + values = map.get('key'); + assertEquals(1, map.getCount()); + assertEquals(1, values.length); + assertContains('v3', values); + assertFalse(map.remove('key', 'v2')); + + assertTrue(map.remove('key', 'v3')); + map.remove('key', 'v3'); + assertTrue(map.isEmpty()); + assertEquals(0, map.get('key').length); + assertFalse(map.remove('key', 'v2')); +} + + +function testRemoveWithNaN() { + map.add('key', NaN); + map.add('key', NaN); + + assertTrue(map.remove('key', NaN)); + var values = map.get('key'); + assertEquals(1, values.length); + assertTrue(isNaN(values[0])); + + assertTrue(map.remove('key', NaN)); + assertEquals(0, map.get('key').length); + assertFalse(map.remove('key', NaN)); +} + + +function testRemoveWithNegativeZero() { + map.add('key', 0); + map.add('key', -0); + + assertTrue(map.remove('key', -0)); + var values = map.get('key'); + assertEquals(1, values.length); + assertTrue(1 / values[0] === 1 / 0); + assertFalse(map.remove('key', -0)); + + map.add('key', -0); + + assertTrue(map.remove('key', 0)); + var values = map.get('key'); + assertEquals(1, values.length); + assertTrue(1 / values[0] === 1 / -0); + assertFalse(map.remove('key', 0)); + + assertTrue(map.remove('key', -0)); + assertEquals(0, map.get('key').length); +} + + +function testRemoveAll() { + map.add('key', 'v'); + map.add('key', 'v2'); + map.add('key', 'v3'); + map.add('key', 'v4'); + map.add('key2', 'v'); + + assertTrue(map.removeAll('key')); + assertSameElements([], map.get('key')); + assertSameElements(['v'], map.get('key2')); + assertFalse(map.removeAll('key')); + assertEquals(1, map.getCount()); + + assertTrue(map.removeAll('key2')); + assertSameElements([], map.get('key2')); + assertFalse(map.removeAll('key2')); + assertTrue(map.isEmpty()); +} + + +function testAddWithDuplicateValue() { + map.add('key', 'v'); + map.add('key', 'v'); + map.add('key', 'v'); + assertArrayEquals(['v', 'v', 'v'], map.get('key')); +} + + +function testContainsEntry() { + assertFalse(map.containsEntry('k', 'v')); + assertFalse(map.containsEntry('k', 'v2')); + assertFalse(map.containsEntry('k2', 'v')); + + map.add('k', 'v'); + assertTrue(map.containsEntry('k', 'v')); + assertFalse(map.containsEntry('k', 'v2')); + assertFalse(map.containsEntry('k2', 'v')); + + map.add('k', 'v2'); + assertTrue(map.containsEntry('k', 'v')); + assertTrue(map.containsEntry('k', 'v2')); + assertFalse(map.containsEntry('k2', 'v')); + + map.add('k2', 'v'); + assertTrue(map.containsEntry('k', 'v')); + assertTrue(map.containsEntry('k', 'v2')); + assertTrue(map.containsEntry('k2', 'v')); +} + + +function testContainsKey() { + assertFalse(map.containsKey('k')); + assertFalse(map.containsKey('k2')); + + map.add('k', 'v'); + assertTrue(map.containsKey('k')); + map.add('k2', 'v'); + assertTrue(map.containsKey('k2')); + + map.remove('k', 'v'); + assertFalse(map.containsKey('k')); + map.remove('k2', 'v'); + assertFalse(map.containsKey('k2')); +} + + +function testContainsValue() { + assertFalse(map.containsValue('v')); + assertFalse(map.containsValue('v2')); + + map.add('key', 'v'); + assertTrue(map.containsValue('v')); + map.add('key', 'v2'); + assertTrue(map.containsValue('v2')); +} + + +function testGetEntries() { + map.add('key', 'v'); + map.add('key', 'v2'); + map.add('key2', 'v3'); + + var entries = map.getEntries(); + assertEquals(3, entries.length); + assertContainsEntry(['key', 'v'], entries); + assertContainsEntry(['key', 'v2'], entries); + assertContainsEntry(['key2', 'v3'], entries); +} + + +function testGetKeys() { + map.add('key', 'v'); + map.add('key', 'v2'); + map.add('key2', 'v3'); + map.add('key3', 'v4'); + map.removeAll('key3'); + + assertSameElements(['key', 'key2'], map.getKeys()); +} + + +function testGetKeys() { + map.add('key', 'v'); + map.add('key', 'v2'); + map.add('key2', 'v2'); + map.add('key3', 'v4'); + map.removeAll('key3'); + + assertSameElements(['v', 'v2', 'v2'], map.getValues()); +} + + +function testGetReturnsDefensiveCopyOfUnderlyingData() { + map.add('key', 'v'); + map.add('key', 'v2'); + map.add('key', 'v3'); + + var values = map.get('key'); + values.push('v4'); + assertFalse(map.containsEntry('key', 'v4')); +} + + +function testClear() { + map.add('key', 'v'); + map.add('key', 'v2'); + map.add('key2', 'v3'); + + map.clear(); + assertTrue(map.isEmpty()); + assertSameElements([], map.getEntries()); +} + + +function assertContainsEntry(entry, entryList) { + for (var i = 0; i < entryList.length; ++i) { + if (entry[0] == entryList[i][0] && entry[1] === entryList[i][1]) { + return; + } + } + fail('Did not find entry: ' + entry + ' in: ' + entryList); +} +</script> +</body> +</html> diff --git a/contexts/data/lib/closure-library/closure/goog/labs/testing/assertthat.js b/contexts/data/lib/closure-library/closure/goog/labs/testing/assertthat.js new file mode 100644 index 0000000..b6e8215 --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/testing/assertthat.js @@ -0,0 +1,59 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Provides main functionality of assertThat. assertThat calls the + * matcher's matches method to test if a matcher matches assertThat's arguments. + */ + + +goog.provide('goog.labs.testing.MatcherError'); +goog.provide('goog.labs.testing.assertThat'); + +goog.require('goog.asserts'); +goog.require('goog.debug.Error'); +goog.require('goog.labs.testing.Matcher'); + + +/** + * Asserts that the actual value evaluated by the matcher is true. + * + * @param {*} actual The object to assert by the matcher. + * @param {!goog.labs.testing.Matcher} matcher A matcher to verify values. + * @param {string=} opt_reason Description of what is asserted. + * + */ +goog.labs.testing.assertThat = function(actual, matcher, opt_reason) { + if (!matcher.matches(actual)) { + // Prefix the error description with a reason from the assert ? + var prefix = opt_reason ? opt_reason + ': ' : ''; + var desc = prefix + matcher.describe(actual); + + // some sort of failure here + throw new goog.labs.testing.MatcherError(desc); + } +}; + + + +/** + * Error thrown when a Matcher fails to match the input value. + * @param {string=} opt_message The error message. + * @constructor + * @extends {goog.debug.Error} + */ +goog.labs.testing.MatcherError = function(opt_message) { + goog.base(this, opt_message); +}; +goog.inherits(goog.labs.testing.MatcherError, goog.debug.Error); diff --git a/contexts/data/lib/closure-library/closure/goog/labs/testing/assertthat_test.html b/contexts/data/lib/closure-library/closure/goog/labs/testing/assertthat_test.html new file mode 100644 index 0000000..a70e9c7 --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/testing/assertthat_test.html @@ -0,0 +1,73 @@ +<!DOCTYPE html> +<html> +<!-- +Copyright 2012 The Closure Library Authors. All Rights Reserved. + +Use of this source code is governed by the Apache License, Version 2.0. +See the COPYING file for details. +--> +<head> +<title>Closure Unit Tests - goog.labs.testing.assertThat</title> +<script src="../../base.js"></script> +<script> + +goog.require('goog.labs.testing.Matcher'); +goog.require('goog.labs.testing.assertThat'); +goog.require('goog.testing.recordFunction'); +goog.require('goog.testing.jsunit'); + +</script> +</head> +<body> +<script> + +var successMatchesFn, failureMatchesFn, describeFn, successTestMatcher; +var failureTestMatcher; + +function setUp() { + successMatchesFn = new goog.testing.recordFunction(function() {return true;}); + failureMatchesFn = new goog.testing.recordFunction(function() {return false;}); + describeFn = new goog.testing.recordFunction(); + + successTestMatcher = function() { + return { matches: successMatchesFn, describe: describeFn }; + }; + failureTestMatcher = function() { + return { matches: failureMatchesFn, describe: describeFn }; + }; +} + +function testAssertthatAlwaysCallsMatches() { + var value = 7; + goog.labs.testing.assertThat(value, successTestMatcher(), + 'matches is called on success'); + + assertEquals(1, successMatchesFn.getCallCount()); + var matchesCall = successMatchesFn.popLastCall(); + assertEquals(value, matchesCall.getArgument(0)); + + var e = assertThrows(goog.bind(goog.labs.testing.assertThat, null, + value, failureTestMatcher(), 'matches is called on failure')); + + assertTrue(e instanceof goog.labs.testing.MatcherError); + + assertEquals(1, failureMatchesFn.getCallCount()); +} + +function testAssertthatCallsDescribeOnFailure() { + var value = 7; + var e = assertThrows(goog.bind(goog.labs.testing.assertThat, null, + value, failureTestMatcher(), 'describe is called on failure')); + + assertTrue(e instanceof goog.labs.testing.MatcherError); + + assertEquals(1, failureMatchesFn.getCallCount()); + assertEquals(1, describeFn.getCallCount()); + + var matchesCall = describeFn.popLastCall(); + assertEquals(value, matchesCall.getArgument(0)); +} + +</script> +</body> +</html> diff --git a/contexts/data/lib/closure-library/closure/goog/labs/testing/decoratormatcher.js b/contexts/data/lib/closure-library/closure/goog/labs/testing/decoratormatcher.js new file mode 100644 index 0000000..1d3c4ed --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/testing/decoratormatcher.js @@ -0,0 +1,94 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Provides the built-in decorators: is, describedAs, anything. + */ + + + +goog.provide('goog.labs.testing.AnythingMatcher'); + + +goog.require('goog.labs.testing.Matcher'); + + + +/** + * The Anything matcher. Matches all possible inputs. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.AnythingMatcher = function() {}; + + +/** + * Matches anything. Useful if one doesn't care what the object under test is. + * + * @override + */ +goog.labs.testing.AnythingMatcher.prototype.matches = + function(actualObject) { + return true; +}; + + +/** + * This method is never called but is needed so AnythingMatcher implements the + * Matcher interface. + * + * @override + */ +goog.labs.testing.AnythingMatcher.prototype.describe = + function(actualObject) { + throw Error('AnythingMatcher should never fail!'); +}; + + +/** + * Returns a matcher that matches anything. + * + * @return {!goog.labs.testing.AnythingMatcher} A AnythingMatcher. + */ +function anything() { + return new goog.labs.testing.AnythingMatcher(); +} + + +/** + * Returnes any matcher that is passed to it (aids readability). + * + * @param {!goog.labs.testing.Matcher} matcher A matcher. + * @return {!goog.labs.testing.Matcher} The wrapped matcher. + */ +function is(matcher) { + return matcher; +} + + +/** + * Returns a matcher with a customized description for the given matcher. + * + * @param {string} description The custom description for the matcher. + * @param {!goog.labs.testing.Matcher} matcher The matcher. + * + * @return {!goog.labs.testing.Matcher} The matcher with custom description. + */ +function describedAs(description, matcher) { + matcher.describe = function(value) { + return description; + }; + return matcher; +} diff --git a/contexts/data/lib/closure-library/closure/goog/labs/testing/decoratormatcher_test.html b/contexts/data/lib/closure-library/closure/goog/labs/testing/decoratormatcher_test.html new file mode 100644 index 0000000..85323dc --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/testing/decoratormatcher_test.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<html> +<!-- +Copyright 2012 The Closure Library Authors. All Rights Reserved. + +Use of this source code is governed by the Apache License, Version 2.0. +See the COPYING file for details. +--> +<head> +<title>Closure Unit Tests - Decorator matchers</title> +<script src="../../base.js"></script> +<script> + +goog.require('goog.labs.testing.AnythingMatcher'); +goog.require('goog.labs.testing.GreaterThanMatcher'); +goog.require('goog.labs.testing.assertThat'); +goog.require('goog.testing.jsunit'); + +</script> +</head> +<body> +<script> + +function testAnythingMatcher() { + goog.labs.testing.assertThat(true, anything(), 'anything matches true'); + goog.labs.testing.assertThat(false, anything(), 'false matches anything'); +} + +function testIs() { + goog.labs.testing.assertThat(5, is(greaterThan(4)), '5 is > 4'); +} + +function testDescribedAs() { + var e = assertThrows(function() { + goog.labs.testing.assertThat(4, describedAs('this is a test', + greaterThan(6)))}); + assertTrue(e instanceof goog.labs.testing.MatcherError); + assertEquals('this is a test', e.message); +} + +</script> +</body> +</html> diff --git a/contexts/data/lib/closure-library/closure/goog/labs/testing/dictionarymatcher.js b/contexts/data/lib/closure-library/closure/goog/labs/testing/dictionarymatcher.js new file mode 100644 index 0000000..9254d8e --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/testing/dictionarymatcher.js @@ -0,0 +1,266 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Provides the built-in dictionary matcher methods like + * hasEntry, hasEntries, hasKey, hasValue, etc. + */ + + + +goog.provide('goog.labs.testing.HasEntriesMatcher'); +goog.provide('goog.labs.testing.HasEntryMatcher'); +goog.provide('goog.labs.testing.HasKeyMatcher'); +goog.provide('goog.labs.testing.HasValueMatcher'); + + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('goog.labs.testing.Matcher'); +goog.require('goog.string'); + + + +/** + * The HasEntries matcher. + * + * @param {!Object} entries The entries to check in the object. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.HasEntriesMatcher = function(entries) { + /** + * @type {Object} + * @private + */ + this.entries_ = entries; +}; + + +/** + * Determines if an object has particular entries. + * + * @override + */ +goog.labs.testing.HasEntriesMatcher.prototype.matches = + function(actualObject) { + goog.asserts.assertObject(actualObject, 'Expected an Object'); + var object = /** @type {!Object} */(actualObject); + return goog.object.every(this.entries_, function(value, key) { + return goog.object.containsKey(object, key) && + object[key] === value; + }); +}; + + +/** + * @override + */ +goog.labs.testing.HasEntriesMatcher.prototype.describe = + function(actualObject) { + goog.asserts.assertObject(actualObject, 'Expected an Object'); + var object = /** @type {!Object} */(actualObject); + var errorString = 'Input object did not contain the following entries:\n'; + goog.object.forEach(this.entries_, function(value, key) { + if (!goog.object.containsKey(object, key) || + object[key] !== value) { + errorString += key + ': ' + value + '\n'; + } + }); + return errorString; +}; + + + +/** + * The HasEntry matcher. + * + * @param {string} key The key for the entry. + * @param {*} value The value for the key. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.HasEntryMatcher = function(key, value) { + /** + * @type {string} + * @private + */ + this.key_ = key; + /** + * @type {*} + * @private + */ + this.value_ = value; +}; + + +/** + * Determines if an object has a particular entry. + * + * @override + */ +goog.labs.testing.HasEntryMatcher.prototype.matches = + function(actualObject) { + goog.asserts.assertObject(actualObject); + return goog.object.containsKey(actualObject, this.key_) && + actualObject[this.key_] === this.value_; +}; + + +/** + * @override + */ +goog.labs.testing.HasEntryMatcher.prototype.describe = + function(actualObject) { + goog.asserts.assertObject(actualObject); + var errorMsg; + if (goog.object.containsKey(actualObject, this.key_)) { + errorMsg = 'Input object did not contain key: ' + this.key_; + } else { + errorMsg = 'Value for key did not match value: ' + this.value_; + } + return errorMsg; +}; + + + +/** + * The HasKey matcher. + * + * @param {string} key The key to check in the object. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.HasKeyMatcher = function(key) { + /** + * @type {string} + * @private + */ + this.key_ = key; +}; + + +/** + * Determines if an object has a key. + * + * @override + */ +goog.labs.testing.HasKeyMatcher.prototype.matches = + function(actualObject) { + goog.asserts.assertObject(actualObject); + return goog.object.containsKey(actualObject, this.key_); +}; + + +/** + * @override + */ +goog.labs.testing.HasKeyMatcher.prototype.describe = + function(actualObject) { + goog.asserts.assertObject(actualObject); + return 'Input object did not contain the key: ' + this.key_; +}; + + + +/** + * The HasValue matcher. + * + * @param {*} value The value to check in the object. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.HasValueMatcher = function(value) { + /** + * @type {*} + * @private + */ + this.value_ = value; +}; + + +/** + * Determines if an object contains a value + * + * @override + */ +goog.labs.testing.HasValueMatcher.prototype.matches = + function(actualObject) { + goog.asserts.assertObject(actualObject, 'Expected an Object'); + var object = /** @type {!Object} */(actualObject); + return goog.object.containsValue(object, this.value_); +}; + + +/** + * @override + */ +goog.labs.testing.HasValueMatcher.prototype.describe = + function(actualObject) { + return 'Input object did not contain the value: ' + this.value_; +}; + + +/** + * Gives a matcher that asserts an object contains all the given key-value pairs + * in the input object. + * + * @param {!Object} entries The entries to check for presence in the object. + * + * @return {!goog.labs.testing.HasEntriesMatcher} A HasEntriesMatcher. + */ +function hasEntries(entries) { + return new goog.labs.testing.HasEntriesMatcher(entries); +} + + +/** + * Gives a matcher that asserts an object contains the given key-value pair. + * + * @param {string} key The key to check for presence in the object. + * @param {*} value The value to check for presence in the object. + * + * @return {!goog.labs.testing.HasEntryMatcher} A HasEntryMatcher. + */ +function hasEntry(key, value) { + return new goog.labs.testing.HasEntryMatcher(key, value); +} + + +/** + * Gives a matcher that asserts an object contains the given key. + * + * @param {string} key The key to check for presence in the object. + * + * @return {!goog.labs.testing.HasKeyMatcher} A HasKeyMatcher. + */ +function hasKey(key) { + return new goog.labs.testing.HasKeyMatcher(key); +} + + +/** + * Gives a matcher that asserts an object contains the given value. + * + * @param {*} value The value to check for presence in the object. + * + * @return {!goog.labs.testing.HasValueMatcher} A HasValueMatcher. + */ +function hasValue(value) { + return new goog.labs.testing.HasValueMatcher(value); +} diff --git a/contexts/data/lib/closure-library/closure/goog/labs/testing/dictionarymatcher_test.html b/contexts/data/lib/closure-library/closure/goog/labs/testing/dictionarymatcher_test.html new file mode 100644 index 0000000..9e86778 --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/testing/dictionarymatcher_test.html @@ -0,0 +1,70 @@ +<!DOCTYPE html> +<html> +<!-- +Copyright 2012 The Closure Library Authors. All Rights Reserved. + +Use of this source code is governed by the Apache License, Version 2.0. +See the COPYING file for details. +--> +<head> +<title>Closure Unit Tests - Object matchers</title> +<script src="../../base.js"></script> +<script> + +goog.require('goog.labs.testing.HasEntriesMatcher'); +goog.require('goog.labs.testing.HasKeyMatcher'); +goog.require('goog.labs.testing.HasValueMatcher'); +goog.require('goog.labs.testing.assertThat'); +goog.require('goog.testing.jsunit'); + +</script> +</head> +<body> +<script> + +function testHasEntries() { + var obj1 = {x: 1, y: 2, z: 3}; + goog.labs.testing.assertThat(obj1, hasEntries({x: 1, y: 2}), + 'obj1 has entries: {x:1, y:2}'); + + assertMatcherError(function() { + goog.labs.testing.assertThat(obj1, hasEntries({z: 5, a: 4})); + }, 'hasEntries should throw exception when it fails'); +} + +function testHasEntry() { + var obj1 = {x: 1, y: 2, z: 3}; + goog.labs.testing.assertThat(obj1, hasEntry('x', 1), + 'obj1 has entry: {x:1}'); + + assertMatcherError(function() { + goog.labs.testing.assertThat(obj1, hasEntry('z', 5)); + }, 'hasEntry should throw exception when it fails'); +} + +function testHasKey() { + var obj1 = {x: 1}; + goog.labs.testing.assertThat(obj1, hasKey('x'), 'obj1 has key x'); + + assertMatcherError(function() { + goog.labs.testing.assertThat(obj1, hasKey('z')); + }, 'hasKey should throw exception when it fails'); +} + +function testHasValue() { + var obj1 = {x: 1}; + goog.labs.testing.assertThat(obj1, hasValue(1), 'obj1 has value 1'); + + assertMatcherError(function() { + goog.labs.testing.assertThat(obj1, hasValue(2)); + }, 'hasValue should throw exception when it fails'); +} + +function assertMatcherError(callable, errorString) { + var e = assertThrows(errorString || 'callable throws exception', callable); + assertTrue(e instanceof goog.labs.testing.MatcherError); +} + +</script> +</body> +</html> diff --git a/contexts/data/lib/closure-library/closure/goog/labs/testing/logicmatcher.js b/contexts/data/lib/closure-library/closure/goog/labs/testing/logicmatcher.js new file mode 100644 index 0000000..3004210 --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/testing/logicmatcher.js @@ -0,0 +1,206 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Provides the built-in logic matchers: anyOf, allOf, and isNot. + * + */ + + +goog.provide('goog.labs.testing.AllOfMatcher'); +goog.provide('goog.labs.testing.AnyOfMatcher'); +goog.provide('goog.labs.testing.IsNotMatcher'); + + +goog.require('goog.array'); +goog.require('goog.labs.testing.Matcher'); + + + +/** + * The AllOf matcher. + * + * @param {!Array.<!goog.labs.testing.Matcher>} matchers Input matchers. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.AllOfMatcher = function(matchers) { + /** + * @type {!Array.<!goog.labs.testing.Matcher>} + * @private + */ + this.matchers_ = matchers; +}; + + +/** + * Determines if all of the matchers match the input value. + * + * @override + */ +goog.labs.testing.AllOfMatcher.prototype.matches = function(actualValue) { + return goog.array.every(this.matchers_, function(matcher) { + return matcher.matches(actualValue); + }); +}; + + +/** + * Describes why the matcher failed. The returned string is a concatenation of + * all the failed matchers' error strings. + * + * @override + */ +goog.labs.testing.AllOfMatcher.prototype.describe = + function(actualValue) { + // TODO(user) : Optimize this to remove duplication with matches ? + var errorString = ''; + goog.array.forEach(this.matchers_, function(matcher) { + if (!matcher.matches(actualValue)) { + errorString += matcher.describe(actualValue) + '\n'; + } + }); + return errorString; +}; + + + +/** + * The AnyOf matcher. + * + * @param {!Array.<!goog.labs.testing.Matcher>} matchers Input matchers. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.AnyOfMatcher = function(matchers) { + /** + * @type {!Array.<!goog.labs.testing.Matcher>} + * @private + */ + this.matchers_ = matchers; +}; + + +/** + * Determines if any of the matchers matches the input value. + * + * @override + */ +goog.labs.testing.AnyOfMatcher.prototype.matches = function(actualValue) { + return goog.array.some(this.matchers_, function(matcher) { + return matcher.matches(actualValue); + }); +}; + + +/** + * Describes why the matcher failed. + * + * @override + */ +goog.labs.testing.AnyOfMatcher.prototype.describe = + function(actualValue) { + // TODO(user) : Optimize this to remove duplication with matches ? + var errorString = ''; + goog.array.forEach(this.matchers_, function(matcher) { + if (!matcher.matches(actualValue)) { + errorString += matcher.describe(actualValue) + '\n'; + } + }); + return errorString; +}; + + + +/** + * The IsNot matcher. + * + * @param {!goog.labs.testing.Matcher} matcher The matcher to negate. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.IsNotMatcher = function(matcher) { + /** + * @type {!goog.labs.testing.Matcher} + * @private + */ + this.matcher_ = matcher; +}; + + +/** + * Determines if the input value doesn't satisfy a matcher. + * + * @override + */ +goog.labs.testing.IsNotMatcher.prototype.matches = function(actualValue) { + return !this.matcher_.matches(actualValue); +}; + + +/** + * Describes why the matcher failed. + * + * @override + */ +goog.labs.testing.IsNotMatcher.prototype.describe = + function(actualValue) { + return 'The following is false: ' + this.matcher_.describe(actualValue); +}; + + +/** + * Creates a matcher that will succeed only if all of the given matchers + * succeed. + * + * @param {...goog.labs.testing.Matcher} var_args The matchers to test + * against. + * + * @return {!goog.labs.testing.AllOfMatcher} The AllOf matcher. + */ +function allOf(var_args) { + var matchers = goog.array.toArray(arguments); + return new goog.labs.testing.AllOfMatcher(matchers); +} + + +/** + * Accepts a set of matchers and returns a matcher which matches + * values which satisfy the constraints of any of the given matchers. + * + * @param {...goog.labs.testing.Matcher} var_args The matchers to test + * against. + * + * @return {!goog.labs.testing.AnyOfMatcher} The AnyOf matcher. + */ +function anyOf(var_args) { + var matchers = goog.array.toArray(arguments); + return new goog.labs.testing.AnyOfMatcher(matchers); +} + + +/** + * Returns a matcher that negates the input matcher. The returned + * matcher matches the values not matched by the input matcher and vice-versa. + * + * @param {!goog.labs.testing.Matcher} matcher The matcher to test against. + * + * @return {!goog.labs.testing.IsNotMatcher} The IsNot matcher. + */ +function isNot(matcher) { + return new goog.labs.testing.IsNotMatcher(matcher); +} diff --git a/contexts/data/lib/closure-library/closure/goog/labs/testing/logicmatcher_test.html b/contexts/data/lib/closure-library/closure/goog/labs/testing/logicmatcher_test.html new file mode 100644 index 0000000..58335fc --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/testing/logicmatcher_test.html @@ -0,0 +1,59 @@ +<!DOCTYPE html> +<html> +<!-- +Copyright 2012 The Closure Library Authors. All Rights Reserved. + +Use of this source code is governed by the Apache License, Version 2.0. +See the COPYING file for details. +--> +<head> +<title>Closure Unit Tests - Logic matchers</title> +<script src="../../base.js"></script> +<script> + +goog.require('goog.labs.testing.AllOfMatcher'); +goog.require('goog.labs.testing.GreaterThanMatcher'); +goog.require('goog.labs.testing.assertThat'); +goog.require('goog.testing.jsunit'); + +</script> +</head> +<body> +<script> + +function testAnyOf() { + goog.labs.testing.assertThat(5, anyOf(greaterThan(4), lessThan(3)), + '5 > 4 || 5 < 3'); + goog.labs.testing.assertThat(2, anyOf(greaterThan(4), lessThan(3)), + '2 > 4 || 2 < 3'); + + assertMatcherError(function() { + goog.labs.testing.assertThat(4, anyOf(greaterThan(5), lessThan(2))); + }, 'anyOf should throw exception when it fails'); +} + +function testAllOf() { + goog.labs.testing.assertThat(5, allOf(greaterThan(4), lessThan(6)), + '5 > 4 && 5 < 6'); + + assertMatcherError(function() { + goog.labs.testing.assertThat(4, allOf(lessThan(5), lessThan(3))); + }, 'allOf should throw exception when it fails'); +} + +function testIsNot() { + goog.labs.testing.assertThat(5, isNot(greaterThan(6)), '5 !> 6'); + + assertMatcherError(function() { + goog.labs.testing.assertThat(4, isNot(greaterThan(3))); + }, 'isNot should throw exception when it fails'); +} + +function assertMatcherError(callable, errorString) { + var e = assertThrows(errorString || 'callable throws exception', callable); + assertTrue(e instanceof goog.labs.testing.MatcherError); +} + +</script> +</body> +</html> diff --git a/contexts/data/lib/closure-library/closure/goog/labs/testing/matcher.js b/contexts/data/lib/closure-library/closure/goog/labs/testing/matcher.js new file mode 100644 index 0000000..b0f5fd4 --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/testing/matcher.js @@ -0,0 +1,51 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Provides the base Matcher interface. User code should use the + * matchers through assertThat statements and not directly. + */ + + +goog.provide('goog.labs.testing.Matcher'); + + + +/** + * A matcher object to be used in assertThat statements. + * @interface + */ +goog.labs.testing.Matcher = function() {}; + + +/** + * Determines whether a value matches the constraints of the match. + * + * @param {*} value The object to match. + * @return {boolean} Whether the input value matches this matcher. + */ +goog.labs.testing.Matcher.prototype.matches = function(value) {}; + + +/** + * Describes why the matcher failed. + * + * @param {*} value The value that didn't match. + * @param {string=} opt_description A partial description to which the reason + * will be appended. + * + * @return {string} Description of why the matcher failed. + */ +goog.labs.testing.Matcher.prototype.describe = + function(value, opt_description) {}; diff --git a/contexts/data/lib/closure-library/closure/goog/labs/testing/numbermatcher.js b/contexts/data/lib/closure-library/closure/goog/labs/testing/numbermatcher.js new file mode 100644 index 0000000..a78e5be --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/testing/numbermatcher.js @@ -0,0 +1,334 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Provides the built-in number matchers like lessThan, + * greaterThan, etc. + */ + + +goog.provide('goog.labs.testing.CloseToMatcher'); +goog.provide('goog.labs.testing.EqualToMatcher'); +goog.provide('goog.labs.testing.GreaterThanEqualToMatcher'); +goog.provide('goog.labs.testing.GreaterThanMatcher'); +goog.provide('goog.labs.testing.LessThanEqualToMatcher'); +goog.provide('goog.labs.testing.LessThanMatcher'); + + +goog.require('goog.asserts'); +goog.require('goog.labs.testing.Matcher'); + + + +/** + * The GreaterThan matcher. + * + * @param {number} value The value to compare. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.GreaterThanMatcher = function(value) { + /** + * @type {number} + * @private + */ + this.value_ = value; +}; + + +/** + * Determines if input value is greater than the expected value. + * + * @override + */ +goog.labs.testing.GreaterThanMatcher.prototype.matches = function(actualValue) { + goog.asserts.assertNumber(actualValue); + return actualValue > this.value_; +}; + + +/** + * @override + */ +goog.labs.testing.GreaterThanMatcher.prototype.describe = + function(actualValue) { + goog.asserts.assertNumber(actualValue); + return actualValue + ' is not greater than ' + this.value_; +}; + + + +/** + * The lessThan matcher. + * + * @param {number} value The value to compare. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.LessThanMatcher = function(value) { + /** + * @type {number} + * @private + */ + this.value_ = value; +}; + + +/** + * Determines if the input value is less than the expected value. + * + * @override + */ +goog.labs.testing.LessThanMatcher.prototype.matches = function(actualValue) { + goog.asserts.assertNumber(actualValue); + return actualValue < this.value_; +}; + + +/** + * @override + */ +goog.labs.testing.LessThanMatcher.prototype.describe = + function(actualValue) { + goog.asserts.assertNumber(actualValue); + return actualValue + ' is not less than ' + this.value_; +}; + + + +/** + * The GreaterThanEqualTo matcher. + * + * @param {number} value The value to compare. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.GreaterThanEqualToMatcher = function(value) { + /** + * @type {number} + * @private + */ + this.value_ = value; +}; + + +/** + * Determines if the input value is greater than equal to the expected value. + * + * @override + */ +goog.labs.testing.GreaterThanEqualToMatcher.prototype.matches = + function(actualValue) { + goog.asserts.assertNumber(actualValue); + return actualValue >= this.value_; +}; + + +/** + * @override + */ +goog.labs.testing.GreaterThanEqualToMatcher.prototype.describe = + function(actualValue) { + goog.asserts.assertNumber(actualValue); + return actualValue + ' is not greater than equal to ' + this.value_; +}; + + + +/** + * The LessThanEqualTo matcher. + * + * @param {number} value The value to compare. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.LessThanEqualToMatcher = function(value) { + /** + * @type {number} + * @private + */ + this.value_ = value; +}; + + +/** + * Determines if the input value is less than or equal to the expected value. + * + * @override + */ +goog.labs.testing.LessThanEqualToMatcher.prototype.matches = + function(actualValue) { + goog.asserts.assertNumber(actualValue); + return actualValue <= this.value_; +}; + + +/** + * @override + */ +goog.labs.testing.LessThanEqualToMatcher.prototype.describe = + function(actualValue) { + goog.asserts.assertNumber(actualValue); + return actualValue + ' is not less than equal to ' + this.value_; +}; + + + +/** + * The EqualTo matcher. + * + * @param {number} value The value to compare. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.EqualToMatcher = function(value) { + /** + * @type {number} + * @private + */ + this.value_ = value; +}; + + +/** + * Determines if the input value is equal to the expected value. + * + * @override + */ +goog.labs.testing.EqualToMatcher.prototype.matches = function(actualValue) { + goog.asserts.assertNumber(actualValue); + return actualValue === this.value_; +}; + + +/** + * @override + */ +goog.labs.testing.EqualToMatcher.prototype.describe = + function(actualValue) { + goog.asserts.assertNumber(actualValue); + return actualValue + ' is not equal to ' + this.value_; +}; + + + +/** + * The CloseTo matcher. + * + * @param {number} value The value to compare. + * @param {number} range The range to check within. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.CloseToMatcher = function(value, range) { + /** + * @type {number} + * @private + */ + this.value_ = value; + /** + * @type {number} + * @private + */ + this.range_ = range; +}; + + +/** + * Determines if input value is within a certain range of the expected value. + * + * @override + */ +goog.labs.testing.CloseToMatcher.prototype.matches = function(actualValue) { + goog.asserts.assertNumber(actualValue); + return Math.abs(this.value_ - actualValue) < this.range_; +}; + + +/** + * @override + */ +goog.labs.testing.CloseToMatcher.prototype.describe = + function(actualValue) { + goog.asserts.assertNumber(actualValue); + return actualValue + ' is not close to(' + this.range_ + ') ' + this.value_; +}; + + +/** + * @param {number} value The expected value. + * + * @return {!goog.labs.testing.GreaterThanMatcher} A GreaterThanMatcher. + */ +function greaterThan(value) { + return new goog.labs.testing.GreaterThanMatcher(value); +} + + +/** + * @param {number} value The expected value. + * + * @return {!goog.labs.testing.GreaterThanEqualToMatcher} A + * GreaterThanEqualToMatcher. + */ +function greaterThanEqualTo(value) { + return new goog.labs.testing.GreaterThanEqualToMatcher(value); +} + + +/** + * @param {number} value The expected value. + * + * @return {!goog.labs.testing.LessThanMatcher} A LessThanMatcher. + */ +function lessThan(value) { + return new goog.labs.testing.LessThanMatcher(value); +} + + +/** + * @param {number} value The expected value. + * + * @return {!goog.labs.testing.LessThanEqualToMatcher} A LessThanEqualToMatcher. + */ +function lessThanEqualTo(value) { + return new goog.labs.testing.LessThanEqualToMatcher(value); +} + + +/** + * @param {number} value The expected value. + * + * @return {!goog.labs.testing.EqualToMatcher} An EqualToMatcher. + */ +function equalTo(value) { + return new goog.labs.testing.EqualToMatcher(value); +} + + +/** + * @param {number} value The expected value. + * @param {number} range The maximum allowed difference from the expected value. + * + * @return {!goog.labs.testing.CloseToMatcher} A CloseToMatcher. + */ +function closeTo(value, range) { + return new goog.labs.testing.CloseToMatcher(value, range); +} diff --git a/contexts/data/lib/closure-library/closure/goog/labs/testing/numbermatcher_test.html b/contexts/data/lib/closure-library/closure/goog/labs/testing/numbermatcher_test.html new file mode 100644 index 0000000..3d1f40f --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/testing/numbermatcher_test.html @@ -0,0 +1,79 @@ +<!DOCTYPE html> +<html> +<!-- +Copyright 2012 The Closure Library Authors. All Rights Reserved. + +Use of this source code is governed by the Apache License, Version 2.0. +See the COPYING file for details. +--> +<head> +<title>Closure Unit Tests - Number matchers</title> +<script src="../../base.js"></script> +<script> + +goog.require('goog.labs.testing.CloseToMatcher'); +goog.require('goog.labs.testing.EqualToMatcher'); +goog.require('goog.labs.testing.GreaterThanEqualToMatcher'); +goog.require('goog.labs.testing.GreaterThanMatcher'); +goog.require('goog.labs.testing.LessThanEqualToMatcher'); +goog.require('goog.labs.testing.LessThanMatcher'); +goog.require('goog.labs.testing.assertThat'); +goog.require('goog.testing.jsunit'); + +</script> +</head> +<body> +<script> + +function testGreaterThan() { + goog.labs.testing.assertThat(4, greaterThan(3), '4 > 3'); + assertMatcherError(function() { + goog.labs.testing.assertThat(2, greaterThan(3)); + }, '2 > 3'); +} + +function testGreaterThanEqualTo() { + goog.labs.testing.assertThat(5, greaterThanEqualTo(4), '5 >= 4'); + goog.labs.testing.assertThat(5, greaterThanEqualTo(5), '5 >= 5'); + assertMatcherError(function() { + goog.labs.testing.assertThat(3, greaterThanEqualTo(5)); + }, '3 >= 5'); +} + +function testLessThan() { + goog.labs.testing.assertThat(6, lessThan(7), '6 < 7'); + assertMatcherError(function() { + goog.labs.testing.assertThat(7, lessThan(5)); + }, '7 < 5'); +} + +function testLessThanEqualTo() { + goog.labs.testing.assertThat(8, lessThanEqualTo(8), '8 <= 8'); + goog.labs.testing.assertThat(8, lessThanEqualTo(9), '8 <= 9'); + assertMatcherError(function() { + goog.labs.testing.assertThat(7, lessThanEqualTo(5)); + }, '7 <= 5'); +} + +function testEqualTo() { + goog.labs.testing.assertThat(7, equalTo(7), '7 equals 7'); + assertMatcherError(function() { + goog.labs.testing.assertThat(7, equalTo(5)); + }, '7 == 5'); +} + +function testCloseTo() { + goog.labs.testing.assertThat(7, closeTo(10, 4), '7 within range(4) of 10'); + assertMatcherError(function() { + goog.labs.testing.assertThat(5, closeTo(10, 3)); + }, '5 within range(3) of 10'); +} + +function assertMatcherError(callable, errorString) { + var e = assertThrows(errorString || 'callable throws exception', callable); + assertTrue(e instanceof goog.labs.testing.MatcherError); +} + +</script> +</body> +</html> diff --git a/contexts/data/lib/closure-library/closure/goog/labs/testing/objectmatcher.js b/contexts/data/lib/closure-library/closure/goog/labs/testing/objectmatcher.js new file mode 100644 index 0000000..974d024 --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/testing/objectmatcher.js @@ -0,0 +1,306 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Provides the built-in object matchers like equalsObject, + * hasProperty, instanceOf, etc. + */ + + + +goog.provide('goog.labs.testing.HasPropertyMatcher'); +goog.provide('goog.labs.testing.InstanceOfMatcher'); +goog.provide('goog.labs.testing.IsNullMatcher'); +goog.provide('goog.labs.testing.IsNullOrUndefinedMatcher'); +goog.provide('goog.labs.testing.IsUndefinedMatcher'); +goog.provide('goog.labs.testing.ObjectEqualsMatcher'); + + +goog.require('goog.labs.testing.Matcher'); +goog.require('goog.string'); + + + +/** + * The Equals matcher. + * + * @param {!Object} expectedObject The expected object. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.ObjectEqualsMatcher = function(expectedObject) { + /** + * @type {!Object} + * @private + */ + this.object_ = expectedObject; +}; + + +/** + * Determines if two objects are the same. + * + * @override + */ +goog.labs.testing.ObjectEqualsMatcher.prototype.matches = + function(actualObject) { + return actualObject === this.object_; +}; + + +/** + * @override + */ +goog.labs.testing.ObjectEqualsMatcher.prototype.describe = + function(actualObject) { + return 'Input object is not the same as the expected object.'; +}; + + + +/** + * The HasProperty matcher. + * + * @param {string} property Name of the property to test. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.HasPropertyMatcher = function(property) { + /** + * @type {string} + * @private + */ + this.property_ = property; +}; + + +/** + * Determines if an object has a property. + * + * @override + */ +goog.labs.testing.HasPropertyMatcher.prototype.matches = + function(actualObject) { + return this.property_ in actualObject; +}; + + +/** + * @override + */ +goog.labs.testing.HasPropertyMatcher.prototype.describe = + function(actualObject) { + return 'Object does not have property: ' + this.property_; +}; + + + +/** + * The InstanceOf matcher. + * + * @param {!Object} object The expected class object. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.InstanceOfMatcher = function(object) { + /** + * @type {!Object} + * @private + */ + this.object_ = object; +}; + + +/** + * Determines if an object is an instance of another object. + * + * @override + */ +goog.labs.testing.InstanceOfMatcher.prototype.matches = + function(actualObject) { + return actualObject instanceof this.object_; +}; + + +/** + * @override + */ +goog.labs.testing.InstanceOfMatcher.prototype.describe = + function(actualObject) { + return 'Input object is not an instance of the expected object'; +}; + + + +/** + * The IsNullOrUndefined matcher. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.IsNullOrUndefinedMatcher = function() {}; + + +/** + * Determines if input value is null or undefined. + * + * @override + */ +goog.labs.testing.IsNullOrUndefinedMatcher.prototype.matches = + function(actualValue) { + return !goog.isDefAndNotNull(actualValue); +}; + + +/** + * @override + */ +goog.labs.testing.IsNullOrUndefinedMatcher.prototype.describe = + function(actualValue) { + return actualValue + ' is not null or undefined.'; +}; + + + +/** + * The IsNull matcher. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.IsNullMatcher = function() {}; + + +/** + * Determines if input value is null. + * + * @override + */ +goog.labs.testing.IsNullMatcher.prototype.matches = + function(actualValue) { + return goog.isNull(actualValue); +}; + + +/** + * @override + */ +goog.labs.testing.IsNullMatcher.prototype.describe = + function(actualValue) { + return actualValue + ' is not null.'; +}; + + + +/** + * The IsUndefined matcher. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.IsUndefinedMatcher = function() {}; + + +/** + * Determines if input value is undefined. + * + * @override + */ +goog.labs.testing.IsUndefinedMatcher.prototype.matches = + function(actualValue) { + return !goog.isDef(actualValue); +}; + + +/** + * @override + */ +goog.labs.testing.IsUndefinedMatcher.prototype.describe = + function(actualValue) { + return actualValue + ' is not undefined.'; +}; + + +/** + * Returns a matcher that matches objects that are equal to the input object. + * Equality in this case means the two objects are references to the same + * object. + * + * @param {!Object} object The expected object. + * + * @return {!goog.labs.testing.ObjectEqualsMatcher} A + * ObjectEqualsMatcher. + */ +function equalsObject(object) { + return new goog.labs.testing.ObjectEqualsMatcher(object); +} + + +/** + * Returns a matcher that matches objects that contain the input property. + * + * @param {string} property The property name to check. + * + * @return {!goog.labs.testing.HasPropertyMatcher} A HasPropertyMatcher. + */ +function hasProperty(property) { + return new goog.labs.testing.HasPropertyMatcher(property); +} + + +/** + * Returns a matcher that matches instances of the input class. + * + * @param {!Object} object The class object. + * + * @return {!goog.labs.testing.InstanceOfMatcher} A + * InstanceOfMatcher. + */ +function instanceOfClass(object) { + return new goog.labs.testing.InstanceOfMatcher(object); +} + + +/** + * Returns a matcher that matches all null values. + * + * @return {!goog.labs.testing.IsNullMatcher} A IsNullMatcher. + */ +function isNull() { + return new goog.labs.testing.IsNullMatcher(); +} + + +/** + * Returns a matcher that matches all null and undefined values. + * + * @return {!goog.labs.testing.IsNullOrUndefinedMatcher} A + * IsNullOrUndefinedMatcher. + */ +function isNullOrUndefined() { + return new goog.labs.testing.IsNullOrUndefinedMatcher(); +} + + +/** + * Returns a matcher that matches undefined values. + * + * @return {!goog.labs.testing.IsUndefinedMatcher} A IsUndefinedMatcher. + */ +function isUndefined() { + return new goog.labs.testing.IsUndefinedMatcher(); +} diff --git a/contexts/data/lib/closure-library/closure/goog/labs/testing/objectmatcher_test.html b/contexts/data/lib/closure-library/closure/goog/labs/testing/objectmatcher_test.html new file mode 100644 index 0000000..92cc2de --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/testing/objectmatcher_test.html @@ -0,0 +1,102 @@ +<!DOCTYPE html> +<html> +<!-- +Copyright 2012 The Closure Library Authors. All Rights Reserved. + +Use of this source code is governed by the Apache License, Version 2.0. +See the COPYING file for details. +--> +<head> +<title>Closure Unit Tests - Object matchers</title> +<script src="../../base.js"></script> +<script> + +goog.require('goog.labs.testing.ObjectEqualsMatcher'); +goog.require('goog.labs.testing.HasPropertyMatcher'); +goog.require('goog.labs.testing.InstanceOfMatcher'); +goog.require('goog.labs.testing.IsNullOrUndefinedMatcher'); +goog.require('goog.labs.testing.IsNullMatcher'); +goog.require('goog.labs.testing.IsUndefinedMatcher'); +goog.require('goog.labs.testing.assertThat'); +goog.require('goog.testing.jsunit'); + +</script> +</head> +<body> +<script> + +function testObjectEquals() { + var obj1 = {x: 1}; + var obj2 = obj1; + goog.labs.testing.assertThat(obj1, equalsObject(obj2), 'obj1 equals obj2'); + + assertMatcherError(function() { + goog.labs.testing.assertThat({x: 1}, equalsObject({})); + }, 'equalsObject does not throw exception on failure'); +} + +function testInstanceOf() { + function expected() { + this.x = 1; + } + var input = new expected(); + goog.labs.testing.assertThat(input, instanceOfClass(expected), + 'input is an instance of expected'); + + assertMatcherError(function() { + goog.labs.testing.assertThat(5, instanceOfClass(function() {})); + }, 'instanceOfClass does not throw exception on failure'); +} + +function testHasProperty() { + goog.labs.testing.assertThat({x: 1}, hasProperty('x'), + '{x:1} has property x}'); + + assertMatcherError(function() { + goog.labs.testing.assertThat({x: 1}, hasProperty('y')); + }, 'hasProperty does not throw exception on failure'); +} + +function testIsNull() { + goog.labs.testing.assertThat(null, isNull(), 'null is null'); + + assertMatcherError(function() { + goog.labs.testing.assertThat(5, isNull()); + }, 'isNull does not throw exception on failure'); +} + +function testIsNullOrUndefined() { + var x; + goog.labs.testing.assertThat(undefined, isNullOrUndefined(), + 'undefined is null or undefined'); + goog.labs.testing.assertThat(x, isNullOrUndefined(), + 'undefined is null or undefined'); + x = null; + goog.labs.testing.assertThat(null, isNullOrUndefined(), + 'null is null or undefined'); + goog.labs.testing.assertThat(x, isNullOrUndefined(), + 'null is null or undefined'); + + assertMatcherError(function() { + goog.labs.testing.assertThat(5, isNullOrUndefined()); + }, 'isNullOrUndefined does not throw exception on failure'); +} + +function testIsUndefined() { + var x; + goog.labs.testing.assertThat(undefined, isUndefined(), + 'undefined is undefined'); + goog.labs.testing.assertThat(x, isUndefined(), 'undefined is undefined'); + + assertMatcherError(function() { + goog.labs.testing.assertThat(5, isUndefined()); + }, 'isUndefined does not throw exception on failure'); +} + +function assertMatcherError(callable, errorString) { + var e = assertThrows(errorString || 'callable throws exception', callable); + assertTrue(e instanceof goog.labs.testing.MatcherError); +} +</script> +</body> +</html> diff --git a/contexts/data/lib/closure-library/closure/goog/labs/testing/stringmatcher.js b/contexts/data/lib/closure-library/closure/goog/labs/testing/stringmatcher.js new file mode 100644 index 0000000..26daabc --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/testing/stringmatcher.js @@ -0,0 +1,350 @@ +// Copyright 2012 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Provides the built-in string matchers like containsString, + * startsWith, endsWith, etc. + */ + + +goog.provide('goog.labs.testing.ContainsStringMatcher'); +goog.provide('goog.labs.testing.EndsWithMatcher'); +goog.provide('goog.labs.testing.EqualToIgnoringCaseMatcher'); +goog.provide('goog.labs.testing.EqualToIgnoringWhitespaceMatcher'); +goog.provide('goog.labs.testing.EqualsMatcher'); +goog.provide('goog.labs.testing.StartsWithMatcher'); +goog.provide('goog.labs.testing.StringContainsInOrderMatcher'); + + +goog.require('goog.asserts'); +goog.require('goog.labs.testing.Matcher'); +goog.require('goog.string'); + + + +/** + * The ContainsString matcher. + * + * @param {string} value The expected string. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.ContainsStringMatcher = function(value) { + /** + * @type {string} + * @private + */ + this.value_ = value; +}; + + +/** + * Determines if input string contains the expected string. + * + * @override + */ +goog.labs.testing.ContainsStringMatcher.prototype.matches = + function(actualValue) { + goog.asserts.assertString(actualValue); + return goog.string.contains(actualValue, this.value_); +}; + + +/** + * @override + */ +goog.labs.testing.ContainsStringMatcher.prototype.describe = + function(actualValue) { + return actualValue + ' does not contain ' + this.value_; +}; + + + +/** + * The EndsWith matcher. + * + * @param {string} value The expected string. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.EndsWithMatcher = function(value) { + /** + * @type {string} + * @private + */ + this.value_ = value; +}; + + +/** + * Determines if input string ends with the expected string. + * + * @override + */ +goog.labs.testing.EndsWithMatcher.prototype.matches = function(actualValue) { + goog.asserts.assertString(actualValue); + return goog.string.endsWith(actualValue, this.value_); +}; + + +/** + * @override + */ +goog.labs.testing.EndsWithMatcher.prototype.describe = + function(actualValue) { + return actualValue + ' does not end with ' + this.value_; +}; + + + +/** + * The EqualToIgnoringWhitespace matcher. + * + * @param {string} value The expected string. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.EqualToIgnoringWhitespaceMatcher = function(value) { + /** + * @type {string} + * @private + */ + this.value_ = value; +}; + + +/** + * Determines if input string contains the expected string. + * + * @override + */ +goog.labs.testing.EqualToIgnoringWhitespaceMatcher.prototype.matches = + function(actualValue) { + goog.asserts.assertString(actualValue); + var string1 = goog.string.collapseWhitespace(actualValue); + + return goog.string.caseInsensitiveCompare(this.value_, string1) === 0; +}; + + +/** + * @override + */ +goog.labs.testing.EqualToIgnoringWhitespaceMatcher.prototype.describe = + function(actualValue) { + return actualValue + ' is not equal(ignoring whitespace) to ' + this.value_; +}; + + + +/** + * The Equals matcher. + * + * @param {string} value The expected string. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.EqualsMatcher = function(value) { + /** + * @type {string} + * @private + */ + this.value_ = value; +}; + + +/** + * Determines if input string is equal to the expected string. + * + * @override + */ +goog.labs.testing.EqualsMatcher.prototype.matches = function(actualValue) { + goog.asserts.assertString(actualValue); + return this.value_ === actualValue; +}; + + +/** + * @override + */ +goog.labs.testing.EqualsMatcher.prototype.describe = + function(actualValue) { + return actualValue + ' is not equal to ' + this.value_; +}; + + + +/** + * The StartsWith matcher. + * + * @param {string} value The expected string. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.StartsWithMatcher = function(value) { + /** + * @type {string} + * @private + */ + this.value_ = value; +}; + + +/** + * Determines if input string starts with the expected string. + * + * @override + */ +goog.labs.testing.StartsWithMatcher.prototype.matches = function(actualValue) { + goog.asserts.assertString(actualValue); + return goog.string.startsWith(actualValue, this.value_); +}; + + +/** + * @override + */ +goog.labs.testing.StartsWithMatcher.prototype.describe = + function(actualValue) { + return actualValue + ' does not start with ' + this.value_; +}; + + + +/** + * The StringContainsInOrdermatcher. + * + * @param {Array.<string>} values The expected string values. + * + * @constructor + * @implements {goog.labs.testing.Matcher} + */ +goog.labs.testing.StringContainsInOrderMatcher = function(values) { + /** + * @type {Array.<string>} + * @private + */ + this.values_ = values; +}; + + +/** + * Determines if input string contains, in order, the expected array of strings. + * + * @override + */ +goog.labs.testing.StringContainsInOrderMatcher.prototype.matches = + function(actualValue) { + goog.asserts.assertString(actualValue); + var currentIndex, previousIndex = 0; + for (var i = 0; i < this.values_.length; i++) { + currentIndex = goog.string.contains(actualValue, this.values_[i]); + if (currentIndex < 0 || currentIndex < previousIndex) { + return false; + } + previousIndex = currentIndex; + } + return true; +}; + + +/** + * @override + */ +goog.labs.testing.StringContainsInOrderMatcher.prototype.describe = + function(actualValue) { + return actualValue + ' does not contain the expected values in order.'; +}; + + +/** + * Matches a string containing the given string. + * + * @param {string} value The expected value. + * + * @return {!goog.labs.testing.ContainsStringMatcher} A + * ContainsStringMatcher. + */ +function containsString(value) { + return new goog.labs.testing.ContainsStringMatcher(value); +} + + +/** + * Matches a string that ends with the given string. + * + * @param {string} value The expected value. + * + * @return {!goog.labs.testing.EndsWithMatcher} A + * EndsWithMatcher. + */ +function endsWith(value) { + return new goog.labs.testing.EndsWithMatcher(value); +} + + +/** + * Matches a string that equals (ignoring whitespace) the given string. + * + * @param {string} value The expected value. + * + * @return {!goog.labs.testing.EqualToIgnoringWhitespaceMatcher} A + * EqualToIgnoringWhitespaceMatcher. + */ +function equalToIgnoringWhitespace(value) { + return new goog.labs.testing.EqualToIgnoringWhitespaceMatcher(value); +} + + +/** + * Matches a string that equals the given string. + * + * @param {string} value The expected value. + * + * @return {!goog.labs.testing.EqualsMatcher} A EqualsMatcher. + */ +function equals(value) { + return new goog.labs.testing.EqualsMatcher(value); +} + + +/** + * Matches a string that starts with the given string. + * + * @param {string} value The expected value. + * + * @return {!goog.labs.testing.StartsWithMatcher} A + * StartsWithMatcher. + */ +function startsWith(value) { + return new goog.labs.testing.StartsWithMatcher(value); +} + + +/** + * Matches a string that contains the given strings in order. + * + * @param {Array.<string>} values The expected value. + * + * @return {!goog.labs.testing.StringContainsInOrderMatcher} A + * StringContainsInOrderMatcher. + */ +function stringContainsInOrder(values) { + return new goog.labs.testing.StringContainsInOrderMatcher(values); +} diff --git a/contexts/data/lib/closure-library/closure/goog/labs/testing/stringmatcher_test.html b/contexts/data/lib/closure-library/closure/goog/labs/testing/stringmatcher_test.html new file mode 100644 index 0000000..9ad6dd4 --- /dev/null +++ b/contexts/data/lib/closure-library/closure/goog/labs/testing/stringmatcher_test.html @@ -0,0 +1,91 @@ +<!DOCTYPE html> +<html> +<!-- +Copyright 2012 The Closure Library Authors. All Rights Reserved. + +Use of this source code is governed by the Apache License, Version 2.0. +See the COPYING file for details. +--> +<head> +<title>Closure Unit Tests - String matchers</title> +<script src="../../base.js"></script> +<script> + +goog.require('goog.labs.testing.ContainsStringMatcher'); +goog.require('goog.labs.testing.EndsWithMatcher'); +goog.require('goog.labs.testing.EqualsMatcher'); +goog.require('goog.labs.testing.EqualToIgnoringWhitespaceMatcher'); +goog.require('goog.labs.testing.StartsWithMatcher'); +goog.require('goog.labs.testing.StringContainsInOrderMatcher'); +goog.require('goog.labs.testing.assertThat'); +goog.require('goog.testing.jsunit'); + +</script> +</head> +<body> +<script> + +function testContainsString() { + goog.labs.testing.assertThat('hello', containsString('ell'), + 'hello contains ell'); + + assertMatcherError(function() { + goog.labs.testing.assertThat('hello', containsString('world!')); + }, 'containsString should throw exception when it fails'); +} + +function testEndsWith() { + goog.labs.testing.assertThat('hello', endsWith('llo'), 'hello ends with llo'); + + assertMatcherError(function() { + goog.labs.testing.assertThat('minutes', endsWith('midnight')); + }, 'endsWith should throw exception when it fails'); +} + +function testEqualToIgnoringWhitespace() { + goog.labs.testing.assertThat(' h\n EL L\tO', + equalToIgnoringWhitespace("h el l o"), + '" h EL L\tO " is equal to "h el l o"'); + + assertMatcherError(function() { + goog.labs.testing.assertThat('hybrid', equalToIgnoringWhitespace('theory')); + }, 'equalToIgnoringWhitespace should throw exception when it fails'); +} + +function testEquals() { + goog.labs.testing.assertThat('hello', equals('hello'), + 'hello equals hello'); + + assertMatcherError(function() { + goog.labs.testing.assertThat('thousand', equals('suns')); + }, 'equals should throw exception when it fails'); +} + +function testStartsWith() { + goog.labs.testing.assertThat('hello', startsWith('hel'), + 'hello starts with hel'); + + assertMatcherError(function() { + goog.labs.testing.assertThat('linkin', startsWith('park')); + }, 'startsWith should throw exception when it fails'); +} + +function testStringContainsInOrder() { + goog.labs.testing.assertThat('hello', + stringContainsInOrder(['h', 'el', 'el', 'l', 'o']), + 'hello contains in order: [h, el, l, o]'); + + assertMatcherError(function() { + goog.labs.testing.assertThat('hybrid', stringContainsInOrder(['hy', 'brid', + 'theory'])); + }, 'stringContainsInOrder should throw exception when it fails'); +} + +function assertMatcherError(callable, errorString) { + var e = assertThrows(errorString || 'callable throws exception', callable); + assertTrue(e instanceof goog.labs.testing.MatcherError); +} + +</script> +</body> +</html> |