chromium/chrome/test/data/webui/chromeos/os_feedback_ui/search_page_test.ts

// Copyright 2022 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://os-feedback/search_page.js';
import 'chrome://webui-test/chromeos/mojo_webui_test_support.js';

import {fakeEmptySearchResponse, fakeFeedbackContext, fakeInternalUserFeedbackContext, fakeLoginFlowFeedbackContext, fakeSearchResponse} from 'chrome://os-feedback/fake_data.js';
import {FakeHelpContentProvider} from 'chrome://os-feedback/fake_help_content_provider.js';
import {FeedbackFlowButtonClickEvent, FeedbackFlowState} from 'chrome://os-feedback/feedback_flow.js';
import {setHelpContentProviderForTesting} from 'chrome://os-feedback/mojo_interface_provider.js';
import {domainQuestions, questionnaireBegin} from 'chrome://os-feedback/questionnaire.js';
import {OS_FEEDBACK_UNTRUSTED_ORIGIN, SearchPageElement} from 'chrome://os-feedback/search_page.js';
import {CrButtonElement} from 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
import {strictQuery} from 'chrome://resources/ash/common/typescript_utils/strict_query.js';
import {getDeepActiveElement} from 'chrome://resources/ash/common/util.js';
import {PromiseResolver} from 'chrome://resources/js/promise_resolver.js';
import {assertEquals, assertFalse, assertNotEquals, 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';

suite('searchPageTestSuite', () => {
  let page: SearchPageElement;

  let provider: FakeHelpContentProvider;

  setup(() => {
    document.body.innerHTML = window.trustedTypes!.emptyHTML;
    // Create provider.
    provider = new FakeHelpContentProvider();
    // Setup search response.
    provider.setFakeSearchResponse(fakeSearchResponse);
    // Set the fake provider.
    setHelpContentProviderForTesting(provider);
  });

  function initializePage() {
    page = document.createElement('search-page');
    assertTrue(!!page);
    page.feedbackContext = fakeFeedbackContext;
    document.body.appendChild(page);

    // Fire search immediately for input change.
    page.searchTimoutInMs = 0;

    return flushTasks();
  }

  /**
   * Test that expected html elements are in the page after loaded.
   */
  test('SearchPageLoaded', async () => {
    await initializePage();
    // Verify the title is in the page.
    const title = strictQuery('.page-title', page!.shadowRoot, HTMLElement);
    assertEquals('Send feedback', title.textContent!.trim());

    // Verify the iframe is in the page.
    const untrustedFrame =
        strictQuery('iframe', page!.shadowRoot, HTMLIFrameElement);
    assertEquals(
        'chrome-untrusted://os-feedback/untrusted_index.html',
        untrustedFrame.src);

    // Focus is set after the iframe is loaded.
    await eventToPromise('load', untrustedFrame);
    // Verify the description input is focused.
    assertEquals(
        strictQuery('textarea', page!.shadowRoot, HTMLTextAreaElement),
        getDeepActiveElement());

    // Verify the descriptionTitle is in the page.
    const descriptionTitle =
        strictQuery('#descriptionTitle', page!.shadowRoot, HTMLElement);
    assertEquals('Description', descriptionTitle.textContent!.trim());

    // Verify the feedback writing guidance link is in the page.
    const writingGuidanceLink = strictQuery(
        '#feedbackWritingGuidance', page!.shadowRoot, HTMLAnchorElement);
    assertEquals(
        'Tips on writing feedback', writingGuidanceLink.textContent!.trim());
    assertEquals('_blank', writingGuidanceLink.target);
    assertEquals(
        'https://support.google.com/chromebook/answer/2982029',
        writingGuidanceLink.href);

    // Verify the help content is not in the page. For security reason, help
    // contents fetched online can't be displayed in trusted context.
    const helpContent = page!.shadowRoot!.querySelector('help-content');
    assertFalse(!!helpContent);

    // Verify the continue button is in the page.
    const buttonContinue =
        strictQuery('#buttonContinue', page!.shadowRoot, CrButtonElement);
    assertEquals('Continue', buttonContinue.textContent!.trim());
  });

  /**
   * Test that the text area accepts input and may fire search query to retrieve
   * help contents.
   * - Case 1: When number of characters newly entered is less than 3, search is
   *   not triggered.
   * - Case 2: When number of characters newly entered is 3 or more, search is
   *   triggered and help contents are populated.
   * - Case 3: When the text area is empty, search is NOT triggered.
   */
  test('HelpContentPopulated', async () => {
    await initializePage();
    // The 'input' event triggers a listener that checks for internal user
    // account, so we need to set up the context.
    page.feedbackContext = fakeFeedbackContext;
    const textAreaElement =
        strictQuery('#descriptionText', page!.shadowRoot, HTMLTextAreaElement);
    // Verify the textarea is empty and hint is showing.
    assertEquals('', textAreaElement.value);
    assertEquals(
        'Share your feedback or describe your issue. ' +
            'If possible, include steps to reproduce your issue.',
        textAreaElement.placeholder);
    assertTrue(page.getIsPopularContentForTesting());

    // Enter three chars.
    textAreaElement.value = 'abc';
    // Setting the value of the textarea in code does not trigger the
    // input event. So we trigger it here.
    textAreaElement.dispatchEvent(new Event('input'));

    await flushTasks();
    // Verify that getHelpContent() has been called with query 'abc'.
    assertEquals('abc', provider.getLastQuery());
    assertFalse(page.getIsPopularContentForTesting());

    // Enter 2 more characters. This should trigger another search.
    textAreaElement.value = 'abc12';
    textAreaElement.dispatchEvent(new Event('input'));

    await flushTasks();
    // Verify that getHelpContent() has been called with query
    // 'abc12'.
    assertEquals('abc12', provider.getLastQuery());

    // Fire search after pausing typing for 10 seconds.
    page.searchTimoutInMs = 10000;
    // Remove some chars. This should NOT trigger another search.
    textAreaElement.value = 'a';
    textAreaElement.dispatchEvent(new Event('input'));

    await flushTasks();
    // Verify that getHelpContent() has NOT been called with query
    // 'a'.
    assertNotEquals('a', provider.getLastQuery());

    // Fire search immediately for input change.
    page.searchTimoutInMs = 0;

    // Enter one more characters. This should trigger another search.
    textAreaElement.value = 'abc123';
    textAreaElement.dispatchEvent(new Event('input'));

    await flushTasks();
    // Verify that getHelpContent() has been called with query 'abc123'.
    assertEquals('abc123', provider.getLastQuery());
    assertFalse(page.getIsPopularContentForTesting());

    // Remove all the text area characters. This should NOT trigger
    // getHelpContent().
    textAreaElement.value = '';
    textAreaElement.dispatchEvent(new Event('input'));
    await flushTasks();

    // Verify that getHelpContent() is not called, and the help content
    // is the default popular content.
    assertNotEquals('', provider.getLastQuery());
    assertTrue(page.getIsPopularContentForTesting());
  });

  test('searchNotFired_on_oobeOrLogin', async () => {
    await initializePage();
    page.feedbackContext = fakeLoginFlowFeedbackContext;
    const textAreaElement =
        strictQuery('#descriptionText', page!.shadowRoot, HTMLTextAreaElement);
    const initCallCounts = provider.getHelpContentsCallCount();

    // Enter three chars.
    textAreaElement.value = 'abc';
    // Setting the value of the textarea in code does not trigger the
    // input event. So we trigger it here.
    textAreaElement.dispatchEvent(new Event('input'));

    await flushTasks();
    // Verify that getHelpContent() was not called.
    assertEquals(initCallCounts, provider.getHelpContentsCallCount());

    // Enter 2 more characters. This should trigger another search.
    textAreaElement.value = 'abc12';
    textAreaElement.dispatchEvent(new Event('input'));

    await flushTasks();
    // Verify that getHelpContent() was not called.
    assertEquals(initCallCounts, provider.getHelpContentsCallCount());
  });

  test('HelpContentSearchResultCountColdStart', async () => {
    await initializePage();
    // The 'input' event triggers a listener that checks for internal user
    // account, so we need to set up the context.
    page.feedbackContext = fakeFeedbackContext;
    const textAreaElement =
        strictQuery('#descriptionText', page!.shadowRoot, HTMLTextAreaElement);
    // Verify the textarea is empty now.
    assertEquals('', textAreaElement.value);

    // Enter three chars.
    textAreaElement.value = 'abc';
    // Setting the value of the textarea in code does not trigger the
    // input event. So we trigger it here.
    textAreaElement.dispatchEvent(new Event('input'));
    await flushTasks();
    // Verify that getHelpContent() has been called with query 'abc'.
    assertEquals('abc', provider.getLastQuery());
    // Search result count should be 5.
    assertEquals(5, page.getSearchResultCountForTesting());

    provider.setFakeSearchResponse(fakeEmptySearchResponse);
    const longTextNoResult =
        'Whenever I try to open ANY file (MS or otherwise) I get a notice ' +
        'that says “checking to find Microsoft 365 Subscription” I have ' +
        'Office on my PC, but not on my Chromebook. How do I run Word Online ' +
        'on a Chromebook?';
    textAreaElement.value = longTextNoResult;
    textAreaElement.dispatchEvent(new Event('input'));
    await flushTasks();

    // Verify that getHelpContent() has been called with query
    // longTextNoSearchResult.
    assertEquals(longTextNoResult, provider.getLastQuery());
    // Search result count should be 0.
    assertEquals(0, page.getSearchResultCountForTesting());
    // Popular content should be displayed (i.e. isPopularContent = true).
    assertTrue(page.getIsPopularContentForTesting());
  });

  /**
   * Test that popular help content is displayed when no match is returned after
   * filtering but there were matches.
   */
  test('NoItemsReturnedButThereWereMatches', async () => {
    await initializePage();
    page.feedbackContext = fakeFeedbackContext;
    const textAreaElement =
        strictQuery('#descriptionText', page!.shadowRoot, HTMLTextAreaElement);

    const fakeResponse = {
      results: [],  // None items returned after filtering out other languages.
      totalResults: 15,  // 15 matches.
    };

    provider.setFakeSearchResponse(fakeResponse);
    textAreaElement.value = 'abc';

    textAreaElement.dispatchEvent(new Event('input'));
    await flushTasks();
    // Verify that getHelpContent() has been called with query 'abc'.
    assertEquals('abc', provider.getLastQuery());
    // Search result count should be 0.
    assertEquals(0, page.getSearchResultCountForTesting());
    // Popular content should be displayed (i.e. isPopularContent = true).
    assertTrue(page.getIsPopularContentForTesting());
  });

  /**
   * Test that if an older query came back later than its next query, then its
   * results are ignored.
   */
  test('IgnoreOutOfOrderSearchResults', async () => {
    await initializePage();
    page.feedbackContext = fakeFeedbackContext;
    const textAreaElement =
        strictQuery('#descriptionText', page!.shadowRoot, HTMLTextAreaElement);
    await flushTasks();

    const iframe = strictQuery('iframe', page!.shadowRoot, HTMLIFrameElement);
    // Wait for the iframe completes loading.
    await eventToPromise('load', iframe);

    // The query seq no starts from 0. When page is initialized, it will fire a
    // query with empty text.
    assertEquals(1, page.getNextQuerySeqNoForTesting());
    assertEquals(0, page.getLastPostedQuerySeqNoForTesting());

    // Enter some chars.
    textAreaElement.value = 'abc';
    // Setting the value of the textarea in code does not trigger the
    // input event. So we trigger it here.
    textAreaElement.dispatchEvent(new Event('input'));
    await flushTasks();

    // Verify that query seq no has been incremented by 1.
    assertEquals(2, page.getNextQuerySeqNoForTesting());
    // Verify that last posted query seq no has been updated correctly.
    assertEquals(1, page.getLastPostedQuerySeqNoForTesting());

    // Reset the next query seq no to 0 (< 1) to simulate the out of order case.
    // The result should be ignored.
    page.setNextQuerySeqNoForTesting(0);
    assertEquals(0, page.getNextQuerySeqNoForTesting());
    assertEquals(1, page.getLastPostedQuerySeqNoForTesting());

    // Update the text.
    textAreaElement.value = 'a';
    textAreaElement.dispatchEvent(new Event('input'));
    await flushTasks();

    // Verify that query seq no has been incremented by 1.
    assertEquals(1, page.getNextQuerySeqNoForTesting());
    // Verify that the last posted query sequence no was not updated to 0. This
    // means the search results are ignored.
    assertEquals(1, page.getLastPostedQuerySeqNoForTesting());
  });

  /**
   * Test that the search page can send help content to embedded untrusted page
   * via postMessage.
   */
  test('CanCommunicateWithUntrustedPage', async () => {
    /** Whether untrusted page has received new help contents. */
    let helpContentReceived = false;
    /** Number of help contents received by untrusted page. */
    let helpContentCountReceived = 0;

    await initializePage();

    const iframe = strictQuery('iframe', page!.shadowRoot, HTMLIFrameElement);
    // Wait for the iframe completes loading.
    await eventToPromise('load', iframe);

    // There is another message posted from iframe which sends the height of
    // the help content.
    const expectedMessageEventCount = 2;
    let messageEventCount = 0;
    const resolver = new PromiseResolver<void>();

    window.addEventListener('message', (event) => {
      if (OS_FEEDBACK_UNTRUSTED_ORIGIN === event.origin &&
          'help-content-received-for-testing' === event.data.id) {
        helpContentReceived = true;
        helpContentCountReceived = event.data.count;
      }
      messageEventCount++;
      if (messageEventCount === expectedMessageEventCount) {
        resolver.resolve();
      }
    });

    const data = {
      contentList: fakeSearchResponse.results,
      isQueryEmpty: true,
      isPopularContent: true,
    };
    iframe.contentWindow!.postMessage(data, OS_FEEDBACK_UNTRUSTED_ORIGIN);

    // Wait for the "help-content-received" message has been received.
    await resolver.promise;
    // Verify that help contents have been received.
    assertTrue(helpContentReceived);
    // Verify that 5 help contents have been received.
    assertEquals(5, helpContentCountReceived);
  });

  /**
   * Test that when there is no text entered and the continue button is clicked,
   * the error message should be displayed below the textarea and the textarea
   * should receive focus to accept input. Once some text has been entered, the
   * error message should be hidden.
   */
  test('DescriptionEmptyError', async () => {
    await initializePage();
    // The 'input' event triggers a listener that checks for internal user
    // account, so we need to set up the context.
    page.feedbackContext = fakeFeedbackContext;

    const errorMsg =
        strictQuery('#emptyErrorContainer', page!.shadowRoot, HTMLElement);
    // Verify that the error message is hidden in the beginning.
    assertFalse(isVisible(errorMsg));

    const textInput =
        strictQuery('#descriptionText', page!.shadowRoot, HTMLTextAreaElement);
    assertTrue(textInput.value.length === 0);
    // Remove focus on the textarea.
    textInput.blur();
    assertNotEquals(getDeepActiveElement(), textInput);

    const buttonContinue =
        strictQuery('#buttonContinue', page!.shadowRoot, CrButtonElement);
    buttonContinue.click();

    // Verify that the message is visible now.
    assertTrue(isVisible(errorMsg));
    assertEquals('Description is required', errorMsg.textContent!.trim());
    // Verify that the textarea received focus again.
    assertEquals(getDeepActiveElement(), textInput);

    // Now enter some spaces. The error message should still be visible.
    textInput.value = '   ';
    textInput.dispatchEvent(new Event('input'));

    assertTrue(isVisible(errorMsg));

    // Now enter some text. The error message should be hidden again.
    textInput.value = 'hello';
    textInput.dispatchEvent(new Event('input'));

    assertFalse(isVisible(errorMsg));

    // Verify that all whitespace input is treated as empty.
    textInput.value = '   ';
    buttonContinue.click();
    assertTrue(isVisible(errorMsg));
    assertEquals(getDeepActiveElement(), textInput);
  });

  /**
   * Test that when there are certain text entered and the continue button is
   * clicked, an on-continue is fired.
   */
  test('Continue', async () => {
    await initializePage();

    const textInput =
        strictQuery('#descriptionText', page!.shadowRoot, HTMLTextAreaElement);
    textInput.value = 'hello';

    const clickPromise = eventToPromise('continue-click', page);
    let actualCurrentState;

    page.addEventListener(
        'continue-click', (event: FeedbackFlowButtonClickEvent) => {
          actualCurrentState = event.detail.currentState;
        });

    const buttonContinue =
        strictQuery('#buttonContinue', page!.shadowRoot, CrButtonElement);
    buttonContinue.click();

    await clickPromise;
    assertTrue(!!actualCurrentState);
    assertEquals(FeedbackFlowState.SEARCH, actualCurrentState);
  });

  /**
   * Test that when the app is opened on oobe or login screen, the help content
   * section and writing tips are hidden.
   */
  test('HideHelpContentSection_oobe_or_login_screen', async () => {
    await initializePage();
    assertTrue(
        isVisible(strictQuery('iframe', page!.shadowRoot, HTMLIFrameElement)));
    page.feedbackContext = fakeLoginFlowFeedbackContext;
    assertEquals('Login', page.feedbackContext.categoryTag);

    assertFalse(
        isVisible(strictQuery('iframe', page!.shadowRoot, HTMLIFrameElement)));
    assertFalse(isVisible(strictQuery(
        '#feedbackWritingGuidance', page!.shadowRoot, HTMLAnchorElement)));
  });

  /**
   * Test that when the app is not opened on oobe or login screen, the help
   * content section and writing tips are visible.
   */
  test('ShowHelpContentSection_if_not_oobe_or_login_screen', async () => {
    await initializePage();
    page.feedbackContext = fakeFeedbackContext;
    assertEquals('MediaApp', page.feedbackContext.categoryTag);

    assertTrue(
        isVisible(strictQuery('iframe', page!.shadowRoot, HTMLIFrameElement)));
    assertTrue(isVisible(strictQuery(
        '#feedbackWritingGuidance', page!.shadowRoot, HTMLAnchorElement)));
  });

  test('typingBluetoothWithInternalAccountShowsQuestionnaire', async () => {
    await initializePage();
    // The questionnaire will be only shown if the account belongs to an
    // internal user.
    page.feedbackContext = fakeInternalUserFeedbackContext;

    const textAreaElement =
        strictQuery('#descriptionText', page!.shadowRoot, HTMLTextAreaElement);
    textAreaElement.value = 'My cat got a blue tooth because of ChromeOS.';
    // Setting the value of the textarea in code does not trigger the
    // input event. So we trigger it here.
    textAreaElement.dispatchEvent(new Event('input'));
    await flushTasks();

    // Check that the questionnaire and all relevant domain questions are
    // present.
    assertTrue(textAreaElement.value.indexOf(questionnaireBegin) >= 0);
    domainQuestions['bluetooth'].forEach((question) => {
      assertTrue(textAreaElement.value.indexOf(question) >= 0);
    });
  });

  test('typingInternetWithInternalAccountShowsQuestionnaire', async () => {
    await initializePage();
    // The questionnaire will be only shown if the account belongs to an
    // internal user.
    page.feedbackContext = fakeInternalUserFeedbackContext;

    const textAreaElement =
        strictQuery('#descriptionText', page!.shadowRoot, HTMLTextAreaElement);
    textAreaElement.value = 'The entire Internet is down.';
    // Setting the value of the textarea in code does not trigger the
    // input event. So we trigger it here.
    textAreaElement.dispatchEvent(new Event('input'));
    await flushTasks();

    // Check that the questionnaire and all relevant domain questions are
    // present.
    assertTrue(textAreaElement.value.indexOf(questionnaireBegin) >= 0);
    domainQuestions['wifi'].forEach((question) => {
      assertTrue(textAreaElement.value.indexOf(question) >= 0);
    });
  });

  test('typing5GWithInternalAccountShowsQuestionnaire', async () => {
    await initializePage();
    // The questionnaire will be only shown if the account belongs to an
    // internal user.
    page.feedbackContext = fakeInternalUserFeedbackContext;

    const textAreaElement =
        strictQuery('#descriptionText', page!.shadowRoot, HTMLTextAreaElement);
    textAreaElement.value = 'These 5G towers control my mind.';
    // Setting the value of the textarea in code does not trigger the
    // input event. So we trigger it here.
    textAreaElement.dispatchEvent(new Event('input'));
    await flushTasks();

    // Check that the questionnaire and all relevant domain questions are
    // present.
    assertTrue(textAreaElement.value.indexOf(questionnaireBegin) >= 0);
    domainQuestions['cellular'].forEach((question) => {
      assertTrue(textAreaElement.value.indexOf(question) >= 0);
    });
  });

  test('typingDisplayWithInternalAccountShowsQuestionnaire', async () => {
    await initializePage();
    // The questionnaire will be only shown if the account belongs to an
    // internal user.
    page.feedbackContext = fakeInternalUserFeedbackContext;

    const textAreaElement =
        strictQuery('#descriptionText', page!.shadowRoot, HTMLTextAreaElement);
    textAreaElement.value = 'My display is working great!';
    // Setting the value of the textarea in code does not trigger the
    // input event. So we trigger it here.
    textAreaElement.dispatchEvent(new Event('input'));
    await flushTasks();

    // Check that the questionnaire and all relevant domain questions are
    // present.
    assertTrue(textAreaElement.value.indexOf(questionnaireBegin) >= 0);
    domainQuestions['display'].forEach((question) => {
      assertTrue(textAreaElement.value.indexOf(question) >= 0);
    });
  });

  test('typingScreenWithInternalAccountShowsQuestionnaire', async () => {
    await initializePage();
    // The questionnaire will be only shown if the account belongs to an
    // internal user.
    page.feedbackContext = fakeInternalUserFeedbackContext;

    const textAreaElement =
        strictQuery('#descriptionText', page!.shadowRoot, HTMLTextAreaElement);
    textAreaElement.value = 'My screen is too awesome!';
    // Setting the value of the textarea in code does not trigger the
    // input event. So we trigger it here.
    textAreaElement.dispatchEvent(new Event('input'));
    await flushTasks();

    // Check that the questionnaire and all relevant domain questions are
    // present.
    assertTrue(textAreaElement.value.indexOf(questionnaireBegin) >= 0);
    domainQuestions['display'].forEach((question) => {
      assertTrue(textAreaElement.value.indexOf(question) >= 0);
    });
  });

  test('typingWifiDisplayWithInternalAccountShowsQuestionnaire', async () => {
    await initializePage();
    // The questionnaire will be only shown if the account belongs to an
    // internal user.
    page.feedbackContext = fakeInternalUserFeedbackContext;

    const textAreaElement =
        strictQuery('#descriptionText', page!.shadowRoot, HTMLTextAreaElement);
    textAreaElement.value = 'My wifi and display is working great!';
    // Setting the value of the textarea in code does not trigger the
    // input event. So we trigger it here.
    textAreaElement.dispatchEvent(new Event('input'));
    await flushTasks();

    // Check that both questionnaires and all relevant domain questions are
    // present.
    assertTrue(textAreaElement.value.indexOf(questionnaireBegin) >= 0);
    domainQuestions['display'].forEach((question) => {
      assertTrue(textAreaElement.value.indexOf(question) >= 0);
    });
    domainQuestions['wifi'].forEach((question) => {
      assertTrue(textAreaElement.value.indexOf(question) >= 0);
    });
  });

  test('typingMultiDisplayWithInternalAccountShowsQuestionnaire', async () => {
    await initializePage();
    // The questionnaire will be only shown if the account belongs to an
    // internal user.
    page.feedbackContext = fakeInternalUserFeedbackContext;

    const textAreaElement =
        strictQuery('#descriptionText', page!.shadowRoot, HTMLTextAreaElement);
    textAreaElement.value = 'My screen and display is working great over HDMI!';
    // Setting the value of the textarea in code does not trigger the
    // input event. So we trigger it here.
    textAreaElement.dispatchEvent(new Event('input'));
    await flushTasks();

    // Check that the questionnaire and all relevant domain questions are
    // present only once.
    assertTrue(textAreaElement.value.indexOf(questionnaireBegin) >= 0);
    domainQuestions['display'].forEach((question) => {
      const idx = textAreaElement.value.indexOf(question);
      assertTrue(
          idx >= 0 && textAreaElement.value.indexOf(question, idx + 1) < 0);
    });
  });

  test(
      'typingSomethingElseWithInternalAccountDoesNotShowQuestionnaire',
      async () => {
        await initializePage();
        // The questionnaire will be only shown if the account belongs to an
        // internal user.
        page.feedbackContext = fakeInternalUserFeedbackContext;

        const textAreaElement = strictQuery(
            '#descriptionText', page!.shadowRoot, HTMLTextAreaElement);
        textAreaElement.value = 'You should just make ChromeOS better.';
        // Setting the value of the textarea in code does not trigger the
        // input event. So we trigger it here.
        textAreaElement.dispatchEvent(new Event('input'));
        await flushTasks();

        // Check that the questionnaire is not shown.
        assertFalse(textAreaElement.value.indexOf(questionnaireBegin) >= 0);
      });

  test(
      'typingBluetoothWithoutInternalAccountDoesNotShowQuestionnaire',
      async () => {
        await initializePage();
        // The questionnaire will be only shown if the account belongs to an
        // internal user.
        page.feedbackContext = fakeFeedbackContext;

        const textAreaElement = strictQuery(
            '#descriptionText', page!.shadowRoot, HTMLTextAreaElement);
        textAreaElement.value = 'My cat got a blue tooth because of ChromeOS.';
        // Setting the value of the textarea in code does not trigger the
        // input event. So we trigger it here.
        textAreaElement.dispatchEvent(new Event('input'));
        await flushTasks();

        // Check that the questionnaire is not shown.
        assertFalse(textAreaElement.value.indexOf(questionnaireBegin) >= 0);
      });

  test('typingBluetoothTwiceOnlyPastesTheQuestionsOnce', async () => {
    await initializePage();
    // The questionnaire will be only shown if the account belongs to an
    // internal user.
    page.feedbackContext = fakeInternalUserFeedbackContext;

    const textAreaElement =
        strictQuery('#descriptionText', page!.shadowRoot, HTMLTextAreaElement);
    textAreaElement.value = 'My cat got a blue tooth because of ChromeOS.';
    // Setting the value of the textarea in code does not trigger the
    // input event. So we trigger it here twice to simulate pressing two keys.
    textAreaElement.dispatchEvent(new Event('input'));
    textAreaElement.dispatchEvent(new Event('input'));
    await flushTasks();

    // Check that there is only one instance of the first question.
    const question = domainQuestions['bluetooth'][0] as string;
    assertEquals(2, textAreaElement.value.split(question).length);
  });

  test('typingUsbWithInternalAccountShowsQuestionnaire', async () => {
    await initializePage();
    // The questionnaire will be only shown if the account belongs to an
    // internal user.
    page.feedbackContext = fakeInternalUserFeedbackContext;

    const textAreaElement =
        strictQuery('#descriptionText', page!.shadowRoot, HTMLTextAreaElement);
    textAreaElement.value = 'My USB port stopped working!';
    // Setting the value of the textarea in code does not trigger the
    // input event. So we trigger it here.
    textAreaElement.dispatchEvent(new Event('input'));
    await flushTasks();

    // Check that the questionnaire with USB questions is shown.
    assertTrue(textAreaElement.value.indexOf(questionnaireBegin) >= 0);
    domainQuestions['usb'].forEach((question) => {
      assertTrue(textAreaElement.value.indexOf(question) >= 0);
    });
  });

  test('typingThunderboltWithInternalAccountShowsQuestionnaire', async () => {
    await initializePage();
    // The questionnaire will be only shown if the account belongs to an
    // internal user.
    page.feedbackContext = fakeInternalUserFeedbackContext;

    const textAreaElement =
        strictQuery('#descriptionText', page!.shadowRoot, HTMLTextAreaElement);
    textAreaElement.value = 'There is an issue with my Thunderbolt 3 device.';
    // Setting the value of the textarea in code does not trigger the
    // input event. So we trigger it here.
    textAreaElement.dispatchEvent(new Event('input'));
    await flushTasks();

    // Check that the questionnaire with Thunderbolt questions is shown.
    assertTrue(textAreaElement.value.indexOf(questionnaireBegin) >= 0);
    domainQuestions['thunderbolt'].forEach((question) => {
      assertTrue(textAreaElement.value.indexOf(question) >= 0);
    });
  });

  test('thunderboltQuestionnaireIsPrioritizedOverUsb', async () => {
    await initializePage();
    // The questionnaire will be only shown if the account belongs to an
    // internal user.
    page.feedbackContext = fakeInternalUserFeedbackContext;

    const textAreaElement =
        strictQuery('#descriptionText', page.shadowRoot, HTMLTextAreaElement);
    textAreaElement.value = 'The USB-C connector on my TBT4 dock is broken';
    // Setting the value of the textarea in code does not trigger the
    // input event. So we trigger it here.
    textAreaElement.dispatchEvent(new Event('input'));
    await flushTasks();

    // Check that Thunderbolt questions are shown in the questionnaire.
    assertTrue(textAreaElement.value.indexOf(questionnaireBegin) >= 0);
    domainQuestions['thunderbolt'].forEach((question) => {
      assertTrue(textAreaElement.value.indexOf(question) >= 0);
    });

    // Check that USB-specific questions are not shown. Questions shared
    // between USB and Thunderbolt will be included.
    domainQuestions['usb'].forEach((question) => {
      if (domainQuestions['thunderbolt'].indexOf(question) < 0) {
        assertTrue(textAreaElement.value.indexOf(question) < 0);
      }
    });
  });
});