chromium/chrome/test/data/webui/chromeos/settings/os_settings_ui/os_settings_ui_page_visibility_revamp_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.

/**
 * @fileoverview
 * Suite of tests for page visibility in the CrOS Settings UI.
 *
 * - These tests expect the OsSettingsRevampWayfinding feature flag to be
 *   enabled.
 * - This suite is separated into a dedicated file to mitigate test timeouts
 *   since the element is very large.
 */

import 'chrome://os-settings/os_settings.js';

import {AccountManagerBrowserProxyImpl} from 'chrome://os-settings/lazy_load.js';
import {createRouterForTesting, CrSettingsPrefs, MainPageContainerElement, OsSettingsMainElement, OsSettingsMenuElement, OsSettingsRoutes, OsSettingsUiElement, PageDisplayerElement, Router, routes, routesMojom, SettingsIdleLoadElement} from 'chrome://os-settings/os_settings.js';
import {assert} from 'chrome://resources/js/assert.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
import {isVisible} from 'chrome://webui-test/test_util.js';

import {TestAccountManagerBrowserProxy} from '../os_people_page/test_account_manager_browser_proxy.js';

suite('<os-settings-ui> page visibility', () => {
  let ui: OsSettingsUiElement;
  let settingsMain: OsSettingsMainElement;
  let mainPageContainer: MainPageContainerElement;
  let menu: OsSettingsMenuElement;
  let browserProxy: TestAccountManagerBrowserProxy;

  async function createUi() {
    ui = document.createElement('os-settings-ui');
    document.body.appendChild(ui);
    flush();
    await CrSettingsPrefs.initialized;

    const mainEl = ui.shadowRoot!.querySelector('os-settings-main');
    assert(mainEl);
    settingsMain = mainEl;

    const mainPageContainerEl =
        settingsMain.shadowRoot!.querySelector('main-page-container');
    assert(mainPageContainerEl);
    mainPageContainer = mainPageContainerEl;

    const menuEl = ui.shadowRoot!.querySelector<OsSettingsMenuElement>(
        '#left os-settings-menu');
    assert(menuEl);
    menu = menuEl;

    // Force load advanced page container
    const advancedPageTemplate =
        mainPageContainer.shadowRoot!.querySelector<SettingsIdleLoadElement>(
            '#advancedPageTemplate');
    assert(advancedPageTemplate);
    await advancedPageTemplate.get();
    flush();
  }

  function queryMenuItemByPath(path: string): HTMLElement|null {
    return menu.shadowRoot!.querySelector<HTMLElement>(
        `os-settings-menu-item[path="${path}"]`);
  }

  /**
   * Asserts the following:
   * - The page for |section| is the only page marked active
   * - The page for |section| is the only page visible
   */
  function assertIsOnlyActiveAndVisiblePage(section: routesMojom.Section):
      void {
    const pages =
        mainPageContainer.shadowRoot!.querySelectorAll('page-displayer');
    for (const page of pages) {
      if (page.section === section) {
        assertTrue(page.active);
        assertTrue(isVisible(page));
      } else {
        assertFalse(page.active);
        assertFalse(isVisible(page));
      }
    }
  }

  /**
   * Asserts the page with the given |section| is focused.
   */
  function assertPageIsFocused(section: routesMojom.Section): void {
    const page = mainPageContainer.shadowRoot!.querySelector(
        `page-displayer[section="${section}"`);
    assertEquals(page, mainPageContainer.shadowRoot!.activeElement);
  }

  function isDefaultRouteElement(element: HTMLElement) {
    return element.getAttribute('route-path') === 'default';
  }

  /**
   * Verifies the page has a visible element (L1 page) for the default route,
   * and that any subpages are hidden.
   */
  function assertSubpagesHidden(section: routesMojom.Section): void {
    const pageDisplayer =
        mainPageContainer.shadowRoot!.querySelector<PageDisplayerElement>(
            `page-displayer[section="${section}"]`);
    assert(pageDisplayer);

    const pages = pageDisplayer.firstElementChild!.shadowRoot!.querySelector(
        'os-settings-animated-pages');
    assert(pages);

    const children =
        pages.shadowRoot!.querySelector('slot')!.assignedNodes({flatten: true})
            .filter(n => n.nodeType === Node.ELEMENT_NODE) as HTMLElement[];

    const stampedChildren = children.filter((element) => {
      return element.tagName !== 'TEMPLATE';
    });

    // The page's default route element should be stamped and visible.
    const defaultRouteElements = stampedChildren.filter(isDefaultRouteElement);
    assertEquals(
        1, defaultRouteElements.length,
        `Default route element not found for section ${section}`);
    const defaultRouteEl = defaultRouteElements[0];
    assert(defaultRouteEl);
    assertTrue(
        isVisible(defaultRouteEl),
        `Default route element for section ${section} should be visible.`);

    // Any other stamped subpages should not be visible.
    const subpages = stampedChildren.filter((element) => {
      return !isDefaultRouteElement(element);
    });
    for (const subpage of subpages) {
      assertFalse(
          isVisible(subpage),
          `Subpages for section ${section} should be hidden.`);
    }
  }

  suiteSetup(async () => {
    browserProxy = new TestAccountManagerBrowserProxy();
    AccountManagerBrowserProxyImpl.setInstanceForTesting(browserProxy);
    assertTrue(
        loadTimeData.getBoolean('isRevampWayfindingEnabled'),
        'This suite expects OsSettingsRevampWayfinding to be enabled.');
    loadTimeData.overrideValues({
      isKerberosEnabled: true,  // Simulate kerberos route exists
    });

    // Reinitialize Router and routes based on load time data
    const testRouter = createRouterForTesting();
    Router.resetInstanceForTesting(testRouter);

    await createUi();
  });

  suiteTeardown(() => {
    ui.remove();
  });

  teardown(() => {
    Router.getInstance().resetRouteForTesting();
  });

  test('Document body has feature class when feature flag is enabled', () => {
    assertTrue(document.body.classList.contains('revamp-wayfinding-enabled'));
  });

  test('Network page should be the default visible page', () => {
    assertIsOnlyActiveAndVisiblePage(routesMojom.Section.kNetwork);
  });

  // Sort by order of menu items
  const routeNames: Array<keyof OsSettingsRoutes> = [
    'INTERNET',
    // Currently in the revamp, the Bluetooth L1 page automatically
    // redirects to the Bluetooth devices subpage.
    // TODO(b/309808834) Uncomment the line for the Bluetooth route below once
    // the L1 page is revamped and re-implemented.
    // 'BLUETOOTH',
    'MULTIDEVICE',
    'OS_PEOPLE',
    'KERBEROS',
    'DEVICE',
    'PERSONALIZATION',
    'OS_PRIVACY',
    'APPS',
    'OS_ACCESSIBILITY',
    'SYSTEM_PREFERENCES',
    'ABOUT',
  ];
  for (const routeName of routeNames) {
    test(
        `Clicking menu item for route ${routeName} should show that page only`,
        async () => {
          const route = routes[routeName];

          const menuItem = queryMenuItemByPath(route.path);
          assert(menuItem, `Menu item with path="${route.path}" not found.`);

          menuItem.click();
          await flushTasks();

          const section = route.section;
          assert(section !== null);  // Value can be 0 (valid enum value)
          assertIsOnlyActiveAndVisiblePage(section);
          assertPageIsFocused(section);
          assertSubpagesHidden(section);
        });
  }
});