// 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.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; }; /** @const {Object} */ 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} */ 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.recordValue = function() {}; /** @type {chromeMocks.MetricsPrivate} */ chromeMocks.metricsPrivate; /** @constructor */ chromeMocks.I18n = function() {}; /** * @param {string} messageName * @param {(string|Array)=} 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; }; })();