// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// clang-format off
import 'chrome://settings/settings.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import type {CrInputElement, CrTextareaElement} from 'chrome://settings/lazy_load.js';
import {AutofillManagerImpl, CountryDetailManagerImpl} from 'chrome://settings/lazy_load.js';
import {assertEquals, assertFalse, assertGT, assertTrue} from 'chrome://webui-test/chai_assert.js';
import type {MetricsTracker} from 'chrome://webui-test/metrics_test_support.js';
import {fakeMetricsPrivate} from 'chrome://webui-test/metrics_test_support.js';
import type {CrLinkRowElement} from 'chrome://settings/settings.js';
import {OpenWindowProxyImpl} from 'chrome://settings/settings.js';
import {eventToPromise, whenAttributeIs, isVisible} from 'chrome://webui-test/test_util.js';
import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
import {TestOpenWindowProxy} from 'chrome://webui-test/test_open_window_proxy.js';
import {AutofillManagerExpectations, createAddressEntry, createEmptyAddressEntry, STUB_USER_ACCOUNT_INFO, TestAutofillManager} from './autofill_fake_data.js';
import {createAutofillSection, initiateRemoving, initiateEditing, CountryDetailManagerTestImpl, createAddressDialog, createRemoveAddressDialog, expectEvent, openAddressDialog, deleteAddress, getAddressFieldValue} from './autofill_section_test_utils.js';
// clang-format on
const FieldType = chrome.autofillPrivate.FieldType;
suite('AutofillSectionUiTest', function() {
test('testAutofillExtensionIndicator', function() {
// Initializing with fake prefs
const section = document.createElement('settings-autofill-section');
section.prefs = {autofill: {profile_enabled: {}}};
document.body.appendChild(section);
assertFalse(
!!section.shadowRoot!.querySelector('#autofillExtensionIndicator'));
section.set('prefs.autofill.profile_enabled.extensionId', 'test-id');
flush();
assertTrue(
!!section.shadowRoot!.querySelector('#autofillExtensionIndicator'));
document.body.removeChild(section);
});
test('verifyAddressDeleteSourceNotice', async () => {
const address = createAddressEntry();
const accountAddress = createAddressEntry();
accountAddress.metadata!.recordType =
chrome.autofillPrivate.AddressRecordType.ACCOUNT;
const autofillManager = new TestAutofillManager();
autofillManager.data.addresses = [address, accountAddress];
autofillManager.data.accountInfo = {
...STUB_USER_ACCOUNT_INFO,
isSyncEnabledForAutofillProfiles: true,
};
AutofillManagerImpl.setInstance(autofillManager);
const section = document.createElement('settings-autofill-section');
document.body.appendChild(section);
await autofillManager.whenCalled('getAddressList');
await flushTasks();
{
const dialog = await initiateRemoving(section, 0);
assertTrue(
!isVisible(dialog.$.accountAddressDescription),
'account notice should be invisible for non-account address');
assertTrue(
!isVisible(dialog.$.localAddressDescription),
'sync is enabled, an appropriate message should be visible');
assertTrue(
isVisible(dialog.$.syncAddressDescription),
'sync is enabled, an appropriate message should be visible');
dialog.$.dialog.close();
// Make sure closing clean-ups are finished.
await eventToPromise('close', dialog.$.dialog);
}
await flushTasks();
const changeListener =
autofillManager.lastCallback.setPersonalDataManagerListener;
assertTrue(
!!changeListener,
'PersonalDataChangedListener should be set in the section element');
// Imitate disabling sync.
changeListener(autofillManager.data.addresses, [], [], {
...STUB_USER_ACCOUNT_INFO,
});
{
const dialog = await initiateRemoving(section, 0);
assertTrue(
!isVisible(dialog.$.accountAddressDescription),
'account notice should be invisible for non-account address');
assertTrue(
isVisible(dialog.$.localAddressDescription),
'sync is disabled, an appropriate message should be visible');
assertTrue(
!isVisible(dialog.$.syncAddressDescription),
'sync is disabled, an appropriate message should be visible');
dialog.$.dialog.close();
// Make sure closing clean-ups are finished.
await eventToPromise('close', dialog.$.dialog);
}
await flushTasks();
// Imitate disabling sync.
changeListener(autofillManager.data.addresses, [], [], undefined);
{
const dialog = await initiateRemoving(section, 0);
assertTrue(
!isVisible(dialog.$.accountAddressDescription),
'account notice should be invisible for non-account address');
assertTrue(
isVisible(dialog.$.localAddressDescription),
'sync is disabled, an appropriate message should be visible');
assertTrue(
!isVisible(dialog.$.syncAddressDescription),
'sync is disabled, an appropriate message should be visible');
dialog.$.dialog.close();
// Make sure closing clean-ups are finished.
await eventToPromise('close', dialog.$.dialog);
}
await flushTasks();
changeListener(autofillManager.data.addresses, [], [], {
...STUB_USER_ACCOUNT_INFO,
isSyncEnabledForAutofillProfiles: true,
});
{
const dialog = await initiateRemoving(section, 1);
assertTrue(
isVisible(dialog.$.accountAddressDescription),
'account notice should be visible for non-account address');
assertTrue(
!isVisible(dialog.$.localAddressDescription),
'non-account messages should not be visible');
assertTrue(
!isVisible(dialog.$.syncAddressDescription),
'non-account messages should not be visible');
dialog.$.dialog.close();
// Make sure closing clean-ups are finished.
await eventToPromise('close', dialog.$.dialog);
}
document.body.removeChild(section);
});
test('verifyAddressEditSourceNotice', async () => {
const email = '[email protected]';
const address = createAddressEntry();
const accouontAddress = createAddressEntry();
accouontAddress.metadata!.recordType =
chrome.autofillPrivate.AddressRecordType.ACCOUNT;
const section =
await createAutofillSection([address, accouontAddress], {}, {
...STUB_USER_ACCOUNT_INFO,
email,
});
{
const dialog = await initiateEditing(section, 0);
assertFalse(
isVisible(dialog.$.accountSourceNotice),
'account notice should be invisible for non-account address');
dialog.$.dialog.close();
// Make sure closing clean-ups are finished.
await eventToPromise('close', dialog.$.dialog);
}
await flushTasks();
{
const dialog = await initiateEditing(section, 1);
assertTrue(
isVisible(dialog.$.accountSourceNotice),
'account notice should be visible for account address');
assertEquals(
dialog.$.accountSourceNotice.innerText,
section.i18n('editAccountAddressSourceNotice', email));
dialog.$.dialog.close();
// Make sure closing clean-ups are finished.
await eventToPromise('close', dialog.$.dialog);
}
document.body.removeChild(section);
});
});
suite('AutofillSectionFocusTest', function() {
// TODO(crbug.com/40279141): Fix the flakiness.
test.skip('verifyFocusLocationAfterRemoving', async () => {
const section = await createAutofillSection(
[
createAddressEntry(),
createAddressEntry(),
createAddressEntry(),
],
{profile_enabled: {value: true}});
const manager = AutofillManagerImpl.getInstance() as TestAutofillManager;
await deleteAddress(section, manager, 1);
const addressesAfterRemovingInTheMiddle =
section.$.addressList.querySelectorAll('.list-item');
assertTrue(
addressesAfterRemovingInTheMiddle[1]!.matches(':focus-within'),
'The focus should remain on the same index on the list (but next ' +
'to the removed address).');
await deleteAddress(section, manager, 1);
const addressesAfterRemovingLastInTheList =
section.$.addressList.querySelectorAll('.list-item');
assertTrue(
addressesAfterRemovingLastInTheList[0]!.matches(':focus-within'),
'After removing the last address on the list the focus should go ' +
'to the preivous address.');
await deleteAddress(section, manager, 0);
assertTrue(
section.$.addAddress.matches(':focus-within'),
'If there are no addresses remaining after removal the focus should ' +
'go to the Add button.');
document.body.removeChild(section);
});
});
suite('AutofillSectionAddressTests', function() {
let metricsTracker: MetricsTracker;
suiteSetup(function() {
CountryDetailManagerImpl.setInstance(new CountryDetailManagerTestImpl());
});
setup(function() {
document.body.innerHTML = window.trustedTypes!.emptyHTML;
metricsTracker = fakeMetricsPrivate();
});
test('verifyNoAddresses', async function() {
const section =
await createAutofillSection([], {profile_enabled: {value: true}});
const addressList = section.$.addressList;
assertTrue(!!addressList);
// 1 for the template element.
assertEquals(1, addressList.children.length);
assertFalse(section.$.noAddressesLabel.hidden);
assertFalse(section.$.addAddress.disabled);
assertFalse(section.$.autofillProfileToggle.disabled);
});
test('verifyAddressCount', async function() {
const addresses = [
createAddressEntry(),
createAddressEntry(),
createAddressEntry(),
createAddressEntry(),
createAddressEntry(),
];
const section = await createAutofillSection(
addresses, {profile_enabled: {value: true}});
const addressList = section.$.addressList;
assertTrue(!!addressList);
assertEquals(
addresses.length, addressList.querySelectorAll('.list-item').length);
assertTrue(section.$.noAddressesLabel.hidden);
assertFalse(section.$.autofillProfileToggle.disabled);
assertFalse(section.$.addAddress.disabled);
});
test('verifyAddressDisabled', async function() {
const section =
await createAutofillSection([], {profile_enabled: {value: false}});
assertFalse(section.$.autofillProfileToggle.disabled);
assertTrue(section.$.addAddress.hidden);
});
test('verifyAddressFields', async function() {
const address = createAddressEntry();
const section = await createAutofillSection([address], {});
const addressList = section.$.addressList;
const row = addressList.children[0];
assertTrue(!!row);
const addressSummary =
address.metadata!.summaryLabel + address.metadata!.summarySublabel;
let actualSummary = '';
// Eliminate white space between nodes!
const addressPieces = row!.querySelector('#addressSummary')!.children;
for (const addressPiece of addressPieces) {
actualSummary += addressPiece.textContent!.trim();
}
assertEquals(addressSummary, actualSummary);
});
test('verifyAddressLocalIndication', async () => {
loadTimeData.overrideValues({
syncEnableContactInfoDataTypeInTransportMode: false,
});
const autofillManager = new TestAutofillManager();
autofillManager.data.addresses = [createAddressEntry()];
autofillManager.data.accountInfo = {
...STUB_USER_ACCOUNT_INFO,
isSyncEnabledForAutofillProfiles: true,
};
AutofillManagerImpl.setInstance(autofillManager);
const section = document.createElement('settings-autofill-section');
document.body.appendChild(section);
await autofillManager.whenCalled('getAddressList');
await flushTasks();
const addressList = section.$.addressList;
assertFalse(
isVisible(addressList.children[0]!.querySelector('[icon*=cloud-off]')),
'Sync for addresses is enabled, the local indicator should be off.');
const changeListener =
autofillManager.lastCallback.setPersonalDataManagerListener!;
changeListener(autofillManager.data.addresses, [], [], STUB_USER_ACCOUNT_INFO);
assertFalse(
isVisible(addressList.children[0]!.querySelector('[icon*=cloud-off]')),
'Sync is disabled but the feature is off, the icon should be hidden.');
changeListener(autofillManager.data.addresses, [], [], undefined);
assertFalse(
isVisible(section.$.addressList.children[0]!.querySelector(
'[icon*=cloud-off]')),
'The local indicator should not be shown to logged-out users');
loadTimeData.overrideValues({
syncEnableContactInfoDataTypeInTransportMode: true,
});
changeListener(
autofillManager.data.addresses, [], [], STUB_USER_ACCOUNT_INFO);
assertTrue(
isVisible(addressList.children[0]!.querySelector('[icon*=cloud-off]')),
'Sync is disabled but the feature is on, the icon should be visible.');
document.body.removeChild(section);
});
test('verifyAddressRowButtonTriggersDropdown', async function() {
const address = createAddressEntry();
const section = await createAutofillSection([address], {});
const addressList = section.$.addressList;
const row = addressList.children[0];
assertTrue(!!row);
const menuButton = row!.querySelector<HTMLElement>('.address-menu');
assertTrue(!!menuButton);
menuButton!.click();
flush();
assertTrue(!!section.shadowRoot!.querySelector('#menuEditAddress'));
});
test('verifyAddAddressDialog', function() {
const address = createEmptyAddressEntry();
return createAddressDialog(address).then(function(dialog) {
const title = dialog.shadowRoot!.querySelector('[slot=title]')!;
assertEquals(
loadTimeData.getString('addAddressTitle'), title.textContent);
// A country is preselected.
const countrySelect = dialog.$.country;
assertTrue(!!countrySelect);
assertTrue(!!countrySelect.value);
});
});
test('verifyEditAddressDialog', function() {
return createAddressDialog(createAddressEntry()).then(function(dialog) {
const title = dialog.shadowRoot!.querySelector('[slot=title]')!;
assertEquals(
loadTimeData.getString('editAddressTitle'), title.textContent);
// Should be possible to save when editing because fields are
// populated.
assertFalse(dialog.$.saveButton.disabled);
});
});
// The first editable element should be focused by default.
test('verifyFirstFieldFocused', async function() {
const dialog = await createAddressDialog(createEmptyAddressEntry());
const currentFocus = dialog.shadowRoot!.activeElement;
const editableElements =
dialog.$.dialog.querySelectorAll('cr-input, select');
assertEquals(editableElements[0], currentFocus);
});
test('verifyRemoveAddressDialogConfirmed', async function() {
const autofillManager = new TestAutofillManager();
const removeAddressDialog =
await createRemoveAddressDialog(autofillManager);
// Wait for the dialog to open.
await whenAttributeIs(removeAddressDialog.$.dialog, 'open', '');
removeAddressDialog.$.remove.click();
// Wait for the dialog to close.
await eventToPromise('close', removeAddressDialog);
assertTrue(removeAddressDialog.wasConfirmed());
assertEquals(
1, metricsTracker.count('Autofill.ProfileDeleted.Settings', true));
assertEquals(
0, metricsTracker.count('Autofill.ProfileDeleted.Settings', false));
assertEquals(1, metricsTracker.count('Autofill.ProfileDeleted.Any', true));
assertEquals(0, metricsTracker.count('Autofill.ProfileDeleted.Any', false));
const expected = new AutofillManagerExpectations();
expected.requestedAddresses = 1;
expected.listeningAddresses = 1;
expected.removeAddress = 1;
autofillManager.assertExpectations(expected);
});
test('verifyRemoveAddressDialogCanceled', async function() {
const autofillManager = new TestAutofillManager();
const removeAddressDialog =
await createRemoveAddressDialog(autofillManager);
// Wait for the dialog to open.
await whenAttributeIs(removeAddressDialog.$.dialog, 'open', '');
removeAddressDialog.$.cancel.click();
// Wait for the dialog to close.
await eventToPromise('close', removeAddressDialog);
assertFalse(removeAddressDialog.wasConfirmed());
assertEquals(
0, metricsTracker.count('Autofill.ProfileDeleted.Settings', true));
assertEquals(
1, metricsTracker.count('Autofill.ProfileDeleted.Settings', false));
assertEquals(0, metricsTracker.count('Autofill.ProfileDeleted.Any', true));
assertEquals(1, metricsTracker.count('Autofill.ProfileDeleted.Any', false));
const expected = new AutofillManagerExpectations();
expected.requestedAddresses = 1;
expected.listeningAddresses = 1;
expected.removeAddress = 0;
autofillManager.assertExpectations(expected);
});
test('verifyCountryIsSaved', function() {
const address = createEmptyAddressEntry();
return createAddressDialog(address).then(function(dialog) {
const countrySelect = dialog.$.country;
assertTrue(!!countrySelect);
// The country should be pre-selected.
assertEquals('US', countrySelect.value);
countrySelect.value = 'GB';
countrySelect.dispatchEvent(new CustomEvent('change'));
flush();
assertEquals('GB', countrySelect.value);
});
});
test('verifyLanguageCodeIsSaved', function() {
const address = createEmptyAddressEntry();
return createAddressDialog(address).then(function(dialog) {
const countrySelect = dialog.$.country;
assertTrue(!!countrySelect);
// The first country is pre-selected.
assertEquals('US', countrySelect.value);
assertEquals('en', address.languageCode);
countrySelect.value = 'IL';
countrySelect.dispatchEvent(new CustomEvent('change'));
flush();
return eventToPromise('on-update-address-wrapper', dialog)
.then(function() {
assertEquals('IL', countrySelect.value);
assertEquals('iw', address.languageCode);
});
});
});
test('verifyPhoneAndEmailAreSaved', async () => {
const address = createEmptyAddressEntry();
const dialog = await createAddressDialog(address);
const rows = dialog.$.dialog.querySelectorAll('.address-row');
assertGT(rows.length, 0, 'dialog should contain address rows');
const lastRow = rows[rows.length - 1]!;
const phoneInput =
lastRow.querySelector<CrInputElement>('cr-input:nth-of-type(1)');
const emailInput =
lastRow.querySelector<CrInputElement>('cr-input:nth-of-type(2)');
assertTrue(!!phoneInput, 'phone element should be the first cr-input');
assertTrue(!!emailInput, 'email element should be the second cr-input');
assertEquals(undefined, phoneInput.value);
assertFalse(
!!getAddressFieldValue(address, FieldType.PHONE_HOME_WHOLE_NUMBER));
assertEquals(undefined, emailInput.value);
assertFalse(!!getAddressFieldValue(address, FieldType.EMAIL_ADDRESS));
const phoneNumber = '(555) 555-5555';
const emailAddress = '[email protected]';
phoneInput.value = phoneNumber;
emailInput.value = emailAddress;
await Promise.all([phoneInput.updateComplete, emailInput.updateComplete]);
await expectEvent(dialog, 'save-address', function() {
dialog.$.saveButton.click();
});
assertEquals(phoneNumber, phoneInput.value);
assertEquals(
phoneNumber,
getAddressFieldValue(address, FieldType.PHONE_HOME_WHOLE_NUMBER));
assertEquals(emailAddress, emailInput.value);
assertEquals(
emailAddress, getAddressFieldValue(address, FieldType.EMAIL_ADDRESS));
});
// TODO(crbug.com/40279141): Fix the flakiness.
test.skip('verifyPhoneAndEmailAreRemoved', function() {
const address = createEmptyAddressEntry();
const phoneNumber = '(555) 555-5555';
const emailAddress = '[email protected]';
address.fields.push({
type: FieldType.ADDRESS_HOME_COUNTRY,
value: 'US',
}); // Set to allow save to be active.
address.fields.push({
type: FieldType.PHONE_HOME_WHOLE_NUMBER,
value: phoneNumber,
});
address.fields.push({type: FieldType.EMAIL_ADDRESS, value: emailAddress});
return createAddressDialog(address).then(function(dialog) {
const rows = dialog.$.dialog.querySelectorAll('.address-row');
assertGT(rows.length, 0, 'dialog should contain address rows');
const lastRow = rows[rows.length - 1]!;
const phoneInput =
lastRow.querySelector<CrInputElement>('cr-input:nth-of-type(1)');
const emailInput =
lastRow.querySelector<CrInputElement>('cr-input:nth-of-type(2)');
assertTrue(!!phoneInput, 'phone element should be the first cr-input');
assertTrue(!!emailInput, 'email element should be the second cr-input');
assertEquals(phoneNumber, phoneInput.value);
assertEquals(emailAddress, emailInput.value);
phoneInput.value = '';
emailInput.value = '';
return expectEvent(dialog, 'save-address', function() {
dialog.$.saveButton.click();
}).then(function() {
assertFalse(
!!getAddressFieldValue(address, FieldType.PHONE_HOME_WHOLE_NUMBER));
assertFalse(!!getAddressFieldValue(address, FieldType.EMAIL_ADDRESS));
});
});
});
// Test will set a value of 'foo' in each text field and verify that the
// save button is enabled, then it will clear the field and verify that the
// save button is disabled. Test passes after all elements have been tested.
test('verifySaveIsNotClickableIfAllInputFieldsAreEmpty', async function() {
const dialog = await createAddressDialog(createEmptyAddressEntry());
const saveButton = dialog.$.saveButton;
const testElements =
dialog.$.dialog.querySelectorAll<CrTextareaElement|CrInputElement>(
'cr-textarea, cr-input');
// The country can be preselected. Clear it to ensure the form is empty.
await expectEvent(dialog, 'on-update-can-save', function() {
const countrySelect = dialog.$.country;
assertTrue(!!countrySelect);
countrySelect.value = '';
countrySelect.dispatchEvent(new CustomEvent('change'));
});
// Default country is 'US' expecting: Name, Organization,
// Street address, City, State, ZIP code, Phone, and Email.
// Unless Company name is disabled.
assertEquals(8, testElements.length);
assertTrue(saveButton.disabled);
for (const element of testElements) {
await expectEvent(dialog, 'on-update-can-save', function() {
element.value = 'foo';
});
assertFalse(saveButton.disabled);
await expectEvent(dialog, 'on-update-can-save', function() {
element.value = '';
});
assertTrue(saveButton.disabled);
}
});
// Setting the country should allow the address to be saved.
test('verifySaveIsNotClickableIfCountryNotSet', async function() {
function simulateCountryChange(countryCode: string) {
const countrySelect = dialog.$.country;
assertTrue(!!countrySelect);
countrySelect.value = countryCode;
countrySelect.dispatchEvent(new CustomEvent('change'));
}
const dialog = await createAddressDialog(createEmptyAddressEntry());
const countrySelect = dialog.$.country;
assertTrue(!!countrySelect);
// A country code is preselected.
assertFalse(dialog.$.saveButton.disabled);
assertEquals(countrySelect.value, 'US');
await expectEvent(
dialog, 'on-update-can-save', simulateCountryChange.bind(null, 'GB'));
assertFalse(dialog.$.saveButton.disabled);
await expectEvent(
dialog, 'on-update-can-save', simulateCountryChange.bind(null, ''));
assertTrue(dialog.$.saveButton.disabled);
});
// Test will timeout if save-address event is not fired.
test('verifyDefaultCountryIsAppliedWhenSaving', function() {
const address = createEmptyAddressEntry();
address.fields.push({type: FieldType.NAME_FULL, value: 'Name'});
return createAddressDialog(address).then(function(dialog) {
return expectEvent(dialog, 'save-address', function() {
// Verify |countryCode| is not set.
assertEquals(
undefined,
getAddressFieldValue(
address, FieldType.ADDRESS_HOME_COUNTRY));
dialog.$.saveButton.click();
}).then(function(_event) {
// 'US' is the default country for these tests.
const countrySelect = dialog.$.country;
assertTrue(!!countrySelect);
assertEquals('US', countrySelect.value);
});
});
});
test(
'verifyNoSaveAddressEventWhenEditDialogCancelButtonIsClicked',
function(done) {
createAddressDialog(createAddressEntry()).then(function(dialog) {
eventToPromise('save-address', dialog).then(function() {
// Fail the test because the save event should not be fired when
// the cancel is clicked.
assertTrue(true);
});
eventToPromise('cancel', dialog).then(function() {
// Test is |done| in a timeout in order to ensure that
// 'save-address' is NOT fired after this test.
assertEquals(
1,
metricsTracker.count('Autofill.Settings.EditAddress', false));
window.setTimeout(done, 100);
});
dialog.$.cancelButton.click();
});
});
test('verifyNoCancelEventWhenEditDialogSaveButtonIsClicked', function(done) {
createAddressDialog(createAddressEntry()).then(function(dialog) {
eventToPromise('cancel', dialog).then(function() {
// Fail the test because the cancel event should not be fired when
// the save is clicked.
assertTrue(false);
});
eventToPromise('save-address', dialog).then(function() {
// Test is |done| in a timeout in order to ensure that
// 'save-address' is NOT fired after this test.
assertEquals(
1, metricsTracker.count('Autofill.Settings.EditAddress', true));
window.setTimeout(done, 100);
});
dialog.$.saveButton.click();
});
});
// TODO(crbug.com/40279141): Fix the flakiness.
test.skip('verifySyncSourceNoticeForNewAddress', async () => {
const section = await createAutofillSection([], {}, {
...STUB_USER_ACCOUNT_INFO,
email: '[email protected]',
isSyncEnabledForAutofillProfiles: true,
isEligibleForAddressAccountStorage: false,
});
const dialog = await openAddressDialog(section);
assertTrue(
!isVisible(dialog.$.accountSourceNotice),
'account notice should be invisible for non-account address');
document.body.removeChild(section);
});
test('verifyAccountSourceNoticeForNewAddress', async () => {
const email = '[email protected]';
const section = await createAutofillSection([], {}, {
...STUB_USER_ACCOUNT_INFO,
email,
isSyncEnabledForAutofillProfiles: true,
isEligibleForAddressAccountStorage: true,
});
const dialog = await openAddressDialog(section);
assertTrue(
isVisible(dialog.$.accountSourceNotice),
'account notice should be visible as the user is eligible');
assertEquals(
dialog.$.accountSourceNotice.innerText,
section.i18n('newAccountAddressSourceNotice', email));
document.body.removeChild(section);
});
// TODO(crbug.com/40943238): Remove when toggle becomes available on the Sync
// page for non-syncing users.
test('verifyAutofillSyncToggleAvailability', async () => {
const autofillManager = new TestAutofillManager();
autofillManager.data.accountInfo = {
...STUB_USER_ACCOUNT_INFO,
isAutofillSyncToggleAvailable: false,
};
AutofillManagerImpl.setInstance(autofillManager);
const section = document.createElement('settings-autofill-section');
document.body.appendChild(section);
await autofillManager.whenCalled('getAddressList');
await flushTasks();
assertFalse(
isVisible(section.$.autofillSyncToggle),
'The toggle should not be visible because of ' +
'accountInfo.isAutofillSyncToggleAvailable == false');
const changeListener =
autofillManager.lastCallback.setPersonalDataManagerListener;
assertTrue(
!!changeListener,
'PersonalDataChangedListener should be set in the section element');
// Imitate native code `PersonalDataChangedListener` triggering.
changeListener([], [], [], {
...STUB_USER_ACCOUNT_INFO,
isAutofillSyncToggleAvailable: true,
isAutofillSyncToggleEnabled: false,
});
await flushTasks();
assertTrue(
isVisible(section.$.autofillSyncToggle),
'The toggle should be visible because of ' +
'accountInfo.isAutofillSyncToggleAvailable == true');
assertFalse(
section.$.autofillSyncToggle.checked,
'The toggle should not be checked because of ' +
'accountInfo.isAutofillSyncToggleEnabled == false');
// Imitate native code `PersonalDataChangedListener` triggering.
changeListener([], [], [], {
...STUB_USER_ACCOUNT_INFO,
isAutofillSyncToggleAvailable: true,
isAutofillSyncToggleEnabled: true,
});
await flushTasks();
assertTrue(
isVisible(section.$.autofillSyncToggle),
'The toggle should be visible because of ' +
'accountInfo.isAutofillSyncToggleAvailable == true');
assertTrue(
section.$.autofillSyncToggle.checked,
'The toggle should be checked because of ' +
'accountInfo.isAutofillSyncToggleEnabled == true');
});
// TODO(crbug.com/40943238): Remove as part of the cleanup work for the ticket.
test('verifyAutofillSyncToggleChanges', async () => {
const autofillManager = new TestAutofillManager();
autofillManager.data.accountInfo = {
...STUB_USER_ACCOUNT_INFO,
isAutofillSyncToggleAvailable: true,
isAutofillSyncToggleEnabled: false,
};
AutofillManagerImpl.setInstance(autofillManager);
const section = document.createElement('settings-autofill-section');
document.body.appendChild(section);
await autofillManager.whenCalled('getAddressList');
await flushTasks();
const changeListener =
autofillManager.lastCallback.setPersonalDataManagerListener;
assertTrue(
!!changeListener,
'PersonalDataChangedListener should be set in the section element');
assertFalse(
section.$.autofillSyncToggle.checked,
'The toggle should not be checked because of initial ' +
'accountInfo.isAutofillSyncToggleEnabled == false');
section.$.autofillSyncToggle.click();
await section.$.autofillSyncToggle.updateComplete;
assertTrue(
section.$.autofillSyncToggle.checked,
'The toggle should be checked after the click.');
assertEquals(
autofillManager.getCallCount('setAutofillSyncToggleEnabled'), 1);
section.$.autofillSyncToggle.click();
await section.$.autofillSyncToggle.updateComplete;
assertFalse(
section.$.autofillSyncToggle.checked,
'The toggle should not be checked after another click.');
assertEquals(
autofillManager.getCallCount('setAutofillSyncToggleEnabled'), 2);
// Imitate native code `PersonalDataChangedListener` triggering. Notice
// that it was unchecked after the second click, but the listener was
// given `true`, the following assert checks it an covers the case when
// the toggle was not updated in the native code for some reason.
changeListener([], [], [], {
...STUB_USER_ACCOUNT_INFO,
isAutofillSyncToggleAvailable: true,
isAutofillSyncToggleEnabled: true,
});
await flushTasks();
assertTrue(
section.$.autofillSyncToggle.checked,
'The toggle should be checked because of ' +
'accountInfo.isAutofillSyncToggleEnabled == true');
});
});
suite('AutofillSectionAddressLocaleTests', function() {
suiteSetup(function() {
CountryDetailManagerImpl.setInstance(new CountryDetailManagerTestImpl());
});
setup(function() {
document.body.innerHTML = window.trustedTypes!.emptyHTML;
});
// US address has 3 fields on the same line.
test('verifyEditingUSAddress', function() {
const address = createEmptyAddressEntry();
address.fields = [
{type: FieldType.NAME_FULL, value: 'Name'},
{type: FieldType.COMPANY_NAME, value: 'Organization'},
{
type: FieldType.ADDRESS_HOME_STREET_ADDRESS,
value: 'Street address',
},
{type: FieldType.ADDRESS_HOME_STATE, value: 'State'},
{type: FieldType.ADDRESS_HOME_CITY, value: 'City'},
{type: FieldType.ADDRESS_HOME_ZIP, value: 'ZIP code'},
{type: FieldType.ADDRESS_HOME_COUNTRY, value: 'US'},
{type: FieldType.PHONE_HOME_WHOLE_NUMBER, value: 'Phone'},
{type: FieldType.EMAIL_ADDRESS, value: 'Email'},
];
return createAddressDialog(address).then(function(dialog) {
const rows = dialog.$.dialog.querySelectorAll('.address-row');
assertEquals(6, rows.length);
let index = 0;
// Country
let row = rows[index]!;
const countrySelect = row.querySelector('select');
assertTrue(!!countrySelect);
assertEquals(
'United States',
countrySelect!.selectedOptions[0]!.textContent!.trim());
index++;
// Name
row = rows[index]!;
let cols = row.querySelectorAll<CrTextareaElement|CrInputElement>(
'.address-column');
assertEquals(1, cols.length);
assertEquals(
getAddressFieldValue(address, FieldType.NAME_FULL), cols[0]!.value);
index++;
// Organization
row = rows[index]!;
cols = row.querySelectorAll<CrTextareaElement|CrInputElement>(
'.address-column');
assertEquals(1, cols.length);
assertEquals(
getAddressFieldValue(address, FieldType.COMPANY_NAME),
cols[0]!.value);
index++;
// Street address
row = rows[index]!;
cols = row.querySelectorAll<CrTextareaElement|CrInputElement>(
'.address-column');
assertEquals(1, cols.length);
assertEquals(
getAddressFieldValue(address, FieldType.ADDRESS_HOME_STREET_ADDRESS),
cols[0]!.value);
index++;
// City, State, ZIP code
row = rows[index]!;
cols = row.querySelectorAll<CrTextareaElement|CrInputElement>(
'.address-column');
assertEquals(3, cols.length);
assertEquals(
getAddressFieldValue(address, FieldType.ADDRESS_HOME_CITY),
cols[0]!.value);
assertEquals(
getAddressFieldValue(address, FieldType.ADDRESS_HOME_STATE),
cols[1]!.value);
assertEquals(
getAddressFieldValue(address, FieldType.ADDRESS_HOME_ZIP),
cols[2]!.value);
index++;
// Phone, Email
row = rows[index]!;
cols = row.querySelectorAll<CrTextareaElement|CrInputElement>(
'.address-column');
assertEquals(2, cols.length);
assertEquals(
getAddressFieldValue(address, FieldType.PHONE_HOME_WHOLE_NUMBER),
cols[0]!.value);
assertEquals(
getAddressFieldValue(address, FieldType.EMAIL_ADDRESS),
cols[1]!.value);
});
});
// GB address has 1 field per line for all lines that change.
test('verifyEditingGBAddress', function() {
const address = createEmptyAddressEntry();
address.fields = [
{type: FieldType.NAME_FULL, value: 'Name'},
{type: FieldType.COMPANY_NAME, value: 'Organization'},
{
type: FieldType.ADDRESS_HOME_STREET_ADDRESS,
value: 'Street address',
},
{type: FieldType.ADDRESS_HOME_STATE, value: 'County'},
{type: FieldType.ADDRESS_HOME_CITY, value: 'Post town'},
{type: FieldType.ADDRESS_HOME_ZIP, value: 'Postal code'},
{type: FieldType.ADDRESS_HOME_COUNTRY, value: 'GB'},
{type: FieldType.PHONE_HOME_WHOLE_NUMBER, value: 'Phone'},
{type: FieldType.EMAIL_ADDRESS, value: 'Email'},
];
return createAddressDialog(address).then(function(dialog) {
const rows = dialog.$.dialog.querySelectorAll('.address-row');
assertEquals(8, rows.length);
let index = 0;
// Country
let row = rows[index]!;
const countrySelect = row.querySelector('select');
assertTrue(!!countrySelect);
assertEquals(
'United Kingdom',
countrySelect!.selectedOptions[0]!.textContent!.trim());
index++;
// Name
row = rows[index]!;
let cols = row.querySelectorAll<CrTextareaElement|CrInputElement>(
'.address-column');
assertEquals(1, cols.length);
assertEquals(
getAddressFieldValue(address, FieldType.NAME_FULL), cols[0]!.value);
index++;
// Organization
row = rows[index]!;
cols = row.querySelectorAll<CrTextareaElement|CrInputElement>(
'.address-column');
assertEquals(1, cols.length);
assertEquals(
getAddressFieldValue(address, FieldType.COMPANY_NAME),
cols[0]!.value);
index++;
// Street address
row = rows[index]!;
cols = row.querySelectorAll<CrTextareaElement|CrInputElement>(
'.address-column');
assertEquals(1, cols.length);
assertEquals(
getAddressFieldValue(address, FieldType.ADDRESS_HOME_STREET_ADDRESS),
cols[0]!.value);
index++;
// Post Town
row = rows[index]!;
cols = row.querySelectorAll<CrTextareaElement|CrInputElement>(
'.address-column');
assertEquals(1, cols.length);
assertEquals(
getAddressFieldValue(address, FieldType.ADDRESS_HOME_CITY),
cols[0]!.value);
index++;
// Postal code
row = rows[index]!;
cols = row.querySelectorAll<CrTextareaElement|CrInputElement>(
'.address-column');
assertEquals(1, cols.length);
assertEquals(
getAddressFieldValue(address, FieldType.ADDRESS_HOME_ZIP),
cols[0]!.value);
index++;
// County
row = rows[index]!;
cols = row.querySelectorAll<CrTextareaElement|CrInputElement>(
'.address-column');
assertEquals(1, cols.length);
assertEquals(
getAddressFieldValue(address, FieldType.ADDRESS_HOME_STATE),
cols[0]!.value);
index++;
// Phone, Email
row = rows[index]!;
cols = row.querySelectorAll<CrTextareaElement|CrInputElement>(
'.address-column');
assertEquals(2, cols.length);
assertEquals(
getAddressFieldValue(address, FieldType.PHONE_HOME_WHOLE_NUMBER),
cols[0]!.value);
assertEquals(
getAddressFieldValue(address, FieldType.EMAIL_ADDRESS),
cols[1]!.value);
});
});
// IL address has 2 fields on the same line and is an RTL locale.
// RTL locale shouldn't affect this test.
test('verifyEditingILAddress', function() {
const address = createEmptyAddressEntry();
address.fields = [
{type: FieldType.NAME_FULL, value: 'Name'},
{type: FieldType.COMPANY_NAME, value: 'Organization'},
{
type: FieldType.ADDRESS_HOME_STREET_ADDRESS,
value: 'Street address',
},
{type: FieldType.ADDRESS_HOME_STATE, value: 'State'},
{type: FieldType.ADDRESS_HOME_CITY, value: 'City'},
{type: FieldType.ADDRESS_HOME_ZIP, value: 'Postal code'},
{type: FieldType.ADDRESS_HOME_COUNTRY, value: 'IL'},
{type: FieldType.PHONE_HOME_WHOLE_NUMBER, value: 'Phone'},
{type: FieldType.EMAIL_ADDRESS, value: 'Email'},
];
return createAddressDialog(address).then(function(dialog) {
const rows = dialog.$.dialog.querySelectorAll('.address-row');
assertEquals(6, rows.length);
let index = 0;
// Country
let row = rows[index]!;
const countrySelect = row.querySelector('select');
assertTrue(!!countrySelect);
assertEquals(
'Israel', countrySelect!.selectedOptions[0]!.textContent!.trim());
index++;
// Name
row = rows[index]!;
let cols = row.querySelectorAll<CrTextareaElement|CrInputElement>(
'.address-column');
assertEquals(1, cols.length);
assertEquals(
getAddressFieldValue(address, FieldType.NAME_FULL)!, cols[0]!.value);
index++;
// Organization
row = rows[index]!;
cols = row.querySelectorAll<CrTextareaElement|CrInputElement>(
'.address-column');
assertEquals(1, cols.length);
assertEquals(
getAddressFieldValue(address, FieldType.COMPANY_NAME),
cols[0]!.value);
index++;
// Street address
row = rows[index]!;
cols = row.querySelectorAll<CrTextareaElement|CrInputElement>(
'.address-column');
assertEquals(1, cols.length);
assertEquals(
getAddressFieldValue(address, FieldType.ADDRESS_HOME_STREET_ADDRESS),
cols[0]!.value);
index++;
// City, Postal code
row = rows[index]!;
cols = row.querySelectorAll<CrTextareaElement|CrInputElement>(
'.address-column');
assertEquals(2, cols.length);
assertEquals(
getAddressFieldValue(address, FieldType.ADDRESS_HOME_CITY),
cols[0]!.value);
assertEquals(
getAddressFieldValue(address, FieldType.ADDRESS_HOME_ZIP),
cols[1]!.value);
index++;
// Phone, Email
row = rows[index]!;
cols = row.querySelectorAll<CrTextareaElement|CrInputElement>(
'.address-column');
assertEquals(2, cols.length);
assertEquals(
getAddressFieldValue(address, FieldType.PHONE_HOME_WHOLE_NUMBER),
cols[0]!.value);
assertEquals(
getAddressFieldValue(address, FieldType.EMAIL_ADDRESS),
cols[1]!.value);
});
});
// US has an extra field 'State'. Validate that this field is
// persisted when switching to IL then back to US.
test('verifyAddressPersistanceWhenSwitchingCountries', function() {
const address = createEmptyAddressEntry();
const experimental_fields_count = 1;
address.fields.push({type: FieldType.ADDRESS_HOME_COUNTRY, value: 'US'});
return createAddressDialog(address).then(function(dialog) {
const city = 'Los Angeles';
const state = 'CA';
const zip = '90291';
const countrySelect = dialog.$.country;
assertTrue(!!countrySelect);
return expectEvent(
dialog, 'on-update-address-wrapper',
function() {
// US:
const rows =
dialog.$.dialog.querySelectorAll('.address-row');
assertEquals(5 + experimental_fields_count, rows.length);
// City, State, ZIP code
const row = rows[3 + experimental_fields_count]!;
const cols =
row.querySelectorAll<CrTextareaElement|CrInputElement>(
'.address-column');
assertEquals(3, cols.length);
cols[0]!.value = city;
cols[1]!.value = state;
cols[2]!.value = zip;
countrySelect.value = 'IL';
countrySelect.dispatchEvent(new CustomEvent('change'));
})
.then(function() {
return expectEvent(dialog, 'on-update-address-wrapper', function() {
// IL:
const rows = dialog.$.dialog.querySelectorAll('.address-row');
assertEquals(5 + experimental_fields_count, rows.length);
// City, Postal code
const row = rows[3 + experimental_fields_count]!;
const cols =
row.querySelectorAll<CrTextareaElement|CrInputElement>(
'.address-column');
assertEquals(2, cols.length);
assertEquals(city, cols[0]!.value);
assertEquals(zip, cols[1]!.value);
countrySelect.value = 'US';
countrySelect.dispatchEvent(new CustomEvent('change'));
});
})
.then(function() {
// US:
const rows = dialog.$.dialog.querySelectorAll('.address-row');
assertEquals(5 + experimental_fields_count, rows.length);
// City, State, ZIP code
const row = rows[3 + experimental_fields_count]!;
const cols = row.querySelectorAll<CrTextareaElement|CrInputElement>(
'.address-column');
assertEquals(3, cols.length);
assertEquals(city, cols[0]!.value);
assertEquals(state, cols[1]!.value);
assertEquals(zip, cols[2]!.value);
});
});
});
});
suite('PlusAddressesTest', function() {
const fakeUrl = 'https://foo.bar';
let openWindowProxy: TestOpenWindowProxy;
setup(function() {
openWindowProxy = new TestOpenWindowProxy();
OpenWindowProxyImpl.setInstance(openWindowProxy);
loadTimeData.overrideValues({
// Required to show the plus address management entry.
plusAddressEnabled: true,
plusAddressManagementUrl: fakeUrl,
});
});
test('verifyPlusAddressManagementEntryExistence', async function() {
const autofillSection = await createAutofillSection([], {});
const plusAddressButton =
autofillSection.shadowRoot!.querySelector<CrLinkRowElement>(
'#plusAddressSettingsButton');
assertTrue(!!plusAddressButton);
autofillSection.remove();
});
test(
'verifyPlusAddressManagementEntryExistenceWhenNotEnabled',
async function() {
loadTimeData.overrideValues({
plusAddressEnabled: false,
});
const autofillSection = await createAutofillSection([], {});
const plusAddressButton =
autofillSection.shadowRoot!.querySelector<CrLinkRowElement>(
'#plusAddressSettingsButton');
assertFalse(!!plusAddressButton);
autofillSection.remove();
});
test(
'verifyClickingPlusAddressManagementEntryOpensWebsite', async function() {
const autofillSection = await createAutofillSection([], {});
const plusAddressButton =
autofillSection.shadowRoot!.querySelector<CrLinkRowElement>(
'#plusAddressSettingsButton');
assertTrue(!!plusAddressButton);
// Validate that, when present, the button results in opening the URL
// passed in via the `loadTimeData` override.
plusAddressButton.click();
const url = await openWindowProxy.whenCalled('openUrl');
assertEquals(url, fakeUrl);
autofillSection.remove();
});
});