diff options
-rw-r--r-- | chrome/test/remoting/me2me_browsertest.cc | 10 | ||||
-rw-r--r-- | remoting/remoting_webapp_files.gypi | 3 | ||||
-rw-r--r-- | remoting/webapp/base/js/client_session_factory_unittest.js | 6 | ||||
-rw-r--r-- | remoting/webapp/base/js/client_session_unittest.js | 46 | ||||
-rw-r--r-- | remoting/webapp/base/js/modal_dialogs.js | 52 | ||||
-rw-r--r-- | remoting/webapp/browser_test/bump_scroll_browser_test.js | 8 | ||||
-rw-r--r-- | remoting/webapp/browser_test/me2me_browser_test.js | 46 | ||||
-rw-r--r-- | remoting/webapp/crd/js/desktop_connected_view.js | 12 | ||||
-rw-r--r-- | remoting/webapp/crd/js/desktop_remoting_activity.js | 6 | ||||
-rw-r--r-- | remoting/webapp/crd/js/it2me_activity.js | 2 | ||||
-rw-r--r-- | remoting/webapp/crd/js/me2me_activity.js | 12 | ||||
-rw-r--r-- | remoting/webapp/crd/js/me2me_telemetry_integration_test.js | 385 | ||||
-rw-r--r-- | remoting/webapp/crd/js/mock_client_plugin.js | 101 | ||||
-rw-r--r-- | remoting/webapp/crd/js/mock_modal_dialog_factory.js | 95 | ||||
-rw-r--r-- | remoting/webapp/crd/js/remoting_activity_test_driver.js | 238 | ||||
-rw-r--r-- | remoting/webapp/js_proto/sinon_proto.js | 5 |
16 files changed, 929 insertions, 98 deletions
diff --git a/chrome/test/remoting/me2me_browsertest.cc b/chrome/test/remoting/me2me_browsertest.cc index 99f989d..3c3a031 100644 --- a/chrome/test/remoting/me2me_browsertest.cc +++ b/chrome/test/remoting/me2me_browsertest.cc @@ -135,16 +135,6 @@ IN_PROC_BROWSER_TEST_F(Me2MeBrowserTest, MANUAL_Me2Me_v2_Alive_OnLostFocus) { Cleanup(); } -IN_PROC_BROWSER_TEST_F(Me2MeBrowserTest, MANUAL_Me2Me_RetryOnHostOffline) { - content::WebContents* content = SetUpTest(); - LoadScript(content, FILE_PATH_LITERAL("me2me_browser_test.js")); - RunJavaScriptTest(content, "RetryOnHostOffline", "{" - "pin: '" + me2me_pin() + "'" - "}"); - - Cleanup(); -} - IN_PROC_BROWSER_TEST_F(Me2MeBrowserTest, MANUAL_Me2Me_Disable_Remote_Connection) { SetUpTest(); diff --git a/remoting/remoting_webapp_files.gypi b/remoting/remoting_webapp_files.gypi index d8f023d..f15f196 100644 --- a/remoting/remoting_webapp_files.gypi +++ b/remoting/remoting_webapp_files.gypi @@ -119,14 +119,17 @@ 'webapp/crd/js/host_table_entry_unittest.js', 'webapp/crd/js/legacy_host_list_api_unittest.js', 'webapp/crd/js/menu_button_unittest.js', + 'webapp/crd/js/me2me_telemetry_integration_test.js', 'webapp/crd/js/mock_xhr_unittest.js', ], 'remoting_webapp_unittests_js_mock_files': [ 'webapp/crd/js/mock_client_plugin.js', 'webapp/crd/js/mock_host_daemon_facade.js', 'webapp/crd/js/mock_host_list_api.js', + 'webapp/crd/js/mock_modal_dialog_factory.js', 'webapp/crd/js/mock_identity.js', 'webapp/crd/js/mock_signal_strategy.js', + 'webapp/crd/js/remoting_activity_test_driver.js', 'webapp/js_proto/chrome_mocks.js', 'webapp/unittests/sinon_helpers.js', 'webapp/crd/js/mock_xhr.js', diff --git a/remoting/webapp/base/js/client_session_factory_unittest.js b/remoting/webapp/base/js/client_session_factory_unittest.js index 93e6982..cd53d1c 100644 --- a/remoting/webapp/base/js/client_session_factory_unittest.js +++ b/remoting/webapp/base/js/client_session_factory_unittest.js @@ -79,8 +79,10 @@ QUnit.test('createSession() should reject on signal strategy failure', QUnit.test('createSession() should reject on plugin initialization failure', function(assert) { var mockSignalStrategy = mockConnection.signalStrategy(); - var plugin = mockConnection.plugin(); - plugin.mock$initializationResult = false; + function onPluginCreated(/** remoting.MockClientPlugin */ plugin) { + plugin.mock$initializationResult = false; + } + mockConnection.pluginFactory().mock$setPluginCreated(onPluginCreated); var signalStrategyDispose = sinon.stub(mockSignalStrategy, 'dispose'); diff --git a/remoting/webapp/base/js/client_session_unittest.js b/remoting/webapp/base/js/client_session_unittest.js index f43a2bc..bff546c 100644 --- a/remoting/webapp/base/js/client_session_unittest.js +++ b/remoting/webapp/base/js/client_session_unittest.js @@ -35,19 +35,27 @@ function connect(opt_error) { var host = new remoting.Host('fake_hostId'); host.jabberId = 'fake_jid'; - var plugin = mockConnection.plugin(); - var State = remoting.ClientSession.State; + function onPluginCreated(/** remoting.MockClientPlugin */ plugin) { + var State = remoting.ClientSession.State; + plugin.mock$onConnect().then(function() { + plugin.mock$setConnectionStatus(State.CONNECTING); + }).then(function() { + var status = (opt_error) ? State.FAILED : State.CONNECTED; + plugin.mock$setConnectionStatus(status, opt_error); + }); + } + mockConnection.pluginFactory().mock$setPluginCreated(onPluginCreated); - plugin.mock$onConnect().then(function() { - plugin.mock$setConnectionStatus(State.CONNECTING); - }).then(function() { - var status = (opt_error) ? State.FAILED : State.CONNECTED; - plugin.mock$setConnectionStatus(status, opt_error); - }); + var sessionFactory = new remoting.ClientSessionFactory( + document.createElement('div'), ['fake_capability']); - session.connect(host, new remoting.CredentialsProvider({ - pairingInfo: { clientId: 'fake_clientId', sharedSecret: 'fake_secret' } - })); + sessionFactory.createSession(listener, logger, true).then( + function(clientSession) { + session = clientSession; + clientSession.connect(host, new remoting.CredentialsProvider({ + pairingInfo: { clientId: 'fake_clientId', sharedSecret: 'fake_secret' } + })); + }); listener.onConnected = function() { deferred.resolve(); @@ -69,14 +77,6 @@ QUnit.module('ClientSession', { logger = new remoting.SessionLogger(remoting.ChromotingEvent.Role.CLIENT, base.doNothing); logToServerStub = sinon.stub(logger, 'logClientSessionStateChange'); - - - var sessionFactory = new remoting.ClientSessionFactory( - document.createElement('div'), ['fake_capability']); - return sessionFactory.createSession(listener, logger, true).then( - function(clientSession) { - session = clientSession; - }); }, afterEach: function() { session.dispose(); @@ -99,8 +99,8 @@ QUnit.test('onOutgoingIq() should send Iq to signalStrategy', function(assert) { }); QUnit.test('should foward Iq from signalStrategy to plugin', function(assert) { - var onIncomingIq = sinon.stub(mockConnection.plugin(), 'onIncomingIq'); return connect().then(function() { + var onIncomingIq = sinon.stub(mockConnection.plugin(), 'onIncomingIq'); var stanza = new DOMParser() .parseFromString('<iq>sample</iq>', 'text/xml') .firstElementChild; @@ -148,10 +148,4 @@ QUnit.test( }); }); -QUnit.test('dispose() should dispose the plugin', function(assert) { - var pluginDispose = sinon.stub(mockConnection.plugin(), 'dispose'); - session.dispose(); - assert.equal(pluginDispose.callCount, 1); -}); - })(); diff --git a/remoting/webapp/base/js/modal_dialogs.js b/remoting/webapp/base/js/modal_dialogs.js index e05d226..e6b750d 100644 --- a/remoting/webapp/base/js/modal_dialogs.js +++ b/remoting/webapp/base/js/modal_dialogs.js @@ -288,6 +288,58 @@ remoting.ConnectingDialog.prototype.hide = function() { this.dialog_.dispose(); }; +/** + * A factory object for the modal dialogs. The factory will be stubbed out in + * unit test to avoid UI dependencies on remoting.setMode(). + * + * @constructor + */ +remoting.ModalDialogFactory = function() {}; + +/** + * @param {Function} cancelCallback + * @return {remoting.ConnectingDialog} + */ +remoting.ModalDialogFactory.prototype.createConnectingDialog = + function(cancelCallback) { + return new remoting.ConnectingDialog(cancelCallback); +}; + +/** + * @param {remoting.Html5ModalDialog.Params} params + * @return {remoting.Html5ModalDialog} + */ +remoting.ModalDialogFactory.prototype.createHtml5ModalDialog = + function(params) { + return new remoting.Html5ModalDialog(params); +}; + +/** + * @param {remoting.AppMode} mode + * @param {HTMLElement} primaryButton + * @param {HTMLElement=} opt_secondaryButton + * @return {remoting.MessageDialog} + */ +remoting.ModalDialogFactory.prototype.createMessageDialog = + function(mode, primaryButton, opt_secondaryButton) { + return new remoting.MessageDialog(mode, primaryButton, opt_secondaryButton); +}; + +/** + * @param {remoting.AppMode} mode + * @param {HTMLElement} formElement + * @param {HTMLElement} inputField + * @param {HTMLElement} cancelButton + * @return {remoting.InputDialog} + */ +remoting.ModalDialogFactory.prototype.createInputDialog = + function(mode, formElement, inputField, cancelButton) { + return new remoting.InputDialog(mode, formElement, inputField, cancelButton); +}; + +/** @type {remoting.ModalDialogFactory} */ +remoting.modalDialogFactory = new remoting.ModalDialogFactory(); + })(); /** diff --git a/remoting/webapp/browser_test/bump_scroll_browser_test.js b/remoting/webapp/browser_test/bump_scroll_browser_test.js index f59c30f..a20cd36 100644 --- a/remoting/webapp/browser_test/bump_scroll_browser_test.js +++ b/remoting/webapp/browser_test/bump_scroll_browser_test.js @@ -89,8 +89,12 @@ browserTest.Bump_Scroll.prototype.run = function(data) { } var mockConnection = new remoting.MockConnection(); - mockConnection.plugin().mock$useDefaultBehavior( - remoting.MockClientPlugin.AuthMethod.PIN); + + function onPluginCreated(/** remoting.MockClientPlugin */ plugin) { + plugin.mock$useDefaultBehavior(remoting.MockClientPlugin.AuthMethod.PIN); + } + mockConnection.pluginFactory().mock$setPluginCreated(onPluginCreated); + function cleanup() { mockConnection.restore(); diff --git a/remoting/webapp/browser_test/me2me_browser_test.js b/remoting/webapp/browser_test/me2me_browser_test.js index d4f0353..392d0b9 100644 --- a/remoting/webapp/browser_test/me2me_browser_test.js +++ b/remoting/webapp/browser_test/me2me_browser_test.js @@ -58,50 +58,4 @@ browserTest.AliveOnLostFocus.prototype.run = function(data) { }); }; - -/** @constructor */ -browserTest.RetryOnHostOffline = function() { - /** @private */ - this.mockConnection_ = new remoting.MockConnection(); - - // Fake an host offline error on first connect. - var plugin = this.mockConnection_.plugin(); - var State = remoting.ClientSession.State; - var Error = remoting.ClientSession.ConnectionError; - var that = this; - - plugin.mock$onConnect().then(function() { - plugin.mock$setConnectionStatus(State.CONNECTING); - }).then(function() { - plugin.mock$setConnectionStatus(State.FAILED, Error.HOST_IS_OFFLINE); - }).then(function() { - that.cleanup_(); - that.mockConnection_ = new remoting.MockConnection(); - var newPlugin = that.mockConnection_.plugin(); - // Let the second connect succeed. - newPlugin.mock$useDefaultBehavior(remoting.MockClientPlugin.AuthMethod.PIN); - }); -}; - -/** @private */ -browserTest.RetryOnHostOffline.prototype.cleanup_ = function() { - this.mockConnection_.restore(); - this.mockConnection_ = null; -}; - -browserTest.RetryOnHostOffline.prototype.run = function() { - var that = this; - browserTest.connectMe2Me().then(function() { - return browserTest.enterPIN('123456'); - }).then(function() { - return browserTest.disconnect(); - }).then(function() { - that.cleanup_(); - browserTest.pass(); - }).catch(function(/** Error */ reason) { - that.cleanup_(); - browserTest.fail(reason); - }); -}; - })(); diff --git a/remoting/webapp/crd/js/desktop_connected_view.js b/remoting/webapp/crd/js/desktop_connected_view.js index ef2dae2..1f528d3 100644 --- a/remoting/webapp/crd/js/desktop_connected_view.js +++ b/remoting/webapp/crd/js/desktop_connected_view.js @@ -330,3 +330,15 @@ remoting.DesktopConnectedView.prototype.startStopRecording = function() { this.videoFrameRecorder_.startStopRecording(); } }; + +/** + * Factory function so that it can be overwritten in unit test to avoid + * UI dependencies. + * + * @param {HTMLElement} container + * @param {remoting.ConnectionInfo} connectionInfo + * @return {remoting.DesktopConnectedView} + */ +remoting.DesktopConnectedView.create = function(container, connectionInfo) { + return new remoting.DesktopConnectedView(container, connectionInfo); +};
\ No newline at end of file diff --git a/remoting/webapp/crd/js/desktop_remoting_activity.js b/remoting/webapp/crd/js/desktop_remoting_activity.js index 9e1e973..03f2ead 100644 --- a/remoting/webapp/crd/js/desktop_remoting_activity.js +++ b/remoting/webapp/crd/js/desktop_remoting_activity.js @@ -33,8 +33,8 @@ remoting.DesktopRemotingActivity = function(parentActivity) { /** @private {remoting.ClientSession} */ this.session_ = null; /** @private {remoting.ConnectingDialog} */ - this.connectingDialog_ = - new remoting.ConnectingDialog(parentActivity.stop.bind(parentActivity)); + this.connectingDialog_ = remoting.modalDialogFactory.createConnectingDialog( + parentActivity.stop.bind(parentActivity)); }; /** @@ -90,7 +90,7 @@ remoting.DesktopRemotingActivity.prototype.onConnected = remoting.toolbar.preview(); } - this.connectedView_ = new remoting.DesktopConnectedView( + this.connectedView_ = remoting.DesktopConnectedView.create( document.getElementById('client-container'), connectionInfo); // Apply the default or previously-specified keyboard remapping. diff --git a/remoting/webapp/crd/js/it2me_activity.js b/remoting/webapp/crd/js/it2me_activity.js index 57ae172..c334ea3 100644 --- a/remoting/webapp/crd/js/it2me_activity.js +++ b/remoting/webapp/crd/js/it2me_activity.js @@ -26,7 +26,7 @@ remoting.It2MeActivity = function() { var form = document.getElementById('access-code-form'); /** @private */ - this.accessCodeDialog_ = new remoting.InputDialog( + this.accessCodeDialog_ = remoting.modalDialogFactory.createInputDialog( remoting.AppMode.CLIENT_UNCONNECTED, form, form.querySelector('#access-code-entry'), diff --git a/remoting/webapp/crd/js/me2me_activity.js b/remoting/webapp/crd/js/me2me_activity.js index 3aa4ad5..1141d07 100644 --- a/remoting/webapp/crd/js/me2me_activity.js +++ b/remoting/webapp/crd/js/me2me_activity.js @@ -195,15 +195,15 @@ remoting.Me2MeActivity.prototype.reconnectOnHostOffline_ = function(error) { * @param {!remoting.Error} error */ remoting.Me2MeActivity.prototype.onConnectionFailed = function(error) { + base.dispose(this.desktopActivity_); + this.desktopActivity_ = null; + if (error.hasTag(remoting.Error.Tag.HOST_IS_OFFLINE) && this.retryOnHostOffline_) { this.reconnectOnHostOffline_(error); } else if (!error.isNone()) { this.showErrorMessage_(error); } - - base.dispose(this.desktopActivity_); - this.desktopActivity_ = null; }; /** @@ -257,7 +257,7 @@ remoting.Me2MeActivity.prototype.showErrorMessage_ = function(error) { * @private */ remoting.Me2MeActivity.prototype.showFinishDialog_ = function(mode) { - var dialog = new remoting.MessageDialog( + var dialog = remoting.modalDialogFactory.createMessageDialog( mode, document.getElementById('client-finished-me2me-button'), document.getElementById('client-reconnect-button')); @@ -285,7 +285,7 @@ remoting.HostNeedsUpdateDialog = function(rootElement, host) { /** @private */ this.host_ = host; /** @private */ - this.dialog_ = new remoting.MessageDialog( + this.dialog_ = remoting.modalDialogFactory.createMessageDialog( remoting.AppMode.CLIENT_HOST_NEEDS_UPGRADE, rootElement.querySelector('.connect-button'), rootElement.querySelector('.cancel-button')); @@ -333,7 +333,7 @@ remoting.PinDialog = function(rootElement, host) { /** @private */ this.host_ = host; /** @private */ - this.dialog_ = new remoting.InputDialog( + this.dialog_ = remoting.modalDialogFactory.createInputDialog( remoting.AppMode.CLIENT_PIN_PROMPT, this.rootElement_.querySelector('form'), this.pinInput_, diff --git a/remoting/webapp/crd/js/me2me_telemetry_integration_test.js b/remoting/webapp/crd/js/me2me_telemetry_integration_test.js new file mode 100644 index 0000000..1ec6a86 --- /dev/null +++ b/remoting/webapp/crd/js/me2me_telemetry_integration_test.js @@ -0,0 +1,385 @@ +// Copyright 2015 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. + + +/** @suppress {duplicate} */ +var remoting = remoting || {}; + +(function() { + +'use strict'; + +/** @type {remoting.Me2MeTestDriver} */ +var testDriver; + +QUnit.module('Me2Me Telemetry Integration', { + // We rely on our telemetry data to gain insight into the reliability and + // the durability on our connections. This test verifies the integrity of our + // telemetry by ensuring that certain well known connection sequences will + // generate the correct sequence of telemetry data. + beforeEach: function() { + testDriver = new remoting.Me2MeTestDriver(); + }, + afterEach: function() { + base.dispose(testDriver); + testDriver = null; + } +}); + +/** + * @param {remoting.Me2MeTestDriver} testDriver + * @param {Object} baseEvent + */ +var expectSucceeded = function(testDriver, baseEvent) { + var ChromotingEvent = remoting.ChromotingEvent; + var sequence = [{ + session_state: ChromotingEvent.SessionState.STARTED, + },{ + session_state: ChromotingEvent.SessionState.SIGNALING, + previous_session_state: ChromotingEvent.SessionState.STARTED + },{ + session_state: ChromotingEvent.SessionState.CREATING_PLUGIN, + previous_session_state: ChromotingEvent.SessionState.SIGNALING + },{ + session_state: ChromotingEvent.SessionState.CONNECTING, + previous_session_state: ChromotingEvent.SessionState.CREATING_PLUGIN + },{ + session_state: ChromotingEvent.SessionState.AUTHENTICATED, + previous_session_state: ChromotingEvent.SessionState.CONNECTING + },{ + session_state: ChromotingEvent.SessionState.CONNECTED, + previous_session_state: ChromotingEvent.SessionState.AUTHENTICATED + },{ + session_state: ChromotingEvent.SessionState.CLOSED, + previous_session_state: ChromotingEvent.SessionState.CONNECTED + }]; + + var expectedEvents = sequence.map(function(/** Object */ sequenceValue) { + var event = /** @type {Object} */ (base.deepCopy(baseEvent)); + base.mix(event, sequenceValue); + return event; + }); + testDriver.expectEvents(expectedEvents); +}; + +/** + * @param {remoting.Me2MeTestDriver} testDriver + * @param {Object} baseEvent + */ +var expectCanceled = function(testDriver, baseEvent) { + var ChromotingEvent = remoting.ChromotingEvent; + var sequence = [{ + session_state: ChromotingEvent.SessionState.STARTED, + },{ + session_state: ChromotingEvent.SessionState.SIGNALING, + previous_session_state: ChromotingEvent.SessionState.STARTED + },{ + session_state: ChromotingEvent.SessionState.CREATING_PLUGIN, + previous_session_state: ChromotingEvent.SessionState.SIGNALING + },{ + session_state: ChromotingEvent.SessionState.CONNECTING, + previous_session_state: ChromotingEvent.SessionState.CREATING_PLUGIN + },{ + session_state: ChromotingEvent.SessionState.CONNECTION_CANCELED, + previous_session_state: ChromotingEvent.SessionState.CONNECTING + }]; + + var expectedEvents = sequence.map(function(/** Object */ sequenceValue) { + var event = /** @type {Object} */ (base.deepCopy(baseEvent)); + base.mix(event, sequenceValue); + return event; + }); + testDriver.expectEvents(expectedEvents); +}; + +/** + * @param {remoting.Me2MeTestDriver} testDriver + * @param {Object} baseEvent + * @param {remoting.ChromotingEvent.ConnectionError} error + */ +var expectFailed = function(testDriver, baseEvent, error) { + var ChromotingEvent = remoting.ChromotingEvent; + var sequence = [{ + session_state: ChromotingEvent.SessionState.STARTED, + },{ + session_state: ChromotingEvent.SessionState.SIGNALING, + previous_session_state: ChromotingEvent.SessionState.STARTED + },{ + session_state: ChromotingEvent.SessionState.CREATING_PLUGIN, + previous_session_state: ChromotingEvent.SessionState.SIGNALING + },{ + session_state: ChromotingEvent.SessionState.CONNECTING, + previous_session_state: ChromotingEvent.SessionState.CREATING_PLUGIN + },{ + session_state: ChromotingEvent.SessionState.CONNECTION_FAILED, + previous_session_state: ChromotingEvent.SessionState.CONNECTING, + connection_error: error + }]; + + var expectedEvents = sequence.map(function(/** Object */ sequenceValue) { + var event = /** @type {Object} */ (base.deepCopy(baseEvent)); + base.mix(event, sequenceValue); + return event; + }); + testDriver.expectEvents(expectedEvents); +}; + +QUnit.test('Connection succeeded', function() { + expectSucceeded(testDriver, { + session_entry_point: + remoting.ChromotingEvent.SessionEntryPoint.CONNECT_BUTTON, + role: remoting.ChromotingEvent.Role.CLIENT, + mode: remoting.ChromotingEvent.Mode.ME2ME, + }); + + /** + * @param {remoting.MockClientPlugin} plugin + * @param {remoting.ClientSession.State} state + */ + function onStatusChanged(plugin, state) { + if (state == remoting.ClientSession.State.CONNECTED) { + testDriver.me2meActivity().stop(); + testDriver.endTest(); + } + } + + testDriver.mockConnection().pluginFactory().mock$setPluginStatusChanged( + onStatusChanged); + + return testDriver.startTest(); +}); + +QUnit.test('Connection canceled - Pin prompt', function() { + expectCanceled(testDriver, { + session_entry_point: + remoting.ChromotingEvent.SessionEntryPoint.CONNECT_BUTTON, + role: remoting.ChromotingEvent.Role.CLIENT, + mode: remoting.ChromotingEvent.Mode.ME2ME, + }); + + /** + * @param {remoting.MockClientPlugin} plugin + * @param {remoting.ClientSession.State} state + */ + function onStatusChanged(plugin, state) { + if (state == remoting.ClientSession.State.CONNECTING) { + testDriver.cancelWhenPinPrompted(); + plugin.mock$onDisposed().then(function(){ + testDriver.endTest(); + }); + } + } + + testDriver.mockConnection().pluginFactory().mock$setPluginStatusChanged( + onStatusChanged); + + return testDriver.startTest(); +}); + +QUnit.test('Connection failed - Signal strategy', function() { + var EntryPoint = remoting.ChromotingEvent.SessionEntryPoint; + testDriver.expectEvents([{ + session_entry_point: EntryPoint.CONNECT_BUTTON, + session_state: remoting.ChromotingEvent.SessionState.STARTED + },{ + session_entry_point: EntryPoint.CONNECT_BUTTON, + session_state: remoting.ChromotingEvent.SessionState.SIGNALING, + previous_session_state: remoting.ChromotingEvent.SessionState.STARTED + },{ + session_entry_point: EntryPoint.CONNECT_BUTTON, + previous_session_state: remoting.ChromotingEvent.SessionState.SIGNALING, + session_state: remoting.ChromotingEvent.SessionState.CONNECTION_FAILED, + connection_error: remoting.ChromotingEvent.ConnectionError.UNEXPECTED + }]); + + var promise = testDriver.startTest(); + + // The message dialog is shown when the connection fails. + testDriver.mockDialogFactory().messageDialog.show = function() { + testDriver.endTest(); + return Promise.resolve(remoting.MessageDialog.Result.PRIMARY); + }; + + var signalStrategy = testDriver.mockConnection().signalStrategy(); + signalStrategy.connect = function() { + Promise.resolve().then(function(){ + signalStrategy.setStateForTesting(remoting.SignalStrategy.State.FAILED); + }); + }; + + return promise; +}); + +QUnit.test('Reconnect', function() { + var EntryPoint = remoting.ChromotingEvent.SessionEntryPoint; + expectSucceeded(testDriver, { + session_entry_point: EntryPoint.CONNECT_BUTTON, + role: remoting.ChromotingEvent.Role.CLIENT, + mode: remoting.ChromotingEvent.Mode.ME2ME, + }); + expectSucceeded(testDriver, { + session_entry_point: EntryPoint.RECONNECT_BUTTON, + role: remoting.ChromotingEvent.Role.CLIENT, + mode: remoting.ChromotingEvent.Mode.ME2ME, + }); + + var count = 0; + /** + * @param {remoting.MockClientPlugin} plugin + * @param {remoting.ClientSession.State} state + */ + function onStatusChanged(plugin, state) { + if (state == remoting.ClientSession.State.CONNECTED) { + count++; + if (count == 1) { + testDriver.clickReconnectWhenFinished(); + testDriver.me2meActivity().stop(); + } else if (count == 2) { + testDriver.clickOkWhenFinished(); + testDriver.me2meActivity().stop(); + testDriver.endTest(); + } + } + } + + testDriver.mockConnection().pluginFactory().mock$setPluginStatusChanged( + onStatusChanged); + + return testDriver.startTest(); +}); + +QUnit.test('HOST_OFFLINE - JID refresh failed', function() { + var EntryPoint = remoting.ChromotingEvent.SessionEntryPoint; + // Expects the first connection to fail with HOST_OFFLINE + expectFailed(testDriver, { + session_entry_point:EntryPoint.CONNECT_BUTTON, + role: remoting.ChromotingEvent.Role.CLIENT, + mode: remoting.ChromotingEvent.Mode.ME2ME, + }, remoting.ChromotingEvent.ConnectionError.HOST_OFFLINE); + + function onPluginCreated(/** remoting.MockClientPlugin */ plugin) { + plugin.mock$returnErrorOnConnect( + remoting.ClientSession.ConnectionError.HOST_IS_OFFLINE); + } + + /** + * @param {remoting.MockClientPlugin} plugin + * @param {remoting.ClientSession.State} state + */ + function onStatusChanged(plugin, state) { + if (state == remoting.ClientSession.State.FAILED) { + testDriver.endTest(); + } + } + + testDriver.mockConnection().pluginFactory().mock$setPluginCreated( + onPluginCreated); + testDriver.mockConnection().pluginFactory().mock$setPluginStatusChanged( + onStatusChanged); + sinon.stub(testDriver.mockHostList(), 'refresh', + function(/** function(boolean)*/ callback) { + // Fail the refresh. + Promise.resolve().then(function(){ + callback(false); + }); + }); + + return testDriver.startTest(); +}); + +QUnit.test('HOST_OFFLINE - JID refresh succeeded', function() { + var EntryPoint = remoting.ChromotingEvent.SessionEntryPoint; + // Expects the first connection to fail with HOST_OFFLINE + expectFailed(testDriver, { + session_entry_point:EntryPoint.CONNECT_BUTTON, + role: remoting.ChromotingEvent.Role.CLIENT, + mode: remoting.ChromotingEvent.Mode.ME2ME, + }, remoting.ChromotingEvent.ConnectionError.HOST_OFFLINE); + // Expects the second connection to succeed with RECONNECT + expectSucceeded(testDriver, { + session_entry_point: EntryPoint.AUTO_RECONNECT_ON_HOST_OFFLINE, + role: remoting.ChromotingEvent.Role.CLIENT, + mode: remoting.ChromotingEvent.Mode.ME2ME, + }); + + var count = 0; + function onPluginCreated(/** remoting.MockClientPlugin */ plugin) { + count++; + if (count == 1) { + plugin.mock$returnErrorOnConnect( + remoting.ClientSession.ConnectionError.HOST_IS_OFFLINE); + } else if (count == 2) { + plugin.mock$useDefaultBehavior( + remoting.MockClientPlugin.AuthMethod.PIN); + } + } + + /** + * @param {remoting.MockClientPlugin} plugin + * @param {remoting.ClientSession.State} state + */ + function onStatusChanged(plugin, state) { + if (state == remoting.ClientSession.State.CONNECTED) { + testDriver.me2meActivity().stop(); + testDriver.endTest(); + } + } + + testDriver.mockConnection().pluginFactory().mock$setPluginCreated( + onPluginCreated); + testDriver.mockConnection().pluginFactory().mock$setPluginStatusChanged( + onStatusChanged); + + return testDriver.startTest(); +}); + +QUnit.test('HOST_OFFLINE - Reconnect failed', function() { + var EntryPoint = remoting.ChromotingEvent.SessionEntryPoint; + // Expects the first connection to fail with HOST_OFFLINE + expectFailed(testDriver, { + session_entry_point:EntryPoint.CONNECT_BUTTON, + role: remoting.ChromotingEvent.Role.CLIENT, + mode: remoting.ChromotingEvent.Mode.ME2ME, + }, remoting.ChromotingEvent.ConnectionError.HOST_OFFLINE); + // Expects the second connection to fail with HOST_OVERLOAD + expectFailed(testDriver, { + session_entry_point:EntryPoint.AUTO_RECONNECT_ON_HOST_OFFLINE, + role: remoting.ChromotingEvent.Role.CLIENT, + mode: remoting.ChromotingEvent.Mode.ME2ME, + }, remoting.ChromotingEvent.ConnectionError.HOST_OVERLOAD); + + var count = 0; + function onPluginCreated(/** remoting.MockClientPlugin */ plugin) { + count++; + if (count == 1) { + plugin.mock$returnErrorOnConnect( + remoting.ClientSession.ConnectionError.HOST_IS_OFFLINE); + } else if (count == 2) { + plugin.mock$returnErrorOnConnect( + remoting.ClientSession.ConnectionError.HOST_OVERLOAD); + } + } + + var failureCount = 0; + /** + * @param {remoting.MockClientPlugin} plugin + * @param {remoting.ClientSession.State} state + */ + function onStatusChanged(plugin, state) { + if (state == remoting.ClientSession.State.FAILED) { + failureCount++; + + if (failureCount == 2) { + testDriver.endTest(); + } + } + } + testDriver.mockConnection().pluginFactory().mock$setPluginCreated( + onPluginCreated); + testDriver.mockConnection().pluginFactory().mock$setPluginStatusChanged( + onStatusChanged); + return testDriver.startTest(); +}); + +})(); diff --git a/remoting/webapp/crd/js/mock_client_plugin.js b/remoting/webapp/crd/js/mock_client_plugin.js index efb624b..319fec1 100644 --- a/remoting/webapp/crd/js/mock_client_plugin.js +++ b/remoting/webapp/crd/js/mock_client_plugin.js @@ -47,12 +47,22 @@ remoting.MockClientPlugin = function() { /** @private */ this.onConnectDeferred_ = new base.Deferred(); + + /** @private */ + this.onDisposedDeferred_ = new base.Deferred(); + + /** + * @private {?function(remoting.MockClientPlugin, + * remoting.ClientSession.State)} + */ + this.mock$onPluginStatusChanged_ = null; }; remoting.MockClientPlugin.prototype.dispose = function() { this.container_.removeChild(this.element_); this.element_ = null; this.connectionStatusUpdateHandler_ = null; + this.onDisposedDeferred_.resolve(); }; remoting.MockClientPlugin.prototype.extensions = function() { @@ -159,6 +169,15 @@ remoting.MockClientPlugin.prototype.mock$onConnect = function() { }; /** + * @return {Promise} Returns a promise that will resolve when the plugin is + * disposed. + */ +remoting.MockClientPlugin.prototype.mock$onDisposed = function() { + this.onDisposedDeferred_ = new base.Deferred(); + return this.onDisposedDeferred_.promise(); +}; + +/** * @param {remoting.ClientSession.State} status * @param {remoting.ClientSession.ConnectionError=} opt_error */ @@ -169,6 +188,9 @@ remoting.MockClientPlugin.prototype.mock$setConnectionStatus = function( var PluginError = remoting.ClientSession.ConnectionError; var error = opt_error ? opt_error : PluginError.NONE; this.connectionEventHandler_.onConnectionStatusUpdate(status, error); + if (this.mock$onPluginStatusChanged_) { + this.mock$onPluginStatusChanged_(this, status); + } }; /** @@ -201,6 +223,15 @@ remoting.MockClientPlugin.prototype.mock$authenticate = function(authMethod) { }; /** + * @param {?function(remoting.MockClientPlugin, remoting.ClientSession.State)} + * callback + */ +remoting.MockClientPlugin.prototype.mock$setPluginStatusChanged = + function(callback) { + this.mock$onPluginStatusChanged_ = callback; +}; + +/** * @param {remoting.MockClientPlugin.AuthMethod} authMethod */ remoting.MockClientPlugin.prototype.mock$useDefaultBehavior = @@ -211,26 +242,87 @@ remoting.MockClientPlugin.prototype.mock$useDefaultBehavior = that.mock$setConnectionStatus(State.CONNECTING); return that.mock$authenticate(authMethod); }).then(function() { + that.mock$setConnectionStatus(State.AUTHENTICATED); + }).then(function() { that.mock$setConnectionStatus(State.CONNECTED); }); }; /** + * @param {remoting.ClientSession.ConnectionError} error + */ +remoting.MockClientPlugin.prototype.mock$returnErrorOnConnect = function(error){ + var that = this; + var State = remoting.ClientSession.State; + this.mock$onConnect().then(function() { + that.mock$setConnectionStatus(State.CONNECTING); + }).then(function() { + that.mock$setConnectionStatus(State.FAILED, error); + }); +}; + +/** * @constructor * @implements {remoting.ClientPluginFactory} */ remoting.MockClientPluginFactory = function() { - /** @private */ - this.plugin_ = new remoting.MockClientPlugin(); + /** @private {?remoting.MockClientPlugin} */ + this.plugin_ = null; + + /** + * @private {?function(remoting.MockClientPlugin)} + */ + this.onPluginCreated_ = null; + + /** + * @private {?function(remoting.MockClientPlugin, + * remoting.ClientSession.State)} + */ + this.onPluginStatusChanged_ = null; }; remoting.MockClientPluginFactory.prototype.createPlugin = function(container, capabilities) { + this.plugin_ = new remoting.MockClientPlugin(); this.plugin_.mock$setContainer(container); this.plugin_.mock$capabilities = capabilities; + + // Notify the listeners on plugin creation. + if (Boolean(this.onPluginCreated_)) { + this.onPluginCreated_(this.plugin_); + } else { + this.plugin_.mock$useDefaultBehavior( + remoting.MockClientPlugin.AuthMethod.PIN); + } + + // Listens for plugin status changed. + if (this.onPluginStatusChanged_) { + this.plugin_.mock$setPluginStatusChanged(this.onPluginStatusChanged_); + } return this.plugin_; }; +/** + * Register a callback to configure the plugin before it returning to the + * caller. + * + * @param {function(remoting.MockClientPlugin)} callback + */ +remoting.MockClientPluginFactory.prototype.mock$setPluginCreated = + function(callback) { + this.onPluginCreated_ = callback; +}; + +/** + * @param {?function(remoting.MockClientPlugin, remoting.ClientSession.State)} + * callback + */ +remoting.MockClientPluginFactory.prototype.mock$setPluginStatusChanged = + function(callback) { + this.onPluginStatusChanged_ = callback; +}; + + /** @return {remoting.MockClientPlugin} */ remoting.MockClientPluginFactory.prototype.plugin = function() { return this.plugin_; @@ -271,6 +363,11 @@ remoting.MockConnection = function() { remoting.settings = new remoting.Settings(); }; +/** @return {remoting.MockClientPluginFactory} */ +remoting.MockConnection.prototype.pluginFactory = function() { + return this.pluginFactory_; +}; + /** @return {remoting.MockClientPlugin} */ remoting.MockConnection.prototype.plugin = function() { return this.pluginFactory_.plugin(); diff --git a/remoting/webapp/crd/js/mock_modal_dialog_factory.js b/remoting/webapp/crd/js/mock_modal_dialog_factory.js new file mode 100644 index 0000000..2c89c6a --- /dev/null +++ b/remoting/webapp/crd/js/mock_modal_dialog_factory.js @@ -0,0 +1,95 @@ +// Copyright 2015 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. + +/** + * @fileoverview + * Mock implementation of remoting.ModalDialogFactory for testing. + */ + +/** @suppress {duplicate} */ +var remoting = remoting || {}; + +(function() { + +'use strict'; + +/** + * @constructor + * @extends {remoting.InputDialog} + */ +remoting.MockInputDialog = function() {}; + +/** @override */ +remoting.MockInputDialog.prototype.show = function() {}; + +/** + * @constructor + * @extends {remoting.MessageDialog} + */ +remoting.MockMessageDialog = function() {}; + +/** @override */ +remoting.MockMessageDialog.prototype.show = function() {}; + +/** + * @constructor + * @extends {remoting.Html5ModalDialog} + */ +remoting.MockHtml5ModalDialog = function() {}; + +/** @override */ +remoting.MockHtml5ModalDialog.prototype.show = function() {}; + +/** + * @constructor + * @extends {remoting.ConnectingDialog} + */ +remoting.MockConnectingDialog = function() {}; + +/** @override */ +remoting.MockConnectingDialog.prototype.show = function() {}; + +/** @override */ +remoting.MockConnectingDialog.prototype.hide = function() {}; + +/** + * @constructor + * @extends {remoting.ModalDialogFactory} + */ +remoting.MockModalDialogFactory = function() { + /** @type {remoting.MockConnectingDialog} */ + this.connectingDialog = new remoting.MockConnectingDialog(); + /** @type {remoting.MockMessageDialog} */ + this.messageDialog = new remoting.MockMessageDialog(); + /** @type {remoting.MockInputDialog} */ + this.inputDialog = new remoting.MockInputDialog(); + /** @type {remoting.MockHtml5ModalDialog} */ + this.html5ModalDialog = new remoting.MockHtml5ModalDialog(); +}; + +/** @override */ +remoting.MockModalDialogFactory.prototype.createConnectingDialog = + function(cancelCallback) { + return this.connectingDialog; +}; + +/** @override */ +remoting.MockModalDialogFactory.prototype.createHtml5ModalDialog = + function(params) { + return this.html5ModalDialog; +}; + +/** @override */ +remoting.MockModalDialogFactory.prototype.createMessageDialog = + function(mode, primaryButton, opt_secondaryButton) { + return this.messageDialog; +}; + +/** @override */ +remoting.MockModalDialogFactory.prototype.createInputDialog = + function(mode, formElement, inputField, cancelButton) { + return this.inputDialog; +}; + +})(); diff --git a/remoting/webapp/crd/js/remoting_activity_test_driver.js b/remoting/webapp/crd/js/remoting_activity_test_driver.js new file mode 100644 index 0000000..2197db4 --- /dev/null +++ b/remoting/webapp/crd/js/remoting_activity_test_driver.js @@ -0,0 +1,238 @@ +// Copyright 2015 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. + + +/** @suppress {duplicate} */ +var remoting = remoting || {}; + +(function() { + +'use strict'; + +/** + * @constructor + * + * The |extends| annotation is used to make JSCompile happy. The mock object + * should never extends from the actual HostList as all its implementation + * should be mocked out. The caller of this class is responsible for ensuring + * the methods that they need are implemented either here or via sinon.stub(). + * + * @extends {remoting.HostList} + */ +var MockHostList = function() {}; + +/** @override */ +MockHostList.prototype.refresh = function(callback) { + Promise.resolve().then(function() { + callback(true); + }); +}; + +/** @override */ +MockHostList.prototype.getHostForId = function(hostId) { + var host = new remoting.Host(hostId); + host.jabberId = 'fake_jabber_id'; + host.loggingChannel = 'APIARY'; + return host; +}; + +/** + * @constructor + * @extends {remoting.DesktopConnectedView} + */ +var MockDesktopConnectedView = function() {}; +/** @override */ +MockDesktopConnectedView.prototype.setVideoFrameRecorder = function() {}; +/** @override */ +MockDesktopConnectedView.prototype.dispose = function() {}; + +/** + * A test driver that mocks out the UI components that are required by the + * DesktopRemotingActivity. + * + * @param {string} mockHTML + * + * @constructor + * @implements {base.Disposable} + */ +var BaseTestDriver = function(mockHTML) { + /** @private */ + this.deferred_ = new base.Deferred(); + /** @protected */ + this.mockConnection_ = new remoting.MockConnection(); + /** @private */ + this.originalDialogFactory_ = remoting.modalDialogFactory; + /** @protected */ + this.mockDialogFactory_ = new remoting.MockModalDialogFactory(); + /** @private */ + this.desktopConnectedViewCreateStub_ = + sinon.stub(remoting.DesktopConnectedView, 'create'); + /** @private */ + this.eventWriterMock_ = sinon.mock(remoting.TelemetryEventWriter.Client); + /** @private */ + this.setModeStub_ = sinon.stub(remoting, 'setMode'); + /** + * Use fake timers to prevent the generation of session ID expiration events. + * @private + */ + this.clock_ = sinon.useFakeTimers(); + + this.init_(mockHTML); +}; + +/** + * @param {string} mockHTML + */ +BaseTestDriver.prototype.init_ = function(mockHTML) { + document.getElementById('qunit-fixture').innerHTML = mockHTML; + // Return a token to pretend that we are signed-in. + chromeMocks.identity.mock$setToken('fake_token'); + this.desktopConnectedViewCreateStub_.returns(new MockDesktopConnectedView()); + remoting.modalDialogFactory = this.mockDialogFactory_; +}; + +BaseTestDriver.prototype.dispose = function() { + this.clock_.restore(); + remoting.modalDialogFactory = this.originalDialogFactory_; + this.setModeStub_.restore(); + this.eventWriterMock_.restore(); + this.desktopConnectedViewCreateStub_.restore(); + + if (Boolean(this.mockConnection_)) { + this.mockConnection_.restore(); + this.mockConnection_ = null; + } +}; + +/** @return {remoting.MockConnection} */ +BaseTestDriver.prototype.mockConnection = function() { + return this.mockConnection_; +}; + +/** @return {remoting.MockModalDialogFactory} */ +BaseTestDriver.prototype.mockDialogFactory = function() { + return this.mockDialogFactory_; +}; + +/** @param {Array<Object>} events */ +BaseTestDriver.prototype.expectEvents = function(events) { + var that = this; + events.forEach(function(/** Object */ event){ + that.eventWriterMock_.expects('write').withArgs(sinon.match(event)); + }); +}; + +/** + * @return {Promise} A promise that will be resolved when endTest() is called. + */ +BaseTestDriver.prototype.startTest = function() { + return this.deferred_.promise(); +}; + +/** + * Resolves the promise that is returned by startTest(). + */ +BaseTestDriver.prototype.endTest = function() { + try { + this.eventWriterMock_.verify(); + this.deferred_.resolve(); + } catch (/** @type {*} */ reason) { + this.deferred_.reject(reason); + } +}; + +/** + * The Me2MeTest Driver mocks out the UI components that are required by the + * Me2MeActivity. It provides test hooks for the caller to fake behavior of + * those components. + * + * @constructor + * @extends {BaseTestDriver} + */ +remoting.Me2MeTestDriver = function() { + base.inherits(this, BaseTestDriver, remoting.Me2MeTestDriver.FIXTURE); + /** @private */ + this.mockHostList_ = new MockHostList(); + /** @private {?remoting.Me2MeActivity} */ + this.me2meActivity_ = null; +}; + +/** @override */ +remoting.Me2MeTestDriver.prototype.dispose = function() { + base.dispose(this.me2meActivity_); + this.me2meActivity_ = null; + + BaseTestDriver.prototype.dispose.call(this); +}; + +remoting.Me2MeTestDriver.prototype.enterPinWhenPrompted = function() { + this.mockDialogFactory().inputDialog.show = function() { + return Promise.resolve('fake_pin'); + }; +}; + +remoting.Me2MeTestDriver.prototype.cancelWhenPinPrompted = function() { + this.mockDialogFactory().inputDialog.show = function() { + return Promise.reject(new remoting.Error(remoting.Error.Tag.CANCELLED)); + }; +}; + +remoting.Me2MeTestDriver.prototype.clickOkWhenFinished = function() { + this.mockDialogFactory().messageDialog.show = function() { + return Promise.resolve(remoting.MessageDialog.Result.PRIMARY); + }; +}; + +remoting.Me2MeTestDriver.prototype.clickReconnectWhenFinished = function() { + this.mockDialogFactory().messageDialog.show = function() { + return Promise.resolve(remoting.MessageDialog.Result.SECONDARY); + }; +}; + +/** @return {MockHostList} */ +remoting.Me2MeTestDriver.prototype.mockHostList = function() { + return this.mockHostList_; +}; + +/** @return {remoting.Me2MeActivity} */ +remoting.Me2MeTestDriver.prototype.me2meActivity = function() { + return this.me2meActivity_; +}; + +/** @return {Promise} */ +remoting.Me2MeTestDriver.prototype.startTest = function() { + var host = new remoting.Host('fake_host_id'); + host.loggingChannel = 'APIARY'; + + // Default behavior. + this.enterPinWhenPrompted(); + this.clickOkWhenFinished(); + + this.me2meActivity_ = new remoting.Me2MeActivity(host, this.mockHostList_); + this.me2meActivity_.start(); + return BaseTestDriver.prototype.startTest.call(this); +}; + +remoting.Me2MeTestDriver.FIXTURE = + '<div id="connect-error-message"></div>' + + '<div id="client-container">' + + '<div class="client-plugin-container">' + + '</div>' + + '<div id="pin-dialog">' + + '<form>' + + '<input type="password" class="pin-inputField" />' + + '<button class="cancel-button"></button>' + + '</form>' + + '<div class="pairing-section">' + + '<input type="checkbox" class="pairing-checkbox" />' + + '<div class="pin-message"></div>' + + '</div>' + + '</div>' + + '<div id="host-needs-update-dialog">' + + '<input class="connect-button" />' + + '<input class="cancel-button" />' + + '<div class="host-needs-update-message"></div>' + + '</div>'; + +})();
\ No newline at end of file diff --git a/remoting/webapp/js_proto/sinon_proto.js b/remoting/webapp/js_proto/sinon_proto.js index c6cd0a8..7679b9c 100644 --- a/remoting/webapp/js_proto/sinon_proto.js +++ b/remoting/webapp/js_proto/sinon_proto.js @@ -24,6 +24,11 @@ sinon.assert.calledOnce = function(f) {}; sinon.assert.calledWith = function(f, data) {}; /** + * @param {*} value + * @return {Object} + */ +sinon.match = function(value) {}; +/** * @param {(sinon.Spy|Function)} f */ sinon.assert.notCalled = function(f) {}; |