chromium/chrome/test/data/webui/chromeos/ash_common/navigation_view_panel_test.js

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {SelectorItem} from 'chrome://resources/ash/common/navigation_selector.js';
import {NavigationViewPanelElement} from 'chrome://resources/ash/common/navigation_view_panel.js';
import {assert} from 'chrome://resources/ash/common/assert.js';
import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';

import {assertEquals, assertFalse, assertThrows, assertTrue} from 'chrome://webui-test/chromeos/chai_assert.js';
import {eventToPromise} from 'chrome://webui-test/test_util.js';

suite('navigationViewPanelTestSuite', () => {
  /** @type {?NavigationViewPanelElement} */
  let viewElement = null;

  /** @type {number} */
  let eventCount = 0;
  /** @type {!Object} */
  let eventDetail = {};
  /** @type {number} */
  let numPageChangedCount = 0;

  setup(() => {
    viewElement =
        /** @type {!NavigationViewPanelElement} */ (
            document.createElement('navigation-view-panel'));
    document.body.appendChild(viewElement);
  });

  teardown(() => {
    viewElement.remove();
    viewElement = null;
    eventCount = 0;
    eventDetail = {};
    numPageChangedCount = 0;
  });

  /**
   * @param {!Object} e
   */
  function handleEvent(e) {
    eventDetail = e;
    eventCount++;
  }

  /**
   * @param {!CustomEvent} e
   */
  function onNavigationPageChanged(e) {
    numPageChangedCount++;
  }

  /**
   * @return {!NodeList<!HTMLElement>}
   */
  function getNavElements() {
    const sideNav = viewElement.shadowRoot.querySelector('navigation-selector');
    assert(!!sideNav);
    const navElements = sideNav.shadowRoot.querySelectorAll('.navigation-item');
    assert(!!navElements);
    return navElements;
  }

  /**
   * Adds a section to the navigation element.
   * @param {string} name
   * @param {string} pageType
   * @param {string} icon
   * @param {?string} id
   * @param {?Object} initialData
   * @return {!Promise}
   */
  function addNavigationSection(
      name, pageType, icon = '', id = null, initialData = null) {
    viewElement.addSelector(name, pageType, icon, id, initialData);
    return flushTasks();
  }

  /**
   * Adds sections to the navigation element.
   * @param {!Array<!SelectorItem>} sections
   * @return {!Promise}
   */
  function addNavigationSections(sections) {
    viewElement.addSelectors(sections);
    return flushTasks();
  }

  function getDrawer() {
    return /** @type {!CrDrawerElement} */ (
        viewElement.shadowRoot.querySelector('#drawer'));
  }

  /**
   * @return {!Promise}
   */
  function clickDrawerIcon() {
    viewElement.shadowRoot.querySelector('#iconButton').click();
    return flushTasks();
  }

  test('twoEntries', async () => {
    const dummyPage1 = 'dummy-page1';
    const dummyPage2 = 'dummy-page2';
    await addNavigationSection('dummyPage1', dummyPage1);
    await addNavigationSection('dummyPage2', dummyPage2);

    // Click the first selector item. Expect that the dummyPage1 to be created
    // and not hidden.
    const navElements = getNavElements();
    navElements[0].click();
    await flushTasks();
    const dummyElement1 =
        viewElement.shadowRoot.querySelector(`#${dummyPage1}`);
    assertFalse(dummyElement1.hidden);
    dummyElement1['onNavigationPageChanged'] = onNavigationPageChanged;

    // Click the second selector item. Expect that the dummyPage2 to be created
    // and not hidden. dummyPage1 should be hidden now.
    navElements[1].click();
    await flushTasks();
    const dummyElement2 =
        viewElement.shadowRoot.querySelector(`#${dummyPage2}`);
    dummyElement2['onNavigationPageChanged'] = onNavigationPageChanged;
    assertFalse(dummyElement2.hidden);
    assertTrue(dummyElement1.hidden);
    // Only one page has implemented "onNavigationPageChanged" by the second
    // navigation click, expect only one client to be notified.
    assertEquals(1, numPageChangedCount);

    // Click the first selector item. Expect that dummyPage2 is now hidden and
    // dummyPage1 is not hidden.
    navElements[0].click();
    await flushTasks();
    assertTrue(dummyElement2.hidden);
    assertFalse(dummyElement1.hidden);
    // Now that both dummy pages have implemented "onNavigationPageChanged",
    // the navigation click will trigger both page's methods.
    assertEquals(3, numPageChangedCount);
  });

  test('notifyEvent', async () => {
    const dummyPage1 = 'dummy-page1';
    await addNavigationSection('dummyPage1', dummyPage1);

    // Create the element.
    const navElements = getNavElements();
    navElements[0].click();
    await flushTasks();
    const dummyElement = viewElement.shadowRoot.querySelector(`#${dummyPage1}`);

    const functionName = 'onEventReceived';
    const expectedDetail = 'test';
    // Set the function handler for the element.
    dummyElement[functionName] = handleEvent;
    // Trigger notifyEvent and expect |dummyElement| to capture the event.
    viewElement.notifyEvent(functionName, {detail: expectedDetail});

    assertEquals(1, eventCount);
    assertEquals(expectedDetail, eventDetail.detail);
  });

  test('defaultPage', async () => {
    const dummyPage1 = 'dummy-page1';
    const dummyPage2 = 'dummy-page2';

    await addNavigationSections([
      viewElement.createSelectorItem('dummyPage1', dummyPage1),
      viewElement.createSelectorItem('dummyPage2', dummyPage2),
    ]);

    assertFalse(viewElement.shadowRoot.querySelector(`#${dummyPage1}`).hidden);
    assertFalse(!!viewElement.shadowRoot.querySelector(`#${dummyPage2}`));
  });

  test('defaultPageSetUsingQueryParam', async () => {
    const queryParams = new URLSearchParams(window.location.search);
    queryParams.set('page2', '');
    // Replace current querystring with the new one.
    window.history.replaceState(null, '', '?' + queryParams.toString());

    await addNavigationSections([
      viewElement.createSelectorItem('dummyPage1', 'dummy-page1', '', 'page1'),
      viewElement.createSelectorItem('dummyPage1', 'dummy-page2', '', 'page2'),
    ]);


    assertFalse(viewElement.shadowRoot.querySelector('#page2').hidden);
    assertFalse(!!viewElement.shadowRoot.querySelector('#page1'));
  });

  test('samePageTypeDifferentId', async () => {
    const pageType = 'myPageType';
    const id1 = 'id1';
    const id2 = 'id2';

    // Add two pages of the the same type with different ids.
    await addNavigationSections([
      viewElement.createSelectorItem('Page 1', pageType, /*icon=*/ '', 'id1'),
      viewElement.createSelectorItem('Page 2', pageType, /*icon=*/ '', 'id2'),
    ]);

    // First page should be created by default.
    assertTrue(!!viewElement.shadowRoot.querySelector(`#${id1}`));
    assertFalse(viewElement.shadowRoot.querySelector(`#${id1}`).hidden);
    assertFalse(!!viewElement.shadowRoot.querySelector(`#${id2}`));

    // Nav to the second page and it should be created and the first page
    // should be hidden.
    const navElements = getNavElements();
    navElements[1].click();
    await flushTasks();

    assertTrue(viewElement.shadowRoot.querySelector(`#${id1}`).hidden);
    assertTrue(!!viewElement.shadowRoot.querySelector(`#${id2}`));
    assertFalse(viewElement.shadowRoot.querySelector(`#${id2}`).hidden);
  });

  test('toolBarVisible', async () => {
    const dummyPage1 = 'dummy-page1';
    const expectedTitle = 'title';
    viewElement.title = expectedTitle;
    viewElement.showToolBar = true;

    await addNavigationSections([
      viewElement.createSelectorItem('dummyPage1', dummyPage1),
    ]);

    assertFalse(viewElement.shadowRoot.querySelector(`#${dummyPage1}`).hidden);
    // The title is only visible if the toolbar is stamped.
    const pageToolbar = viewElement.shadowRoot.querySelector('page-toolbar');
    const toolbarTitle = pageToolbar.$.title.textContent.trim();
    assertEquals(expectedTitle, toolbarTitle);
  });

  test('ToggleDrawer', async () => {
    const dummyPage1 = 'dummy-page1';
    const expectedTitle = 'title';
    viewElement.title = expectedTitle;
    viewElement.showToolBar = true;

    await addNavigationSections([
      viewElement.createSelectorItem('dummyPage1', dummyPage1),
    ]);

    const drawer = getDrawer();
    drawer.openDrawer();
    await eventToPromise('cr-drawer-opened', drawer);
    assertTrue(drawer.open);

    // Clicking the drawer icon closes the drawer.
    await clickDrawerIcon();
    await eventToPromise('close', drawer);
    assertFalse(drawer.open);
    assertTrue(drawer.wasCanceled());
  });

  test('CloseDrawerWhenClickingSameItem', async () => {
    const pageType = 'myPageType';
    const id1 = 'id1';
    const id2 = 'id2';

    viewElement.title = 'title';

    await addNavigationSections([
      viewElement.createSelectorItem('Page 1', pageType, /*icon=*/ '', 'id1'),
      viewElement.createSelectorItem('Page 2', pageType, /*icon=*/ '', 'id2'),
    ]);

    // The first element is visible, others hidden.
    assertTrue(!!viewElement.shadowRoot.querySelector(`#${id1}`));
    assertFalse(viewElement.shadowRoot.querySelector(`#${id1}`).hidden);
    assertFalse(!!viewElement.shadowRoot.querySelector(`#${id2}`));

    const drawer = getDrawer();
    drawer.openDrawer();
    await eventToPromise('cr-drawer-opened', drawer);
    assertTrue(drawer.open);

    // Clicking the first entry closes the drawer.
    const navElements = getNavElements();
    navElements[0].click();
    await flushTasks();

    await eventToPromise('close', drawer);
    assertFalse(drawer.open);

    // The first element is visible, others hidden.
    assertTrue(!!viewElement.shadowRoot.querySelector(`#${id1}`));
    assertFalse(viewElement.shadowRoot.querySelector(`#${id1}`).hidden);
    assertFalse(!!viewElement.shadowRoot.querySelector(`#${id2}`));

    // Re-open and click the first navigation item, expect drawer to close.
    drawer.openDrawer();
    await eventToPromise('cr-drawer-opened', drawer);
    assertTrue(drawer.open);

    const navElements1 = getNavElements();
    navElements1[0].click();
    await flushTasks();

    await eventToPromise('close', drawer);
    assertFalse(drawer.open);
  });

  test('removeSelectedPage', async () => {
    await addNavigationSections([
      viewElement.createSelectorItem(
          'Page 1', 'dummy-page1', /*icon=*/ '', 'dummy1'),
      viewElement.createSelectorItem(
          'Page 2', 'dummy-page2', /*icon=*/ '', 'dummy2'),
    ]);

    const navElements = getNavElements();
    navElements[1].click();
    await flushTasks();

    viewElement.removeSelectorById('dummy2');
    assertEquals('dummy1', viewElement.selectedItem.id);
  });

  test('removeLastPage', async () => {
    await addNavigationSection('dummyPage1', 'dummy-page1', '', 'dummy1');
    await flushTasks();
    assertThrows(
        () => viewElement.removeSelectorById('dummy1'),
        'Removing the last selector is not supported.');
  });

  test('selectPageById', async () => {
    await addNavigationSections([
      viewElement.createSelectorItem(
          /*name=*/ 'Page 1',
          /*pageIs=*/ 'dummy-page1',
          /*icon=*/ '',
          /*id=*/ 'dummy1'),
      viewElement.createSelectorItem(
          /*name=*/ 'Page 2',
          /*pageIs=*/ 'dummy-page2',
          /*icon=*/ '',
          /*id=*/ 'dummy2'),
      viewElement.createSelectorItem(
          /*name=*/ 'Page 3',
          /*pageIs=*/ 'dummy-page3',
          /*icon=*/ '',
          /*id=*/ 'dummy3'),
    ]);

    // The first page should be selected by default.
    assertEquals('dummy1', viewElement.selectedItem.id);

    // Select a different page id and verify that the correct page is selected.
    viewElement.selectPageById('dummy2');
    assertEquals('dummy2', viewElement.selectedItem.id);

    // Select a different page id and verify that the correct page is selected.
    viewElement.selectPageById('dummy3');
    assertEquals('dummy3', viewElement.selectedItem.id);

    // Select a non-existent page ID and verify that the selected page did
    // not change.
    viewElement.selectPageById('does-not-exist');
    assertEquals('dummy3', viewElement.selectedItem.id);
  });
});