0

[CrOS Bluetooth] Sanitize Bluetooth device names

This CL updates getDeviceName for in bluetooth_utils.ts and marks it as
unsafe. This is because we allow users to enter any string as the device
name. We make sure to use  loadTimeData.getStringF when displaying
the device name in HTML so the HTML is not rendered.

Fixed: b/298724102
Test: Deployed to DUT, added test and ran CQ
Change-Id: Icddf7ad76fff13bcf4511f118a6500b0009e275f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5238635
Commit-Queue: Theo Johnson-kanu <tjohnsonkanu@google.com>
Reviewed-by: Chad Duffin <chadduffin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1257566}
This commit is contained in:
Theo Johnson-Kanu
2024-02-07 21:01:50 +00:00
committed by Chromium LUCI CQ
parent e027c63ebf
commit 473d45cf37
14 changed files with 143 additions and 73 deletions

@ -7,16 +7,20 @@ import {BluetoothDeviceProperties, PairedBluetoothDeviceProperties} from 'chrome
import {BatteryType} from './bluetooth_types.js';
export function getDeviceName(device: PairedBluetoothDeviceProperties | null): string {
if (!device) {
/**
* WARNING: The returned string may contain malicious HTML and should not be
* used for Polymer bindings in CSS code. For additional information see
* b/298724102.
*/
export function getDeviceNameUnsafe(device: PairedBluetoothDeviceProperties|
null): string {
if (!device || (!device.nickname && !device.deviceProperties?.publicName)) {
return '';
}
if (device.nickname) {
return device.nickname;
}
return mojoString16ToString(device.deviceProperties.publicName);
return device.nickname ||
mojoString16ToString(device.deviceProperties.publicName);
}
/**

@ -11,7 +11,7 @@ import '../settings_shared.css.js';
import 'chrome://resources/ash/common/cr_elements/cr_input/cr_input.js';
import 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js';
import {getDeviceName} from 'chrome://resources/ash/common/bluetooth/bluetooth_utils.js';
import {getDeviceNameUnsafe} from 'chrome://resources/ash/common/bluetooth/bluetooth_utils.js';
import {getBluetoothConfig} from 'chrome://resources/ash/common/bluetooth/cros_bluetooth_config.js';
import {CrDialogElement} from 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js';
import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
@ -52,6 +52,11 @@ export class SettingsBluetoothChangeDeviceNameDialogElement extends
value: MAX_INPUT_LENGTH,
},
/**
* WARNING: This string may contain malicious HTML and should not be used
* for Polymer bindings in CSS code. For additional information see
* b/298724102.
*/
deviceName_: {
type: String,
value: '',
@ -71,7 +76,7 @@ export class SettingsBluetoothChangeDeviceNameDialogElement extends
private isInputInvalid_: boolean;
private onDeviceChanged_(): void {
this.deviceName_ = getDeviceName(this.device);
this.deviceName_ = getDeviceNameUnsafe(this.device);
}
private onCancelClick_(): void {
@ -118,7 +123,7 @@ export class SettingsBluetoothChangeDeviceNameDialogElement extends
}
private isDoneDisabled_(): boolean {
if (this.deviceName_ === getDeviceName(this.device)) {
if (this.deviceName_ === getDeviceNameUnsafe(this.device)) {
return true;
}
@ -128,6 +133,10 @@ export class SettingsBluetoothChangeDeviceNameDialogElement extends
return false;
}
getNameForTest(): string|null {
return getDeviceNameUnsafe(this.device);
}
}
declare global {

@ -104,7 +104,7 @@
aria-hidden="true">
$i18n{bluetoothDeviceDetailName}
<div class="secondary" id="bluetoothDeviceNameLabel">
[[getDeviceName_(device_.*)]]
[[getDeviceNameUnsafe_(device_.*)]]
</div>
</div>
<cr-button id="changeNameBtn"

@ -17,7 +17,7 @@ import 'chrome://resources/ash/common/bluetooth/bluetooth_device_battery_info.js
import {BluetoothUiSurface, recordBluetoothUiSurfaceMetrics} from 'chrome://resources/ash/common/bluetooth/bluetooth_metrics_utils.js';
import {BatteryType} from 'chrome://resources/ash/common/bluetooth/bluetooth_types.js';
import {getBatteryPercentage, getDeviceName, hasAnyDetailedBatteryInfo, hasDefaultImage, hasTrueWirelessImages} from 'chrome://resources/ash/common/bluetooth/bluetooth_utils.js';
import {getBatteryPercentage, getDeviceNameUnsafe, hasAnyDetailedBatteryInfo, hasDefaultImage, hasTrueWirelessImages} from 'chrome://resources/ash/common/bluetooth/bluetooth_utils.js';
import {getBluetoothConfig} from 'chrome://resources/ash/common/bluetooth/cros_bluetooth_config.js';
import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
import {WebUiListenerMixin} from 'chrome://resources/ash/common/cr_elements/web_ui_listener_mixin.js';
@ -211,11 +211,8 @@ export class SettingsBluetoothDeviceDetailSubpageElement extends
this.i18n('bluetoothDeviceDetailDisconnected');
}
private getDeviceName_(): string {
if (!this.device_) {
return '';
}
return getDeviceName(this.device_);
private getDeviceNameUnsafe_(): string {
return getDeviceNameUnsafe(this.device_);
}
private shouldShowConnectDisconnectBtn_(): boolean {
@ -235,7 +232,7 @@ export class SettingsBluetoothDeviceDetailSubpageElement extends
return;
}
(this.parentNode as OsSettingsSubpageElement).pageTitle =
getDeviceName(this.device_);
getDeviceNameUnsafe(this.device_);
// Special case a where user is still on detail page and has
// tried to connect to device but failed. The current |pageState_|
@ -299,9 +296,9 @@ export class SettingsBluetoothDeviceDetailSubpageElement extends
return '';
}
return this.i18n(
return loadTimeData.getStringF(
'bluetoothDeviceDetailChangeDeviceNameBtnA11yLabel',
this.getDeviceName_());
getDeviceNameUnsafe(this.device_));
}
private getMultipleBatteryInfoA11yLabel_(): string {
@ -363,20 +360,22 @@ export class SettingsBluetoothDeviceDetailSubpageElement extends
switch (this.pageState_) {
case PageState.CONNECTING:
return this.i18n(
'bluetoothDeviceDetailConnectingA11yLabel', this.getDeviceName_());
return loadTimeData.getStringF(
'bluetoothDeviceDetailConnectingA11yLabel',
getDeviceNameUnsafe(this.device_));
case PageState.CONNECTED:
return this.i18n(
'bluetoothDeviceDetailConnectedA11yLabel', this.getDeviceName_());
return loadTimeData.getStringF(
'bluetoothDeviceDetailConnectedA11yLabel',
getDeviceNameUnsafe(this.device_));
case PageState.CONNECTION_FAILED:
return this.i18n(
return loadTimeData.getStringF(
'bluetoothDeviceDetailConnectionFailureA11yLabel',
this.getDeviceName_());
getDeviceNameUnsafe(this.device_));
case PageState.DISCONNECTED:
case PageState.DISCONNECTING:
return this.i18n(
return loadTimeData.getStringF(
'bluetoothDeviceDetailDisconnectedA11yLabel',
this.getDeviceName_());
getDeviceNameUnsafe(this.device_));
default:
assertNotReached();
}
@ -525,8 +524,9 @@ export class SettingsBluetoothDeviceDetailSubpageElement extends
}
private getForgetA11yLabel_(): string {
return this.i18n(
'bluetoothDeviceDetailForgetA11yLabel', this.getDeviceName_());
return loadTimeData.getStringF(
'bluetoothDeviceDetailForgetA11yLabel',
getDeviceNameUnsafe(this.device_));
}
private onForgetButtonClicked_(): void {

@ -10,7 +10,7 @@ import '../settings_shared.css.js';
import 'chrome://resources/ash/common/cr_elements/cr_input/cr_input.js';
import 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js';
import {getDeviceName} from 'chrome://resources/ash/common/bluetooth/bluetooth_utils.js';
import {getDeviceNameUnsafe} from 'chrome://resources/ash/common/bluetooth/bluetooth_utils.js';
import {CrDialogElement} from 'chrome://resources/ash/common/cr_elements/cr_dialog/cr_dialog.js';
import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
@ -45,15 +45,11 @@ class SettingsBluetoothForgetDeviceDialogElement extends
private device_: PairedBluetoothDeviceProperties;
private getForgetDeviceDialogBodyText_(): string {
return this.i18n(
'bluetoothDevicesDialogLabel', this.getDeviceName_(),
return loadTimeData.getStringF(
'bluetoothDevicesDialogLabel', getDeviceNameUnsafe(this.device_),
loadTimeData.getString('primaryUserEmail'));
}
private getDeviceName_(): string {
return getDeviceName(this.device_);
}
private onForgetClick_(event: Event): void {
const fireEvent = new CustomEvent(
'forget-bluetooth-device', {bubbles: true, composed: true});

@ -12,7 +12,7 @@ import '../settings_shared.css.js';
import 'chrome://resources/ash/common/cr_elements/cr_icon_button/cr_icon_button.js';
import 'chrome://resources/ash/common/cr_elements/icons.html.js';
import {getDeviceName} from 'chrome://resources/ash/common/bluetooth/bluetooth_utils.js';
import {getDeviceNameUnsafe} from 'chrome://resources/ash/common/bluetooth/bluetooth_utils.js';
import {getBluetoothConfig} from 'chrome://resources/ash/common/bluetooth/cros_bluetooth_config.js';
import {getInstance as getAnnouncerInstance} from 'chrome://resources/ash/common/cr_elements/cr_a11y_announcer/cr_a11y_announcer.js';
import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
@ -175,31 +175,32 @@ export class SettingsBluetoothSummaryElement extends
}
const isA11yLabel = labelType === LabelType.A11Y;
const firstConnectedDeviceName = getDeviceName(connectedDevices[0]);
const firstConnectedDeviceName = getDeviceNameUnsafe(connectedDevices[0]);
if (connectedDevices.length === 1) {
return isA11yLabel ? this.i18n(
return isA11yLabel ? loadTimeData.getStringF(
'bluetoothSummaryPageConnectedA11yOneDevice',
firstConnectedDeviceName) :
firstConnectedDeviceName;
}
if (connectedDevices.length === 2) {
const secondConnectedDeviceName = getDeviceName(connectedDevices[1]);
const secondConnectedDeviceName =
getDeviceNameUnsafe(connectedDevices[1]);
return isA11yLabel ?
this.i18n(
loadTimeData.getStringF(
'bluetoothSummaryPageConnectedA11yTwoDevices',
firstConnectedDeviceName, secondConnectedDeviceName) :
this.i18n(
loadTimeData.getStringF(
'bluetoothSummaryPageTwoDevicesDescription',
firstConnectedDeviceName, secondConnectedDeviceName);
}
return isA11yLabel ?
this.i18n(
loadTimeData.getStringF(
'bluetoothSummaryPageConnectedA11yTwoOrMoreDevices',
firstConnectedDeviceName, connectedDevices.length - 1) :
this.i18n(
loadTimeData.getStringF(
'bluetoothSummaryPageTwoOrMoreDevicesDescription',
firstConnectedDeviceName, connectedDevices.length - 1);
}

@ -14,7 +14,7 @@
on-click="onSelected_">
<bluetooth-icon device="[[device.deviceProperties]]"></bluetooth-icon>
<div class="middle" aria-hidden="true">
<div id="deviceName">[[getDeviceName_(device)]]</div>
<div id="deviceName">[[getDeviceNameUnsafe_(device)]]</div>
<template is="dom-if"
if="[[shouldShowBatteryInfo_(device)]]" restamp>
<bluetooth-device-battery-info

@ -16,10 +16,11 @@ import 'chrome://resources/ash/common/bluetooth/bluetooth_icon.js';
import 'chrome://resources/ash/common/bluetooth/bluetooth_device_battery_info.js';
import {BatteryType} from 'chrome://resources/ash/common/bluetooth/bluetooth_types.js';
import {getBatteryPercentage, getDeviceName, hasAnyDetailedBatteryInfo} from 'chrome://resources/ash/common/bluetooth/bluetooth_utils.js';
import {getBatteryPercentage, getDeviceNameUnsafe, hasAnyDetailedBatteryInfo} from 'chrome://resources/ash/common/bluetooth/bluetooth_utils.js';
import {FocusRowMixin} from 'chrome://resources/ash/common/cr_elements/focus_row_mixin.js';
import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
import {assert, assertNotReached} from 'chrome://resources/js/assert.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {DeviceConnectionState, DeviceType, PairedBluetoothDeviceProperties} from 'chrome://resources/mojo/chromeos/ash/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom-webui.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
@ -101,8 +102,9 @@ export class SettingsPairedBluetoothListItemElement extends
Router.getInstance().navigateTo(routes.BLUETOOTH_DEVICE_DETAIL, params);
}
private getDeviceName_(device: PairedBluetoothDeviceProperties): string {
return getDeviceName(device);
private getDeviceNameUnsafe_(device: PairedBluetoothDeviceProperties):
string {
return getDeviceNameUnsafe(device);
}
private shouldShowBatteryInfo_(device: PairedBluetoothDeviceProperties):
@ -159,9 +161,9 @@ export class SettingsPairedBluetoothListItemElement extends
private getAriaLabel_(device: PairedBluetoothDeviceProperties): string {
// Start with the base information of the device name and location within
// the list of devices with the same connection state.
let a11yLabel = this.i18n(
let a11yLabel = loadTimeData.getStringF(
'bluetoothA11yDeviceName', this.itemIndex + 1, this.listSize,
this.getDeviceName_(device));
this.getDeviceNameUnsafe_(device));
// Include the connection status.
a11yLabel +=

@ -28,7 +28,7 @@
</template>
</div>
<div class="middle" aria-hidden="true">
<div id="deviceName">[[getDeviceName_(device)]]</div>
<div id="deviceName">[[getDeviceNameUnsafe_(device)]]</div>
</div>
<div>
<cr-icon-button class="icon-more-vert"

@ -16,8 +16,8 @@ import 'chrome://resources/ash/common/cr_elements/cr_action_menu/cr_action_menu.
import {FastPairSavedDevicesUiEvent, recordSavedDevicesUiEventMetrics} from 'chrome://resources/ash/common/bluetooth/bluetooth_metrics_utils.js';
import {CrActionMenuElement} from 'chrome://resources/ash/common/cr_elements/cr_action_menu/cr_action_menu.js';
import {FocusRowMixin} from 'chrome://resources/ash/common/cr_elements/focus_row_mixin.js';
import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
import {WebUiListenerMixin} from 'chrome://resources/ash/common/cr_elements/web_ui_listener_mixin.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {getTemplate} from './os_saved_devices_list_item.html.js';
@ -30,7 +30,7 @@ interface SettingsSavedDevicesListItemElement {
}
const SettingsSavedDevicesListItemElementBase =
FocusRowMixin(WebUiListenerMixin(I18nMixin(PolymerElement)));
FocusRowMixin(WebUiListenerMixin(PolymerElement));
class SettingsSavedDevicesListItemElement extends
SettingsSavedDevicesListItemElementBase {
@ -69,7 +69,7 @@ class SettingsSavedDevicesListItemElement extends
listSize: number;
private shouldShowRemoveSavedDeviceDialog_: boolean;
private getDeviceName_(device: FastPairSavedDevice): string {
private getDeviceNameUnsafe_(device: FastPairSavedDevice): string {
return device.name;
}
@ -95,15 +95,16 @@ class SettingsSavedDevicesListItemElement extends
}
private getAriaLabel_(device: FastPairSavedDevice): string {
const deviceName = this.getDeviceName_(device);
return this.i18n(
const deviceName = this.getDeviceNameUnsafe_(device);
return loadTimeData.getStringF(
'savedDeviceItemA11yLabel', this.itemIndex + 1, this.listSize,
deviceName);
}
private getSubpageButtonA11yLabel_(device: FastPairSavedDevice): string {
const deviceName = this.getDeviceName_(device);
return this.i18n('savedDeviceItemButtonA11yLabel', deviceName);
const deviceName = this.getDeviceNameUnsafe_(device);
return loadTimeData.getStringF(
'savedDeviceItemButtonA11yLabel', deviceName);
}
}

@ -15,13 +15,13 @@ import '../settings_shared.css.js';
import '../os_settings_icons.html.js';
import './menu_item.js';
import {getDeviceName} from 'chrome://resources/ash/common/bluetooth/bluetooth_utils.js';
import {getDeviceNameUnsafe} from 'chrome://resources/ash/common/bluetooth/bluetooth_utils.js';
import {getBluetoothConfig} from 'chrome://resources/ash/common/bluetooth/cros_bluetooth_config.js';
import {I18nMixin, I18nMixinInterface} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
import {WebUiListenerMixin, WebUiListenerMixinInterface} from 'chrome://resources/ash/common/cr_elements/web_ui_listener_mixin.js';
import {MojoInterfaceProviderImpl} from 'chrome://resources/ash/common/network/mojo_interface_provider.js';
import {NetworkListenerBehavior, NetworkListenerBehaviorInterface} from 'chrome://resources/ash/common/network/network_listener_behavior.js';
import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js';
import {I18nMixin, I18nMixinInterface} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
import {WebUiListenerMixin, WebUiListenerMixinInterface} from 'chrome://resources/ash/common/cr_elements/web_ui_listener_mixin.js';
import {BluetoothSystemProperties, BluetoothSystemState, DeviceConnectionState, PairedBluetoothDeviceProperties, SystemPropertiesObserverReceiver as BluetoothPropertiesObserverReceiver} from 'chrome://resources/mojo/chromeos/ash/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom-webui.js';
import {CrosNetworkConfigInterface, FilterType, NO_LIMIT} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js';
import {NetworkType} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
@ -664,7 +664,7 @@ export class OsSettingsMenuElement extends OsSettingsMenuElementBase {
if (connectedDevices.length === 1) {
const device = castExists(connectedDevices[0]);
this.bluetoothMenuItemDescription_ = getDeviceName(device);
this.bluetoothMenuItemDescription_ = getDeviceNameUnsafe(device);
return;
}

@ -6,7 +6,7 @@ import 'chrome://os-settings/lazy_load.js';
import {SettingsBluetoothChangeDeviceNameDialogElement} from 'chrome://os-settings/lazy_load.js';
import {CrInputElement} from 'chrome://os-settings/os_settings.js';
import {getDeviceName} from 'chrome://resources/ash/common/bluetooth/bluetooth_utils.js';
import {getDeviceNameUnsafe} from 'chrome://resources/ash/common/bluetooth/bluetooth_utils.js';
import {setBluetoothConfigForTesting} from 'chrome://resources/ash/common/bluetooth/cros_bluetooth_config.js';
import {DeviceConnectionState} from 'chrome://resources/mojo/chromeos/ash/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom-webui.js';
import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
@ -18,6 +18,7 @@ suite('<os-settings-bluetooth-change-device-name-dialog>', () => {
let bluetoothDeviceChangeNameDialog:
SettingsBluetoothChangeDeviceNameDialogElement;
let bluetoothConfig: FakeBluetoothConfig;
const deviceId = '12//345&6789';
setup(() => {
bluetoothConfig = new FakeBluetoothConfig();
@ -61,7 +62,7 @@ suite('<os-settings-bluetooth-change-device-name-dialog>', () => {
test('Input is sanitized', async () => {
const device1 = createDefaultBluetoothDevice(
/*id=*/ '12//345&6789',
/*id=*/ deviceId,
/*publicName=*/ 'BeatsX',
/*connectionState=*/
DeviceConnectionState.kConnected,
@ -109,8 +110,10 @@ suite('<os-settings-bluetooth-change-device-name-dialog>', () => {
});
test('Device name is changed', async () => {
const id = '12//345&6789';
const nickname = 'Nickname';
const initialNickname = 'device1';
const newNickname = 'nickname';
const htmlNickname = '<a>html</a>';
const getDoneBtn = () => {
const doneButton = bluetoothDeviceChangeNameDialog.shadowRoot!
.querySelector<HTMLButtonElement>('#done');
@ -119,11 +122,11 @@ suite('<os-settings-bluetooth-change-device-name-dialog>', () => {
};
const device = createDefaultBluetoothDevice(
id,
deviceId,
/*publicName=*/ 'BeatsX',
/*connectionState=*/
DeviceConnectionState.kConnected,
/*opt_nickname=*/ 'device1');
/*opt_nickname=*/ initialNickname);
bluetoothDeviceChangeNameDialog.set('device', {...device});
bluetoothConfig.appendToPairedDeviceList([device]);
@ -132,18 +135,27 @@ suite('<os-settings-bluetooth-change-device-name-dialog>', () => {
const input = bluetoothDeviceChangeNameDialog.shadowRoot!
.querySelector<CrInputElement>('#changeNameInput');
assertTrue(!!input);
assertEquals('device1', input.value);
assertEquals(initialNickname, input.value);
assertTrue(getDoneBtn().disabled);
input.value = nickname;
input.value = newNickname;
await flushTasks();
assertFalse(getDoneBtn().disabled);
getDoneBtn().click();
await flushTasks();
assertEquals(
newNickname,
getDeviceNameUnsafe(bluetoothConfig.getPairedDeviceById(deviceId)));
const newName = getDeviceName(bluetoothConfig.getPairedDeviceById(id));
input.value = htmlNickname;
await flushTasks();
assertFalse(getDoneBtn().disabled);
assertEquals(nickname, newName);
getDoneBtn().click();
await flushTasks();
assertEquals(
htmlNickname,
getDeviceNameUnsafe(bluetoothConfig.getPairedDeviceById(deviceId)));
});
});

@ -195,6 +195,32 @@ suite('<os-settings-paired-bluetooth-list-item>', () => {
getItemA11yLabel());
});
test('Device name is set correctly', async () => {
const getDeviceName = () => {
const deviceName =
pairedBluetoothListItem.shadowRoot!.querySelector<HTMLElement>(
'#deviceName');
assertTrue(!!deviceName);
return deviceName;
};
const publicName = 'BeatsX';
const nameWithHtml = '<a>testname</a>';
const device = createDefaultBluetoothDevice(
/*id=*/ '123456789', /*publicName=*/ publicName,
/*connectionState=*/
DeviceConnectionState.kConnected);
pairedBluetoothListItem.set('device', device);
await flushTasks();
assertEquals(publicName, getDeviceName().innerText);
device.nickname = nameWithHtml;
pairedBluetoothListItem.set('device', {...device});
await flushTasks();
assertTrue(!!getDeviceName());
assertEquals(nameWithHtml, getDeviceName().innerText);
});
test('Battery percentage out of bounds', async () => {
const device = createDefaultBluetoothDevice(
/*id=*/ '123456789', /*publicName=*/ 'BeatsX',

@ -177,4 +177,23 @@ suite('<os-settings-saved-devices-list>', () => {
assertTrue(isVisible(
getListItems()[1]!.shadowRoot!.querySelector('#deviceImage')));
});
test('Device names are set properly', async () => {
const getDeviceName = () => {
return getListItems()[0]!.shadowRoot!
.querySelector<HTMLElement>('#deviceName')!.innerText;
};
const deviceName = 'deviceName';
const device = {name: deviceName, imageUrl: 'fakeUrl', accountKey: '1'};
savedDevicesList.set('devices', [device]);
await flushTasks();
assertEquals(deviceName, getDeviceName());
const nameWithHtml = '<a>test</a>';
device.name = nameWithHtml;
savedDevicesList.set('devices', [{...device}]);
await flushTasks();
assertEquals(nameWithHtml, getDeviceName());
});
});