// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'chrome://nearby/strings.m.js';
import 'chrome://nearby/shared/nearby_contact_visibility.js';
import 'chrome://webui-test/chromeos/mojo_webui_test_support.js';
import {setContactManagerForTesting} from 'chrome://nearby/shared/nearby_contact_manager.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {DataUsage, FastInitiationNotificationState, Visibility} from 'chrome://resources/mojo/chromeos/ash/services/nearby/public/mojom/nearby_share_settings.mojom-webui.js';
import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
import {assertEquals, assertFalse, assertGE, assertTrue} from '../../chromeos/chai_assert.js';
import {isChildVisible} from '../../chromeos/test_util.js';
import {FakeContactManager} from './fake_nearby_contact_manager.js';
suite('nearby-contact-visibility', () => {
/** @type {!NearbyContactVisibilityElement} */
let visibilityElement;
/** @type {!FakeContactManager} */
const fakeContactManager = new FakeContactManager();
setup(function() {
document.body.innerHTML = trustedTypes.emptyHTML;
setContactManagerForTesting(fakeContactManager);
visibilityElement = /** @type {!NearbyContactVisibilityElement} */ (
document.createElement('nearby-contact-visibility'));
visibilityElement.settings = /** @type {!NearbySettings} */ ({
enabled: false,
fastInitiationNotificationState: FastInitiationNotificationState.kEnabled,
isFastInitiationHardwareSupported: true,
deviceName: 'deviceName',
dataUsage: DataUsage.kOnline,
visibility: Visibility.kUnknown,
isOnboardingComplete: false,
allowedContacts: [],
});
document.body.appendChild(visibilityElement);
});
teardown(() => {
visibilityElement.remove();
});
function succeedContactDownload() {
fakeContactManager.setupContactRecords();
fakeContactManager.completeDownload();
}
/**
* @return {boolean} true when zero state elements are visible
*/
function isNoContactsSectionVisible() {
return isChildVisible(visibilityElement, '#noContactsContainer', false);
}
/**
* @return {boolean} true when zero state elements are visible
*/
function isZeroStateVisible() {
return isChildVisible(visibilityElement, '#zeroStateContainer', false);
}
/**
* @return {boolean} true when failed download stat is visible
*/
function isDownloadContactsFailedVisible() {
return isChildVisible(visibilityElement, '#contactsFailed', false);
}
/**
* @return {boolean} true when pending contacts state is visible
*/
function isDownloadContactsPendingVisible() {
return isChildVisible(visibilityElement, '#contactsPending', false);
}
/**
* @return {boolean} true when the checkboxes for contacts are visible
*/
function areContactCheckBoxesVisibleAndAllContactsToggledOff() {
return visibilityElement.selectedVisibility === 'contacts' &&
!visibilityElement.isAllContactsToggledOn_;
}
/**
* @return {boolean} true when visibility selection radio group is disabled
*/
function isRadioGroupDisabled() {
return visibilityElement.shadowRoot.querySelector('#visibilityRadioGroup')
.disabled;
}
/**
* @return {boolean} true when the unreachable contacts message is visibile
*/
function isUnreachableMessageVisible() {
return isChildVisible(visibilityElement, '#unreachableMessage', false);
}
/**
* Checks the state of the contacts toggle button group
* @param {boolean} contacts is contacts checked?
* @param {boolean} yourDevices is yourDevices checked?
* @param {boolean} no is noContacts checked?
*/
function assertToggleState(contacts, yourDevices, noContacts) {
assertEquals(
contacts,
visibilityElement.shadowRoot.querySelector('#contacts').checked);
assertEquals(
yourDevices,
visibilityElement.shadowRoot.querySelector('#yourDevices').checked);
assertEquals(
noContacts,
visibilityElement.shadowRoot.querySelector('#noContacts').checked);
}
/**
* If visibility is set to kSelectedDevices, checks that each contact is
* toggled as expected.
* @param {Array<boolean>} expected state of contacts toggled in
* order from top to bottom on the page.
*/
function assertContactsToggled(expected) {
assertFalse(visibilityElement.isAllContactsToggledOn_);
const contacts = visibilityElement.contacts;
assertEquals(contacts.length, expected.length);
for (let i = 0; i < contacts.length; i++) {
assertEquals(contacts[i].checked, expected[i]);
}
}
test('Downloads failed show failure ui', async function() {
// Failed the download right away so we see the failure screen.
fakeContactManager.failDownload();
visibilityElement.set('settings.visibility', Visibility.kSelectedContacts);
await waitAfterNextRender(visibilityElement);
assertToggleState(
/*contacts=*/ true, /*yourDevices=*/ false, /*no=*/ false);
areContactCheckBoxesVisibleAndAllContactsToggledOff();
assertFalse(isZeroStateVisible());
assertFalse(isNoContactsSectionVisible());
assertTrue(isDownloadContactsFailedVisible());
assertFalse(isDownloadContactsPendingVisible());
// If we click retry, we should go into pending state.
visibilityElement.shadowRoot.querySelector('#tryAgainLink').click();
await waitAfterNextRender(visibilityElement);
assertFalse(isDownloadContactsFailedVisible());
assertTrue(isDownloadContactsPendingVisible());
// If we succeed the download we should see results in the list.
succeedContactDownload();
await waitAfterNextRender(visibilityElement);
assertFalse(isDownloadContactsFailedVisible());
assertFalse(isDownloadContactsPendingVisible());
assertTrue(areContactCheckBoxesVisibleAndAllContactsToggledOff());
const items = visibilityElement.shadowRoot.querySelector('#contactList')
.querySelectorAll('.contact-item');
assertEquals(fakeContactManager.contactRecords.length, items.length);
});
test('Radio group disabled until successful download', async function() {
// Radio group disabled after download failure
fakeContactManager.failDownload();
await waitAfterNextRender(visibilityElement);
assertTrue(isDownloadContactsFailedVisible());
assertTrue(isRadioGroupDisabled());
// Radio group disabled while downloading
visibilityElement.shadowRoot.querySelector('#tryAgainLink').click();
await waitAfterNextRender(visibilityElement);
assertTrue(isDownloadContactsPendingVisible());
assertTrue(isRadioGroupDisabled());
// Radio group enabled after successful download
succeedContactDownload();
await waitAfterNextRender(visibilityElement);
assertFalse(isRadioGroupDisabled());
});
test('Visibility component shows zero state for kUnknown', async function() {
succeedContactDownload();
// need to wait for the next render to see if the zero
await waitAfterNextRender(visibilityElement);
assertToggleState(
/*contacts=*/ false, /*yourDevices=*/ false, /*no=*/ false);
assertTrue(isZeroStateVisible());
assertFalse(isNoContactsSectionVisible());
});
test(
'Visibility component shows contacts for kAllContacts', async function() {
succeedContactDownload();
visibilityElement.set('settings.visibility', Visibility.kAllContacts);
// need to wait for the next render to see results
await waitAfterNextRender(visibilityElement);
assertToggleState(
/*contacts=*/ true, /*yourDevices=*/ false, /*no=*/ false);
assertFalse(isZeroStateVisible());
assertFalse(areContactCheckBoxesVisibleAndAllContactsToggledOff());
assertFalse(isNoContactsSectionVisible());
});
test(
'Visibility component shows contacts for kSelectedContacts',
async function() {
succeedContactDownload();
visibilityElement.set(
'settings.visibility', Visibility.kSelectedContacts);
// need to wait for the next render to see results
await waitAfterNextRender(visibilityElement);
assertToggleState(
/*contacts=*/ true, /*yourDevices=*/ false, /*no=*/ false);
areContactCheckBoxesVisibleAndAllContactsToggledOff();
assertFalse(isZeroStateVisible());
assertTrue(areContactCheckBoxesVisibleAndAllContactsToggledOff());
assertFalse(isNoContactsSectionVisible());
});
test('Visibility component shows no contacts for kNoOne', async function() {
visibilityElement.set('settings.visibility', Visibility.kNoOne);
succeedContactDownload();
// need to wait for the next render to see results
await waitAfterNextRender(visibilityElement);
assertToggleState(
/*contacts=*/ false, /*yourDevices=*/ false, /*no=*/ true);
assertFalse(isZeroStateVisible());
assertEquals(visibilityElement.querySelectorAll('#contactList').length, 0);
assertFalse(isNoContactsSectionVisible());
});
test(
'Visibility component shows no contacts when there are zero contacts',
async function() {
fakeContactManager.contactRecords = [];
fakeContactManager.completeDownload();
visibilityElement.set('settings.visibility', Visibility.kAllContacts);
visibilityElement.set('contacts', []);
// need to wait for the next render to see results
await waitAfterNextRender(visibilityElement);
assertToggleState(
/*contacts=*/ true, /*yourDevices=*/ false, /*no=*/ false);
assertFalse(isZeroStateVisible());
assertFalse(areContactCheckBoxesVisibleAndAllContactsToggledOff());
assertTrue(isNoContactsSectionVisible());
});
test(
'Unreachable message appears for 1 unreachable contact',
async function() {
fakeContactManager.setupContactRecords();
fakeContactManager.setNumUnreachable(1);
fakeContactManager.completeDownload();
visibilityElement.set('settings.visibility', Visibility.kAllContacts);
// need to wait for the next render to see results
await waitAfterNextRender(visibilityElement);
assertTrue(isUnreachableMessageVisible());
});
test(
'Unreachable message appears for more than 1 unreachable contact',
async function() {
fakeContactManager.setupContactRecords();
fakeContactManager.setNumUnreachable(3);
fakeContactManager.completeDownload();
visibilityElement.set('settings.visibility', Visibility.kAllContacts);
// need to wait for the next render to see results
await waitAfterNextRender(visibilityElement);
assertTrue(isUnreachableMessageVisible());
});
test(
'Unreachable message hidden for 0 unreachable contacts',
async function() {
fakeContactManager.setupContactRecords();
fakeContactManager.setNumUnreachable(0);
fakeContactManager.completeDownload();
visibilityElement.set('settings.visibility', Visibility.kAllContacts);
// need to wait for the next render to see results
await waitAfterNextRender(visibilityElement);
assertFalse(isUnreachableMessageVisible());
});
test(
'Save persists visibility setting and allowed contacts',
async function() {
fakeContactManager.setupContactRecords();
fakeContactManager.setNumUnreachable(0);
fakeContactManager.completeDownload();
visibilityElement.set('settings.visibility', Visibility.kAllContacts);
await waitAfterNextRender(visibilityElement);
// visibility setting is not immediately updated
visibilityElement.shadowRoot.querySelector('#AllContactsToggle')
.click();
await waitAfterNextRender(visibilityElement);
assertTrue(areContactCheckBoxesVisibleAndAllContactsToggledOff());
assertEquals(
visibilityElement.get('settings.visibility'),
Visibility.kAllContacts);
// allow only contact 2, check that allowed contacts are not yet pushed
// to the contact manager
fakeContactManager.setAllowedContacts(['1']);
for (let i = 0; i < visibilityElement.contacts.length; ++i) {
visibilityElement.set(
['contacts', i, 'checked'],
visibilityElement.contacts[i].id === '2');
}
await waitAfterNextRender(visibilityElement);
assertEquals(fakeContactManager.allowedContacts.length, 1);
assertEquals(fakeContactManager.allowedContacts[0], '1');
// after save, ui state is persisted
visibilityElement.saveVisibilityAndAllowedContacts();
assertEquals(
visibilityElement.get('settings.visibility'),
Visibility.kSelectedContacts);
assertEquals(fakeContactManager.allowedContacts.length, 1);
assertEquals(fakeContactManager.allowedContacts[0], '2');
});
test('System Settings changed on Save only', async () => {
fakeContactManager.setupContactRecords();
fakeContactManager.setNumUnreachable(0);
fakeContactManager.completeDownload();
visibilityElement.set('settings.visibility', Visibility.kAllContacts);
await waitAfterNextRender(visibilityElement);
// System visibility setting is not immediately updated to Selected
// Devices despite toggling Selected Devices in Dialog.
visibilityElement.shadowRoot.querySelector('#AllContactsToggle').click();
await waitAfterNextRender(visibilityElement);
assertTrue(areContactCheckBoxesVisibleAndAllContactsToggledOff());
assertEquals(
Visibility.kAllContacts, visibilityElement.get('settings.visibility'));
// Allow only contact 2, check that allowed contacts are not yet pushed
// to the contact manager.
fakeContactManager.setAllowedContacts(['1']);
for (let i = 0; i < visibilityElement.contacts.length; ++i) {
visibilityElement.set(
['contacts', i, 'checked'], visibilityElement.contacts[i].id === '2');
}
await waitAfterNextRender(visibilityElement);
assertEquals(1, fakeContactManager.allowedContacts.length);
assertEquals('1', fakeContactManager.allowedContacts[0]);
// After save, the system settings recognize the new visibility setting
// and allowed contact list.
visibilityElement.saveVisibilityAndAllowedContacts();
assertEquals(
Visibility.kSelectedContacts,
visibilityElement.get('settings.visibility'));
assertEquals(1, fakeContactManager.allowedContacts.length);
assertEquals('2', fakeContactManager.allowedContacts[0]);
});
test('Toggle some contacts from all contacts', async () => {
fakeContactManager.setupContactRecords();
fakeContactManager.setNumUnreachable(0);
fakeContactManager.completeDownload();
visibilityElement.set('settings.visibility', Visibility.kAllContacts);
await waitAfterNextRender(visibilityElement);
assertFalse(areContactCheckBoxesVisibleAndAllContactsToggledOff());
// Toggles for each contact appear when some contacts is toggled on.
visibilityElement.shadowRoot.querySelector('#AllContactsToggle').click();
await waitAfterNextRender(visibilityElement);
assertTrue(areContactCheckBoxesVisibleAndAllContactsToggledOff());
});
test('Toggle all contacts from some contacts', async () => {
fakeContactManager.setupContactRecords();
fakeContactManager.setNumUnreachable(0);
fakeContactManager.completeDownload();
visibilityElement.set('settings.visibility', Visibility.kSelectedContacts);
await waitAfterNextRender(visibilityElement);
assertTrue(areContactCheckBoxesVisibleAndAllContactsToggledOff());
// Toggles for each contact disappear when some contacts is toggled off.
visibilityElement.shadowRoot.querySelector('#AllContactsToggle').click();
await waitAfterNextRender(visibilityElement);
assertFalse(areContactCheckBoxesVisibleAndAllContactsToggledOff());
});
test('Your devices visibility implies empty contact list', async () => {
fakeContactManager.setupContactRecords();
fakeContactManager.setNumUnreachable(0);
fakeContactManager.completeDownload();
visibilityElement.set('settings.visibility', Visibility.kAllContacts);
await waitAfterNextRender(visibilityElement);
// Toggle Your Devices.
visibilityElement.shadowRoot.querySelector('#yourDevices').click();
await waitAfterNextRender(visibilityElement);
visibilityElement.saveVisibilityAndAllowedContacts();
assertEquals(
Visibility.kYourDevices, visibilityElement.get('settings.visibility'));
assertEquals(0, fakeContactManager.allowedContacts.length);
});
test('Hidden visibility implies empty contact list', async () => {
fakeContactManager.setupContactRecords();
fakeContactManager.setNumUnreachable(0);
fakeContactManager.completeDownload();
visibilityElement.set('settings.visibility', Visibility.kAllContacts);
await waitAfterNextRender(visibilityElement);
// Toggle Hidden.
visibilityElement.shadowRoot.querySelector('#noContacts').click();
await waitAfterNextRender(visibilityElement);
visibilityElement.saveVisibilityAndAllowedContacts();
assertEquals(
Visibility.kNoOne, visibilityElement.get('settings.visibility'));
assertEquals(0, fakeContactManager.allowedContacts.length);
});
test(
'Only contacts toggled are saved in allowed contact list when selected contacts toggled',
async () => {
fakeContactManager.setupContactRecords();
fakeContactManager.setNumUnreachable(0);
fakeContactManager.completeDownload();
visibilityElement.set('settings.visibility', Visibility.kAllContacts);
await waitAfterNextRender(visibilityElement);
// Toggle Selected Contacts.
visibilityElement.shadowRoot.querySelector('#AllContactsToggle')
.click();
await waitAfterNextRender(visibilityElement);
const list = visibilityElement.shadowRoot.querySelector('#contactList');
const contacts = list.querySelectorAll('.contact-item');
assertEquals(contacts.length, 2);
// FakeContactManager.setupContactRecords() creates contacts such that
// the first is toggled, the second is not.
assertContactsToggled([true, false]);
assertEquals(1, fakeContactManager.allowedContacts.length);
assertEquals('1', fakeContactManager.allowedContacts[0]);
// Invert the selected contact toggle state for the contact list.
for (const contact of contacts) {
contact.querySelector('.contact-toggle').click();
}
await waitAfterNextRender(visibilityElement);
// Assert that internal state matches external state of contact toggles.
assertContactsToggled([false, true]);
// Save contacts, assert only the second contact is in the set of
// allowed contacts.
visibilityElement.saveVisibilityAndAllowedContacts();
assertEquals(1, fakeContactManager.allowedContacts.length);
assertEquals('2', fakeContactManager.allowedContacts[0]);
});
test('Visibility component shows contacts for kAllContacts', async () => {
succeedContactDownload();
visibilityElement.set('settings.visibility', Visibility.kAllContacts);
// need to wait for the next render to see results
await waitAfterNextRender(visibilityElement);
assertToggleState(
/*contacts=*/ true, /*yourDevices=*/ false, /*no=*/ false);
assertFalse(isZeroStateVisible());
assertFalse(areContactCheckBoxesVisibleAndAllContactsToggledOff());
assertFalse(isNoContactsSectionVisible());
});
test(
'Visibility component shows contacts for kSelectedContacts', async () => {
succeedContactDownload();
visibilityElement.set(
'settings.visibility', Visibility.kSelectedContacts);
// need to wait for the next render to see results
await waitAfterNextRender(visibilityElement);
assertToggleState(
/*contacts=*/ true, /*yourDevices=*/ false, /*no=*/ false);
assertFalse(isZeroStateVisible());
assertTrue(areContactCheckBoxesVisibleAndAllContactsToggledOff());
assertFalse(isNoContactsSectionVisible());
});
test('Visibility component shows yourDevices for kYourDevices', async () => {
succeedContactDownload();
visibilityElement.set('settings.visibility', Visibility.kYourDevices);
// Results visible after next render.
await waitAfterNextRender(visibilityElement);
assertToggleState(
/*contacts=*/ false, /*yourDevices=*/ true, /*no=*/ false);
assertFalse(isZeroStateVisible());
assertFalse(areContactCheckBoxesVisibleAndAllContactsToggledOff());
assertFalse(isNoContactsSectionVisible());
});
test('Visibility component shows no contacts for kNoOne', async () => {
visibilityElement.set('settings.visibility', Visibility.kNoOne);
succeedContactDownload();
// need to wait for the next render to see results
await waitAfterNextRender(visibilityElement);
assertToggleState(
/*contacts=*/ false, /*yourDevices=*/ false, /*no=*/ true);
assertFalse(isZeroStateVisible());
assertFalse(areContactCheckBoxesVisibleAndAllContactsToggledOff());
assertFalse(isNoContactsSectionVisible());
});
});