blob: e247a3efb785f915f08369d5114e69f4cfa1f684 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file contains various mock objects for the chrome platform to make
// unit testing easier.
var chromeMocks = {};
(function(){
'use strict'
/**
* @constructor
* @extends {ChromeEvent}
*/
chromeMocks.Event = function() {
this.listeners_ = [];
};
/** @param {Function} callback */
chromeMocks.Event.prototype.addListener = function(callback) {
this.listeners_.push(callback);
};
/** @param {Function} callback */
chromeMocks.Event.prototype.removeListener = function(callback) {
for (var i = 0; i < this.listeners_.length; i++) {
if (this.listeners_[i] === callback) {
this.listeners_.splice(i, 1);
break;
}
}
};
/**
* @param {...*} var_args
* @return {void}
* @suppress {reportUnknownTypes}
*/
chromeMocks.Event.prototype.mock$fire = function(var_args) {
var params = Array.prototype.slice.call(arguments);
this.listeners_.forEach(
/** @param {Function} listener */
function(listener){
listener.apply(null, params);
});
};
/** @type {Object} */
chromeMocks.runtime = {};
/** @constructor */
chromeMocks.runtime.Port = function() {
/** @const */
this.onMessage = new chromeMocks.Event();
/** @const */
this.onDisconnect = new chromeMocks.Event();
/** @type {string} */
this.name = '';
/** @type {MessageSender} */
this.sender = null;
};
chromeMocks.runtime.Port.prototype.disconnect = function() {};
/**
* @param {Object} message
*/
chromeMocks.runtime.Port.prototype.postMessage = function(message) {};
/** @type {chromeMocks.Event} */
chromeMocks.runtime.onMessage = new chromeMocks.Event();
/** @type {chromeMocks.Event} */
chromeMocks.runtime.onMessageExternal = new chromeMocks.Event();
/** @type {chromeMocks.Event} */
chromeMocks.runtime.onSuspend = new chromeMocks.Event();
/**
* @param {string?} extensionId
* @param {*} message
* @param {function(*)=} responseCallback
*/
chromeMocks.runtime.sendMessage = function(extensionId, message,
responseCallback) {
console.assert(
extensionId === null,
'The mock only supports sending messages to the same extension.');
extensionId = chrome.runtime.id;
Promise.resolve().then(function() {
var message_copy = base.deepCopy(message);
chromeMocks.runtime.onMessage.mock$fire(
message_copy, {id: extensionId}, responseCallback);
});
};
/**
* Always returns the same mock port for given application name.
* @param {string} application
* @return {chromeMocks.runtime.Port}
*/
chromeMocks.runtime.connectNative = function(application) {
var port = nativePorts[application];
if (port === undefined) {
port = new chromeMocks.runtime.Port();
port.name = application;
nativePorts[application] = port;
}
return port;
};
/** @type {Object<!chromeMocks.runtime.Port>} */
var nativePorts = null;
/** @type {string} */
chromeMocks.runtime.id = 'extensionId';
/** @type {Object} */
chromeMocks.runtime.lastError = {
/** @type {string|undefined} */
message: undefined
};
chromeMocks.runtime.getManifest = function() {
return {
version: 10,
app: {
background: true
}
};
};
// Sample implementation of chrome.StorageArea according to
// https://developer.chrome.com/apps/storage#type-StorageArea
/**
* @constructor
* @extends {StorageArea}
*/
chromeMocks.StorageArea = function() {
/** @type {!Object} */
this.storage_ = {};
};
/**
* @param {Object|string} keys
* @return {Array<string>}
*/
function getKeys(keys) {
if (typeof keys === 'string') {
return [keys];
} else if (typeof keys === 'object') {
var objectKeys = /** @type {!Object} */ (keys);
return Object.keys(objectKeys);
}
return [];
}
chromeMocks.StorageArea.prototype.get = function(keys, onDone) {
if (!keys) {
// No keys are specified, returns the entire storage.
var storageCopy = base.deepCopy(this.storage_);
onDone(/** @type {!Object} */ (storageCopy));
return;
}
var result = (typeof keys === 'object') ? keys : {};
getKeys(keys).forEach(
/** @param {string} key */
function(key) {
if (key in this.storage_) {
result[key] = base.deepCopy(this.storage_[key]);
}
}, this);
onDone(result);
};
chromeMocks.StorageArea.prototype.set = function(value, opt_onDone) {
for (var key in value) {
this.storage_[key] = base.deepCopy(value[key]);
}
if (opt_onDone) {
opt_onDone();
}
};
chromeMocks.StorageArea.prototype.remove = function(keys, opt_onDone) {
getKeys(keys).forEach(
/** @param {string} key */
function(key) {
delete this.storage_[key];
}, this);
if (opt_onDone) {
opt_onDone();
}
};
/** @return {!Object} */
chromeMocks.StorageArea.prototype.mock$getStorage = function() {
return this.storage_;
};
chromeMocks.StorageArea.prototype.clear = function() {
this.storage_ = {};
};
/** @type {Object} */
chromeMocks.storage = {};
/** @type {chromeMocks.StorageArea} */
chromeMocks.storage.local = new chromeMocks.StorageArea();
/** @constructor */
chromeMocks.Identity = function() {
/** @private {string|undefined} */
this.token_ = undefined;
};
/**
* @param {Object} options
* @param {function(string=):void} callback
*/
chromeMocks.Identity.prototype.getAuthToken = function(options, callback) {
// Append the 'scopes' array, if present, to the dummy token.
var token = this.token_;
if (token !== undefined && options['scopes'] !== undefined) {
token += JSON.stringify(options['scopes']);
}
// Don't use setTimeout because sinon mocks it.
Promise.resolve().then(callback.bind(null, token));
};
/** @param {string} token */
chromeMocks.Identity.prototype.mock$setToken = function(token) {
this.token_ = token;
};
chromeMocks.Identity.prototype.mock$clearToken = function() {
this.token_ = undefined;
};
/** @type {chromeMocks.Identity} */
chromeMocks.identity;
/** @constructor */
chromeMocks.MetricsPrivate = function() {};
chromeMocks.MetricsPrivate.prototype.MetricTypeType = {
HISTOGRAM_LOG: 'histogram-log',
HISTOGRAM_LINEAR: 'histogram-linear',
};
chromeMocks.MetricsPrivate.prototype.recordValue = function() {};
/** @type {chromeMocks.MetricsPrivate} */
chromeMocks.metricsPrivate;
/** @constructor */
chromeMocks.I18n = function() {};
/**
* @param {string} messageName
* @param {(string|Array<string>)=} opt_args
* @return {string}
*/
chromeMocks.I18n.prototype.getMessage = function(messageName, opt_args) {};
/**
* @return {string}
*/
chromeMocks.I18n.prototype.getUILanguage = function() {};
/** @constructor */
chromeMocks.WindowManager = function() {
this.current_ = new chromeMocks.AppWindow();
};
chromeMocks.WindowManager.prototype.current = function() {
return this.current_;
};
/** @constructor */
chromeMocks.AppWindow = function() {};
var originals_ = null;
/**
* Activates a list of Chrome components to mock
*/
chromeMocks.activate = function() {
if (originals_) {
throw new Error('chromeMocks.activate() can only be called once.');
}
originals_ = {};
nativePorts = {};
chromeMocks.i18n = new chromeMocks.I18n();
chromeMocks.identity = new chromeMocks.Identity();
chromeMocks.metricsPrivate = new chromeMocks.MetricsPrivate();
['identity', 'i18n', 'runtime', 'storage', 'metricsPrivate'].forEach(
function(/** string */ component) {
if (!chromeMocks[component]) {
throw new Error('No mocks defined for chrome.' + component);
}
originals_[component] = chrome[component];
chrome[component] = chromeMocks[component];
});
chrome.app['window'] = new chromeMocks.WindowManager();
};
chromeMocks.restore = function() {
if (!originals_) {
throw new Error('You must call activate() before restore().');
}
for (var components in originals_) {
chrome[components] = originals_[components];
}
originals_ = null;
nativePorts = null;
};
})();