chromium/chrome/test/data/pdf/viewer_toolbar_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 type {ViewerToolbarElement} from 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/pdf_viewer_wrapper.js';
import {FittingType} from 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/pdf_viewer_wrapper.js';
import {eventToPromise, microtasksFinished} from 'chrome://webui-test/test_util.js';

import {assertCheckboxMenuButton, openToolbarMenu} from './test_util.js';

function createToolbar() {
  document.body.innerHTML = '';
  const toolbar = document.createElement('viewer-toolbar');
  document.body.appendChild(toolbar);
  return toolbar;
}

/**
 * Returns the cr-icon-buttons in |toolbar|'s shadowRoot under |parentId|.
 */
function getCrIconButtons(toolbar: ViewerToolbarElement, parentId: string) {
  return toolbar.shadowRoot!.querySelector(`#${parentId}`)!.querySelectorAll(
      'cr-icon-button');
}

// Unit tests for the viewer-toolbar element.
const tests = [
  /**
   * Test that the toolbar toggles between showing the fit-to-page and
   * fit-to-width buttons.
   */
  async function testFitButton() {
    const toolbar = createToolbar();
    const fitButton = getCrIconButtons(toolbar, 'center')[2]!;
    const fitWidthIcon = 'pdf:fit-to-width';
    const fitHeightIcon = 'pdf:fit-to-height';

    let lastFitType = '';
    let numEvents = 0;
    toolbar.addEventListener('fit-to-changed', e => {
      lastFitType = e.detail;
      numEvents++;
    });

    // Initially FIT_TO_WIDTH, show FIT_TO_PAGE.
    chrome.test.assertEq(fitHeightIcon, fitButton.ironIcon);

    // Tap 1: Fire fit-to-changed(FIT_TO_PAGE), show fit-to-width.
    fitButton.click();
    chrome.test.assertEq(FittingType.FIT_TO_PAGE, lastFitType);
    chrome.test.assertEq(1, numEvents);
    await microtasksFinished();
    chrome.test.assertEq(fitWidthIcon, fitButton.ironIcon);

    // Tap 2: Fire fit-to-changed(FIT_TO_WIDTH), show fit-to-page.
    fitButton.click();
    chrome.test.assertEq(FittingType.FIT_TO_WIDTH, lastFitType);
    chrome.test.assertEq(2, numEvents);
    await microtasksFinished();
    chrome.test.assertEq(fitHeightIcon, fitButton.ironIcon);

    // Do the same as above, but with fitToggle().
    toolbar.fitToggle();
    chrome.test.assertEq(FittingType.FIT_TO_PAGE, lastFitType);
    chrome.test.assertEq(3, numEvents);
    await microtasksFinished();
    chrome.test.assertEq(fitWidthIcon, fitButton.ironIcon);
    toolbar.fitToggle();
    chrome.test.assertEq(FittingType.FIT_TO_WIDTH, lastFitType);
    chrome.test.assertEq(4, numEvents);
    await microtasksFinished();
    chrome.test.assertEq(fitHeightIcon, fitButton.ironIcon);

    // Test forceFit(FIT_TO_PAGE): Updates the icon, does not fire an event.
    toolbar.forceFit(FittingType.FIT_TO_PAGE);
    chrome.test.assertEq(4, numEvents);
    await microtasksFinished();
    chrome.test.assertEq(fitWidthIcon, fitButton.ironIcon);

    // Force fitting the same fit as the existing fit should do nothing.
    toolbar.forceFit(FittingType.FIT_TO_PAGE);
    chrome.test.assertEq(4, numEvents);
    await microtasksFinished();
    chrome.test.assertEq(fitWidthIcon, fitButton.ironIcon);

    // Force fit width.
    toolbar.forceFit(FittingType.FIT_TO_WIDTH);
    chrome.test.assertEq(4, numEvents);
    await microtasksFinished();
    chrome.test.assertEq(fitHeightIcon, fitButton.ironIcon);

    // Force fit height.
    toolbar.forceFit(FittingType.FIT_TO_HEIGHT);
    chrome.test.assertEq(4, numEvents);
    await microtasksFinished();
    chrome.test.assertEq(fitWidthIcon, fitButton.ironIcon);

    chrome.test.succeed();
  },

  async function testZoomButtons() {
    const toolbar = createToolbar();
    toolbar.zoomBounds = {min: 25, max: 500};
    toolbar.viewportZoom = 1;
    await microtasksFinished();

    let zoomInCount = 0;
    let zoomOutCount = 0;
    toolbar.addEventListener('zoom-in', () => zoomInCount++);
    toolbar.addEventListener('zoom-out', () => zoomOutCount++);

    const zoomButtons = getCrIconButtons(toolbar, 'zoom-controls');
    chrome.test.assertEq(2, zoomButtons.length);
    chrome.test.assertFalse(zoomButtons[0]!.disabled);
    chrome.test.assertFalse(zoomButtons[1]!.disabled);

    // Zoom out
    chrome.test.assertEq('pdf:remove', zoomButtons[0]!.ironIcon);
    zoomButtons[0]!.click();
    await microtasksFinished();
    chrome.test.assertEq(0, zoomInCount);
    chrome.test.assertEq(1, zoomOutCount);

    // Set zoom to min. Zoom out is disabled.
    toolbar.viewportZoom = .25;
    await microtasksFinished();
    chrome.test.assertTrue(zoomButtons[0]!.disabled);
    chrome.test.assertFalse(zoomButtons[1]!.disabled);

    // Zoom in
    chrome.test.assertEq('pdf:add', zoomButtons[1]!.ironIcon);
    zoomButtons[1]!.click();
    await microtasksFinished();
    chrome.test.assertEq(1, zoomInCount);
    chrome.test.assertEq(1, zoomOutCount);

    // Set zoom to max. Zoom in is disabled.
    toolbar.zoomBounds = {min: 25, max: 500};
    toolbar.viewportZoom = 5;
    await microtasksFinished();
    chrome.test.assertFalse(zoomButtons[0]!.disabled);
    chrome.test.assertTrue(zoomButtons[1]!.disabled);

    chrome.test.succeed();
  },

  async function testRotateButton() {
    const toolbar = createToolbar();
    const rotateButton = getCrIconButtons(toolbar, 'center')[3]!;
    chrome.test.assertEq('pdf:rotate-left', rotateButton.ironIcon);

    const whenRotateLeft = eventToPromise('rotate-left', toolbar);
    rotateButton.click();
    await whenRotateLeft;
    chrome.test.succeed();
  },

  async function testZoomField() {
    const toolbar = createToolbar();
    toolbar.viewportZoom = .8;
    toolbar.zoomBounds = {min: 25, max: 500};
    await microtasksFinished();
    const zoomField = toolbar.shadowRoot!.querySelector<HTMLInputElement>(
        '#zoom-controls input')!;
    chrome.test.assertEq('80%', zoomField.value);

    // Value is set based on viewport zoom.
    toolbar.viewportZoom = .533;
    await microtasksFinished();
    chrome.test.assertEq('53%', zoomField.value);

    // Setting a non-number value resets to viewport zoom.
    zoomField.value = 'abc';
    zoomField.dispatchEvent(new CustomEvent('change'));
    await microtasksFinished();
    chrome.test.assertEq('53%', zoomField.value);

    // Setting a value that is over the max zoom clips to the max value.
    const whenSent = eventToPromise('zoom-changed', toolbar);
    zoomField.value = '90000%';
    zoomField.dispatchEvent(new CustomEvent('change'));
    let event = await whenSent;
    chrome.test.assertEq(500, event.detail);

    // This happens in the parent.
    toolbar.viewportZoom = 5;
    await microtasksFinished();
    chrome.test.assertEq('500%', zoomField.value);

    // Setting a value that is over the maximum again restores the max
    // value, even though no event is sent.
    zoomField.value = '80000%';
    zoomField.dispatchEvent(new CustomEvent('change'));
    await microtasksFinished();
    chrome.test.assertEq('500%', zoomField.value);

    // Setting a new value sends the value in a zoom-changed event.
    const whenSentNew = eventToPromise('zoom-changed', toolbar);
    zoomField.value = '110%';
    zoomField.dispatchEvent(new CustomEvent('change'));
    event = await whenSentNew;
    chrome.test.assertEq(110, event.detail);

    // Setting a new value and blurring sends the value in a zoom-changed
    // event. If the value is below the minimum, this sends the minimum
    // zoom.
    const whenSentFromBlur = eventToPromise('zoom-changed', toolbar);
    zoomField.value = '18%';
    zoomField.dispatchEvent(new CustomEvent('blur'));
    event = await whenSentFromBlur;
    chrome.test.assertEq(25, event.detail);
    chrome.test.succeed();
  },

  // Test that the overflow menu closes when an action is triggered.
  function testOverflowMenuCloses() {
    const toolbar = createToolbar();
    const menu = toolbar.$.menu;
    chrome.test.assertFalse(menu.open);

    const more = toolbar.shadowRoot!.querySelector<HTMLElement>('#more')!;
    const buttons = menu.querySelectorAll<HTMLElement>('.dropdown-item');
    chrome.test.assertTrue(buttons.length > 0);

    for (const button of buttons) {
      // Open overflow menu.
      more.click();
      chrome.test.assertTrue(menu.open);
      button.click();
      chrome.test.assertFalse(menu.open);
    }
    chrome.test.succeed();
  },

  async function testTwoPageViewToggle() {
    const toolbar = createToolbar();

    // The menu needs to be open to check for visible menu elements.
    await openToolbarMenu(toolbar);

    toolbar.twoUpViewEnabled = false;
    await microtasksFinished();
    const button = toolbar.shadowRoot!.querySelector<HTMLElement>(
        '#two-page-view-button')!;
    assertCheckboxMenuButton(toolbar, button, false);

    let whenChanged = eventToPromise('two-up-view-changed', toolbar);
    button.click();
    let event = await whenChanged;

    // Clicking the button closes the menu, so re-open it.
    await openToolbarMenu(toolbar);

    // Happens in the parent.
    toolbar.twoUpViewEnabled = true;
    await microtasksFinished();
    chrome.test.assertEq(true, event.detail);
    assertCheckboxMenuButton(toolbar, button, true);
    whenChanged = eventToPromise('two-up-view-changed', toolbar);
    button.click();
    event = await whenChanged;

    await openToolbarMenu(toolbar);

    // Happens in the parent.
    toolbar.twoUpViewEnabled = false;
    await microtasksFinished();
    chrome.test.assertEq(false, event.detail);
    assertCheckboxMenuButton(toolbar, button, false);
    chrome.test.succeed();
  },

  async function testShowAnnotationsToggle() {
    const toolbar = createToolbar();

    // The menu needs to be open to check for visible menu elements.
    await openToolbarMenu(toolbar);

    const button = toolbar.shadowRoot!.querySelector<HTMLElement>(
        '#show-annotations-button')!;
    assertCheckboxMenuButton(toolbar, button, true);

    let whenChanged = eventToPromise('display-annotations-changed', toolbar);
    button.click();
    let event = await whenChanged;

    // Clicking the button closes the menu, so re-open it.
    await openToolbarMenu(toolbar);

    chrome.test.assertEq(false, event.detail);
    assertCheckboxMenuButton(toolbar, button, false);
    whenChanged = eventToPromise('display-annotations-changed', toolbar);
    button.click();
    event = await whenChanged;

    await openToolbarMenu(toolbar);

    chrome.test.assertEq(true, event.detail);
    assertCheckboxMenuButton(toolbar, button, true);
    chrome.test.succeed();
  },

  async function testSidenavToggleButton() {
    const toolbar = createToolbar();
    chrome.test.assertFalse(toolbar.sidenavCollapsed);

    const toggleButton = toolbar.$.sidenavToggle;
    chrome.test.assertTrue(toggleButton.hasAttribute('aria-label'));
    chrome.test.assertTrue(toggleButton.hasAttribute('title'));
    chrome.test.assertEq('true', toggleButton.getAttribute('aria-expanded'));

    toolbar.sidenavCollapsed = true;
    await microtasksFinished();
    chrome.test.assertEq('false', toggleButton.getAttribute('aria-expanded'));

    const event = eventToPromise('sidenav-toggle-click', toolbar);
    toggleButton.click();
    await event;
    chrome.test.succeed();
  },

  async function testPresentButton() {
    const toolbar = createToolbar();
    const button =
        toolbar.shadowRoot!.querySelector<HTMLElement>('#present-button');
    chrome.test.assertTrue(!!button);

    chrome.test.assertFalse(toolbar.$['present-button'].disabled);
    const whenFired = eventToPromise('present-click', toolbar);
    button!.click();
    await whenFired;

    // The present button should be disabled if the PDF Viewer is embedded.
    toolbar.embeddedViewer = true;
    await microtasksFinished();
    chrome.test.assertTrue(toolbar.$['present-button'].disabled);
    chrome.test.succeed();
  },

  async function testPropertiesButton() {
    const toolbar = createToolbar();
    const button =
        toolbar.shadowRoot!.querySelector<HTMLElement>('#properties-button');
    chrome.test.assertTrue(!!button);

    const whenFired = eventToPromise('properties-click', toolbar);
    button!.click();
    await whenFired;
    chrome.test.succeed();
  },
];

chrome.test.runTests(tests);