Commit ec15b409 authored by Gordon Seto's avatar Gordon Seto Committed by Commit Bot

[CrOS Settings] Connect eSIM setup flow to Mojo API

Create eSIM setup flow navigation state machine and
connect to Mojo API, add unit tests

Bug: 1093185
Change-Id: I8160f01f4d2c46e00512b9f0bbb8395533b19e34
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2544905
Commit-Queue: Gordon Seto <gordonseto@google.com>
Reviewed-by: default avatarKyle Horimoto <khorimoto@chromium.org>
Reviewed-by: default avatarAzeem Arshad <azeemarshad@chromium.org>
Cr-Commit-Position: refs/heads/master@{#829889}
parent 3da47124
......@@ -19,6 +19,12 @@ suite('CrComponentsEsimFlowUiTest', function() {
let eSimPage;
let eSimManagerRemote;
async function flushAsync() {
Polymer.dom.flush();
// Use setTimeout to wait for the next macrotask.
return new Promise(resolve => setTimeout(resolve));
}
setup(function() {
eSimManagerRemote = new cellular_setup.FakeESimManagerRemote();
cellular_setup.setESimManagerRemoteForTesting(eSimManagerRemote);
......@@ -30,41 +36,164 @@ suite('CrComponentsEsimFlowUiTest', function() {
Polymer.dom.flush();
});
test('Forward navigation goes to final page', function() {
test('No eSIM profile flow', async function() {
eSimManagerRemote.addEuiccForTest(0);
const profileLoadingPage = eSimPage.$$('#profileLoadingPage');
const activationCodePage = eSimPage.$$('#activationCodePage');
const finalPage = eSimPage.$$('#finalPage');
assertTrue(!!profileLoadingPage);
assertTrue(!!activationCodePage);
assertTrue(!!finalPage);
// Loading page should be showing.
assertTrue(
eSimPage.selectedESimPageName_ ===
cellular_setup.ESimPageName.PROFILE_LOADING &&
eSimPage.selectedESimPageName_ === profileLoadingPage.id);
await flushAsync();
// Should now be at the activation code page.
assertTrue(
eSimPage.selectedESimPageName_ ===
cellular_setup.ESimPageName.ACTIVATION_CODE &&
eSimPage.selectedESimPageName_ === activationCodePage.id);
// Insert an activation code.
activationCodePage.$$('#activationCode').value = 'ACTIVATION_CODE';
// Next button should now be enabled.
assertTrue(
eSimPage.buttonState.next ===
cellularSetup.ButtonState.SHOWN_AND_ENABLED);
eSimPage.navigateForward();
Polymer.dom.flush();
// Should now be at the final page.
assertTrue(
eSimPage.selectedESimPageName_ === cellular_setup.ESimPageName.FINAL &&
eSimPage.selectedESimPageName_ === finalPage.id);
});
test('Single eSIM profile flow', async function() {
eSimManagerRemote.addEuiccForTest(1);
const profileLoadingPage = eSimPage.$$('#profileLoadingPage');
const finalPage = eSimPage.$$('#finalPage');
assertTrue(!!profileLoadingPage);
assertTrue(!!finalPage);
// Loading page should be showing.
assertTrue(
eSimPage.selectedESimPageName_ ===
cellular_setup.ESimPageName.PROFILE_LOADING &&
eSimPage.selectedESimPageName_ === profileLoadingPage.id);
await flushAsync();
// Should go directly to final page.
assertTrue(
eSimPage.selectedESimPageName_ === cellular_setup.ESimPageName.FINAL &&
eSimPage.selectedESimPageName_ === finalPage.id);
});
test('Multiple eSIM profiles skip discovery flow', async function() {
eSimManagerRemote.addEuiccForTest(2);
const profileLoadingPage = eSimPage.$$('#profileLoadingPage');
const profileDiscoveryPage = eSimPage.$$('#profileDiscoveryPage');
const activationCodePage = eSimPage.$$('#activationCodePage');
const finalPage = eSimPage.$$('#finalPage');
assertTrue(!!profileLoadingPage);
assertTrue(!!profileDiscoveryPage);
assertTrue(!!activationCodePage);
assertTrue(!!finalPage);
// Loading page should be showing.
assertTrue(
eSimPage.selectedESimPageName_ ===
cellular_setup.ESimPageName.PROFILE_LOADING &&
eSimPage.selectedESimPageName_ === profileLoadingPage.id);
await flushAsync();
// Should go to profile discovery page.
assertTrue(
eSimPage.selectedESimPageName_ ===
cellular_setup.ESimPageName.PROFILE_DISCOVERY &&
eSimPage.selectedESimPageName_ === profileDiscoveryPage.id);
// Simulate pressing 'Skip'.
assertTrue(
eSimPage.buttonState.skipDiscovery ===
cellularSetup.ButtonState.SHOWN_AND_ENABLED);
eSimPage.navigateForward();
Polymer.dom.flush();
// TODO(crbug.com/1093185) Update this test when the navigation between
// profile discovery and activation code pages is wired up.
// Should now be at the activation code page.
assertTrue(
eSimPage.selectedESimPageName_ ===
cellular_setup.ESimPageName.ACTIVATION_CODE &&
eSimPage.selectedESimPageName_ === activationCodePage.id);
// Insert an activation code.
activationCodePage.$$('#activationCode').value = 'ACTIVATION_CODE';
// Simulate pressing 'Next'.
assertTrue(
eSimPage.buttonState.next ===
cellularSetup.ButtonState.SHOWN_AND_ENABLED);
eSimPage.navigateForward();
Polymer.dom.flush();
// Should now be at the final page.
assertTrue(
eSimPage.selectedESimPageName_ === cellular_setup.ESimPageName.FINAL &&
eSimPage.selectedESimPageName_ === finalPage.id);
});
test('Multiple eSIM profiles select flow', async function() {
eSimManagerRemote.addEuiccForTest(2);
// TODO(crbug.com/1093185) Update this test when the navigation between
// profile discovery and activation code pages is wired up.
test('Enable done button', function() {
assertTrue(eSimPage.buttonState.done === cellularSetup.ButtonState.HIDDEN);
const profileLoadingPage = eSimPage.$$('#profileLoadingPage');
const profileDiscoveryPage = eSimPage.$$('#profileDiscoveryPage');
const activationCodePage = eSimPage.$$('#activationCodePage');
const finalPage = eSimPage.$$('#finalPage');
assertTrue(!!profileLoadingPage);
assertTrue(!!profileDiscoveryPage);
assertTrue(!!activationCodePage);
assertTrue(!!finalPage);
// Loading page should be showing.
assertTrue(
eSimPage.selectedESimPageName_ ===
cellular_setup.ESimPageName.PROFILE_LOADING &&
eSimPage.selectedESimPageName_ === profileLoadingPage.id);
await flushAsync();
// Should go to profile discovery page.
assertTrue(
eSimPage.selectedESimPageName_ ===
cellular_setup.ESimPageName.PROFILE_DISCOVERY &&
eSimPage.selectedESimPageName_ === profileDiscoveryPage.id);
// Select the first profile on the list.
const profileList = profileDiscoveryPage.$$('#profileList');
profileList.selectItem(profileList.items[0]);
Polymer.dom.flush();
// The 'Done' button should now be enabled.
assertTrue(
eSimPage.buttonState.done ===
cellularSetup.ButtonState.SHOWN_AND_ENABLED);
assertTrue(
eSimPage.buttonState.skipDiscovery ===
cellularSetup.ButtonState.HIDDEN);
});
});
......@@ -4,43 +4,98 @@
cr.define('cellular_setup', function() {
/** @implements {chromeos.cellularSetup.mojom.ESimManagerInterface} */
/* #export */ class FakeESimManagerRemote {
/** @implements {chromeos.cellularSetup.mojom.ESimProfile} */
class FakeProfile {
constructor(id) {
this.properties_ = {
activationCode: 'activation-code-' + id,
eid: '1',
iccid: id,
name: 'profile' + id,
nickname: 'profile' + id,
serviceProvider: 'provider' + id,
state: chromeos.cellularSetup.mojom.ProfileState.kPending,
};
}
/**
* @override
* @return {!Promise<{euiccs: !Array<!Euicc>,}>}
* @return {!Promise<{properties:
* chromeos.cellularSetup.mojom.ESimProfileProperties},}>}
*/
getAvailableEuiccs() {
getProperties() {
return new Promise((res) => {
res({
euiccs: [{
eid: '1',
isActive: true,
}]
properties: this.properties_,
});
});
}
}
/** @implements {chromeos.cellularSetup.mojom.Euicc} */
class FakeEuicc {
constructor(numProfiles) {
this.profiles_ = [];
for (let i = 0; i < numProfiles; i++) {
this.addProfileForTest_();
}
}
/**
* @override
* @return {!Promise<{result:
* chromeos.cellularSetup.mojom.ESimOperationResult},}>}
*/
requestPendingProfiles() {
return new Promise((res) => {
res({
result: chromeos.cellularSetup.mojom.ESimOperationResult.kSuccess
});
});
}
/**
* @override
* @param { !string } eid
* @return {!Promise<{profiles: Array<!ESimProfile>,}>}
*/
getProfiles(eid) {
getProfileList() {
return new Promise((res) => {
res({
profiles: [{
activationCode: 'activation-code-1',
eid: '1',
iccid: '1',
name: 'profile1',
nickname: 'profile1',
serviceProvider: 'provider1',
state: chromeos.cellularSetup.mojom.ProfileState.kPending,
}]
profiles: this.profiles_,
});
});
}
/** @private */
addProfileForTest_() {
const id = this.profiles_.length + 1;
this.profiles_.push(new FakeProfile(id));
}
}
/** @implements {chromeos.cellularSetup.mojom.ESimManagerInterface} */
/* #export */ class FakeESimManagerRemote {
constructor() {
this.euiccs_ = [];
}
/**
* @override
* @return {!Promise<{euiccs: !Array<!Euicc>,}>}
*/
getAvailableEuiccs() {
return new Promise((res) => {
res({euiccs: this.euiccs_});
});
}
/**
* @param {number} numProfiles The number of profiles the EUICC has.
*/
addEuiccForTest(numProfiles) {
const euicc = new FakeEuicc(numProfiles);
this.euiccs_.push(euicc);
}
}
// #cr_define_end
......
......@@ -113,6 +113,7 @@ js_library("esim_flow_ui") {
":profile_discovery_list_item",
":profile_discovery_list_page",
":subflow_behavior",
"//ui/webui/resources/js:assert.m",
"//ui/webui/resources/js:i18n_behavior",
]
}
......@@ -330,6 +331,7 @@ js_library("esim_flow_ui.m") {
":profile_discovery_list_page.m",
":subflow_behavior.m",
"//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
"//ui/webui/resources/js:assert.m",
"//ui/webui/resources/js:i18n_behavior.m",
]
extra_deps = [ ":esim_flow_ui_module" ]
......
......@@ -4,6 +4,7 @@
<link rel="import" href="subflow_behavior.html">
<link rel="import" href="cellular_types.html">
<link rel="import" href="cellular_setup_delegate.html">
<link rel="import" href="chrome://resources/html/assert.html">
<link rel="import" href="chrome://resources/polymer/v1_0/iron-pages/iron-pages.html">
<link rel="import" href="setup_loading_page.html">
<link rel="import" href="activation_code_page.html">
......
......@@ -7,9 +7,19 @@ cr.define('cellular_setup', function() {
/* #export */ const ESimPageName = {
PROFILE_LOADING: 'profileLoadingPage',
PROFILE_DISCOVERY: 'profileDiscoveryPage',
ESIM: 'activationCodePage',
ACTIVATION_CODE: 'activationCodePage',
FINAL: 'finalPage',
};
/** @enum{string} */
/* #export */ const ESimUiState = {
PROFILE_SEARCH: 'profile-search',
ACTIVATION_CODE_ENTRY: 'activation-code-entry',
MULTI_PROFILE_SELECTION: 'multi-profile-selection',
SETUP_SUCCESS: 'setup-success',
SETUP_FAILURE: 'setup-failure',
};
/**
* Root element for the eSIM cellular setup flow. This element interacts with
* the CellularSetup service to carry out the esim activation flow.
......@@ -26,6 +36,15 @@ cr.define('cellular_setup', function() {
/** @type {!cellular_setup.CellularSetupDelegate} */
delegate: Object,
/**
* @type {!cellular_setup.ESimUiState}
* @private
*/
state_: {
type: String,
value: ESimUiState.PROFILE_SEARCH,
},
/**
* Element name of the current selected sub-page.
* @type {!cellular_setup.ESimPageName}
......@@ -33,8 +52,7 @@ cr.define('cellular_setup', function() {
*/
selectedESimPageName_: {
type: String,
// TODO(crbug.com/1093185) Make initial page PROFILE_LOADING.
value: ESimPageName.PROFILE_DISCOVERY,
value: ESimPageName.PROFILE_LOADING,
},
/**
......@@ -55,23 +73,158 @@ cr.define('cellular_setup', function() {
},
},
/**
* Provides an interface to the ESimManager Mojo service.
* @private {?chromeos.cellularSetup.mojom.ESimManagerRemote}
*/
eSimManagerRemote_: null,
listeners: {
'activation-code-updated': 'onActivationCodeUpdated_',
},
observers: ['onSelectedProfilesChanged_(selectedProfiles_.splices)'],
observers: [
'updateSelectedPage_(state_)', 'updateButtonBarState_(state_)',
'onSelectedProfilesChanged_(selectedProfiles_.splices)'
],
/** @override */
created() {
this.eSimManagerRemote_ = cellular_setup.getESimManagerRemote();
},
initSubflow() {
this.buttonState = {
backward: cellularSetup.ButtonState.HIDDEN,
cancel: this.delegate.shouldShowCancelButton() ?
cellularSetup.ButtonState.SHOWN_AND_ENABLED :
cellularSetup.ButtonState.HIDDEN,
done: cellularSetup.ButtonState.HIDDEN,
next: cellularSetup.ButtonState.HIDDEN,
tryAgain: cellularSetup.ButtonState.HIDDEN,
skipDiscovery: cellularSetup.ButtonState.SHOWN_AND_ENABLED,
};
this.fetchProfiles_();
},
/** @private */
fetchProfiles_() {
let euicc;
this.eSimManagerRemote_.getAvailableEuiccs()
.then(response => {
// TODO(crbug.com/1093185) User should have at least 1 EUICC or
// we shouldn't have gotten to this flow. Add check for this in
// cellular_setup.
euicc = response.euiccs[0];
return euicc.requestPendingProfiles();
})
.then(response => {
if (response.result ===
chromeos.cellularSetup.mojom.ESimOperationResult.kFailure) {
console.error('Error requesting pending profiles: ' + response);
}
return euicc.getProfileList();
})
.then(response => {
return this.filterForPendingProfiles_(response.profiles);
})
.then(profiles => {
switch (profiles.length) {
case 0:
this.state_ = ESimUiState.ACTIVATION_CODE_ENTRY;
break;
case 1:
// TODO(crbug.com/1093185) Install the
// profile. Handle error state. Handle
// confirmation code if needed.
this.state_ = ESimUiState.SETUP_SUCCESS;
break;
default:
// TODO(crbug.com/1093185) Populate the profile discovery with
// profiles.
this.state_ = ESimUiState.MULTI_PROFILE_SELECTION;
break;
}
});
},
/**
* @private
* @param {!Array<!chromeos.cellularSetup.mojom.ESimProfileRemote>} profiles
* @return {!Promise<Array<!chromeos.cellularSetup.mojom.ESimProfileRemote>>}
*/
filterForPendingProfiles_(profiles) {
const profilePromises = profiles.map(profile => {
return profile.getProperties().then(response => {
if (response.properties.state !==
chromeos.cellularSetup.mojom.ProfileState.kPending) {
return null;
}
return profile;
});
});
return Promise.all(profilePromises).then(profiles => {
return profiles.filter(profile => {
return profile !== null;
});
});
},
/** @private */
updateSelectedPage_() {
switch (this.state_) {
case ESimUiState.PROFILE_SEARCH:
this.selectedESimPageName_ = ESimPageName.PROFILE_LOADING;
break;
case ESimUiState.ACTIVATION_CODE_ENTRY:
this.selectedESimPageName_ = ESimPageName.ACTIVATION_CODE;
break;
case ESimUiState.MULTI_PROFILE_SELECTION:
this.selectedESimPageName_ = ESimPageName.PROFILE_DISCOVERY;
break;
case ESimUiState.SETUP_SUCCESS:
this.selectedESimPageName_ = ESimPageName.FINAL;
break;
default:
assertNotReached();
break;
}
},
/** @private */
updateButtonBarState_() {
let buttonState;
switch (this.state_) {
case ESimUiState.PROFILE_SEARCH:
case ESimUiState.ACTIVATION_CODE_ENTRY:
buttonState = {
backward: cellularSetup.ButtonState.SHOWN_AND_ENABLED,
cancel: this.delegate.shouldShowCancelButton() ?
cellularSetup.ButtonState.SHOWN_AND_ENABLED :
cellularSetup.ButtonState.HIDDEN,
done: cellularSetup.ButtonState.HIDDEN,
next: cellularSetup.ButtonState.SHOWN_BUT_DISABLED,
tryAgain: cellularSetup.ButtonState.HIDDEN,
skipDiscovery: cellularSetup.ButtonState.HIDDEN,
};
break;
case ESimUiState.MULTI_PROFILE_SELECTION:
buttonState = {
backward: cellularSetup.ButtonState.HIDDEN,
cancel: this.delegate.shouldShowCancelButton() ?
cellularSetup.ButtonState.SHOWN_AND_ENABLED :
cellularSetup.ButtonState.HIDDEN,
done: cellularSetup.ButtonState.HIDDEN,
next: cellularSetup.ButtonState.HIDDEN,
tryAgain: cellularSetup.ButtonState.HIDDEN,
skipDiscovery: cellularSetup.ButtonState.SHOWN_AND_ENABLED,
};
break;
case ESimUiState.SETUP_SUCCESS:
buttonState = {
backward: cellularSetup.ButtonState.HIDDEN,
cancel: cellularSetup.ButtonState.HIDDEN,
done: cellularSetup.ButtonState.SHOWN_AND_ENABLED,
next: cellularSetup.ButtonState.HIDDEN,
tryAgain: cellularSetup.ButtonState.HIDDEN,
skipDiscovery: cellularSetup.ButtonState.HIDDEN,
};
break;
default:
assertNotReached();
break;
}
this.set('buttonState', buttonState);
},
/** @private */
......@@ -87,9 +240,9 @@ cr.define('cellular_setup', function() {
/** @private */
onSelectedProfilesChanged_() {
// TODO(crbug.com/1093185): Add navigation logic.
if (this.selectedProfiles_.length > 0) {
this.set('buttonState.skipDiscovery', cellularSetup.ButtonState.HIDDEN);
// TODO(crbug.com/1093185): Install the profiles when 'Done' is pressed.
this.set(
'buttonState.done', cellularSetup.ButtonState.SHOWN_AND_ENABLED);
} else {
......@@ -101,7 +254,19 @@ cr.define('cellular_setup', function() {
},
navigateForward() {
this.selectedESimPageName_ = ESimPageName.FINAL;
switch (this.state_) {
case ESimUiState.ACTIVATION_CODE_ENTRY:
// TODO(crbug.com/1093185) Install the profile. Handle error state.
// Handle confirmation code if needed.
this.state_ = ESimUiState.SETUP_SUCCESS;
break;
case ESimUiState.MULTI_PROFILE_SELECTION:
this.state_ = ESimUiState.ACTIVATION_CODE_ENTRY;
break;
default:
assertNotReached();
break;
}
},
/**
......@@ -114,5 +279,5 @@ cr.define('cellular_setup', function() {
});
// #cr_define_end
return {ESimPageName: ESimPageName};
return {ESimPageName: ESimPageName, ESimUiState: ESimUiState};
});
\ No newline at end of file
......@@ -11,6 +11,7 @@ cr_components_chromeos_namespace_rewrites = [
"cellularSetup.ButtonBarState|ButtonBarState",
"cellularSetup.CellularSetupPageName|CellularSetupPageName",
"cellular_setup.ESimPageName|ESimPageName",
"cellular_setup.ESimUiState|ESimUiState",
"cellularSetup.PSimPageName|PSimPageName",
"cellularSetup.PSimUIState|PSimUIState",
"cellularSetup.getTimeoutMsForPSimUIState|getTimeoutMsForPSimUIState",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment