// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/** @fileoverview Suite of tests for extensions-review-panel. */
import 'chrome://extensions/extensions.js';
import type {ExtensionsReviewPanelElement} from 'chrome://extensions/extensions.js';
import {PluralStringProxyImpl} from 'chrome://extensions/extensions.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
import {TestPluralStringProxy} from 'chrome://webui-test/test_plural_string_proxy.js';
import {isVisible} from 'chrome://webui-test/test_util.js';
import {createExtensionInfo, MockItemDelegate} from './test_util.js';
suite('ExtensionsReviewPanel', function() {
let element: ExtensionsReviewPanelElement;
let pluralString: TestPluralStringProxy;
setup(function() {
pluralString = new TestPluralStringProxy();
PluralStringProxyImpl.setInstance(pluralString);
loadTimeData.overrideValues({'safetyHubShowReviewPanel': true});
document.body.innerHTML = window.trustedTypes!.emptyHTML;
element = document.createElement('extensions-review-panel');
const extensionItems = [
createExtensionInfo({
name: 'Alpha',
id: 'a'.repeat(32),
safetyCheckText: {panelString: 'This extension contains malware.'},
}),
];
element.extensions = extensionItems;
document.body.appendChild(element);
return flushTasks();
});
test('ReviewPanelTextExists', async function() {
// Review panel should be visible.
const reviewPanelContainer = element.$.reviewPanelContainer;
assertTrue(!!reviewPanelContainer);
assertTrue(isVisible(reviewPanelContainer));
// The expand button should be visible along with the review panel.
const expandButton = element.$.expandButton;
assertTrue(!!expandButton);
assertTrue(isVisible(expandButton));
// Verify that review panel heading exists.
const headingContainer = element.$.headingText;
assertTrue(!!headingContainer);
// TODO(http://crbug.com/1432194): Update the unsafe extensions number
const headingArgs = pluralString.getArgs('getPluralString')[0];
assertEquals('safetyCheckTitle', headingArgs.messageName);
assertEquals(1, headingArgs.itemCount);
const descriptionArgs = pluralString.getArgs('getPluralString')[1];
assertEquals('safetyCheckDescription', descriptionArgs.messageName);
assertEquals(1, descriptionArgs.itemCount);
const safetyHubHeader = element.$.safetyHubTitleContainer;
assertTrue(isVisible(safetyHubHeader));
});
test('CollapsibleList', async function() {
const expandButton = element.$.expandButton;
assertTrue(!!expandButton);
const extensionsList = element.shadowRoot!.querySelector('cr-collapse');
assertTrue(!!extensionsList);
// Button and list start out expanded.
assertTrue(expandButton.expanded);
assertTrue(extensionsList.opened);
// User collapses the list.
expandButton.click();
await expandButton.updateComplete;
// Button and list are collapsed.
assertFalse(expandButton.expanded);
assertFalse(extensionsList.opened);
// User expands the list.
expandButton.click();
await expandButton.updateComplete;
// Button and list are expanded.
assertTrue(expandButton.expanded);
assertTrue(extensionsList.opened);
});
test('ReviewPanelUnsafeExtensionRowsExist', async function() {
const extensionNameContainers =
element.shadowRoot!.querySelectorAll('.panel-extension-row');
assertEquals(extensionNameContainers.length, 1);
assertEquals(
extensionNameContainers[0]
?.querySelector('.extension-representation')
?.textContent,
'Alpha');
});
test(
'CompletionStateShouldNotBeShownIfNoExtensionsAndNoAction',
async function() {
const completionTextContainer =
element.shadowRoot!.querySelector('.completion-container');
assertTrue(!!completionTextContainer);
assertFalse(isVisible(completionTextContainer));
element.set('extensions', []);
await flushTasks();
assertFalse(isVisible(completionTextContainer));
});
test('CompletionStateShouldBeShownAfterDeletingItems', async function() {
const completionTextContainer =
element.shadowRoot!.querySelector('.completion-container');
assertFalse(isVisible(completionTextContainer));
class MockUninstallItemDelegate extends MockItemDelegate {
override uninstallItem(id: string): Promise<void> {
// Mock deleting the extension.
element.extensions =
element.extensions.filter(extension => extension.id !== id);
return Promise.resolve();
}
override setItemSafetyCheckWarningAcknowledged(): void {}
}
element.delegate = new MockUninstallItemDelegate();
element.shadowRoot!.querySelector('cr-icon-button')?.click();
await flushTasks();
const completionText = pluralString.getArgs('getPluralString')[5];
assertTrue(!!completionTextContainer);
assertTrue(isVisible(completionTextContainer));
assertEquals(completionText.messageName, 'safetyCheckAllDoneForNow');
assertEquals(completionText.itemCount, 1);
});
test(
'CompletionStateShouldBeShownAfterDeletingMultipleExtensions',
async function() {
const completionTextContainer =
element.shadowRoot!.querySelector('.completion-container');
assertFalse(isVisible(completionTextContainer));
class MockDeleteItemDelegate extends MockItemDelegate {
override deleteItems(ids: string[]) {
element.extensions = element.extensions.filter(
extension => !ids.includes(extension.id));
return Promise.resolve();
}
override setItemSafetyCheckWarningAcknowledged(): void {}
}
const extensionItems = [
createExtensionInfo({
name: 'Alpha',
id: 'a'.repeat(32),
safetyCheckText: {panelString: 'This extension contains malware.'},
}),
createExtensionInfo({
name: 'Bravo',
id: 'b'.repeat(32),
safetyCheckText: {panelString: 'This extension contains malware.'},
}),
createExtensionInfo({
name: 'Charlie',
id: 'c'.repeat(29),
safetyCheckText: {panelString: 'This extension contains malware.'},
}),
];
element.extensions = extensionItems;
element.delegate = new MockDeleteItemDelegate();
// Wait until the async response comes back.
element.shadowRoot!.querySelector<HTMLElement>(
'#removeAllButton')!.click();
await flushTasks();
const completionText = pluralString.getArgs('getPluralString')[7];
assertTrue(!!completionTextContainer);
assertTrue(isVisible(completionTextContainer));
assertEquals(completionText.messageName, 'safetyCheckAllDoneForNow');
assertEquals(completionText.itemCount, 3);
});
test('CompletionStateShouldBeShownAfterKeepingItems', async function() {
const completionTextContainer =
element.shadowRoot!.querySelector('.completion-container');
class MockKeepItemDelegate extends MockItemDelegate {
override setItemSafetyCheckWarningAcknowledged(): void {
// Update extensions to be an empty list since the only previous
// extension was marked as acknowledged.
element.set('extensions', []);
}
}
element.delegate = new MockKeepItemDelegate();
assertFalse(isVisible(completionTextContainer));
const extensionRowContainers =
element.shadowRoot!.querySelectorAll('.panel-extension-row');
assertEquals(1, extensionRowContainers.length);
const menuButton = extensionRowContainers[0]!.querySelector<HTMLElement>(
'.icon-more-vert')!;
const actionMenu = element.$.makeExceptionMenu;
assertFalse(actionMenu.open);
// Open the three dots action menu.
menuButton.click();
// The three dots action menu should be open.
assertTrue(actionMenu.open);
// Click the Keep the Extension button.
actionMenu.querySelector('button')!.click();
// The extension row should be removed and the completion state should be
// shown.
assertTrue(!!completionTextContainer);
assertTrue(isVisible(completionTextContainer));
});
});