diff --git a/packages/fxa-content-server/app/scripts/lib/app-start.js b/packages/fxa-content-server/app/scripts/lib/app-start.js
index c55cf308972..966965fb553 100644
--- a/packages/fxa-content-server/app/scripts/lib/app-start.js
+++ b/packages/fxa-content-server/app/scripts/lib/app-start.js
@@ -44,7 +44,7 @@ import Session from './session';
import Storage from './storage';
import StorageMetrics from './storage-metrics';
import SupplicantRelier from '../models/reliers/pairing/supplicant';
-import SyncRelier from '../models/reliers/sync';
+import BrowserRelier from '../models/reliers/browser';
import Translator from './translator';
import UniqueUserId from '../models/unique-user-id';
import Url from './url';
@@ -269,8 +269,12 @@ Start.prototype = {
window: this._window,
}
);
- } else if (this._isServiceSync()) {
- relier = new SyncRelier(
+ } else if (
+ this._isServiceSync() ||
+ // context v3 is able to sign in to Firefox without enabling Sync
+ this._searchParam('context') === Constants.FX_DESKTOP_V3_CONTEXT
+ ) {
+ relier = new BrowserRelier(
{ context },
{
isVerification: this._isVerification(),
diff --git a/packages/fxa-content-server/app/scripts/lib/router.js b/packages/fxa-content-server/app/scripts/lib/router.js
index 502bfba56c4..7dac6dbdf45 100644
--- a/packages/fxa-content-server/app/scripts/lib/router.js
+++ b/packages/fxa-content-server/app/scripts/lib/router.js
@@ -58,6 +58,7 @@ import SubscriptionsProductRedirectView from '../views/subscriptions_product_red
import SubscriptionsManagementRedirectView from '../views/subscriptions_management_redirect';
import TwoStepAuthenticationView from '../views/settings/two_step_authentication';
import VerificationReasons from './verification-reasons';
+import WouldYouLikeToSync from '../views/would_you_like_to_sync';
import WhyConnectAnotherDeviceView from '../views/why_connect_another_device';
function getView(ViewOrPath) {
@@ -278,6 +279,7 @@ const Router = Backbone.Router.extend({
'verify_secondary_email(/)': createViewHandler(CompleteSignUpView, {
type: VerificationReasons.SECONDARY_EMAIL_VERIFIED,
}),
+ 'would_you_like_to_sync(/)': createViewHandler(WouldYouLikeToSync),
},
initialize(options = {}) {
diff --git a/packages/fxa-content-server/app/scripts/models/auth_brokers/fx-sync-channel.js b/packages/fxa-content-server/app/scripts/models/auth_brokers/fx-sync-channel.js
index 710f41b05aa..8e34706e229 100644
--- a/packages/fxa-content-server/app/scripts/models/auth_brokers/fx-sync-channel.js
+++ b/packages/fxa-content-server/app/scripts/models/auth_brokers/fx-sync-channel.js
@@ -23,6 +23,7 @@ const ALLOWED_LOGIN_FIELDS = [
'keyFetchToken',
'offeredSyncEngines',
'sessionToken',
+ 'services',
'uid',
'unwrapBKey',
'verified',
@@ -273,11 +274,39 @@ const FxSyncChannelAuthenticationBroker = FxSyncAuthenticationBroker.extend(
* @private
*/
_getLoginData(account) {
- const loginData = account.pick(ALLOWED_LOGIN_FIELDS);
+ let loginData = account.pick(ALLOWED_LOGIN_FIELDS) || {};
+ const isMultiService = this.relier && this.relier.get('multiService');
+ if (isMultiService) {
+ loginData = this._formatForMultiServiceBrowser(loginData);
+ }
+
loginData.verified = !!loginData.verified;
loginData.verifiedCanLinkAccount = !!this._verifiedCanLinkEmail;
return _.omit(loginData, _.isUndefined);
},
+
+ /**
+ * if browser is multi service capable then we should send 'sync' properties
+ * in a different format
+ * @param loginData
+ * @private
+ */
+ _formatForMultiServiceBrowser(loginData) {
+ loginData.services = {};
+ if (!this.relier.get('doNotSync') && loginData.offeredSyncEngines) {
+ // make sure to NOT send an empty 'sync' object,
+ // this can happen in the `force_auth` flow.
+ loginData.services.sync = {
+ offeredEngines: loginData.offeredSyncEngines,
+ declinedEngines: loginData.declinedSyncEngines,
+ };
+ }
+ // these should not be sent to a multi-service capable browser
+ delete loginData.offeredSyncEngines;
+ delete loginData.declinedSyncEngines;
+
+ return loginData;
+ },
},
{
REQUIRED_LOGIN_FIELDS,
diff --git a/packages/fxa-content-server/app/scripts/models/auth_brokers/fx-sync.js b/packages/fxa-content-server/app/scripts/models/auth_brokers/fx-sync.js
index e9882ca06d3..87b3eb84178 100644
--- a/packages/fxa-content-server/app/scripts/models/auth_brokers/fx-sync.js
+++ b/packages/fxa-content-server/app/scripts/models/auth_brokers/fx-sync.js
@@ -52,6 +52,19 @@ export default BaseAuthenticationBroker.extend({
*/
onFxaStatus(response = {}) {
const syncEngines = this.get('chooseWhatToSyncWebV1Engines');
+ const multiService =
+ response.capabilities && response.capabilities.multiService;
+ this.relier.set('multiService', multiService);
+ if (multiService) {
+ // we get the OAuth client id for the browser
+ // in order to replicate the uses of the 'service' param on the backend.
+ // See: https://github.com/mozilla/fxa/issues/2396#issuecomment-530662772
+ if (!this.relier.has('service')) {
+ // the service in the query parameter currently overrides the status message
+ // this is due to backwards compatibility
+ this.relier.set('service', response.clientId);
+ }
+ }
const additionalEngineIds =
response.capabilities && response.capabilities.engines;
if (syncEngines && additionalEngineIds) {
diff --git a/packages/fxa-content-server/app/scripts/models/reliers/base.js b/packages/fxa-content-server/app/scripts/models/reliers/base.js
index a6b02aff4dd..da9d8625186 100644
--- a/packages/fxa-content-server/app/scripts/models/reliers/base.js
+++ b/packages/fxa-content-server/app/scripts/models/reliers/base.js
@@ -12,6 +12,7 @@ import Backbone from 'backbone';
var Relier = Backbone.Model.extend({
defaults: {
context: null,
+ name: 'base',
},
fetch() {
@@ -36,6 +37,15 @@ var Relier = Backbone.Model.extend({
return false;
},
+ /**
+ * Check if the relier should offer Sync
+ *
+ * @returns {Boolean}
+ */
+ shouldOfferToSync() {
+ return false;
+ },
+
/**
* Check if the relier wants access to the account encryption keys.
*
diff --git a/packages/fxa-content-server/app/scripts/models/reliers/sync.js b/packages/fxa-content-server/app/scripts/models/reliers/browser.js
similarity index 85%
rename from packages/fxa-content-server/app/scripts/models/reliers/sync.js
rename to packages/fxa-content-server/app/scripts/models/reliers/browser.js
index 0d8cf16f6a8..fc739931319 100644
--- a/packages/fxa-content-server/app/scripts/models/reliers/sync.js
+++ b/packages/fxa-content-server/app/scripts/models/reliers/browser.js
@@ -34,6 +34,9 @@ const QUERY_PARAMETER_SCHEMA = {
export default Relier.extend({
defaults: _.extend({}, Relier.prototype.defaults, {
action: undefined,
+ name: 'browser',
+ doNotSync: false,
+ multiService: false,
signinCode: undefined,
tokenCode: false,
}),
@@ -50,6 +53,9 @@ export default Relier.extend({
if (this.get('service')) {
this.set('serviceName', t('Firefox Sync'));
+ } else {
+ // if no service provided, then we are just signing into the browser
+ this.set('serviceName', t('Firefox'));
}
});
},
@@ -58,6 +64,10 @@ export default Relier.extend({
return true;
},
+ shouldOfferToSync(viewName) {
+ return this.get('service') !== 'sync' && viewName !== 'force-auth';
+ },
+
/**
* Desktop clients will always want keys so they can sync.
*
diff --git a/packages/fxa-content-server/app/scripts/models/reliers/oauth.js b/packages/fxa-content-server/app/scripts/models/reliers/oauth.js
index 53698dc19af..61ccc7e3131 100644
--- a/packages/fxa-content-server/app/scripts/models/reliers/oauth.js
+++ b/packages/fxa-content-server/app/scripts/models/reliers/oauth.js
@@ -85,6 +85,7 @@ var OAuthRelier = Relier.extend({
acrValues: null,
clientId: null,
context: Constants.OAUTH_CONTEXT,
+ name: 'oauth',
keysJwk: null,
// permissions are individual scopes
permissions: null,
diff --git a/packages/fxa-content-server/app/scripts/models/reliers/pairing/authority.js b/packages/fxa-content-server/app/scripts/models/reliers/pairing/authority.js
index 4699f719b87..fd3e8d8fa33 100644
--- a/packages/fxa-content-server/app/scripts/models/reliers/pairing/authority.js
+++ b/packages/fxa-content-server/app/scripts/models/reliers/pairing/authority.js
@@ -17,6 +17,8 @@ const AUTHORITY_QUERY_PARAM_SCHEMA = {
/*eslint-enable camelcase, sorting/sort-object-props*/
export default class AuthorityRelier extends OAuthRelier {
+ name = 'pairing-authority';
+
fetch() {
return Promise.resolve().then(() => {
this.importSearchParamsUsingSchema(
diff --git a/packages/fxa-content-server/app/scripts/models/reliers/pairing/supplicant.js b/packages/fxa-content-server/app/scripts/models/reliers/pairing/supplicant.js
index be4f2eeedc8..337cc831e9d 100644
--- a/packages/fxa-content-server/app/scripts/models/reliers/pairing/supplicant.js
+++ b/packages/fxa-content-server/app/scripts/models/reliers/pairing/supplicant.js
@@ -44,6 +44,8 @@ const SUPPLICANT_HASH_PARAMETER_SCHEMA = {
/*eslint-enable camelcase, sorting/sort-object-props*/
export default class SupplicantRelier extends OAuthRelier {
+ name = 'pairing-supplicant';
+
fetch() {
return Promise.resolve().then(() => {
this.importHashParamsUsingSchema(
diff --git a/packages/fxa-content-server/app/scripts/templates/choose_what_to_sync.mustache b/packages/fxa-content-server/app/scripts/templates/choose_what_to_sync.mustache
index 19b06d231cb..5963b9ebe27 100644
--- a/packages/fxa-content-server/app/scripts/templates/choose_what_to_sync.mustache
+++ b/packages/fxa-content-server/app/scripts/templates/choose_what_to_sync.mustache
@@ -1,5 +1,4 @@
-
{{#t}}%(email)s registered{{/t}}
@@ -17,9 +16,14 @@
{{/engines}}
-
-
+
+
+
+ {{#showDoNotSyncButton}}
+
+ {{/showDoNotSyncButton}}
diff --git a/packages/fxa-content-server/app/scripts/templates/would-you-like-to-sync.mustache b/packages/fxa-content-server/app/scripts/templates/would-you-like-to-sync.mustache
new file mode 100644
index 00000000000..2a6bdde9182
--- /dev/null
+++ b/packages/fxa-content-server/app/scripts/templates/would-you-like-to-sync.mustache
@@ -0,0 +1,20 @@
+
diff --git a/packages/fxa-content-server/app/scripts/views/choose_what_to_sync.js b/packages/fxa-content-server/app/scripts/views/choose_what_to_sync.js
index 456dcd448ec..c92169c7205 100644
--- a/packages/fxa-content-server/app/scripts/views/choose_what_to_sync.js
+++ b/packages/fxa-content-server/app/scripts/views/choose_what_to_sync.js
@@ -6,6 +6,7 @@ import _ from 'underscore';
import $ from 'jquery';
import BackMixin from './mixins/back-mixin';
import Cocktail from 'cocktail';
+import DoNotSyncMixin from './mixins/do-not-sync-mixin';
import FlowEventsMixin from './mixins/flow-events-mixin';
import FormView from './form';
import SessionVerificationPollMixin from './mixins/session-verification-poll-mixin';
@@ -19,7 +20,7 @@ const View = FormView.extend(
template: Template,
className: 'choose-what-to-sync',
- initialize(options = {}) {
+ initialize() {
// Account data is passed in from sign up flow.
this._account = this.user.initAccount(this.model.get('account'));
@@ -27,6 +28,13 @@ const View = FormView.extend(
// a continuation function is passed in that should be called
// when submit has completed.
this.onSubmitComplete = this.model.get('onSubmitComplete');
+
+ // we only want to show the do not sync button for multi-service browser,
+ // that are not logging into sync and that came from a sign up flow.
+ this.allowToDisableSync = this.model.get('allowToDisableSync');
+ // in some cases where the user has a choice to decline Sync
+ // we don't want to poll for verification
+ this.pollVerification = this.model.get('pollVerification');
},
getAccount() {
@@ -59,9 +67,13 @@ const View = FormView.extend(
// See #5554
.then(() => this.broker.persistVerificationData(account))
.then(() => {
- this.waitForSessionVerification(account, () =>
- this.validateAndSubmit()
- );
+ if (this.pollVerification) {
+ // we should only wait for session verification here
+ // if the user doesn't have an option to disable sync
+ this.waitForSessionVerification(account, () =>
+ this.validateAndSubmit()
+ );
+ }
})
);
},
@@ -72,12 +84,13 @@ const View = FormView.extend(
},
setInitialContext(context) {
- var account = this.getAccount();
+ const account = this.getAccount();
const engines = this._getOfferedEngines();
context.set({
email: account.get('email'),
engines,
+ showDoNotSyncButton: this.allowToDisableSync,
});
},
@@ -166,6 +179,12 @@ const View = FormView.extend(
}
);
-Cocktail.mixin(View, BackMixin, FlowEventsMixin, SessionVerificationPollMixin);
+Cocktail.mixin(
+ View,
+ BackMixin,
+ FlowEventsMixin,
+ SessionVerificationPollMixin,
+ DoNotSyncMixin
+);
export default View;
diff --git a/packages/fxa-content-server/app/scripts/views/mixins/do-not-sync-mixin.js b/packages/fxa-content-server/app/scripts/views/mixins/do-not-sync-mixin.js
new file mode 100644
index 00000000000..591ccabd11a
--- /dev/null
+++ b/packages/fxa-content-server/app/scripts/views/mixins/do-not-sync-mixin.js
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * View mixin to handle the Do Not Sync button in views.
+ *
+ * @mixin DoNotSync mixin
+ */
+import SigninMixin from './signin-mixin';
+
+export default {
+ dependsOn: [SigninMixin],
+
+ events: {
+ 'click #do-not-sync-device': 'doNotSync',
+ },
+
+ doNotSync() {
+ this.relier.set('doNotSync', true);
+ return Promise.resolve().then(() => {
+ const account = this.getAccount();
+ return this.onSubmitComplete(account);
+ });
+ },
+};
diff --git a/packages/fxa-content-server/app/scripts/views/mixins/signin-mixin.js b/packages/fxa-content-server/app/scripts/views/mixins/signin-mixin.js
index 13045846f40..68435255412 100644
--- a/packages/fxa-content-server/app/scripts/views/mixins/signin-mixin.js
+++ b/packages/fxa-content-server/app/scripts/views/mixins/signin-mixin.js
@@ -104,6 +104,18 @@ export default {
});
}
+ if (this.relier.shouldOfferToSync(this.viewName)) {
+ // flows that are a part of the 'browser' relier which
+ // do not pass a service get asked of they want to Sync
+
+ // force_auth attempts do not a choice for Sync
+ return this.navigate('would_you_like_to_sync', {
+ account: account,
+ // propagate the onSubmitComplete to choose_what_to_sync screen if needed
+ onSubmitComplete: this.onSignInSuccess.bind(this),
+ });
+ }
+
if (typeof options.onSuccess === 'function') {
options.onSuccess();
}
diff --git a/packages/fxa-content-server/app/scripts/views/mixins/signup-mixin.js b/packages/fxa-content-server/app/scripts/views/mixins/signup-mixin.js
index 79fcdf3787d..815d9f73bb6 100644
--- a/packages/fxa-content-server/app/scripts/views/mixins/signup-mixin.js
+++ b/packages/fxa-content-server/app/scripts/views/mixins/signup-mixin.js
@@ -60,6 +60,8 @@ export default {
} else if (this.broker.get('chooseWhatToSyncWebV1Engines')) {
return this.navigate('choose_what_to_sync', {
account: account,
+ allowToDisableSync: this.relier.get('service') !== 'sync',
+ pollVerification: this.relier.get('service') === 'sync',
// choose_what_to_sync screen will call onSubmitComplete
// with an updated account
onSubmitComplete: onSubmitComplete,
diff --git a/packages/fxa-content-server/app/scripts/views/mixins/sync-suggestion-mixin.js b/packages/fxa-content-server/app/scripts/views/mixins/sync-suggestion-mixin.js
index a55580815cd..df318363eef 100644
--- a/packages/fxa-content-server/app/scripts/views/mixins/sync-suggestion-mixin.js
+++ b/packages/fxa-content-server/app/scripts/views/mixins/sync-suggestion-mixin.js
@@ -74,7 +74,9 @@ export default function(config) {
* @returns {Boolean}
*/
isSyncSuggestionEnabled() {
- return !this.relier.get('service');
+ return (
+ !this.relier.get('service') && this.relier.get('context') === 'web'
+ );
},
onSuggestSyncDismiss() {
diff --git a/packages/fxa-content-server/app/scripts/views/would_you_like_to_sync.js b/packages/fxa-content-server/app/scripts/views/would_you_like_to_sync.js
new file mode 100644
index 00000000000..ca729700f73
--- /dev/null
+++ b/packages/fxa-content-server/app/scripts/views/would_you_like_to_sync.js
@@ -0,0 +1,62 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import BackMixin from './mixins/back-mixin';
+import Cocktail from 'cocktail';
+import DoNotSyncMixin from './mixins/do-not-sync-mixin';
+import FlowEventsMixin from './mixins/flow-events-mixin';
+import FormView from './form';
+import Template from 'templates/would-you-like-to-sync.mustache';
+
+const SCREEN_CLASS = 'screen-would-you-like-to-sync';
+
+const View = FormView.extend(
+ {
+ template: Template,
+ className: 'would-you-like-to-sync',
+
+ initialize(options = {}) {
+ // Account data is passed in from sign up flow.
+ this._account = this.user.initAccount(this.model.get('account'));
+
+ // to keep the view from knowing too much about the state machine,
+ // a continuation function is passed in that should be called
+ // when submit has completed.
+ this.onSubmitComplete = this.model.get('onSubmitComplete');
+ },
+
+ getAccount() {
+ return this._account;
+ },
+
+ beforeRender() {
+ // user cannot proceed if they have not initiated a sign up/in.
+ if (!this.getAccount().get('sessionToken')) {
+ this.navigate('signup');
+ }
+ },
+
+ submit() {
+ return Promise.resolve().then(() => {
+ const account = this.getAccount();
+
+ // we replace the current view to avoid various problems with the back button
+ return this.replaceCurrentPage('choose_what_to_sync', {
+ account: account,
+ // choose_what_to_sync screen will call onSubmitComplete
+ // with an updated account
+ onSubmitComplete: this.onSubmitComplete,
+ allowToDisableSync: false,
+ });
+ });
+ },
+ },
+ {
+ SCREEN_CLASS,
+ }
+);
+
+Cocktail.mixin(View, BackMixin, FlowEventsMixin, DoNotSyncMixin);
+
+export default View;
diff --git a/packages/fxa-content-server/app/tests/spec/lib/app-start.js b/packages/fxa-content-server/app/tests/spec/lib/app-start.js
index 5265b403aa5..bdb35c2c918 100644
--- a/packages/fxa-content-server/app/tests/spec/lib/app-start.js
+++ b/packages/fxa-content-server/app/tests/spec/lib/app-start.js
@@ -22,7 +22,7 @@ import SameBrowserVerificationModel from 'models/verification/same-browser';
import sinon from 'sinon';
import Storage from 'lib/storage';
import StorageMetrics from 'lib/storage-metrics';
-import SyncRelier from 'models/reliers/sync';
+import BrowserRelier from 'models/reliers/browser';
import Url from 'lib/url';
import User from 'models/user';
import WindowMock from '../../mocks/window';
@@ -400,11 +400,11 @@ describe('lib/app-start', () => {
});
});
- it('creates an SyncRelier if Sync', () => {
+ it('creates an BrowserRelier if Sync', () => {
sinon.stub(appStart, '_isServiceSync').callsFake(() => true);
appStart.initializeRelier();
- assert.instanceOf(appStart._relier, SyncRelier);
+ assert.instanceOf(appStart._relier, BrowserRelier);
});
it('creates an OAuthRelier if in the OAuth flow, even if service=sync is specified', () => {
diff --git a/packages/fxa-content-server/app/tests/spec/models/auth_brokers/fx-sync-channel.js b/packages/fxa-content-server/app/tests/spec/models/auth_brokers/fx-sync-channel.js
index f4537e26c24..f11b6139f8c 100644
--- a/packages/fxa-content-server/app/tests/spec/models/auth_brokers/fx-sync-channel.js
+++ b/packages/fxa-content-server/app/tests/spec/models/auth_brokers/fx-sync-channel.js
@@ -9,11 +9,13 @@ import FxSyncChannelAuthenticationBroker from 'models/auth_brokers/fx-sync-chann
import NullChannel from 'lib/channels/null';
import sinon from 'sinon';
import User from 'models/user';
+import Relier from 'models/reliers/relier';
import WindowMock from '../../../mocks/window';
describe('models/auth_brokers/fx-sync-channel', () => {
let account;
let broker;
+ let relier;
let channelMock;
let user;
let windowMock;
@@ -35,6 +37,7 @@ describe('models/auth_brokers/fx-sync-channel', () => {
VERIFIED: 'verified',
},
window: windowMock,
+ relier,
},
options
)
@@ -47,6 +50,7 @@ describe('models/auth_brokers/fx-sync-channel', () => {
channelMock.send = sinon.spy(() => {
return Promise.resolve();
});
+ relier = new Relier();
user = new User();
account = user.initAccount({
@@ -440,4 +444,59 @@ describe('models/auth_brokers/fx-sync-channel', () => {
assert.equal(broker.getCommand('LOGIN'), 'login');
});
});
+
+ describe('_getLoginData', () => {
+ it('formats the login data', () => {
+ const loginData = broker._getLoginData(account);
+ assert.deepEqual(loginData, {
+ declinedSyncEngines: ['addons'],
+ email: 'testuser@testuser.com',
+ keyFetchToken: 'key-fetch-token',
+ offeredSyncEngines: ['tabs', 'addons', 'creditcards', 'addresses'],
+ sessionToken: 'session-token',
+ uid: 'uid',
+ unwrapBKey: 'unwrap-b-key',
+ verified: false,
+ verifiedCanLinkAccount: false,
+ });
+ });
+
+ it('formats the login data for multi-service', () => {
+ broker.relier.set('multiService', true);
+ const loginData = broker._getLoginData(account);
+
+ assert.deepEqual(loginData, {
+ email: 'testuser@testuser.com',
+ keyFetchToken: 'key-fetch-token',
+ sessionToken: 'session-token',
+ uid: 'uid',
+ unwrapBKey: 'unwrap-b-key',
+ verified: false,
+ services: {
+ sync: {
+ offeredEngines: ['tabs', 'addons', 'creditcards', 'addresses'],
+ declinedEngines: ['addons'],
+ },
+ },
+ verifiedCanLinkAccount: false,
+ });
+ });
+
+ it('formats the login data for multi-service that did not opt-in to sync', () => {
+ broker.relier.set('multiService', true);
+ broker.relier.set('doNotSync', true);
+ const loginData = broker._getLoginData(account);
+
+ assert.deepEqual(loginData, {
+ email: 'testuser@testuser.com',
+ keyFetchToken: 'key-fetch-token',
+ sessionToken: 'session-token',
+ uid: 'uid',
+ unwrapBKey: 'unwrap-b-key',
+ verified: false,
+ services: {},
+ verifiedCanLinkAccount: false,
+ });
+ });
+ });
});
diff --git a/packages/fxa-content-server/app/tests/spec/models/reliers/sync.js b/packages/fxa-content-server/app/tests/spec/models/reliers/browser.js
similarity index 93%
rename from packages/fxa-content-server/app/tests/spec/models/reliers/sync.js
rename to packages/fxa-content-server/app/tests/spec/models/reliers/browser.js
index 78f2ee97c78..379b7ac129b 100644
--- a/packages/fxa-content-server/app/tests/spec/models/reliers/sync.js
+++ b/packages/fxa-content-server/app/tests/spec/models/reliers/browser.js
@@ -4,7 +4,7 @@
import { assert } from 'chai';
import AuthErrors from 'lib/auth-errors';
-import Relier from 'models/reliers/sync';
+import Relier from 'models/reliers/browser';
import TestHelpers from '../../../lib/helpers';
import Translator from 'lib/translator';
import WindowMock from '../../../mocks/window';
@@ -14,7 +14,7 @@ const CONTEXT = 'fx_desktop_v3';
const COUNTRY = 'RO';
const SYNC_SERVICE = 'sync';
-describe('models/reliers/sync', () => {
+describe('models/reliers/browser', () => {
let err;
let relier;
let translator;
@@ -41,6 +41,10 @@ describe('models/reliers/sync', () => {
);
});
+ it('has a proper relier name', () => {
+ assert.equal(relier.get('name'), 'browser');
+ });
+
describe('fetch', () => {
it('populates model from the search parameters', () => {
windowMock.location.search = TestHelpers.toSearchString({
@@ -269,6 +273,16 @@ describe('models/reliers/sync', () => {
assert.equal(relier.get('serviceName'), 'Firefox Sync');
});
});
+
+ it('translates no `service` to generic Firefox `serviceName`', () => {
+ windowMock.location.search = TestHelpers.toSearchString({
+ context: CONTEXT,
+ });
+
+ return relier.fetch().then(() => {
+ assert.equal(relier.get('serviceName'), 'Firefox');
+ });
+ });
});
describe('isSync', () => {
diff --git a/packages/fxa-content-server/app/tests/spec/views/choose_what_to_sync.js b/packages/fxa-content-server/app/tests/spec/views/choose_what_to_sync.js
index b86994fb4f9..92b620f4246 100644
--- a/packages/fxa-content-server/app/tests/spec/views/choose_what_to_sync.js
+++ b/packages/fxa-content-server/app/tests/spec/views/choose_what_to_sync.js
@@ -7,9 +7,9 @@ import Account from 'models/account';
import { assert } from 'chai';
import Backbone from 'backbone';
import Broker from 'models/auth_brokers/base';
-import { CHOOSE_WHAT_TO_SYNC } from '../../../../tests/functional/lib/selectors';
import Metrics from 'lib/metrics';
import Notifier from 'lib/channels/notifier';
+import Relier from 'models/reliers/base';
import sinon from 'sinon';
import SentryMetrics from 'lib/sentry';
import SessionVerificationPoll from 'models/polls/session-verification';
@@ -19,8 +19,6 @@ import User from 'models/user';
import View from 'views/choose_what_to_sync';
import WindowMock from '../../mocks/window';
-const Selectors = CHOOSE_WHAT_TO_SYNC;
-
describe('views/choose_what_to_sync', () => {
let account;
let broker;
@@ -29,6 +27,7 @@ describe('views/choose_what_to_sync', () => {
let metrics;
let notifier;
let onSubmitComplete;
+ let relier;
let sessionVerificationPoll;
let syncEngines;
let user;
@@ -55,6 +54,7 @@ describe('views/choose_what_to_sync', () => {
broker = new Broker({
chooseWhatToSyncWebV1Engines: syncEngines,
});
+ relier = new Relier();
sinon
.stub(broker, 'persistVerificationData')
.callsFake(() => Promise.resolve());
@@ -95,12 +95,13 @@ describe('views/choose_what_to_sync', () => {
view = null;
});
- function initView(options = {}) {
+ function initView() {
view = new View({
broker,
metrics,
model,
notifier,
+ relier,
sessionVerificationPoll,
user,
viewName: 'choose-what-to-sync',
@@ -128,30 +129,62 @@ describe('views/choose_what_to_sync', () => {
assert.isTrue(view.navigate.calledWith('signup'));
});
});
+ });
- it('renders email info, adds SCREEN_CLASS to body', () => {
- return initView().then(() => {
- assert.include(view.$('.success-email-created').text(), email);
+ describe('pollVerification', () => {
+ it('polls', () => {
+ model.set('pollVerification', true);
+ return initView()
+ .then(() => {
+ sinon.spy(view, 'waitForSessionVerification');
+ return view.afterVisible();
+ })
+ .then(() => {
+ assert.isTrue(view.waitForSessionVerification.calledOnce);
+ });
+ });
- assert.isTrue($('body').hasClass(View.SCREEN_CLASS));
+ it('does not poll', () => {
+ model.set('pollVerification', false);
+ return initView()
+ .then(() => {
+ sinon.spy(view, 'waitForSessionVerification');
+ return view.afterVisible();
+ })
+ .then(() => {
+ assert.isFalse(view.waitForSessionVerification.calledOnce);
+ });
+ });
+ });
- const $rowEls = view.$('.choose-what-to-sync-row');
- assert.lengthOf($rowEls, DISPLAYED_ENGINE_IDS.length);
+ describe('showDoNotSyncButton', () => {
+ it('shows the do not sync button', () => {
+ model.set('allowToDisableSync', true);
+ return initView()
+ .then(() => {
+ return view.afterVisible();
+ })
+ .then(() => {
+ assert.lengthOf(view.$('#do-not-sync-device'), 1);
+ });
+ });
- assert.lengthOf(view.$(Selectors.NEWSLETTERS_HEADER), 0);
- assert.lengthOf(
- view.$(Selectors.NEWSLETTERS.FIREFOX_ACCOUNTS_JOURNEY),
- 0
- );
- assert.lengthOf(view.$(Selectors.NEWSLETTERS.HEALTHY_INTERNET), 0);
- assert.lengthOf(view.$(Selectors.NEWSLETTERS.CONSUMER_BETA), 0);
- assert.lengthOf(view.$(Selectors.NEWSLETTERS.ONLINE_SAFETY), 0);
- });
+ it('hides the do not sync button', () => {
+ model.set('allowToDisableSync', false);
+ return initView()
+ .then(() => {
+ return view.afterVisible();
+ })
+ .then(() => {
+ assert.lengthOf(view.$('#do-not-sync-device'), 0);
+ });
});
});
describe('afterVisible', () => {
it('persists verification data, starts poll', () => {
+ model.set('pollVerification', true);
+
return initView()
.then(() => {
sinon.stub(view, 'waitForSessionVerification').callsFake(() => {});
@@ -169,6 +202,8 @@ describe('views/choose_what_to_sync', () => {
describe('session verification polling', () => {
it('invokes `validateAndSubmit` on verification', () => {
+ model.set('pollVerification', true);
+
return initView()
.then(() => {
sinon.spy(view, 'waitForSessionVerification');
diff --git a/packages/fxa-content-server/app/tests/spec/views/complete_sign_up.js b/packages/fxa-content-server/app/tests/spec/views/complete_sign_up.js
index 4049a0b73cd..42c4b68d7f3 100644
--- a/packages/fxa-content-server/app/tests/spec/views/complete_sign_up.js
+++ b/packages/fxa-content-server/app/tests/spec/views/complete_sign_up.js
@@ -12,7 +12,7 @@ import Metrics from 'lib/metrics';
import Notifier from 'lib/channels/notifier';
import Relier from 'models/reliers/relier';
import sinon from 'sinon';
-import SyncRelier from 'models/reliers/sync';
+import BrowserRelier from 'models/reliers/browser';
import TestHelpers from '../../lib/helpers';
import Translator from 'lib/translator';
import User from 'models/user';
@@ -212,7 +212,7 @@ describe('views/complete_sign_up', function() {
validUid +
'&service=' +
validService;
- relier = new SyncRelier(
+ relier = new BrowserRelier(
{},
{
window: windowMock,
@@ -290,7 +290,7 @@ describe('views/complete_sign_up', function() {
validService +
'&reminder=' +
validReminder;
- relier = new SyncRelier(
+ relier = new BrowserRelier(
{},
{
window: windowMock,
diff --git a/packages/fxa-content-server/app/tests/spec/views/mixins/do-not-sync-mixin.js b/packages/fxa-content-server/app/tests/spec/views/mixins/do-not-sync-mixin.js
new file mode 100644
index 00000000000..55525b6ed69
--- /dev/null
+++ b/packages/fxa-content-server/app/tests/spec/views/mixins/do-not-sync-mixin.js
@@ -0,0 +1,51 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { assert } from 'chai';
+import DoNotSyncMixin from 'views/mixins/do-not-sync-mixin';
+import BaseView from 'views/base';
+import Relier from 'models/reliers/base';
+import Broker from 'models/auth_brokers/base';
+import Cocktail from 'cocktail';
+import sinon from 'sinon';
+
+class View extends BaseView {
+ template() {
+ return 'Do not Sync';
+ }
+
+ constructor(options) {
+ super(options);
+ this.onSubmitComplete = options.onSubmitComplete;
+ }
+}
+
+Cocktail.mixin(View, DoNotSyncMixin);
+
+describe('views/mixins/do-not-sync-mixin', function() {
+ let broker;
+ let view;
+ let relier;
+
+ beforeEach(function() {
+ relier = new Relier();
+ broker = new Broker();
+ view = new View({
+ relier,
+ broker,
+ onSubmitComplete: sinon.spy(),
+ });
+
+ return view.render();
+ });
+
+ describe('doNotSync', function() {
+ it('triggers onSubmitComplete', () => {
+ return view.doNotSync().then(() => {
+ assert.isTrue(view.onSubmitComplete.calledOnce);
+ assert.isTrue(relier.get('doNotSync'));
+ });
+ });
+ });
+});
diff --git a/packages/fxa-content-server/app/tests/spec/views/mixins/signin-mixin.js b/packages/fxa-content-server/app/tests/spec/views/mixins/signin-mixin.js
index c93f6061dc1..de351f406c0 100644
--- a/packages/fxa-content-server/app/tests/spec/views/mixins/signin-mixin.js
+++ b/packages/fxa-content-server/app/tests/spec/views/mixins/signin-mixin.js
@@ -9,6 +9,7 @@ import AuthErrors from 'lib/auth-errors';
import Backbone from 'backbone';
import OAuthErrors from 'lib/oauth-errors';
import Relier from 'models/reliers/relier';
+import BrowserRelier from 'models/reliers/browser';
import SignInMixin from 'views/mixins/signin-mixin';
import sinon from 'sinon';
import User from 'models/user';
@@ -114,6 +115,53 @@ describe('views/mixins/signin-mixin', function() {
});
});
+ describe('offers to Sync', function() {
+ it('navigates to the correct view', function() {
+ view.relier = new BrowserRelier();
+ sinon.spy(view.relier, 'shouldOfferToSync');
+
+ return view.signIn(account, 'password').then(() => {
+ assert.isTrue(view.relier.shouldOfferToSync.calledOnce);
+ assert.isTrue(view.navigate.calledOnce);
+
+ var args = view.navigate.args[0];
+ assert.equal(args[0], 'would_you_like_to_sync');
+ assert.deepEqual(args[1].account, account);
+ assert.isFunction(args[1].onSubmitComplete);
+ });
+ });
+
+ it('skips if relier does not support it', function() {
+ sinon.spy(view.relier, 'shouldOfferToSync');
+
+ return view.signIn(account, 'password').then(() => {
+ assert.isTrue(view.relier.shouldOfferToSync.calledOnce);
+ assert.isFalse(view.navigate.calledOnce);
+ });
+ });
+
+ it('skips if service is sync already', function() {
+ view.relier = new BrowserRelier();
+ view.relier.set('service', 'sync');
+ sinon.spy(view.relier, 'shouldOfferToSync');
+
+ return view.signIn(account, 'password').then(() => {
+ assert.isTrue(view.relier.shouldOfferToSync.calledOnce);
+ assert.isFalse(view.navigate.calledOnce);
+ });
+ });
+
+ it('skips if viewName is force-auth', function() {
+ view.relier = new BrowserRelier();
+ sinon.spy(view.relier, 'shouldOfferToSync');
+
+ view.viewName = 'force-auth';
+ return view.signIn(account, 'password').then(() => {
+ assert.isFalse(view.navigate.called);
+ });
+ });
+ });
+
describe('verified account', function() {
describe('with `redirectTo` specified', function() {
beforeEach(function() {
diff --git a/packages/fxa-content-server/app/tests/spec/views/mixins/sync-suggestion-mixin.js b/packages/fxa-content-server/app/tests/spec/views/mixins/sync-suggestion-mixin.js
index 4969685fb1c..886ceeb1995 100644
--- a/packages/fxa-content-server/app/tests/spec/views/mixins/sync-suggestion-mixin.js
+++ b/packages/fxa-content-server/app/tests/spec/views/mixins/sync-suggestion-mixin.js
@@ -8,7 +8,7 @@ import BaseView from 'views/base';
import Cocktail from 'cocktail';
import Constants from 'lib/constants';
import Notifier from 'lib/channels/notifier';
-import Relier from 'models/reliers/sync';
+import Relier from 'models/reliers/browser';
import sinon from 'sinon';
import SyncSuggestionMixin from 'views/mixins/sync-suggestion-mixin';
@@ -45,6 +45,7 @@ describe('views/mixins/sync-suggestion-mixin', () => {
describe('sync suggestion', () => {
it('displays sync suggestion message if no service', () => {
relier.set('service', null);
+ relier.set('context', 'web');
return view
.render()
@@ -67,6 +68,8 @@ describe('views/mixins/sync-suggestion-mixin', () => {
it('does not have sync auth supported', () => {
relier.set('service', null);
+ relier.set('context', 'web');
+
sinon.stub(view, 'isSyncAuthSupported').callsFake(() => false);
return view.render().then(() => {
const $getStartedEl = view.$('#suggest-sync').find('a');
@@ -79,6 +82,8 @@ describe('views/mixins/sync-suggestion-mixin', () => {
it('has sync auth supported on Firefox for Desktop', () => {
relier.set('service', null);
+ relier.set('context', 'web');
+
sinon.stub(view, 'isSyncAuthSupported').callsFake(() => true);
sinon.stub(view, 'getUserAgent').callsFake(() => {
return {
@@ -98,6 +103,8 @@ describe('views/mixins/sync-suggestion-mixin', () => {
it('has sync auth supported on Firefox for Android', () => {
relier.set('service', null);
+ relier.set('context', 'web');
+
sinon.stub(view, 'isSyncAuthSupported').callsFake(() => true);
sinon.stub(view, 'getUserAgent').callsFake(() => {
return {
@@ -117,6 +124,7 @@ describe('views/mixins/sync-suggestion-mixin', () => {
it('can be dismissed', () => {
relier.set('service', null);
+ relier.set('context', 'web');
return view.render().then(() => {
$('#container').html(view.el);
@@ -128,6 +136,7 @@ describe('views/mixins/sync-suggestion-mixin', () => {
it('does not display sync suggestion message if there is a relier service', () => {
relier.set('service', 'sync');
+ relier.set('context', 'web');
return view.render().then(() => {
assert.lengthOf(view.$('#suggest-sync'), 0);
diff --git a/packages/fxa-content-server/app/tests/spec/views/ready.js b/packages/fxa-content-server/app/tests/spec/views/ready.js
index dcbdb7ef196..23aee660cd8 100644
--- a/packages/fxa-content-server/app/tests/spec/views/ready.js
+++ b/packages/fxa-content-server/app/tests/spec/views/ready.js
@@ -12,7 +12,7 @@ import Notifier from 'lib/channels/notifier';
import OAuthBroker from 'models/auth_brokers/oauth-redirect';
import Session from 'lib/session';
import sinon from 'sinon';
-import SyncRelier from 'models/reliers/sync';
+import BrowserRelier from 'models/reliers/browser';
import View from 'views/ready';
import WindowMock from '../../mocks/window';
@@ -32,7 +32,7 @@ describe('views/ready', function() {
windowMock.navigator.userAgent =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:50.0) Gecko/20100101 Firefox/50.0';
- relier = new SyncRelier({
+ relier = new BrowserRelier({
window: windowMock,
});
broker = new OAuthBroker({
diff --git a/packages/fxa-content-server/app/tests/spec/views/sign_up.js b/packages/fxa-content-server/app/tests/spec/views/sign_up.js
index e4d42155f34..ca100b699d1 100644
--- a/packages/fxa-content-server/app/tests/spec/views/sign_up.js
+++ b/packages/fxa-content-server/app/tests/spec/views/sign_up.js
@@ -17,7 +17,7 @@ import FxaClient from 'lib/fxa-client';
import Metrics from 'lib/metrics';
import Notifier from 'lib/channels/notifier';
import p from 'lib/promise';
-import Relier from 'models/reliers/sync';
+import Relier from 'models/reliers/browser';
import sinon from 'sinon';
import TestHelpers from '../../lib/helpers';
import Translator from 'lib/translator';
diff --git a/packages/fxa-content-server/app/tests/test_start.js b/packages/fxa-content-server/app/tests/test_start.js
index c85e0439fe4..dddf58aa7b6 100644
--- a/packages/fxa-content-server/app/tests/test_start.js
+++ b/packages/fxa-content-server/app/tests/test_start.js
@@ -120,7 +120,7 @@ require('./spec/models/reliers/oauth');
require('./spec/models/reliers/pairing/authority');
require('./spec/models/reliers/pairing/supplicant');
require('./spec/models/reliers/relier');
-require('./spec/models/reliers/sync');
+require('./spec/models/reliers/browser');
require('./spec/models/resume-token');
require('./spec/models/security-events');
require('./spec/models/support-form');
@@ -172,6 +172,7 @@ require('./spec/views/mixins/cached-credentials-mixin');
require('./spec/views/mixins/connect-another-device-mixin');
require('./spec/views/mixins/coppa-mixin');
require('./spec/views/mixins/disable-form-mixin');
+require('./spec/views/mixins/do-not-sync-mixin');
require('./spec/views/mixins/pairing-graphics-mixin');
require('./spec/views/mixins/email-first-experiment-mixin');
require('./spec/views/mixins/email-opt-in-mixin');
diff --git a/packages/fxa-content-server/server/lib/routes/get-frontend.js b/packages/fxa-content-server/server/lib/routes/get-frontend.js
index e386a4820ca..896a00a17bf 100644
--- a/packages/fxa-content-server/server/lib/routes/get-frontend.js
+++ b/packages/fxa-content-server/server/lib/routes/get-frontend.js
@@ -81,6 +81,7 @@ module.exports = function() {
'verify_email',
'verify_primary_email',
'verify_secondary_email',
+ 'would_you_like_to_sync',
].join('|'); // prepare for use in a RegExp
return {
diff --git a/packages/fxa-content-server/tests/functional.js b/packages/fxa-content-server/tests/functional.js
index bc49217e7e3..71526bb791f 100644
--- a/packages/fxa-content-server/tests/functional.js
+++ b/packages/fxa-content-server/tests/functional.js
@@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module.exports = [
+ 'tests/functional/fx_browser_relier.js',
'tests/functional/oauth_webchannel.js',
'tests/functional/reset_password.js',
'tests/functional/oauth_require_totp.js',
diff --git a/packages/fxa-content-server/tests/functional/fx_browser_relier.js b/packages/fxa-content-server/tests/functional/fx_browser_relier.js
new file mode 100644
index 00000000000..b613b6d1889
--- /dev/null
+++ b/packages/fxa-content-server/tests/functional/fx_browser_relier.js
@@ -0,0 +1,204 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+const { registerSuite } = intern.getInterface('object');
+const TestHelpers = require('../lib/helpers');
+const FunctionalHelpers = require('./lib/helpers');
+const selectors = require('./lib/selectors');
+const uaStrings = require('./lib/ua-strings');
+
+const config = intern._config;
+const URL_PARAMS =
+ 'context=fx_desktop_v3&forceAboutAccounts=true&automatedBrowser=true&action=email';
+const EMAIL_FIRST_URL = `${config.fxaContentRoot}?${URL_PARAMS}`;
+const FIREFOX_CLIENT_ID = '5882386c6d801776';
+const CAPABILITIES = {
+ multiService: true,
+ pairing: false,
+ engines: ['history'],
+};
+
+let email;
+const PASSWORD = 'passwordvx2';
+
+const {
+ clearBrowserState,
+ click,
+ createUser,
+ type,
+ closeCurrentWindow,
+ openPage,
+ openVerificationLinkInNewTab,
+ switchToWindow,
+ testIsBrowserNotified,
+} = FunctionalHelpers;
+
+registerSuite('Firefox Desktop non-sync', {
+ beforeEach: function() {
+ email = TestHelpers.createEmail();
+ return this.remote.then(clearBrowserState());
+ },
+
+ afterEach: function() {
+ return this.remote.then(clearBrowserState());
+ },
+ tests: {
+ 'signup with no service - do not sync': function() {
+ return (
+ this.remote
+ .then(
+ openPage(EMAIL_FIRST_URL, selectors.ENTER_EMAIL.SUB_HEADER, {
+ query: {
+ forceUA: uaStrings['desktop_firefox_57'],
+ },
+ webChannelResponses: {
+ 'fxaccounts:can_link_account': { ok: true },
+ 'fxaccounts:fxa_status': {
+ signedInUser: null,
+ clientId: FIREFOX_CLIENT_ID,
+ capabilities: CAPABILITIES,
+ },
+ },
+ })
+ )
+ .then(type(selectors.ENTER_EMAIL.EMAIL, email))
+ .then(click(selectors.ENTER_EMAIL.SUBMIT))
+ .then(testIsBrowserNotified('fxaccounts:can_link_account'))
+
+ .then(type(selectors.SIGNUP_PASSWORD.PASSWORD, PASSWORD))
+ .then(type(selectors.SIGNUP_PASSWORD.VPASSWORD, PASSWORD))
+ .then(type(selectors.SIGNUP_PASSWORD.AGE, 21))
+ .then(
+ click(
+ selectors.SIGNUP_PASSWORD.SUBMIT,
+ selectors.CHOOSE_WHAT_TO_SYNC.HEADER
+ )
+ )
+ // verify the account
+ .then(openVerificationLinkInNewTab(email, 0))
+ .then(switchToWindow(1))
+ // switch back to the original window, choose to "do not sync".
+ .then(closeCurrentWindow())
+ .then(click(selectors.CHOOSE_WHAT_TO_SYNC.DO_NOT_SYNC))
+ .then(testIsBrowserNotified('fxaccounts:login'))
+ );
+ },
+ 'signup with no service - sync': function() {
+ return (
+ this.remote
+ .then(
+ openPage(EMAIL_FIRST_URL, selectors.ENTER_EMAIL.SUB_HEADER, {
+ query: {
+ forceUA: uaStrings['desktop_firefox_57'],
+ },
+ webChannelResponses: {
+ 'fxaccounts:can_link_account': { ok: true },
+ 'fxaccounts:fxa_status': {
+ signedInUser: null,
+ clientId: FIREFOX_CLIENT_ID,
+ capabilities: CAPABILITIES,
+ },
+ },
+ })
+ )
+ .then(type(selectors.ENTER_EMAIL.EMAIL, email))
+ .then(click(selectors.ENTER_EMAIL.SUBMIT))
+ .then(testIsBrowserNotified('fxaccounts:can_link_account'))
+
+ .then(type(selectors.SIGNUP_PASSWORD.PASSWORD, PASSWORD))
+ .then(type(selectors.SIGNUP_PASSWORD.VPASSWORD, PASSWORD))
+ .then(type(selectors.SIGNUP_PASSWORD.AGE, 21))
+ .then(
+ click(
+ selectors.SIGNUP_PASSWORD.SUBMIT,
+ selectors.CHOOSE_WHAT_TO_SYNC.HEADER
+ )
+ )
+ // verify the account
+ .then(openVerificationLinkInNewTab(email, 0))
+ .then(switchToWindow(1))
+ // switch back to the original window, choose to "do not sync".
+ .then(closeCurrentWindow())
+ .then(click(selectors.CHOOSE_WHAT_TO_SYNC.SUBMIT))
+ .then(testIsBrowserNotified('fxaccounts:login'))
+ );
+ },
+ 'signin with no service - do not sync': function() {
+ return this.remote
+ .then(createUser(email, PASSWORD, { preVerified: true }))
+ .then(
+ openPage(EMAIL_FIRST_URL, selectors.ENTER_EMAIL.SUB_HEADER, {
+ query: {
+ forceUA: uaStrings['desktop_firefox_57'],
+ },
+ webChannelResponses: {
+ 'fxaccounts:can_link_account': { ok: true },
+ 'fxaccounts:fxa_status': {
+ signedInUser: null,
+ clientId: FIREFOX_CLIENT_ID,
+ capabilities: CAPABILITIES,
+ },
+ },
+ })
+ )
+ .then(type(selectors.ENTER_EMAIL.EMAIL, email))
+ .then(
+ click(selectors.ENTER_EMAIL.SUBMIT, selectors.SIGNIN_PASSWORD.HEADER)
+ )
+ .then(testIsBrowserNotified('fxaccounts:can_link_account'))
+
+ .then(type(selectors.SIGNIN_PASSWORD.PASSWORD, PASSWORD))
+ .then(
+ click(
+ selectors.SIGNIN_PASSWORD.SUBMIT,
+ selectors.WOULD_YOU_LIKE_SYNC.HEADER
+ )
+ )
+ .then(click(selectors.WOULD_YOU_LIKE_SYNC.DO_NOT_SYNC))
+ .then(testIsBrowserNotified('fxaccounts:login'));
+ },
+ 'signin with no service - sync': function() {
+ return this.remote
+ .then(createUser(email, PASSWORD, { preVerified: true }))
+ .then(
+ openPage(EMAIL_FIRST_URL, selectors.ENTER_EMAIL.SUB_HEADER, {
+ query: {
+ forceUA: uaStrings['desktop_firefox_57'],
+ },
+ webChannelResponses: {
+ 'fxaccounts:can_link_account': { ok: true },
+ 'fxaccounts:fxa_status': {
+ signedInUser: null,
+ clientId: FIREFOX_CLIENT_ID,
+ capabilities: CAPABILITIES,
+ },
+ },
+ })
+ )
+ .then(type(selectors.ENTER_EMAIL.EMAIL, email))
+ .then(
+ click(selectors.ENTER_EMAIL.SUBMIT, selectors.SIGNIN_PASSWORD.HEADER)
+ )
+ .then(testIsBrowserNotified('fxaccounts:can_link_account'))
+
+ .then(type(selectors.SIGNIN_PASSWORD.PASSWORD, PASSWORD))
+ .then(
+ click(
+ selectors.SIGNIN_PASSWORD.SUBMIT,
+ selectors.WOULD_YOU_LIKE_SYNC.HEADER
+ )
+ )
+ .then(
+ click(
+ selectors.WOULD_YOU_LIKE_SYNC.SUBMIT,
+ selectors.CHOOSE_WHAT_TO_SYNC.HEADER
+ )
+ )
+ .then(click(selectors.CHOOSE_WHAT_TO_SYNC.SUBMIT))
+ .then(testIsBrowserNotified('fxaccounts:login'));
+ },
+ },
+});
diff --git a/packages/fxa-content-server/tests/functional/lib/selectors.js b/packages/fxa-content-server/tests/functional/lib/selectors.js
index c900f4251ff..f04ba476db4 100644
--- a/packages/fxa-content-server/tests/functional/lib/selectors.js
+++ b/packages/fxa-content-server/tests/functional/lib/selectors.js
@@ -49,7 +49,13 @@ module.exports = {
SUBMIT: '#change-password button[type="submit"]',
TOOLTIP: '.tooltip',
},
+ WOULD_YOU_LIKE_SYNC: {
+ HEADER: '#would-you-like-to-sync',
+ SUBMIT: 'button[type=submit]',
+ DO_NOT_SYNC: '#do-not-sync-device',
+ },
CHOOSE_WHAT_TO_SYNC: {
+ DO_NOT_SYNC: '#do-not-sync-device',
ENGINE_ADDRESSES: '#sync-engine-addresses',
ENGINE_BOOKMARKS: '#sync-engine-bookmarks',
ENGINE_CREDIT_CARDS: '#sync-engine-creditcards',
diff --git a/packages/fxa-content-server/tests/functional/sync_v3_email_first.js b/packages/fxa-content-server/tests/functional/sync_v3_email_first.js
index b314e3b4a9c..5a115c1c718 100644
--- a/packages/fxa-content-server/tests/functional/sync_v3_email_first.js
+++ b/packages/fxa-content-server/tests/functional/sync_v3_email_first.js
@@ -248,7 +248,7 @@ registerSuite('Firefox Desktop Sync v3 email first', {
.then(testIsBrowserNotified('fxaccounts:can_link_account'));
},
- 'signin verified ': function() {
+ 'signin verified': function() {
return (
this.remote
.then(createUser(email, PASSWORD, { preVerified: true }))