chromium/chrome/test/data/webui/tab_search/tab_search_media_tabs_test.ts

// 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 {MetricsReporterImpl} from 'chrome://resources/js/metrics_reporter/metrics_reporter.js';
import type {ProfileData, Tab, TabSearchPageElement} from 'chrome://tab-search.top-chrome/tab_search.js';
import {TabAlertState, TabSearchApiProxyImpl} from 'chrome://tab-search.top-chrome/tab_search.js';
import {assertEquals} from 'chrome://webui-test/chai_assert.js';
import {keyDownOn} from 'chrome://webui-test/keyboard_mock_interactions.js';
import {MockedMetricsReporter} from 'chrome://webui-test/mocked_metrics_reporter.js';
import {eventToPromise, microtasksFinished} from 'chrome://webui-test/test_util.js';

import {createProfileData, createTab, SAMPLE_WINDOW_DATA, SAMPLE_WINDOW_DATA_WITH_MEDIA_TAB, SAMPLE_WINDOW_HEIGHT} from './tab_search_test_data.js';
import {initLoadTimeDataWithDefaults} from './tab_search_test_helper.js';
import {TestTabSearchApiProxy} from './test_tab_search_api_proxy.js';

suite('TabSearchMediaTabsTest', () => {
  let tabSearchPage: TabSearchPageElement;
  let testProxy: TestTabSearchApiProxy;

  function verifyTabIds(rows: NodeListOf<HTMLElement>, ids: number[]) {
    assertEquals(ids.length, rows.length);
    rows.forEach((row, index) => {
      assertEquals(ids[index]!.toString(), row.getAttribute('id'));
    });
  }

  function queryRows(): NodeListOf<HTMLElement> {
    return tabSearchPage.$.tabsList.querySelectorAll(
        'tab-search-item, tab-search-group-item');
  }

  function queryListTitle(): NodeListOf<HTMLElement> {
    return tabSearchPage.$.tabsList.querySelectorAll('.list-section-title');
  }

  /**
   * @param sampleData A mock data object containing relevant profile data for
   *     the test.
   */
  async function setupTest(
      sampleData: ProfileData,
      loadTimeOverriddenData?: {[key: string]: number|string|boolean}) {
    initLoadTimeDataWithDefaults(loadTimeOverriddenData);

    MetricsReporterImpl.setInstanceForTest(new MockedMetricsReporter());

    testProxy = new TestTabSearchApiProxy();
    testProxy.setProfileData(sampleData);
    TabSearchApiProxyImpl.setInstance(testProxy);

    tabSearchPage = document.createElement('tab-search-page');

    document.body.innerHTML = window.trustedTypes!.emptyHTML;
    document.body.appendChild(tabSearchPage);
    await eventToPromise('selected-change', tabSearchPage.$.tabsList);
    await microtasksFinished();
  }

  test('Verify initially selected tab is most recently used tab', async () => {
    await setupTest(
        createProfileData({
          windows: SAMPLE_WINDOW_DATA_WITH_MEDIA_TAB,
        }),
        {mediaTabsEnabled: true});
    assertEquals(1, tabSearchPage.getSelectedTabIndex());
    const tabSearchItems = queryRows();
    keyDownOn(tabSearchItems[1]!, 0, [], 'ArrowUp');
    await eventToPromise('selected-change', tabSearchPage.$.tabsList);
    await microtasksFinished();
    assertEquals(0, tabSearchPage.getSelectedTabIndex());

    Object.defineProperty(
        document, 'visibilityState', {value: 'hidden', writable: true});
    document.dispatchEvent(new Event('visibilitychange'));
    await microtasksFinished();
    // Note that unlike the 'Verify hiding document resets selection and
    // search text' test case, if no search query was originally provided
    // onSearchChanged will not be called when hidden and the index is not
    // reset until the state is visible again.
    assertEquals(-1, tabSearchPage.getSelectedTabIndex());

    // The selected tab should again be the most recently used tab.
    Object.defineProperty(
        document, 'visibilityState', {value: 'visible', writable: true});
    document.dispatchEvent(new Event('visibilitychange'));
    await microtasksFinished();
    assertEquals(1, tabSearchPage.getSelectedTabIndex());

    // During search there should be no Audio & Video section and the selected
    // index should be 0.
    tabSearchPage.setValue('Google');
    await microtasksFinished();
    verifyTabIds(queryRows(), [2, 1]);
    assertEquals(0, tabSearchPage.getSelectedTabIndex());

    // When the search query is reset the initially selected index should also
    // be reset.
    tabSearchPage.setValue('');
    await microtasksFinished();
    assertEquals(1, tabSearchPage.getSelectedTabIndex());
  });

  test('Verify initially selected tab is not the active tab', async () => {
    const tabs = [
      createTab({
        active: false,
        alertStates: [TabAlertState.kMediaRecording],
        index: 0,
        tabId: 1,
        title: 'Meet',
        url: {url: 'https://meet.google.com/'},
        lastActiveTimeTicks: {internalValue: BigInt(4)},
      }),
      createTab({
        active: false,
        alertStates: [TabAlertState.kAudioPlaying],
        index: 1,
        tabId: 2,
        title: 'Youtube',
        url: {url: 'https://youtube.com/'},
        lastActiveTimeTicks: {internalValue: BigInt(3)},
      }),
      createTab({
        active: true,
        index: 2,
        tabId: 3,
        title: 'Google',
        url: {url: 'https://www.google.com'},
        lastActiveTimeTicks: {internalValue: BigInt(5)},
      }),
      createTab({
        active: false,
        index: 3,
        tabId: 4,
        title: 'Example',
        url: {url: 'https://www.example.com'},
        lastActiveTimeTicks: {internalValue: BigInt(2)},
      }),
    ];

    await setupTest(
        createProfileData({
          windows: [{active: true, height: SAMPLE_WINDOW_HEIGHT, tabs}],
        }),
        {mediaTabsEnabled: true});

    // MRU is the tab with Id 3 but since it is the active tab the selected
    // index should be the next MRU tab.
    assertEquals(0, tabSearchPage.getSelectedTabIndex());
  });

  test('Show media tab in Audio & Video section', async () => {
    await setupTest(
        createProfileData({windows: SAMPLE_WINDOW_DATA_WITH_MEDIA_TAB}));
    // One media tab and two non-media tabs.
    assertEquals(3, queryRows().length);
    // "Audio and Video" and "Open Tabs" section should both exist.
    assertEquals(2, queryListTitle().length);
  });


  test('Tab is no longer media tab', async () => {
    await setupTest(
        createProfileData({windows: SAMPLE_WINDOW_DATA_WITH_MEDIA_TAB}));
    const updatedTab: Tab = createTab({
      alertStates: [],
      tabId: 1,
      lastActiveTimeTicks: {internalValue: BigInt(5)},
    });

    const tabUpdateInfo = {
      inActiveWindow: true,
      tab: updatedTab,
    };
    testProxy.getCallbackRouterRemote().tabUpdated(tabUpdateInfo);
    await microtasksFinished();
    // Three non-media tabs.
    assertEquals(3, queryRows().length);
    // Only "Open Tabs" section should exist.
    assertEquals(1, queryListTitle().length);
  });


  test('Non-media tab becomes media tab', async () => {
    await setupTest(
        createProfileData({windows: SAMPLE_WINDOW_DATA}),
    );
    assertEquals(1, queryListTitle().length);

    const updatedTab: Tab = createTab({
      alertStates: [TabAlertState.kAudioPlaying],
      tabId: 5,
      lastActiveTimeTicks: {internalValue: BigInt(1)},
    });

    const tabUpdateInfo = {
      inActiveWindow: true,
      tab: updatedTab,
    };
    testProxy.getCallbackRouterRemote().tabUpdated(tabUpdateInfo);
    await microtasksFinished();
    // One media tab, the rest are non-media tabs.
    assertEquals(6, queryRows().length);
    // "Audio and Video" and "Open Tabs" section should both exist.
    assertEquals(2, queryListTitle().length);
  });


  test('Search for media tab', async () => {
    await setupTest(
        createProfileData({windows: SAMPLE_WINDOW_DATA_WITH_MEDIA_TAB}));
    tabSearchPage.setValue('google');
    await microtasksFinished();
    // No media tabs section when there is a search query.
    assertEquals(1, queryListTitle().length);
    assertEquals(2, queryRows().length);
  });
});