From 1ef91174ad4df8d55ae050d89412fa825f8f2181 Mon Sep 17 00:00:00 2001 From: Timothy Guan-tin Chien Date: Mon, 25 Aug 2014 11:50:44 +0800 Subject: [PATCH] Bug 1054184 - Don't clear active targets right away --- .../js/keyboard/active_targets_manager.js | 5 + .../js/keyboard/input_method_manager.js | 9 +- apps/keyboard/js/keyboard/state_manager.js | 72 ++- .../test/unit/keyboard/state_manager_test.js | 472 ++++++++++-------- 4 files changed, 320 insertions(+), 238 deletions(-) diff --git a/apps/keyboard/js/keyboard/active_targets_manager.js b/apps/keyboard/js/keyboard/active_targets_manager.js index f97d18232084..5aecd0097fd9 100644 --- a/apps/keyboard/js/keyboard/active_targets_manager.js +++ b/apps/keyboard/js/keyboard/active_targets_manager.js @@ -71,6 +71,11 @@ ActiveTargetsManager.prototype.stop = function() { }; ActiveTargetsManager.prototype.clearAllTargets = function() { + if (this.activeTargets.size) { + console.warn('ActiveTargetsManager: clear ' + + this.activeTargets.size + ' active target(s).'); + } + this.activeTargets.forEach(function(target, id) { if (typeof this.ontargetcancelled === 'function') { this.ontargetcancelled(target); diff --git a/apps/keyboard/js/keyboard/input_method_manager.js b/apps/keyboard/js/keyboard/input_method_manager.js index b028dbc8de6e..137aa7e412ec 100644 --- a/apps/keyboard/js/keyboard/input_method_manager.js +++ b/apps/keyboard/js/keyboard/input_method_manager.js @@ -338,9 +338,13 @@ InputMethodManager.prototype.updateInputContextData = function() { return; } - var p = this.app.inputContext.getText().then(function(value) { + // Save inputContext as a local variable; + // It is important that the promise is getting the inputContext + // it calls getText() on when resolved/rejected. + var inputContext = this.app.inputContext; + + var p = inputContext.getText().then(function(value) { this.app.perfTimer.printTime('updateInputContextData:promise resolved'); - var inputContext = this.app.inputContext; // Resolve to this object containing information of inputContext return { @@ -353,7 +357,6 @@ InputMethodManager.prototype.updateInputContextData = function() { }; }.bind(this), function(error) { console.warn('InputMethodManager: inputcontext.getText() was rejected.'); - var inputContext = this.app.inputContext; // Resolve to this object containing information of inputContext // With empty string as value. diff --git a/apps/keyboard/js/keyboard/state_manager.js b/apps/keyboard/js/keyboard/state_manager.js index 0a372e4401e4..07e9eb54a4c5 100644 --- a/apps/keyboard/js/keyboard/state_manager.js +++ b/apps/keyboard/js/keyboard/state_manager.js @@ -10,6 +10,12 @@ var StateManager = function(app) { this._layoutName = undefined; }; +// Don't switch away IMEngine right away since the transition won't +// start until then, and we need to keep the keyboard responsive to user +// touch when visible. +// (This number corresponds to BLUR_CHANGE_DELAY in input management.) +StateManager.prototype.DEACTIVATE_DELAY = 120; + StateManager.prototype.start = function() { // Start with inactive state. this._isActive = false; @@ -75,6 +81,13 @@ StateManager.prototype._updateActiveState = function(active) { // since eventually IMEngine will be switched. this.app.inputMethodManager.updateInputContextData(); + // Before switching away, clean up anything pending in the previous + // active layout. + // We however don't clear active target here because the user might + // want to input continuously between two layouts. + this.app.candidatePanelManager.hideFullPanel(); + this.app.candidatePanelManager.updateCandidates([]); + // Perform the following async actions with a promise chain. // Switch the layout, this.app.layoutManager.switchCurrentLayout(this._layoutName) @@ -97,29 +110,53 @@ StateManager.prototype._updateActiveState = function(active) { this.app.settingsPromiseManager.set({ 'keyboard.current': undefined }); - // Finish off anything pending except removing the rendering -- - // input management need it for transition. - this.app.candidatePanelManager.hideFullPanel(); - this.app.candidatePanelManager.updateCandidates([]); - this.app.targetHandlersManager.activeTargetsManager.clearAllTargets(); - this.app.inputMethodManager.switchCurrentIMEngine('default') - // ... make sure error is not silently ignored. - .catch(function(e) { (e !== undefined) && console.error(e); }); + + var imManager = this.app.inputMethodManager; + + // Finish off anything pending except removing the rendering after a delay + // -- input management need it for transition. + this._delayDeactivate() + // ... cancel everything + .then(function() { + this.app.candidatePanelManager.hideFullPanel(); + this.app.candidatePanelManager.updateCandidates([]); + this.app.targetHandlersManager.activeTargetsManager.clearAllTargets(); + }.bind(this)) + // ... switch away IMEngine + .then(imManager.switchCurrentIMEngine.bind(imManager, 'default')) + // ... make sure error is not silently ignored. + .catch(function(e) { (e !== undefined) && console.error(e); }); } this._isActive = active; }; +StateManager.prototype._delayDeactivate = function() { + var p = new Promise(function(resolve, reject) { + setTimeout(function() { + // If state has switched to active, do not deactivate the keyboard. + if (this._isActive) { + console.warn('StateManager: Reactivated before DEACTIVATE_DELAY.'); + reject(); + } + + resolve(); + }.bind(this), this.DEACTIVATE_DELAY); + }.bind(this)); + + return p; +}; + StateManager.prototype._preloadLayout = function() { var layoutLoader = this.app.layoutManager.loader; var p = layoutLoader.getLayoutAsync(this._layoutName).then(function(layout) { - var imEngineName = layout.imEngine; - var imEngineLoader = this.app.inputMethodManager.loader; - // Ask the loader to start loading IMEngine - if (imEngineName) { - var p = imEngineLoader.getInputMethodAsync(imEngineName); - return p; - } + var imEngineName = layout.imEngine; + var imEngineLoader = this.app.inputMethodManager.loader; + // Ask the loader to start loading IMEngine + if (imEngineName) { + var p = imEngineLoader.getInputMethodAsync(imEngineName); + return p; + } }.bind(this)).catch(function(e) { if (e !== undefined) { console.error(e); @@ -158,11 +195,6 @@ StateManager.prototype._updateLayoutRendering = function() { } this.app.perfTimer.startTimer('_updateLayoutRendering'); - // Clean up anything pending in the previous active layout. - this.app.candidatePanelManager.hideFullPanel(); - this.app.candidatePanelManager.updateCandidates([]); - this.app.targetHandlersManager.activeTargetsManager.clearAllTargets(); - // everything.me uses this setting to improve searches, // but they really shouldn't. this.app.settingsPromiseManager.set({ diff --git a/apps/keyboard/test/unit/keyboard/state_manager_test.js b/apps/keyboard/test/unit/keyboard/state_manager_test.js index d12991dcdc37..460e4cf3acd3 100644 --- a/apps/keyboard/test/unit/keyboard/state_manager_test.js +++ b/apps/keyboard/test/unit/keyboard/state_manager_test.js @@ -4,7 +4,7 @@ ActiveTargetsManager, UpperCaseStateManager, SettingsPromiseManager, LayoutRenderingManager, PerformanceTimer, LayoutLoader, InputMethodLoader, StateManager, MockInputMethod, MockInputContext, - MockEventTarget, Promise */ + MockEventTarget, MockPromise, Promise */ require('/js/keyboard/performance_timer.js'); require('/js/keyboard/input_method_manager.js'); @@ -16,6 +16,7 @@ require('/js/keyboard/active_targets_manager.js'); require('/js/keyboard/candidate_panel_manager.js'); require('/js/keyboard/upper_case_state_manager.js'); require('/js/keyboard/layout_rendering_manager.js'); +require('/shared/test/unit/mocks/mock_promise.js'); require('/shared/test/unit/mocks/mock_event_target.js'); require('/shared/test/unit/mocks/mock_navigator_input_method.js'); @@ -26,6 +27,7 @@ suite('StateManager', function() { var manager; var app; var realMozInputMethod; + var realPromise; var windowStub; suiteSetup(function() { @@ -70,6 +72,11 @@ suite('StateManager', function() { realMozInputMethod = navigator.mozInputMethod; navigator.mozInputMethod = new MockInputMethod(); + realPromise = window.Promise; + window.Promise = this.sinon.spy(MockPromise); + + this.sinon.stub(window, 'setTimeout'); + manager = new StateManager(app); }); @@ -82,25 +89,29 @@ suite('StateManager', function() { window.removeEventListener.calledWith('visibilitychange', manager)); navigator.mozInputMethod = realMozInputMethod; + window.Promise = realPromise; }); suite('start with not hidden', function() { - setup(function(done) { + setup(function() { document.hidden = false; navigator.mozInputMethod.setInputContext(new MockInputContext()); app.inputContext = navigator.mozInputMethod.inputcontext; - var p = Promise.resolve(); - var p2 = Promise.resolve(); - var p3 = Promise.resolve(); + var switchCurrentLayoutPromise = Promise.resolve(); + var switchCurrentIMEnginePromise = Promise.resolve(); + var updateLayoutRenderingPromise = Promise.resolve(); app.layoutManager.currentModifiedLayout = { imEngine: 'bar' }; - app.layoutManager.switchCurrentLayout.returns(p); - app.inputMethodManager.switchCurrentIMEngine.returns(p2); - app.layoutRenderingManager.updateLayoutRendering.returns(p3); + app.layoutManager.switchCurrentLayout + .returns(switchCurrentLayoutPromise); + app.inputMethodManager.switchCurrentIMEngine + .returns(switchCurrentIMEnginePromise); + app.layoutRenderingManager.updateLayoutRendering + .returns(updateLayoutRenderingPromise); manager.start(); @@ -118,131 +129,155 @@ suite('StateManager', function() { assert.isTrue(app.inputMethodManager.updateInputContextData.calledOnce); + assert.isTrue(app.candidatePanelManager.hideFullPanel.calledOnce); + assert.isTrue(app.candidatePanelManager.updateCandidates.calledOnce); + assert.isFalse(app.targetHandlersManager + .activeTargetsManager.clearAllTargets.called); + assert.isTrue(app.layoutManager.switchCurrentLayout.calledWith('foo')); - p.then(function() { - assert.isTrue(app.upperCaseStateManager.reset.calledOnce); - assert.isTrue( - app.inputMethodManager.switchCurrentIMEngine.calledWith('bar')); - assert.isTrue(app.upperCaseStateManager.reset.calledBefore( - app.inputMethodManager.switchCurrentIMEngine), - 'Reset the state before engine activates.'); - return p2; - }, function() { - assert.isTrue(false, 'Should not reject.'); - }).then(function() { - assert.isTrue(app.candidatePanelManager.hideFullPanel.calledOnce); - assert.isTrue(app.candidatePanelManager.updateCandidates.calledOnce); - assert.isTrue(app.targetHandlersManager - .activeTargetsManager.clearAllTargets.calledOnce); + // calling _switchCurrentIMEngine() + var p = switchCurrentLayoutPromise; + var pReturn = p.mFulfillToValue(); + assert.isTrue(app.upperCaseStateManager.reset.calledOnce); + assert.isTrue( + app.inputMethodManager.switchCurrentIMEngine.calledWith('bar')); + assert.isTrue(app.upperCaseStateManager.reset.calledBefore( + app.inputMethodManager.switchCurrentIMEngine), + 'Reset the state before engine activates.'); - assert.isTrue( - app.layoutRenderingManager.updateLayoutRendering.calledOnce); + assert.equal(pReturn, switchCurrentIMEnginePromise); + + // Calling _updateLayoutRendering() + var p2 = p.mGetNextPromise(); + var p2Return = p2.mFulfillToValue(); + + assert.isTrue( + app.layoutRenderingManager.updateLayoutRendering.calledOnce); - return p3; - }, function() { - assert.isTrue(false, 'Should not reject.'); - }).then(function() { - assert.isTrue(app.l10nLoader.load.calledOnce); - assert.isTrue(app.l10nLoader.load.calledOn(app.l10nLoader)); - }, function() { - assert.isTrue(false, 'Should not reject.'); - }).then(done, done); + assert.equal(p2Return, updateLayoutRenderingPromise); + var p3 = p2.mGetNextPromise(); + p3.mFulfillToValue(); + + assert.isTrue(app.l10nLoader.load.calledOnce); + assert.isTrue(app.l10nLoader.load.calledOn(app.l10nLoader)); }); - test('hashchange', function(done) { + test('hashchange', function() { window.location.hash = '#foo2'; var evt = { type: 'hashchange' }; - var p = Promise.resolve(); - var p2 = Promise.resolve(); - var p3 = Promise.resolve(); + var switchCurrentLayoutPromise = Promise.resolve(); + var switchCurrentIMEnginePromise = Promise.resolve(); + var updateLayoutRenderingPromise = Promise.resolve(); app.layoutManager.currentModifiedLayout = { imEngine: 'bar2' }; - app.layoutManager.switchCurrentLayout.returns(p); - app.inputMethodManager.switchCurrentIMEngine.returns(p2); - app.layoutRenderingManager.updateLayoutRendering.returns(p3); + app.layoutManager.switchCurrentLayout + .returns(switchCurrentLayoutPromise); + app.inputMethodManager.switchCurrentIMEngine + .returns(switchCurrentIMEnginePromise); + app.layoutRenderingManager.updateLayoutRendering + .returns(updateLayoutRenderingPromise); windowStub.dispatchEvent(evt); // Start layout switching - assert.isTrue(app.inputMethodManager.updateInputContextData.calledTwice); + assert.isTrue(app.candidatePanelManager.hideFullPanel.calledTwice); + assert.isTrue(app.candidatePanelManager.updateCandidates.calledTwice); + assert.isFalse(app.targetHandlersManager + .activeTargetsManager.clearAllTargets.called); + + assert.isTrue(app.layoutManager.switchCurrentLayout.calledWith('foo2')); + // calling _switchCurrentIMEngine() + var p = switchCurrentLayoutPromise; + var pReturn = p.mFulfillToValue(); + assert.isTrue(app.upperCaseStateManager.reset.calledTwice); assert.isTrue( - app.layoutManager.switchCurrentLayout.getCall(1).calledWith('foo2')); - p.then(function() { - assert.isTrue(app.upperCaseStateManager.reset.calledTwice); - assert.isTrue(app.inputMethodManager.switchCurrentIMEngine - .getCall(1).calledWith('bar2')); - assert.isTrue(app.upperCaseStateManager.reset.getCall(1).calledBefore( - app.inputMethodManager.switchCurrentIMEngine.getCall(1)), - 'Reset the state before engine activates.'); + app.inputMethodManager.switchCurrentIMEngine.calledWith('bar2')); + assert.isTrue(app.upperCaseStateManager.reset.calledBefore( + app.inputMethodManager.switchCurrentIMEngine), + 'Reset the state before engine activates.'); - return p2; - }).then(function() { - assert.isTrue(app.candidatePanelManager.hideFullPanel.calledTwice); - assert.isTrue(app.candidatePanelManager.updateCandidates.calledTwice); - assert.isTrue(app.targetHandlersManager - .activeTargetsManager.clearAllTargets.calledTwice); + assert.equal(pReturn, switchCurrentIMEnginePromise); - assert.isTrue( - app.layoutRenderingManager.updateLayoutRendering.calledTwice); + // Calling _updateLayoutRendering() + var p2 = p.mGetNextPromise(); + var p2Return = p2.mFulfillToValue(); - return p3; - }).then(function() { - assert.isTrue(app.l10nLoader.load.calledTwice); - assert.isTrue(app.l10nLoader.load.getCall(1).calledOn(app.l10nLoader)); - }).then(done, done); + assert.isTrue( + app.layoutRenderingManager.updateLayoutRendering.calledTwice); + + assert.equal(p2Return, updateLayoutRenderingPromise); + var p3 = p2.mGetNextPromise(); + p3.mFulfillToValue(); + + assert.isTrue(app.l10nLoader.load.calledTwice); + assert.isTrue(app.l10nLoader.load.calledOn(app.l10nLoader)); }); - suite('not active', function() { + suite('deactivate', function() { + var p, resolved, rejected; setup(function() { - document.hidden = true; + // triggers inputcontextchange + navigator.mozInputMethod.setInputContext(); + app.inputContext = null; - var evt = { - type: 'visibilitychange' - }; + p = window.Promise.firstCall.returnValue; + p.mExecuteCallback(function resolve() { + resolved = true; + }, function reject() { + rejected = true; + }); - windowStub.dispatchEvent(evt); + assert.equal(window.setTimeout.firstCall.args[1], + manager.DEACTIVATE_DELAY); + }); + + test('finishing deactivation', function() { + window.setTimeout.firstCall.args[0].call(window); + assert.isTrue(resolved); + + p.mFulfillToValue(); assert.isTrue(app.candidatePanelManager.hideFullPanel.calledTwice); assert.isTrue(app.candidatePanelManager.updateCandidates.calledTwice); assert.isTrue(app.targetHandlersManager - .activeTargetsManager.clearAllTargets.calledTwice); + .activeTargetsManager.clearAllTargets.calledOnce); + + var p2 = p.mGetNextPromise(); + p2.mFulfillToValue(); + assert.isTrue(app.inputMethodManager.switchCurrentIMEngine .getCall(1).calledWith('default')); - navigator.mozInputMethod.setInputContext(); - app.inputContext = null; - - assert.isFalse(app.candidatePanelManager.hideFullPanel.calledThrice, - 'Should not disable current layout again.'); - assert.isFalse(app.candidatePanelManager.updateCandidates.calledThrice, - 'Should not disable current layout again.'); - assert.isFalse(app.targetHandlersManager - .activeTargetsManager.clearAllTargets.calledThrice, - 'Should not disable current layout again.'); - assert.isFalse( - app.inputMethodManager.switchCurrentIMEngine.calledThrice, - 'Should not disable current layout again.'); - }); + // trigger visibilitychange (after inputcontextchange) + document.hidden = true; + var evt = { + type: 'visibilitychange' + }; + windowStub.dispatchEvent(evt); - test('not active', function() { + assert.isFalse(window.setTimeout.calledTwice, + 'should not attempt to hide again.'); }); - test('re-active right away', function(done) { - var p = Promise.resolve(); - var p2 = Promise.resolve(); - var p3 = Promise.resolve(); + test('re-activate right away', function() { + var switchCurrentLayoutPromise = Promise.resolve(); + var switchCurrentIMEnginePromise = Promise.resolve(); + var updateLayoutRenderingPromise = Promise.resolve(); - app.layoutManager.switchCurrentLayout.returns(p); - app.inputMethodManager.switchCurrentIMEngine.returns(p2); - app.layoutRenderingManager.updateLayoutRendering.returns(p3); + app.layoutManager.switchCurrentLayout + .returns(switchCurrentLayoutPromise); + app.inputMethodManager.switchCurrentIMEngine + .returns(switchCurrentIMEnginePromise); + app.layoutRenderingManager.updateLayoutRendering + .returns(updateLayoutRenderingPromise); document.hidden = false; var evt = { @@ -253,6 +288,9 @@ suite('StateManager', function() { navigator.mozInputMethod.setInputContext(new MockInputContext()); app.inputContext = navigator.mozInputMethod.inputcontext; + window.setTimeout.firstCall.args[0].call(window); + assert.isTrue(rejected, 'Should stop unloading.'); + assert.isFalse(app.layoutManager.loader.getLayoutAsync.calledOnce, 'Should not try to preload layout.'); assert.isFalse( @@ -263,46 +301,52 @@ suite('StateManager', function() { assert.isTrue( app.inputMethodManager.updateInputContextData.calledTwice); - assert.isTrue(app.layoutManager.switchCurrentLayout.calledWith('foo')); - p.then(function() { - assert.isTrue(app.upperCaseStateManager.reset.calledTwice); - assert.isTrue(app.inputMethodManager.switchCurrentIMEngine - .getCall(2).calledWith('bar')); - assert.isTrue(app.upperCaseStateManager.reset.getCall(1).calledBefore( - app.inputMethodManager.switchCurrentIMEngine.getCall(2)), - 'Reset the state before engine activates.'); - - return p2; - }).then(function() { - assert.isTrue(app.candidatePanelManager.hideFullPanel.calledThrice); - assert.isTrue( - app.candidatePanelManager.updateCandidates.calledThrice); - assert.isTrue(app.targetHandlersManager - .activeTargetsManager.clearAllTargets.calledThrice); - - assert.isTrue( - app.layoutRenderingManager.updateLayoutRendering.calledTwice); - - return p3; - }).then(function() { - assert.isTrue(app.l10nLoader.load.calledTwice); - assert.isTrue( - app.l10nLoader.load.getCall(1).calledOn(app.l10nLoader)); - }).then(done, done); + assert.isTrue(app.candidatePanelManager.hideFullPanel.calledTwice); + assert.isTrue(app.candidatePanelManager.updateCandidates.calledTwice); + + assert.isTrue( + app.layoutManager.switchCurrentLayout.getCall(1).calledWith('foo')); + + // calling _switchCurrentIMEngine() + var pLoading = switchCurrentLayoutPromise; + var pLoadingReturn = pLoading.mFulfillToValue(); + assert.isTrue(app.upperCaseStateManager.reset.calledTwice); + assert.isTrue(app.inputMethodManager.switchCurrentIMEngine + .getCall(1).calledWith('bar')); + assert.isTrue(app.upperCaseStateManager.reset.getCall(1).calledBefore( + app.inputMethodManager.switchCurrentIMEngine.getCall(1)), + 'Reset the state before engine activates.'); + + assert.equal(pLoadingReturn, switchCurrentIMEnginePromise); + + // Calling _updateLayoutRendering() + var pLoading2 = pLoading.mGetNextPromise(); + var pLoading2Return = pLoading2.mFulfillToValue(); + + assert.isTrue( + app.layoutRenderingManager.updateLayoutRendering.calledTwice); + + assert.equal(pLoading2Return, updateLayoutRenderingPromise); + var pLoading3 = pLoading2.mGetNextPromise(); + pLoading3.mFulfillToValue(); + + assert.isTrue(app.l10nLoader.load.calledTwice); + assert.isTrue(app.l10nLoader.load.getCall(1).calledOn(app.l10nLoader)); }); }); }); suite('start with hidden', function() { - setup(function(done) { + setup(function() { document.hidden = true; var layout = { imEngine: 'bar' }; - var p = Promise.resolve(layout); - var p2 = Promise.resolve({}); + var getLayoutAsyncPromise = Promise.resolve(); + var getInputMethodAsyncPromise = Promise.resolve(); - app.layoutManager.loader.getLayoutAsync.returns(p); - app.inputMethodManager.loader.getInputMethodAsync.returns(p2); + app.layoutManager.loader.getLayoutAsync.returns(getLayoutAsyncPromise); + app.inputMethodManager.loader.getInputMethodAsync + .returns(getInputMethodAsyncPromise); manager.start(); @@ -312,49 +356,49 @@ suite('StateManager', function() { window.addEventListener.calledWith('visibilitychange', manager)); assert.isTrue(app.layoutManager.loader.getLayoutAsync.calledWith('foo')); - p.then(function() { - assert.isTrue( - app.inputMethodManager.loader.getInputMethodAsync.calledWith('bar')); - return p2; - }, function() { - assert.isTrue(false, 'Should not reject.'); - }).then(function() { - assert.isTrue(app.l10nLoader.load.calledOnce); - assert.isTrue(app.l10nLoader.load.calledOn(app.l10nLoader)); - }, function() { - assert.isTrue(false, 'Should not reject.'); - }).then(done, done); + var p = getLayoutAsyncPromise; + p.mFulfillToValue(layout); + + assert.isTrue( + app.inputMethodManager.loader.getInputMethodAsync.calledWith('bar')); + + var p2 = p.mGetNextPromise(); + var p3 = p2.catch.firstCall.returnValue; + p3.mFulfillToValue({}); + + assert.isTrue(app.l10nLoader.load.calledOnce); + assert.isTrue(app.l10nLoader.load.calledOn(app.l10nLoader)); }); - test('hashchange', function(done) { + test('hashchange', function() { window.location.hash = '#foo2'; var evt = { type: 'hashchange' }; var layout = { imEngine: 'bar2' }; - var p = Promise.resolve(layout); - var p2 = Promise.resolve({}); + var getLayoutAsyncPromise = Promise.resolve(); + var getInputMethodAsyncPromise = Promise.resolve(); - app.layoutManager.loader.getLayoutAsync.returns(p); - app.inputMethodManager.loader.getInputMethodAsync.returns(p2); + app.layoutManager.loader.getLayoutAsync.returns(getLayoutAsyncPromise); + app.inputMethodManager.loader.getInputMethodAsync + .returns(getInputMethodAsyncPromise); windowStub.dispatchEvent(evt); assert.isTrue(app.layoutManager.loader.getLayoutAsync.calledWith('foo2'), 'Start loading foo2 when hashchanged'); - p.then(function() { - assert.isTrue( - app.inputMethodManager.loader.getInputMethodAsync.calledWith('bar2'), - 'Start loading bar2 when hashchanged'); - return p2; - }).then(function() { - return; - }).then(done, done); + var p = getLayoutAsyncPromise; + p.mFulfillToValue(layout); + + assert.isTrue( + app.inputMethodManager.loader.getInputMethodAsync.calledWith('bar2')); }); - suite('active', function() { - var p, p2, p3; + suite('activate', function() { + var switchCurrentLayoutPromise; + var switchCurrentIMEnginePromise; + var updateLayoutRenderingPromise; setup(function() { document.hidden = false; @@ -368,17 +412,20 @@ suite('StateManager', function() { assert.isFalse(app.layoutManager.switchCurrentLayout.calledOnce, 'Not launched yet with visibilitychange only.'); - p = Promise.resolve(); - p2 = Promise.resolve(); - p3 = Promise.resolve(); + switchCurrentLayoutPromise = Promise.resolve(); + switchCurrentIMEnginePromise = Promise.resolve(); + updateLayoutRenderingPromise = Promise.resolve(); app.layoutManager.currentModifiedLayout = { imEngine: 'bar' }; - app.layoutManager.switchCurrentLayout.returns(p); - app.inputMethodManager.switchCurrentIMEngine.returns(p2); - app.layoutRenderingManager.updateLayoutRendering.returns(p3); + app.layoutManager.switchCurrentLayout + .returns(switchCurrentLayoutPromise); + app.inputMethodManager.switchCurrentIMEngine + .returns(switchCurrentIMEnginePromise); + app.layoutRenderingManager.updateLayoutRendering + .returns(updateLayoutRenderingPromise); navigator.mozInputMethod.setInputContext(new MockInputContext()); app.inputContext = navigator.mozInputMethod.inputcontext; @@ -392,37 +439,43 @@ suite('StateManager', function() { assert.isTrue(app.inputMethodManager.updateInputContextData.calledOnce); + assert.isTrue(app.candidatePanelManager.hideFullPanel.calledOnce); + assert.isTrue(app.candidatePanelManager.updateCandidates.calledOnce); + assert.isFalse(app.targetHandlersManager + .activeTargetsManager.clearAllTargets.called); + assert.isTrue(app.layoutManager.switchCurrentLayout.calledWith('foo')); }); - test('active', function(done) { - p.then(function() { - assert.isTrue(app.upperCaseStateManager.reset.calledOnce); - assert.isTrue( - app.inputMethodManager.switchCurrentIMEngine.calledWith('bar')); - assert.isTrue(app.upperCaseStateManager.reset.calledBefore( - app.inputMethodManager.switchCurrentIMEngine), - 'Reset the state before engine activates.'); - - return p2; - }).then(function() { - assert.isTrue(app.candidatePanelManager.hideFullPanel.calledOnce); - assert.isTrue(app.candidatePanelManager.updateCandidates.calledOnce); - assert.isTrue(app.targetHandlersManager - .activeTargetsManager.clearAllTargets.calledOnce); - - assert.isTrue( - app.layoutRenderingManager.updateLayoutRendering.calledOnce); - - return p3; - }).then(function() { - assert.isTrue(app.l10nLoader.load.calledTwice); - assert.isTrue( - app.l10nLoader.load.getCall(1).calledOn(app.l10nLoader)); - }).then(done, done); + test('finishing activation', function() { + // calling _switchCurrentIMEngine() + var p = switchCurrentLayoutPromise; + var pReturn = p.mFulfillToValue(); + assert.isTrue(app.upperCaseStateManager.reset.calledOnce); + assert.isTrue( + app.inputMethodManager.switchCurrentIMEngine.calledWith('bar')); + assert.isTrue(app.upperCaseStateManager.reset.calledBefore( + app.inputMethodManager.switchCurrentIMEngine), + 'Reset the state before engine activates.'); + + assert.equal(pReturn, switchCurrentIMEnginePromise); + + // Calling _updateLayoutRendering() + var p2 = p.mGetNextPromise(); + var p2Return = p2.mFulfillToValue(); + + assert.isTrue( + app.layoutRenderingManager.updateLayoutRendering.calledOnce); + + assert.equal(p2Return, updateLayoutRenderingPromise); + var p3 = p2.mGetNextPromise(); + p3.mFulfillToValue(); + + assert.isTrue(app.l10nLoader.load.calledTwice); + assert.isTrue(app.l10nLoader.load.getCall(1).calledOn(app.l10nLoader)); }); - test('de-active right away', function(done) { + test('de-activate right away', function() { document.hidden = true; var evt = { @@ -431,42 +484,31 @@ suite('StateManager', function() { windowStub.dispatchEvent(evt); - assert.isTrue(app.candidatePanelManager.hideFullPanel.calledOnce); - assert.isTrue(app.candidatePanelManager.updateCandidates.calledOnce); - assert.isTrue(app.targetHandlersManager - .activeTargetsManager.clearAllTargets.calledOnce); - assert.isTrue( - app.inputMethodManager.switchCurrentIMEngine.calledWith('default')); + var p = window.Promise.firstCall.returnValue; + var resolved; + p.mExecuteCallback(function resolve() { + resolved = true; + }, function reject() { + assert.isTrue(false, 'should not reject'); + }); - navigator.mozInputMethod.setInputContext(); - app.inputContext = null; + assert.equal(window.setTimeout.firstCall.args[1], + manager.DEACTIVATE_DELAY); - assert.isFalse(app.candidatePanelManager.hideFullPanel.calledThrice, - 'Should not disable current layout again.'); - assert.isFalse(app.candidatePanelManager.updateCandidates.calledThrice, - 'Should not disable current layout again.'); - assert.isFalse(app.targetHandlersManager - .activeTargetsManager.clearAllTargets.calledThrice, - 'Should not disable current layout again.'); + this.sinon.stub(window.Promise, 'reject'); + + // calling _switchCurrentIMEngine() + var pLoading = switchCurrentLayoutPromise; + var pReturn = pLoading.mFulfillToValue(); + assert.isFalse(app.upperCaseStateManager.reset.calledOnce); assert.isFalse( - app.inputMethodManager.switchCurrentIMEngine.calledThrice, - 'Should not disable current layout again.'); - - p.then(function() { - assert.isFalse( - app.inputMethodManager.switchCurrentIMEngine.calledWith('bar'), - 'Loading stops.'); - - return p2; - }).then(function() { - assert.isFalse( - app.layoutRenderingManager.updateLayoutRendering.calledOnce, - 'Loading stops.'); - - return p3; - }).then(function() { - assert.isFalse(app.l10nLoader.load.calledTwice); - }).then(done, done); + app.inputMethodManager.switchCurrentIMEngine.calledWith('bar')); + + assert.equal(pReturn, window.Promise.reject.firstCall.returnValue, + 'Loading should stop when hidden.'); + + var pLoading2 = pLoading.mGetNextPromise(); + pLoading2.mRejectToError(); }); }); });