chromium/chrome/test/data/webui/password_manager/share_password_flow_test.ts

// 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.

import 'chrome://password-manager/password_manager.js';

import type {SharePasswordFlowElement} from 'chrome://password-manager/password_manager.js';
import {PasswordManagerImpl, ShareFlowState} from 'chrome://password-manager/password_manager.js';
import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {assertEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
import {eventToPromise, isVisible} from 'chrome://webui-test/test_util.js';

import {TestPasswordManagerProxy} from './test_password_manager_proxy.js';
import {makeFamilyFetchResults, makeRecipientInfo} from './test_util.js';

const SITE = 'test.com';

function startPasswordShare(passwordName: string = SITE):
    SharePasswordFlowElement {
  const shareElement = document.createElement('share-password-flow');
  shareElement.passwordName = passwordName;
  document.body.appendChild(shareElement);
  flush();
  return shareElement;
}

function assertVisibleTextContent(element: HTMLElement, expectedText: string) {
  assertTrue(isVisible(element));
  assertEquals(expectedText, element?.textContent!.trim());
}

suite('SharePasswordFlowTest', function() {
  let passwordManager: TestPasswordManagerProxy;

  setup(function() {
    document.body.innerHTML = window.trustedTypes!.emptyHTML;
    passwordManager = new TestPasswordManagerProxy();
    PasswordManagerImpl.setInstance(passwordManager);
  });

  test('Has correct loading state', async function() {
    const shareElement = startPasswordShare(/*passwordName=*/ SITE);

    assertEquals(ShareFlowState.FETCHING, shareElement.flowState);
    const dialog =
        shareElement.shadowRoot!.querySelector('share-password-loading-dialog');
    assertTrue(!!dialog);

    await passwordManager.whenCalled('fetchFamilyMembers');
  });

  test('Has correct error state', async function() {
    passwordManager.data.familyFetchResults = makeFamilyFetchResults(
        chrome.passwordsPrivate.FamilyFetchStatus.UNKNOWN_ERROR);
    const shareElement = startPasswordShare();
    await passwordManager.whenCalled('fetchFamilyMembers');
    await flushTasks();

    const dialog =
        shareElement.shadowRoot!.querySelector('share-password-error-dialog');
    assertTrue(!!dialog);

    assertVisibleTextContent(
        dialog.$.header, shareElement.i18n('sharePasswordErrorTitle'));
    assertVisibleTextContent(
        dialog.$.description,
        shareElement.i18n('sharePasswordErrorDescription'));
    assertVisibleTextContent(dialog.$.cancel, shareElement.i18n('cancel'));
    assertVisibleTextContent(
        dialog.$.tryAgain, shareElement.i18n('sharePasswordTryAgain'));
  });

  test('Try again button restarts the flow', async function() {
    passwordManager.data.familyFetchResults = makeFamilyFetchResults(
        chrome.passwordsPrivate.FamilyFetchStatus.UNKNOWN_ERROR);
    const shareElement = startPasswordShare();
    await passwordManager.whenCalled('fetchFamilyMembers');
    await flushTasks();

    const dialog =
        shareElement.shadowRoot!.querySelector('share-password-error-dialog');
    assertTrue(!!dialog);
    dialog.$.tryAgain.click();
    assertEquals(ShareFlowState.FETCHING, shareElement.flowState);
    await passwordManager.whenCalled('fetchFamilyMembers');
  });

  test('Cancel button should hide the error dialog', async function() {
    passwordManager.data.familyFetchResults = makeFamilyFetchResults(
        chrome.passwordsPrivate.FamilyFetchStatus.UNKNOWN_ERROR);
    const shareElement = startPasswordShare();
    await passwordManager.whenCalled('fetchFamilyMembers');
    await flushTasks();

    const shareFlowDone = eventToPromise('share-flow-done', shareElement);

    const dialog =
        shareElement.shadowRoot!.querySelector('share-password-error-dialog');
    assertTrue(!!dialog);
    dialog.$.cancel.click();
    await flushTasks();

    await shareFlowDone;
  });

  test('Has correct no other members state', async function() {
    passwordManager.data.familyFetchResults = makeFamilyFetchResults(
        chrome.passwordsPrivate.FamilyFetchStatus.SUCCESS, /*members=*/[]);
    const shareElement = startPasswordShare();
    await passwordManager.whenCalled('fetchFamilyMembers');
    await flushTasks();

    const dialog = shareElement.shadowRoot!.querySelector(
        'share-password-no-other-family-members-dialog');
    assertTrue(!!dialog);

    assertVisibleTextContent(
        dialog.$.header, shareElement.i18n('shareDialogTitle', SITE));
    assertVisibleTextContent(
        dialog.$.description,
        shareElement.i18n('sharePasswordNoOtherFamilyMembers'));
    assertVisibleTextContent(
        dialog.$.action, shareElement.i18n('sharePasswordGotIt'));
  });

  test('Has correct not a family member state', async function() {
    passwordManager.data.familyFetchResults = makeFamilyFetchResults(
        chrome.passwordsPrivate.FamilyFetchStatus.NO_MEMBERS);
    const shareElement = startPasswordShare();
    await passwordManager.whenCalled('fetchFamilyMembers');
    await flushTasks();

    const dialog = shareElement.shadowRoot!.querySelector(
        'share-password-not-family-member-dialog');
    assertTrue(!!dialog);

    assertVisibleTextContent(
        dialog.$.header, shareElement.i18n('shareDialogTitle', SITE));
    assertVisibleTextContent(
        dialog.$.description,
        shareElement.i18n('sharePasswordNotFamilyMember'));
    assertVisibleTextContent(
        dialog.$.action, shareElement.i18n('sharePasswordGotIt'));
  });

  test('Action button should hide not family member dialog', async function() {
    passwordManager.data.familyFetchResults = makeFamilyFetchResults(
        chrome.passwordsPrivate.FamilyFetchStatus.NO_MEMBERS);
    const shareElement = startPasswordShare();
    await passwordManager.whenCalled('fetchFamilyMembers');
    await flushTasks();

    const shareFlowDone = eventToPromise('share-flow-done', shareElement);

    const dialog = shareElement.shadowRoot!.querySelector(
        'share-password-not-family-member-dialog');
    assertTrue(!!dialog);
    dialog.$.action.click();
    await flushTasks();

    await shareFlowDone;
  });

  test('Action button should hide no other members dialog', async function() {
    passwordManager.data.familyFetchResults = makeFamilyFetchResults(
        chrome.passwordsPrivate.FamilyFetchStatus.SUCCESS, /*members=*/[]);
    const shareElement = startPasswordShare();
    await passwordManager.whenCalled('fetchFamilyMembers');
    await flushTasks();

    const shareFlowDone = eventToPromise('share-flow-done', shareElement);

    const dialog = shareElement.shadowRoot!.querySelector(
        'share-password-no-other-family-members-dialog');
    assertTrue(!!dialog);
    dialog.$.action.click();
    await flushTasks();

    await shareFlowDone;
  });

  test('Cancel button should hide family picker dialog', async function() {
    passwordManager.data.familyFetchResults = makeFamilyFetchResults(
        chrome.passwordsPrivate.FamilyFetchStatus.SUCCESS,
        /*members=*/[makeRecipientInfo()]);
    const shareElement = startPasswordShare();
    await passwordManager.whenCalled('fetchFamilyMembers');
    await flushTasks();

    const shareFlowDone = eventToPromise('share-flow-done', shareElement);

    const dialog = shareElement.shadowRoot!.querySelector(
        'share-password-family-picker-dialog');
    assertTrue(!!dialog);
    dialog.$.cancel.click();
    await flushTasks();

    await shareFlowDone;
  });
});