This commit is contained in:
Éric Lemoine
2013-03-11 13:35:17 +01:00
parent 849774dceb
commit f150259eee
1189 changed files with 341774 additions and 2001 deletions
@@ -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.labs.result.SimpleResult');
goog.require('goog.net.EventType');
goog.require('goog.userAgent');
/**
* Loads a single image. Useful for preloading images. May be combined with
* goog.labs.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.labs.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.labs.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;
};
@@ -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)
*/
goog.provide('goog.labs.net.imageTest');
goog.require('goog.events');
goog.require('goog.labs.net.image');
goog.require('goog.labs.result');
goog.require('goog.labs.result.Result');
goog.require('goog.net.EventType');
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.labs.result.waitOnSuccess(result, function(value) {
assertEquals(goog.labs.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.labs.result.wait(result, function(result) {
assertEquals(goog.labs.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.labs.result.waitOnSuccess(result, function(value) {
assertEquals(goog.labs.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.labs.result.waitOnSuccess(result, function(value) {
assertEquals(goog.labs.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();
});
}
@@ -0,0 +1,444 @@
// 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, results 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.debug.Error');
goog.require('goog.json');
goog.require('goog.labs.result');
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 Result = goog.labs.result.Result;
var SimpleResult = goog.labs.result.SimpleResult;
var Wait = goog.labs.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;
/**
* 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 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.labs.result.transform(result,
_.getResponseText_);
return transformedResult;
};
/**
* 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.labs.result.transform(result,
_.getResponseText_);
return transformedResult;
};
/**
* 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 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;
};
/**
* 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.labs.result.Result.
goog.labs.result.waitOnError(result, function(result) {
if (result.isCanceled()) {
xhr.abort();
xhr.onreadystatechange = goog.nullFunction;
}
});
function callback(data) {
result.setValue(data);
}
function errback(err) {
result.setError(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));
}
}
};
// 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 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;
};
/**
* 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.labs.result.transform(result,
_.getResponseText_);
var prefixStrippedResult = resultWithResponseText;
if (options && options.xssiPrefix) {
prefixStrippedResult = goog.labs.result.transform(resultWithResponseText,
goog.partial(_.stripXssiPrefix_, options.xssiPrefix));
}
var jsonParsedResult = goog.labs.result.transform(prefixStrippedResult,
goog.json.parse);
return jsonParsedResult;
};
/**
* 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);
/** @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);
/**
* The HTTP status code for the error.
* @type {number}
*/
this.status = status;
};
goog.inherits(_.HttpError, _.Error);
/** @override */
_.HttpError.prototype.name = 'XhrHttpError';
/**
* 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
@@ -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;
};
@@ -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_;
};
@@ -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) {};
@@ -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);
};
@@ -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;
};
@@ -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_;
};
@@ -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;
};
@@ -0,0 +1,57 @@
// 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 adaptor from a Result to a Deferred.
*
* TODO (vbhasin): cancel() support.
* TODO (vbhasin): See if we can make this a static.
* TODO (gboyer, vbhasin): Rename to "Adapter" once this graduates; this is the
* proper programmer spelling.
*/
goog.provide('goog.labs.result.DeferredAdaptor');
goog.require('goog.async.Deferred');
goog.require('goog.labs.result');
goog.require('goog.labs.result.Result');
/**
* An adaptor from Result to a Deferred, for use with existing Deferred chains.
*
* @param {!goog.labs.result.Result} result A result.
* @constructor
* @extends {goog.async.Deferred}
*/
goog.labs.result.DeferredAdaptor = function(result) {
goog.base(this);
goog.labs.result.wait(result, function(result) {
if (this.hasFired()) {
return;
}
if (result.getState() == goog.labs.result.Result.State.SUCCESS) {
this.callback(result.getValue());
} else if (result.getState() == goog.labs.result.Result.State.ERROR) {
if (result.getError() instanceof goog.labs.result.Result.CancelError) {
this.cancel();
} else {
this.errback(result.getError());
}
}
}, this);
};
goog.inherits(goog.labs.result.DeferredAdaptor, goog.async.Deferred);
@@ -0,0 +1,108 @@
// 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 Defines an interface that represents a Result.
*/
goog.provide('goog.labs.result.Result');
goog.require('goog.debug.Error');
/**
* A Result object represents a value returned by an asynchronous
* operation at some point in the future (e.g. a network fetch). This is akin
* to a 'Promise' or a 'Future' in other languages and frameworks.
*
* @interface
*/
goog.labs.result.Result = function() {};
/**
* Attaches handlers to be called when the value of this Result is available.
*
* @param {!function(!goog.labs.result.Result)} handler The function called when
* the value is available. The function is passed the Result object as the
* only argument.
*/
goog.labs.result.Result.prototype.wait = function(handler) {};
/**
* The States this object can be in.
*
* @enum {string}
*/
goog.labs.result.Result.State = {
/** The operation was a success and the value is available. */
SUCCESS: 'success',
/** The operation resulted in an error. */
ERROR: 'error',
/** The operation is incomplete and the value is not yet available. */
PENDING: 'pending'
};
/**
* @return {!goog.labs.result.Result.State} The state of this Result.
*/
goog.labs.result.Result.prototype.getState = function() {};
/**
* @return {*} The value of this Result. Will return undefined if the Result is
* pending or was an error.
*/
goog.labs.result.Result.prototype.getValue = function() {};
/**
* @return {*} The error slug for this Result. Will return undefined if the
* Result was a success, the error slug was not set, or if the Result is
* pending.
*/
goog.labs.result.Result.prototype.getError = function() {};
/**
* Cancels the current Result, invoking the canceler function, if set.
*
* @return {boolean} Whether the Result was canceled.
*/
goog.labs.result.Result.prototype.cancel = function() {};
/**
* @return {boolean} Whether this Result was canceled.
*/
goog.labs.result.Result.prototype.isCanceled = function() {};
/**
* The value to be passed to the error handlers invoked upon cancellation.
* @constructor
* @param {string=} opt_msg The error message for CancelError.
* @extends {goog.debug.Error}
*/
goog.labs.result.Result.CancelError = function(opt_msg) {
var msg = opt_msg || 'Result canceled';
goog.base(this, msg);
};
goog.inherits(goog.labs.result.Result.CancelError, goog.debug.Error);
@@ -0,0 +1,382 @@
// 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 This file provides primitives and tools (wait, transform,
* chain, combine) that make it easier to work with Results. This section
* gives an overview of their functionality along with some examples and the
* actual definitions have detailed descriptions next to them.
*
*/
goog.provide('goog.labs.result');
goog.require('goog.array');
goog.require('goog.labs.result.Result');
goog.require('goog.labs.result.SimpleResult');
/**
* Calls the handler on resolution of the result (success or failure).
* The handler is passed the result object as the only parameter. The call will
* be immediate if the result is no longer pending.
*
* Example:
* <pre>
*
* var result = xhr.get('testdata/xhr_test_text.data');
*
* // Wait for the result to be resolved and alert it's state.
* goog.labs.result.wait(result, function(result) {
* alert('State: ' + result.getState());
* });
* </pre>
*
* @param {!goog.labs.result.Result} result The result to install the handlers.
* @param {!function(!goog.labs.result.Result)} handler The handler to be
* called. The handler is passed the result object as the only parameter.
* @param {!Object=} opt_scope Optional scope for the handler.
*/
goog.labs.result.wait = function(result, handler, opt_scope) {
result.wait(opt_scope ? goog.bind(handler, opt_scope) : handler);
};
/**
* Calls the handler if the result succeeds. The result object is the only
* parameter passed to the handler. The call will be immediate if the result
* has already succeeded.
*
* Example:
* <pre>
*
* var result = xhr.get('testdata/xhr_test_text.data');
*
* // attach a success handler.
* goog.labs.result.waitOnSuccess(result, function(result) {
* var datavalue = result.getvalue();
* alert('value : ' + datavalue);
* });
* </pre>
*
* @param {!goog.labs.result.Result} result The result to install the handlers.
* @param {!function(*, !goog.labs.result.Result)} handler The handler to be
* called. The handler is passed the result value and the result as
* parameters.
* @param {!Object=} opt_scope Optional scope for the handler.
*/
goog.labs.result.waitOnSuccess = function(result, handler, opt_scope) {
goog.labs.result.wait(result, function(res) {
if (res.getState() == goog.labs.result.Result.State.SUCCESS) {
// 'this' refers to opt_scope
handler.call(this, res.getValue(), res);
}
}, opt_scope);
};
/**
* Calls the handler if the result action errors. The result object is passed as
* the only parameter to the handler. The call will be immediate if the result
* object has already resolved to an error.
*
* Example:
*
* <pre>
*
* var result = xhr.get('testdata/xhr_test_text.data');
*
* // Attach a failure handler.
* goog.labs.result.waitOnError(result, function(error) {
* // Failed asynchronous call!
* });
* </pre>
*
* @param {!goog.labs.result.Result} result The result to install the handlers.
* @param {!function(!goog.labs.result.Result)} handler The handler to be
* called. The handler is passed the result object as the only parameter.
* @param {!Object=} opt_scope Optional scope for the handler.
*/
goog.labs.result.waitOnError = function(result, handler, opt_scope) {
goog.labs.result.wait(result, function(res) {
if (res.getState() == goog.labs.result.Result.State.ERROR) {
// 'this' refers to opt_scope
handler.call(this, res);
}
}, opt_scope);
};
/**
* Given a result and a transform function, returns a new result whose value,
* on success, will be the value of the given result after having been passed
* through the transform function.
*
* If the given result is an error, the returned result is also an error and the
* transform will not be called.
*
* Example:
* <pre>
*
* var result = xhr.getJson('testdata/xhr_test_json.data');
*
* // Transform contents of returned data using 'processJson' and create a
* // transformed result to use returned JSON.
* var transformedResult = goog.labs.result.transform(result, processJson);
*
* // Attach success and failure handlers to the tranformed result.
* goog.labs.result.waitOnSuccess(transformedResult, function(result) {
* var jsonData = result.getValue();
* assertEquals('ok', jsonData['stat']);
* });
*
* goog.labs.result.waitOnError(transformedResult, function(error) {
* // Failed getJson call
* });
* </pre>
*
* @param {!goog.labs.result.Result} result The result whose value will be
* transformed.
* @param {!Function} transformer The transformer
* function. The return value of this function will become the value of the
* returned result.
*
* @return {!goog.labs.result.Result} A new Result whose eventual value will be
* the returned value of the transformer function.
*/
goog.labs.result.transform = function(result, transformer) {
var returnedResult = new goog.labs.result.SimpleResult();
goog.labs.result.wait(result, function(res) {
if (res.getState() == goog.labs.result.Result.State.SUCCESS) {
returnedResult.setValue(transformer(res.getValue()));
} else {
returnedResult.setError(res.getError());
}
});
return returnedResult;
};
/**
* The chain function aids in chaining of asynchronous Results. This provides a
* convenience for use cases where asynchronous operations must happen serially
* i.e. subsequent asynchronous operations are dependent on data returned by
* prior asynchronous operations.
*
* It accepts a result and an action callback as arguments and returns a
* result. The action callback is called when the first result succeeds and is
* supposed to return a second result. The returned result is resolved when one
* of both of the results resolve (depending on their success or failure.) The
* state and value of the returned result in the various cases is documented
* below:
*
* First Result State: Second Result State: Returned Result State:
* SUCCESS SUCCESS SUCCESS
* SUCCESS ERROR ERROR
* ERROR Not created ERROR
*
* The value of the returned result, in the case both results succeed, is the
* value of the second result (the result returned by the action callback.)
*
* Example:
* <pre>
*
* var testDataResult = xhr.get('testdata/xhr_test_text.data');
*
* // Chain this result to perform another asynchronous operation when this
* // Result is resolved.
* var chainedResult = goog.labs.result.chain(testDataResult,
* function(testDataResult) {
*
* // The result value of testDataResult is the URL for JSON data.
* var jsonDataUrl = testDataResult.getValue();
*
* // Create a new Result object when the original result is resolved.
* var jsonResult = xhr.getJson(jsonDataUrl);
*
* // Return the newly created Result.
* return jsonResult;
* });
*
* // The chained result resolves to success when both results resolve to
* // success.
* goog.labs.result.waitOnSuccess(chainedResult, function(result) {
*
* // At this point, both results have succeeded and we can use the JSON
* // data returned by the second asynchronous call.
* var jsonData = result.getValue();
* assertEquals('ok', jsonData['stat']);
* });
*
* // Attach the error handler to be called when either Result fails.
* goog.labs.result.waitOnError(chainedResult, function(result) {
* alert('chained result failed!');
* });
* </pre>
*
* @param {!goog.labs.result.Result} result The result to chain.
* @param {!function(!goog.labs.result.Result):!goog.labs.result.Result}
* actionCallback The callback called when the result is resolved. This
* callback must return a Result.
*
* @return {!goog.labs.result.Result} A result that is resolved when both
* the given Result and the Result returned by the actionCallback have
* resolved.
*/
goog.labs.result.chain = function(result, actionCallback) {
var returnedResult = new goog.labs.result.SimpleResult();
// Wait for the first action.
goog.labs.result.wait(result, function(result) {
if (result.getState() == goog.labs.result.Result.State.SUCCESS) {
// The first action succeeded. Chain the dependent action.
var dependentResult = actionCallback(result);
goog.labs.result.wait(dependentResult, function(dependentResult) {
// The dependent action completed. Set the returned result based on the
// dependent action's outcome.
if (dependentResult.getState() ==
goog.labs.result.Result.State.SUCCESS) {
returnedResult.setValue(dependentResult.getValue());
} else {
returnedResult.setError(dependentResult.getError());
}
});
} else {
// First action failed, the returned result should also fail.
returnedResult.setError(result.getError());
}
});
return returnedResult;
};
/**
* Returns a result that waits on all given results to resolve. Once all have
* resolved, the returned result will succeed (and never error).
*
* Example:
* <pre>
*
* var result1 = xhr.get('testdata/xhr_test_text.data');
*
* // Get a second independent Result.
* var result2 = xhr.getJson('testdata/xhr_test_json.data');
*
* // Create a Result that resolves when both prior results resolve.
* var combinedResult = goog.labs.result.combine(result1, result2);
*
* // Process data after resolution of both results.
* goog.labs.result.waitOnSuccess(combinedResult, function(results) {
* goog.array.forEach(results, function(result) {
* alert(result.getState());
* });
* });
* </pre>
*
* @param {...!goog.labs.result.Result} var_args The results to wait on.
*
* @return {!goog.labs.result.Result} A new Result whose eventual value will be
* the resolved given Result objects.
*/
goog.labs.result.combine = function(var_args) {
var results = goog.array.clone(arguments);
var combinedResult = new goog.labs.result.SimpleResult();
var isResolved = function(res) {
return res.getState() != goog.labs.result.Result.State.PENDING;
};
var checkResults = function() {
if (goog.array.every(results, isResolved)) {
combinedResult.setValue(results);
}
};
goog.array.forEach(results, function(result) {
goog.labs.result.wait(result, checkResults);
});
return combinedResult;
};
/**
* Returns a result that waits on all given results to resolve. Once all have
* resolved, the returned result will succeed if and only if all given results
* succeeded. Otherwise it will error.
*
* Example:
* <pre>
*
* var result1 = xhr.get('testdata/xhr_test_text.data');
*
* // Get a second independent Result.
* var result2 = xhr.getJson('testdata/xhr_test_json.data');
*
* // Create a Result that resolves when both prior results resolve.
* var combinedResult = goog.labs.result.combineOnSuccess(result1, result2);
*
* // Process data after successful resolution of both results.
* goog.labs.result.waitOnSuccess(combinedResult, function(results) {
* var textData = results[0].getValue();
* var jsonData = results[1].getValue();
* assertEquals('Just some data.', textData);
* assertEquals('ok', jsonData['stat']);
* });
*
* // Handle errors when either or both results failed.
* goog.labs.result.waitOnError(combinedResult, function(combined) {
* var results = combined.getError();
*
* if (results[0].getState() == goog.labs.result.Result.State.ERROR) {
* alert('result1 failed');
* }
*
* if (results[1].getState() == goog.labs.result.Result.State.ERROR) {
* alert('result2 failed');
* }
* });
* </pre>
*
* @param {...!goog.labs.result.Result} var_args The results to wait on.
*
* @return {!goog.labs.result.Result} A new Result whose eventual value will be
* an array of values of the given Result objects.
*/
goog.labs.result.combineOnSuccess = function(var_args) {
var combinedResult = new goog.labs.result.SimpleResult();
var resolvedSuccessfully = function(res) {
return res.getState() == goog.labs.result.Result.State.SUCCESS;
};
goog.labs.result.wait(
goog.labs.result.combine.apply(goog.labs.result.combine, arguments),
// The combined result never ERRORs
function(res) {
var results = /** @type {Array} */ (res.getValue());
if (goog.array.every(results, resolvedSuccessfully)) {
combinedResult.setValue(results);
} else {
combinedResult.setError(results);
}
});
return combinedResult;
};
@@ -0,0 +1,197 @@
// 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 SimpleResult object that implements goog.labs.result.Result.
* See below for a more detailed description.
*/
goog.provide('goog.labs.result.SimpleResult');
goog.provide('goog.labs.result.SimpleResult.StateError');
goog.require('goog.debug.Error');
goog.require('goog.labs.result.Result');
/**
* A SimpleResult object is a basic implementation of the
* goog.labs.result.Result interface. This could be subclassed(e.g. XHRResult)
* or instantiated and returned by another class as a form of result. The caller
* receiving the result could then attach handlers to be called when the result
* is resolved(success or error).
*
* @constructor
* @implements {goog.labs.result.Result}
*/
goog.labs.result.SimpleResult = function() {
/**
* The current state of this Result.
* @type {goog.labs.result.Result.State}
* @private
*/
this.state_ = goog.labs.result.Result.State.PENDING;
/**
* The list of handlers to call when this Result is resolved.
* @type {!Array.<!function(goog.labs.result.SimpleResult)>}
* @private
*/
this.handlers_ = [];
};
/**
* The 'value' of this Result.
* @type {*}
* @private
*/
goog.labs.result.SimpleResult.prototype.value_;
/**
* The error slug for this Result.
* @type {*}
* @private
*/
goog.labs.result.SimpleResult.prototype.error_;
/**
* Error thrown if there is an attempt to set the value or error for this result
* more than once.
*
* @constructor
* @extends {goog.debug.Error}
*/
goog.labs.result.SimpleResult.StateError = function() {
goog.base(this, 'Multiple attempts to set the state of this Result');
};
goog.inherits(goog.labs.result.SimpleResult.StateError, goog.debug.Error);
/** @override */
goog.labs.result.SimpleResult.prototype.getState = function() {
return this.state_;
};
/** @override */
goog.labs.result.SimpleResult.prototype.getValue = function() {
return this.value_;
};
/** @override */
goog.labs.result.SimpleResult.prototype.getError = function() {
return this.error_;
};
/**
* Attaches handlers to be called when the value of this Result is available.
*
* @param {!function(!goog.labs.result.SimpleResult)} handler The function
* called when the value is available. The function is passed the Result
* object as the only argument.
* @override
*/
goog.labs.result.SimpleResult.prototype.wait = function(handler) {
if (this.isPending_()) {
this.handlers_.push(handler);
} else {
handler(this);
}
};
/**
* Sets the value of this Result, changing the state.
*
* @param {*} value The value to set for this Result.
*/
goog.labs.result.SimpleResult.prototype.setValue = function(value) {
if (this.isPending_()) {
this.value_ = value;
this.state_ = goog.labs.result.Result.State.SUCCESS;
this.callHandlers_();
} else if (!this.isCanceled()) {
// setValue is a no-op if this Result has been canceled.
throw new goog.labs.result.SimpleResult.StateError();
}
};
/**
* Sets the Result to be an error Result.
*
* @param {*=} opt_error Optional error slug to set for this Result.
*/
goog.labs.result.SimpleResult.prototype.setError = function(opt_error) {
if (this.isPending_()) {
this.error_ = opt_error;
this.state_ = goog.labs.result.Result.State.ERROR;
this.callHandlers_();
} else if (!this.isCanceled()) {
// setError is a no-op if this Result has been canceled.
throw new goog.labs.result.SimpleResult.StateError();
}
};
/**
* Calls the handlers registered for this Result.
*
* @private
*/
goog.labs.result.SimpleResult.prototype.callHandlers_ = function() {
while (this.handlers_.length) {
var callback = this.handlers_.shift();
callback(this);
}
};
/**
* @return {boolean} Whether the Result is pending.
* @private
*/
goog.labs.result.SimpleResult.prototype.isPending_ = function() {
return this.state_ == goog.labs.result.Result.State.PENDING;
};
/**
* Cancels the Result.
*
* @return {boolean} Whether the result was canceled. It will not be canceled if
* the result was already canceled or has already resolved.
* @override
*/
goog.labs.result.SimpleResult.prototype.cancel = function() {
// cancel is a no-op if the result has been resolved.
if (this.isPending_()) {
this.setError(new goog.labs.result.Result.CancelError());
return true;
}
return false;
};
/** @override */
goog.labs.result.SimpleResult.prototype.isCanceled = function() {
return this.state_ == goog.labs.result.Result.State.ERROR &&
this.error_ instanceof goog.labs.result.Result.CancelError;
};
@@ -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
};
@@ -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
@@ -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;
};
@@ -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);
@@ -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);
}
@@ -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);
}
@@ -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) {};
@@ -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);
}
@@ -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();
}
@@ -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);
}