diff --git a/app/scripts/lib/app-start.js b/app/scripts/lib/app-start.js index a153587e5f..ce041b84e6 100644 --- a/app/scripts/lib/app-start.js +++ b/app/scripts/lib/app-start.js @@ -314,6 +314,7 @@ define(function (require, exports, module) { assertionLibrary: this._assertionLibrary, iframeChannel: this._iframeChannel, metrics: this._metrics, + notifier: this._notifier, oAuthClient: this._oAuthClient, relier: this._relier, session: Session, diff --git a/app/scripts/models/auth_brokers/fx-firstrun-v2.js b/app/scripts/models/auth_brokers/fx-firstrun-v2.js index 9cacd5a500..ff64248b59 100644 --- a/app/scripts/models/auth_brokers/fx-firstrun-v2.js +++ b/app/scripts/models/auth_brokers/fx-firstrun-v2.js @@ -15,10 +15,13 @@ define(function (require, exports, module) { const _ = require('underscore'); const Constants = require('lib/constants'); const FxFirstrunV1AuthenticationBroker = require('./fx-firstrun-v1'); + const NotifierMixin = require('views/mixins/notifier-mixin'); var proto = FxFirstrunV1AuthenticationBroker.prototype; var FxFirstrunV2AuthenticationBroker = FxFirstrunV1AuthenticationBroker.extend({ + type: 'fx-firstrun-v2', + defaultCapabilities: _.extend({}, proto.defaultCapabilities, { chooseWhatToSyncCheckbox: false, chooseWhatToSyncWebV1: { @@ -26,9 +29,87 @@ define(function (require, exports, module) { } }), - type: 'fx-firstrun-v2' + initialize (options = {}) { + proto.initialize.call(this, options); + + NotifierMixin.initialize.call(this, options); + }, + + notifications: { + 'form.disabled': '_sendFormDisabled', + 'form.enabled': '_sendFormEnabled', + 'form.engaged': '_sendFormEngaged', + 'show-child-view': '_onShowChildView', + 'show-view': '_onShowView' + }, + + _iframeCommands: _.extend({}, proto._iframeCommands, { + FORM_DISABLED: 'form_disabled', + FORM_ENABLED: 'form_enabled', + FORM_ENGAGED: 'form_engaged', + NAVIGATED: 'navigated' + }), + + /** + * Notify the parent the form has been modified. + * + * @private + */ + _sendFormEngaged () { + this._iframeChannel.send(this._iframeCommands.FORM_ENGAGED); + }, + + /** + * Notify the parent the form has been disabled. + * + * @private + */ + _sendFormDisabled () { + this._iframeChannel.send(this._iframeCommands.FORM_DISABLED); + }, + + /** + * Notify the parent the form has been enabled. + * + * @private + */ + _sendFormEnabled () { + this._iframeChannel.send(this._iframeCommands.FORM_ENABLED); + }, + + /** + * Called whenever a View is displayed + * + * @param {Function} View constructor + * @param {String} currentPage - URL being navigated to + * @private + */ + _onShowView (View, { currentPage }) { + this._sendNavigated(currentPage); + }, + + /** + * Notify the parent a view has been navigated to. + * + * @param {Function} ChildView constructor + * @param {Function} ParentView constructor + * @param {String} currentPage - URL being navigated to + * @private + */ + _onShowChildView (ChildView, ParentView, { currentPage }) { + this._sendNavigated(currentPage); + }, + + /** + * Notify the parent when the URL pathname has changed + * + * @param {String} url - URL being navigated to + * @private + */ + _sendNavigated (url) { + this._iframeChannel.send(this._iframeCommands.NAVIGATED, { url }); + } }); module.exports = FxFirstrunV2AuthenticationBroker; }); - diff --git a/app/scripts/views/form.js b/app/scripts/views/form.js index 637920fe43..e69a4ee961 100644 --- a/app/scripts/views/form.js +++ b/app/scripts/views/form.js @@ -72,6 +72,7 @@ define(function (require, exports, module) { 'submit form': preventDefaultThen('validateAndSubmit') }, + _notifiedOfEngaged: false, onFormChange () { // the change event can be called after the form is already // submitted if the user presses "enter" in the form. If the @@ -84,6 +85,11 @@ define(function (require, exports, module) { this.hideError(); this.hideSuccess(); this.enableSubmitIfValid(); + + if (! this._notifiedOfEngaged) { + this._notifiedOfEngaged = true; + this.notifier.trigger('form.engaged'); + } }, afterRender () { @@ -142,25 +148,36 @@ define(function (require, exports, module) { }, /** - * TODO - this should be called disableSubmit + * Disable the form */ disableForm () { // the disabled class is used instead of the disabled attribute // so that the submit handler is still called. With the submit attribute // applied, no submit handler is fired, and the form validation does not // take place. - this.$('button[type=submit]').addClass('disabled'); - this._isFormEnabled = false; + if (this.isFormEnabled()) { + this.$('button[type=submit]').addClass('disabled'); + this.notifier.trigger('form.disabled'); + } }, + /** + * Enable the form + */ enableForm () { - this.$('button[type=submit]').removeClass('disabled'); - this._isFormEnabled = true; + if (! this.isFormEnabled()) { + this.$('button[type=submit]').removeClass('disabled'); + this.notifier.trigger('form.enabled'); + } }, - _isFormEnabled: true, + /** + * Check if the form is enabled + * + * @returns {Boolean} + */ isFormEnabled () { - return !! this._isFormEnabled; + return ! this.$('button[type=submit]').hasClass('disabled'); }, /** diff --git a/app/scripts/views/sign_up.js b/app/scripts/views/sign_up.js index c1dda90f72..44e736adee 100644 --- a/app/scripts/views/sign_up.js +++ b/app/scripts/views/sign_up.js @@ -75,6 +75,7 @@ define(function (require, exports, module) { el: this.$('#coppa'), formPrefill: this._formPrefill, metrics: this.metrics, + notifier: this.notifier, shouldFocus: autofocusEl === null, viewName: this.getViewName() }; diff --git a/app/tests/spec/lib/app-start.js b/app/tests/spec/lib/app-start.js index 0a04412f70..a5b6a6a53e 100644 --- a/app/tests/spec/lib/app-start.js +++ b/app/tests/spec/lib/app-start.js @@ -269,6 +269,7 @@ define(function (require, exports, module) { beforeEach(function () { appStart = new AppStart({ history: backboneHistoryMock, + notifier, router: routerMock, user: userMock, window: windowMock @@ -921,5 +922,3 @@ define(function (require, exports, module) { }); }); }); - - diff --git a/app/tests/spec/models/auth_brokers/fx-firstrun-v2.js b/app/tests/spec/models/auth_brokers/fx-firstrun-v2.js index da893a5d0b..ecf6f88f67 100644 --- a/app/tests/spec/models/auth_brokers/fx-firstrun-v2.js +++ b/app/tests/spec/models/auth_brokers/fx-firstrun-v2.js @@ -5,33 +5,73 @@ define(function (require, exports, module) { 'use strict'; - const chai = require('chai'); + const { assert } = require('chai'); const Constants = require('lib/constants'); const FxFirstrunV2AuthenticationBroker = require('models/auth_brokers/fx-firstrun-v2'); + const Notifier = require('lib/channels/notifier'); + const NullChannel = require('lib/channels/null'); + const sinon = require('sinon'); const WindowMock = require('../../../mocks/window'); - var assert = chai.assert; + describe('models/auth_brokers/fx-firstrun-v2', () => { + let broker; + let iframeChannel; + let notifier; + let windowMock; - describe('models/auth_brokers/fx-firstrun-v2', function () { - var broker; - var windowMock; - - before(function () { + beforeEach(function () { + notifier = new Notifier(); + iframeChannel = new NullChannel(); + sinon.spy(iframeChannel, 'send'); windowMock = new WindowMock(); broker = new FxFirstrunV2AuthenticationBroker({ + iframeChannel, + notifier, window: windowMock }); }); - it('has all sync content types', function () { + it('has all sync content types', () => { assert.equal(broker.defaultCapabilities.chooseWhatToSyncWebV1.engines, Constants.DEFAULT_DECLINED_ENGINES); }); - describe('capabilities', function () { + describe('capabilities', () => { it('has the `chooseWhatToSyncWebV1` capability by default', function () { assert.isTrue(broker.hasCapability('chooseWhatToSyncWebV1')); }); }); + + describe('notifications', () => { + function testNotificationCausesSend(notification, expectedSentMessage) { + assert.isFalse(iframeChannel.send.calledWith(expectedSentMessage)); + notifier.trigger(notification); + assert.isTrue(iframeChannel.send.calledWith(expectedSentMessage)); + } + + it('form.engaged sends a form_engaged message to the iframe parent', () => { + testNotificationCausesSend('form.engaged', 'form_engaged'); + }); + + it('form.disabled sends a form_disabled message to the iframe parent', () => { + testNotificationCausesSend('form.disabled', 'form_disabled'); + }); + + it('form.disabled sends a form_disabled message to the iframe parent', () => { + testNotificationCausesSend('form.enabled', 'form_enabled'); + }); + + it('show-view sends a `navigated` message to the iframe parent', () => { + assert.isFalse(iframeChannel.send.calledWith('navigated')); + notifier.trigger('show-view', null, { currentPage: 'signin' }); + assert.isTrue(iframeChannel.send.calledWith('navigated', { url: 'signin' })); + }); + + it('show-child-view sends a `navigated` message to the iframe parent', () => { + assert.isFalse(iframeChannel.send.calledWith('navigated')); + notifier.trigger('show-child-view', null, null, { currentPage: 'signup' }); + assert.isTrue(iframeChannel.send.calledWith('navigated', { url: 'signup' })); + }); + }); }); }); diff --git a/app/tests/spec/views/coppa/coppa-age-input.js b/app/tests/spec/views/coppa/coppa-age-input.js index 5739e38d25..9a7cb6b88e 100644 --- a/app/tests/spec/views/coppa/coppa-age-input.js +++ b/app/tests/spec/views/coppa/coppa-age-input.js @@ -6,26 +6,26 @@ define(function (require, exports, module) { 'use strict'; const $ = require('jquery'); + const { assert } = require('chai'); const AuthErrors = require('lib/auth-errors'); - const chai = require('chai'); const FormPrefill = require('models/form-prefill'); const Metrics = require('lib/metrics'); + const Notifier = require('lib/channels/notifier'); const sinon = require('sinon'); const TestHelpers = require('../../../lib/helpers'); const View = require('views/coppa/coppa-age-input'); const KeyCodes = require('lib/key-codes'); - var assert = chai.assert; - describe('views/coppa/coppa-age-input', function () { - var view; var formPrefill; var metrics; + var view; function createView() { view = new View({ - formPrefill: formPrefill, - metrics: metrics, + formPrefill, + metrics, + notifier: new Notifier(), viewName: 'signup' }); } diff --git a/app/tests/spec/views/form.js b/app/tests/spec/views/form.js index 50bd83af34..7d32469dec 100644 --- a/app/tests/spec/views/form.js +++ b/app/tests/spec/views/form.js @@ -6,20 +6,19 @@ define(function (require, exports, module) { 'use strict'; const $ = require('jquery'); + const { assert } = require('chai'); const AuthErrors = require('lib/auth-errors'); const Backbone = require('backbone'); - const chai = require('chai'); const Constants = require('lib/constants'); const FormView = require('views/form'); const HaltBehavior = require('views/behaviors/halt'); const Metrics = require('lib/metrics'); + const Notifier = require('lib/channels/notifier'); const p = require('lib/promise'); const sinon = require('sinon'); const Template = require('stache!templates/test_template'); const TestHelpers = require('../../lib/helpers'); - var assert = chai.assert; - var View = FormView.extend({ template: Template, @@ -48,9 +47,10 @@ define(function (require, exports, module) { }); describe('views/form', function () { - var metrics; - var model; - var view; + let metrics; + let model; + let notifier; + let view; function testErrorDisplayed(expectedMessage) { return view.validateAndSubmit() @@ -102,9 +102,13 @@ define(function (require, exports, module) { beforeEach(function () { metrics = new Metrics(); model = new Backbone.Model({}); + notifier = new Notifier(); + sinon.spy(notifier, 'trigger'); + view = new View({ - metrics: metrics, - model: model + metrics, + model, + notifier }); return view.render(); @@ -167,6 +171,18 @@ define(function (require, exports, module) { view.onFormChange(); assert.isFalse(view.enableSubmitIfValid.called); }); + + it('notifies of `form.engage` for the first change', () => { + sinon.stub(view, 'isHalted', () => false); + sinon.stub(view, 'isSubmitting', () => false); + + view.onFormChange(); + assert.isTrue(notifier.trigger.calledOnce); + assert.isTrue(notifier.trigger.calledWith('form.engaged')); + view.onFormChange(); + view.onFormChange(); + assert.isTrue(notifier.trigger.calledOnce); + }); }); describe('enableSubmitIfValid', function () { @@ -509,7 +525,9 @@ define(function (require, exports, module) { template: Template }); - view = new ShowValidationErrorTestView(); + view = new ShowValidationErrorTestView({ + notifier: new Notifier() + }); return view.render() .then(function () { @@ -688,7 +706,9 @@ define(function (require, exports, module) { template: Template }); - view = new IsValidTestView(); + view = new IsValidTestView({ + notifier: new Notifier() + }); return view.render() .then(function () { @@ -738,5 +758,35 @@ define(function (require, exports, module) { assert.isFalse(view.enableSubmitIfValid.called); }); }); + + describe('disableForm/enableForm', () => { + let isEnabled; + + beforeEach(() => { + isEnabled = true; + sinon.stub(view, 'isFormEnabled', () => { + const _isEnabled = isEnabled; + isEnabled = ! isEnabled; + return _isEnabled; + }); + }); + + it('disableForm disables the form, if enabled, notifies', () => { + view.disableForm(); + assert.equal(notifier.trigger.callCount, 1); + assert.isTrue(notifier.trigger.calledWith('form.disabled')); + view.disableForm(); + assert.equal(notifier.trigger.callCount, 1); + }); + + it('enableForm enables the form, if disabled, notifies', () => { + isEnabled = false; + view.enableForm(); + assert.equal(notifier.trigger.callCount, 1); + assert.isTrue(notifier.trigger.calledWith('form.enabled')); + view.enableForm(); + assert.equal(notifier.trigger.callCount, 1); + }); + }); }); }); diff --git a/app/tests/spec/views/mixins/floating-placeholder-mixin.js b/app/tests/spec/views/mixins/floating-placeholder-mixin.js index ef626e0b47..356e0d9ab3 100644 --- a/app/tests/spec/views/mixins/floating-placeholder-mixin.js +++ b/app/tests/spec/views/mixins/floating-placeholder-mixin.js @@ -6,16 +6,15 @@ define(function (require, exports, module) { 'use strict'; const $ = require('jquery'); - const chai = require('chai'); + const { assert } = require('chai'); const Cocktail = require('cocktail'); const FloatingPlaceholderMixin = require('views/mixins/floating-placeholder-mixin'); const FormView = require('views/form'); const KeyCodes = require('lib/key-codes'); + const Notifier = require('lib/channels/notifier'); const Template = require('stache!templates/test_template'); - var assert = chai.assert; - - var TestView = FormView.extend({ + const TestView = FormView.extend({ template: Template }); @@ -25,10 +24,12 @@ define(function (require, exports, module) { ); describe('views/mixins/floating-placeholder-mixin', function () { - var view; + let view; beforeEach(function () { - view = new TestView(); + view = new TestView({ + notifier: new Notifier() + }); return view.render(); }); diff --git a/app/tests/spec/views/mixins/password-prompt-mixin.js b/app/tests/spec/views/mixins/password-prompt-mixin.js index 927556c905..89ed7f69e9 100644 --- a/app/tests/spec/views/mixins/password-prompt-mixin.js +++ b/app/tests/spec/views/mixins/password-prompt-mixin.js @@ -8,6 +8,7 @@ define(function (require, exports, module) { const $ = require('jquery'); const { assert } = require('chai'); const Cocktail = require('cocktail'); + const Notifier = require('lib/channels/notifier'); const p = require('lib/promise'); const PasswordPromptMixin = require('views/mixins/password-prompt-mixin'); const PasswordStrengthMixin = require('views/mixins/password-strength-mixin'); @@ -22,6 +23,7 @@ define(function (require, exports, module) { }); const viewOpts = { + notifier: new Notifier(), translator: new Translator({forceEnglish: true}) }; @@ -66,7 +68,7 @@ define(function (require, exports, module) { describe('event triggers call the correct methods', function () { beforeEach(function () { - view = new TestView(); + view = new TestView(viewOpts); sinon.spy(view, 'showPasswordPrompt'); return view.render(); }); @@ -90,7 +92,7 @@ define(function (require, exports, module) { describe('checks password strength and displays tooltip if required', function () { beforeEach(function () { - view = new TestView(); + view = new TestView(viewOpts); return view.render(); }); diff --git a/app/tests/spec/views/mixins/settings-panel-mixin.js b/app/tests/spec/views/mixins/settings-panel-mixin.js index e7497baf4b..de4cb0f3d0 100644 --- a/app/tests/spec/views/mixins/settings-panel-mixin.js +++ b/app/tests/spec/views/mixins/settings-panel-mixin.js @@ -12,6 +12,7 @@ define(function (require, exports, module) { const FormView = require('views/form'); const KeyCodes = require('lib/key-codes'); const Metrics = require('lib/metrics'); + const Notifier = require('lib/channels/notifier'); const SettingsPanelMixin = require('views/mixins/settings-panel-mixin'); const sinon = require('sinon'); const TestTemplate = require('stache!templates/test_template'); @@ -37,6 +38,7 @@ define(function (require, exports, module) { view = new SettingsPanelView({ metrics: metrics, + notifier: new Notifier(), parentView: { displaySuccess: sinon.spy() } @@ -148,4 +150,3 @@ define(function (require, exports, module) { }); }); - diff --git a/app/tests/spec/views/report_sign_in.js b/app/tests/spec/views/report_sign_in.js index 6479151b43..321bd33e53 100644 --- a/app/tests/spec/views/report_sign_in.js +++ b/app/tests/spec/views/report_sign_in.js @@ -8,6 +8,7 @@ define(function (require, exports, module) { const { assert } = require('chai'); const AuthErrors = require('lib/auth-errors'); const { createRandomHexString } = require('../../lib/helpers'); + const Notifier = require('lib/channels/notifier'); const p = require('lib/promise'); const sinon = require('sinon'); const { BLOCKED_SIGNIN_SUPPORT_URL, UID_LENGTH, UNBLOCK_CODE_LENGTH } = require('lib/constants'); @@ -33,6 +34,7 @@ define(function (require, exports, module) { windowMock.location.search = `?uid=${uid}&unblockCode=${unblockCode}`; view = new View({ + notifier: new Notifier(), user, window: windowMock }); diff --git a/app/tests/spec/views/reset_password.js b/app/tests/spec/views/reset_password.js index 303ed96784..a8395e11fa 100644 --- a/app/tests/spec/views/reset_password.js +++ b/app/tests/spec/views/reset_password.js @@ -259,6 +259,7 @@ define(function (require, exports, module) { view = new View({ broker: broker, formPrefill: formPrefill, + notifier: new Notifier(), relier: relier }); @@ -294,6 +295,7 @@ define(function (require, exports, module) { broker: broker, formPrefill: formPrefill, model: model, + notifier: new Notifier(), relier: relier }); diff --git a/app/tests/spec/views/settings/change_password.js b/app/tests/spec/views/settings/change_password.js index 1179f71892..ba836f7e8d 100644 --- a/app/tests/spec/views/settings/change_password.js +++ b/app/tests/spec/views/settings/change_password.js @@ -11,6 +11,7 @@ define(function (require, exports, module) { const Broker = require('models/auth_brokers/base'); const chai = require('chai'); const Metrics = require('lib/metrics'); + const Notifier = require('lib/channels/notifier'); const p = require('lib/promise'); const Relier = require('models/reliers/relier'); const sinon = require('sinon'); @@ -47,6 +48,7 @@ define(function (require, exports, module) { broker: broker, metrics: metrics, model: model, + notifier: new Notifier(), relier: relier, user: user }); diff --git a/app/tests/spec/views/settings/communication_preferences.js b/app/tests/spec/views/settings/communication_preferences.js index d073d948f6..f15fa58b75 100644 --- a/app/tests/spec/views/settings/communication_preferences.js +++ b/app/tests/spec/views/settings/communication_preferences.js @@ -12,6 +12,7 @@ define(function (require, exports, module) { const MarketingEmailErrors = require('lib/marketing-email-errors'); const MarketingEmailPrefs = require('models/marketing-email-prefs'); const Metrics = require('lib/metrics'); + const Notifier = require('lib/channels/notifier'); const p = require('lib/promise'); const Relier = require('models/reliers/relier'); const sinon = require('sinon'); @@ -73,6 +74,7 @@ define(function (require, exports, module) { view = new View({ metrics: metrics, + notifier: new Notifier(), relier: relier, translator: translator, user: user @@ -228,4 +230,3 @@ define(function (require, exports, module) { }); }); }); - diff --git a/app/tests/spec/views/settings/gravatar_permissions.js b/app/tests/spec/views/settings/gravatar_permissions.js index 01a2f7123d..a7f80075c7 100644 --- a/app/tests/spec/views/settings/gravatar_permissions.js +++ b/app/tests/spec/views/settings/gravatar_permissions.js @@ -9,6 +9,7 @@ define(function (require, exports, module) { const chai = require('chai'); const Metrics = require('lib/metrics'); const p = require('lib/promise'); + const Notifier = require('lib/channels/notifier'); const Relier = require('models/reliers/relier'); const sinon = require('sinon'); const TestHelpers = require('../../../lib/helpers'); @@ -55,6 +56,7 @@ define(function (require, exports, module) { function initView () { view = new View({ metrics: metrics, + notifier: new Notifier(), relier: relier, user: user }); diff --git a/app/tests/spec/views/sign_up.js b/app/tests/spec/views/sign_up.js index 98da247169..a824b8080c 100644 --- a/app/tests/spec/views/sign_up.js +++ b/app/tests/spec/views/sign_up.js @@ -76,6 +76,13 @@ define(function (require, exports, module) { view = new View(viewOpts); } + function testExpectTriggered(callIndex, expectedMessage) { + assert.equal(notifier.trigger.thisValues[callIndex], notifier); + var args = notifier.trigger.args[callIndex]; + assert.lengthOf(args, 3); + assert.equal(args[0], expectedMessage); + } + beforeEach(function () { document.cookie = 'tooyoung=1; expires=Thu, 01-Jan-1970 00:00:01 GMT'; @@ -655,12 +662,10 @@ define(function (require, exports, module) { }); it('calls notifier.trigger correctly', function () { - assert.equal(notifier.trigger.callCount, 1); + assert.equal(notifier.trigger.callCount, 2); - assert.equal(notifier.trigger.thisValues[0], notifier); - var args = notifier.trigger.args[0]; - assert.lengthOf(args, 3); - assert.equal(args[0], 'signup.submit'); + testExpectTriggered(0, 'form.enabled'); + testExpectTriggered(1, 'signup.submit'); }); it('does not display any errors', function () { @@ -700,12 +705,10 @@ define(function (require, exports, module) { }); it('calls notifier.trigger correctly', function () { - assert.equal(notifier.trigger.callCount, 1); + assert.equal(notifier.trigger.callCount, 2); - assert.equal(notifier.trigger.thisValues[0], notifier); - var args = notifier.trigger.args[0]; - assert.lengthOf(args, 3); - assert.equal(args[0], 'signup.submit'); + testExpectTriggered(0, 'form.enabled'); + testExpectTriggered(1, 'signup.submit'); }); it('fails with the correct error', function () { @@ -735,22 +738,12 @@ define(function (require, exports, module) { }); it('calls notifier.trigger correctly', function () { - assert.equal(notifier.trigger.callCount, 3); - - assert.equal(notifier.trigger.thisValues[0], notifier); - var args = notifier.trigger.args[0]; - assert.lengthOf(args, 3); - assert.equal(args[0], 'signup.submit'); - - assert.equal(notifier.trigger.thisValues[1], notifier); - args = notifier.trigger.args[1]; - assert.lengthOf(args, 3); - assert.equal(args[0], 'signup.tooyoung'); - - assert.equal(notifier.trigger.thisValues[1], notifier); - args = notifier.trigger.args[2]; - assert.lengthOf(args, 3); - assert.equal(args[0], 'navigate'); + assert.equal(notifier.trigger.callCount, 4); + + testExpectTriggered(0, 'form.enabled'); + testExpectTriggered(1, 'signup.submit'); + testExpectTriggered(2, 'signup.tooyoung'); + testExpectTriggered(3, 'navigate'); }); it('calls view.navigate correctly', function () { @@ -813,12 +806,10 @@ define(function (require, exports, module) { }); it('calls notifier.trigger correctly', function () { - assert.equal(notifier.trigger.callCount, 1); + assert.equal(notifier.trigger.callCount, 2); - assert.equal(notifier.trigger.thisValues[0], notifier); - var args = notifier.trigger.args[0]; - assert.lengthOf(args, 3); - assert.equal(args[0], 'signup.submit'); + testExpectTriggered(0, 'form.enabled'); + testExpectTriggered(1, 'signup.submit'); }); it('calls view.unsafeDisplayError correctly', function () { @@ -922,12 +913,10 @@ define(function (require, exports, module) { }); it('calls notifier.trigger correctly', function () { - assert.equal(notifier.trigger.callCount, 1); + assert.equal(notifier.trigger.callCount, 2); - assert.equal(notifier.trigger.thisValues[0], notifier); - var args = notifier.trigger.args[0]; - assert.lengthOf(args, 3); - assert.equal(args[0], 'signup.submit'); + testExpectTriggered(0, 'form.enabled'); + testExpectTriggered(1, 'signup.submit'); }); it('fails correctly', function () { diff --git a/docs/relier-communication-protocols/firstrun.md b/docs/relier-communication-protocols/firstrun.md index 1381cadbeb..afe0acccf7 100644 --- a/docs/relier-communication-protocols/firstrun.md +++ b/docs/relier-communication-protocols/firstrun.md @@ -1,10 +1,22 @@ # FIRSTRUN channel protocol -## v1 & v2 - -### Commands A command is a postMessage sent from FxA to the parent IFRAME. +## Message format +Since all messages are sent over postMessage, commands and data must be represented by strings. A JSON.stringify serialized object containing a command and data object is used. + +``` +{ + command: , + data: +} +``` + +* `command` {String} - The command. +* `data` {Object} - Data object. Can be empty, i.e., `{}`. + +## v1 +### Commands #### loaded Sent on startup. Sent after the first screen is visible. No data is sent. @@ -12,6 +24,10 @@ Sent on startup. Sent after the first screen is visible. No data is sent. Sent when a user successfully signs in, including after a user verifies their email after an account unlock. +#### resize +Sent when the content height changes. The `data` field will be an object that contains `height`. +* `height` {Number} - the content height + #### signup_must_verify The user has successfully completed the signup form and must verify their email address. @@ -22,24 +38,45 @@ The `data` field contains: #### verification_complete The user has successfully verified their email address. +## v2 +### Commands +#### form_disabled +The form on the current page has been disabled. + +#### form_enabled +The form on the current page has been enabled. + +#### form_engaged +The form on the current page has been engaged with, i.e., modified. + +#### loaded +Sent on startup. Sent after the first screen is visible. No data is sent. + +#### login +Sent when a user successfully signs in, including after a +user verifies their email after an account unlock. + +### navigated +The user has navigated to a new page within the app. +`data` will include a `url` field. +* `url` {String} - the updated url pathname + #### resize Sent when the content height changes. The `data` field will be an object that contains `height`. * `height` {Number} - the content height -### Message format -Since all messages are sent over postMessage, commands and data must be represented by strings. A JSON.stringify serialized object containing a command and data object is used. +#### signup_must_verify +The user has successfully completed the signup form and must verify +their email address. -``` -{ - command: , - data: -} -``` +The `data` field contains: +* `emailOptIn` {Boolean} - whether the user has opted in to receiving marketing email -* `command` {String} - The command. -* `data` {Object} - Data object. Can be empty, i.e., `{}`. +#### verification_complete +The user has successfully verified their email address. -### Command order + +## Command order 1. loaded 1. login @@ -50,4 +87,3 @@ OR 1. verification_complete (if firstrun page is still open) The same format is used for both incoming messages and responses. - diff --git a/tests/server/routes.js b/tests/server/routes.js index dd3939d67b..497c824388 100644 --- a/tests/server/routes.js +++ b/tests/server/routes.js @@ -282,6 +282,11 @@ define([ var promise = makeRequest(url, requestOptions) .then(function (res) { + if (/support.mozilla.org/.test(url)) { + // Do not check support.mozilla.org URLs. Issue #4712 + // In February 2017 SUMO links started returning 404s to non-browser redirect requests + return; + } assert.equal(res.statusCode, 200); var headers = res.headers;