chromium/chrome/test/data/pdf/ink2_viewer_toolbar_test.ts

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

import {PluginController} from 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/pdf_viewer_wrapper.js';
import {isMac} from 'chrome://resources/js/platform.js';
import {keyDownOn} from 'chrome://webui-test/keyboard_mock_interactions.js';
import {eventToPromise, microtasksFinished} from 'chrome://webui-test/test_util.js';

import {assertCheckboxMenuButton, createMockPdfPluginForTest, enterFullscreenWithUserGesture, finishInkStroke, getRequiredElement, openToolbarMenu} from './test_util.js';

const viewer = document.body.querySelector('pdf-viewer')!;
const viewerToolbar = viewer.$.toolbar;
const controller = PluginController.getInstance();
const mockPlugin = createMockPdfPluginForTest();
controller.setPluginForTesting(mockPlugin);

function getUndoRedoModifier() {
  return isMac ? 'meta' : 'ctrl';
}

chrome.test.runTests([
  // Test that clicking the annotation button toggles annotation mode.
  async function testAnnotationButton() {
    chrome.test.assertFalse(viewerToolbar.annotationMode);

    const annotateButton = getRequiredElement(viewerToolbar, '#annotate');

    annotateButton.click();
    await microtasksFinished();
    chrome.test.assertTrue(viewerToolbar.annotationMode);

    annotateButton.click();
    await microtasksFinished();
    chrome.test.assertFalse(viewerToolbar.annotationMode);
    chrome.test.succeed();
  },
  // Test that toggling annotation mode does not affect displaying annotations.
  async function testTogglingAnnotationModeDoesNotAffectDisplayAnnotations() {
    // The menu needs to be open to check for visible menu elements.
    await openToolbarMenu(viewerToolbar);

    // Start the test with annotation mode disabled and annotations displayed.
    chrome.test.assertFalse(viewerToolbar.annotationMode);
    const showAnnotationsButton =
        getRequiredElement(viewerToolbar, '#show-annotations-button');
    assertCheckboxMenuButton(viewerToolbar, showAnnotationsButton, true);

    // Enabling and disabling annotation mode shouldn't affect displaying
    // annotations.
    viewerToolbar.toggleAnnotation();
    await microtasksFinished();
    chrome.test.assertTrue(viewerToolbar.annotationMode);
    assertCheckboxMenuButton(viewerToolbar, showAnnotationsButton, true);
    viewerToolbar.toggleAnnotation();
    await microtasksFinished();
    chrome.test.assertFalse(viewerToolbar.annotationMode);
    assertCheckboxMenuButton(viewerToolbar, showAnnotationsButton, true);

    // Hide annotations.
    showAnnotationsButton.click();
    await microtasksFinished();

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

    assertCheckboxMenuButton(viewerToolbar, showAnnotationsButton, false);

    // Enabling and disabling annotation mode shouldn't affect displaying
    // annotations.
    viewerToolbar.toggleAnnotation();
    await microtasksFinished();
    chrome.test.assertTrue(viewerToolbar.annotationMode);
    assertCheckboxMenuButton(viewerToolbar, showAnnotationsButton, false);
    viewerToolbar.toggleAnnotation();
    await microtasksFinished();
    chrome.test.assertFalse(viewerToolbar.annotationMode);
    assertCheckboxMenuButton(viewerToolbar, showAnnotationsButton, false);
    chrome.test.succeed();
  },
  // Test that toggling annotation mode sends a message to the PDF content.
  async function testToggleAnnotationModeSendsMessage() {
    chrome.test.assertFalse(viewerToolbar.annotationMode);

    viewerToolbar.toggleAnnotation();
    await microtasksFinished();
    chrome.test.assertTrue(viewerToolbar.annotationMode);

    const enableMessage = mockPlugin.findMessage('setAnnotationMode');
    chrome.test.assertTrue(enableMessage !== null);
    chrome.test.assertEq(enableMessage!.enable, true);

    mockPlugin.clearMessages();

    viewerToolbar.toggleAnnotation();
    await microtasksFinished();
    chrome.test.assertFalse(viewerToolbar.annotationMode);

    const disableMessage = mockPlugin.findMessage('setAnnotationMode');
    chrome.test.assertTrue(disableMessage !== null);
    chrome.test.assertEq(disableMessage!.enable, false);
    chrome.test.succeed();
  },
  // Test that entering presentation mode exits annotation mode, and exiting
  // presentation mode re-enters annotation mode.
  async function testPresentationModeExitsAnnotationMode() {
    // First, check that there's no interaction with toggling presentation mode
    // when annotation mode is disabled.
    chrome.test.assertFalse(viewerToolbar.annotationMode);

    await enterFullscreenWithUserGesture();
    chrome.test.assertFalse(viewerToolbar.annotationMode);

    document.exitFullscreen();
    await eventToPromise('fullscreenchange', viewer.$.scroller);
    chrome.test.assertFalse(viewerToolbar.annotationMode);

    // Now, check the interaction of toggling presentation mode when annotation
    // mode is enabled.
    viewerToolbar.toggleAnnotation();
    await microtasksFinished();
    chrome.test.assertTrue(viewerToolbar.annotationMode);

    // Entering presentation mode should disable annotation mode.
    await enterFullscreenWithUserGesture();
    chrome.test.assertFalse(viewerToolbar.annotationMode);

    // Exiting presentation mode should re-enable annotation mode.
    document.exitFullscreen();
    await eventToPromise('fullscreenchange', viewer.$.scroller);
    chrome.test.assertTrue(viewerToolbar.annotationMode);
    chrome.test.succeed();
  },
  // Test the behavior of the undo and redo buttons.
  async function testUndoRedo() {
    mockPlugin.clearMessages();

    const undoButton =
        getRequiredElement<HTMLButtonElement>(viewerToolbar, '#undo');
    const redoButton =
        getRequiredElement<HTMLButtonElement>(viewerToolbar, '#redo');

    // The buttons should be disabled when there aren't any strokes.
    chrome.test.assertTrue(undoButton.disabled);
    chrome.test.assertTrue(redoButton.disabled);

    // Draw a stroke. The undo button should be enabled.
    finishInkStroke(controller);
    await microtasksFinished();

    chrome.test.assertTrue(
        mockPlugin.findMessage('annotationUndo') === undefined);
    chrome.test.assertFalse(undoButton.disabled);
    chrome.test.assertTrue(redoButton.disabled);

    // Undo the stroke. The redo button should be enabled.
    undoButton.click();
    await microtasksFinished();

    chrome.test.assertTrue(
        mockPlugin.findMessage('annotationUndo') !== undefined);
    chrome.test.assertTrue(undoButton.disabled);
    chrome.test.assertFalse(redoButton.disabled);

    // Redo the stroke. The undo button should be enabled.
    mockPlugin.clearMessages();
    redoButton.click();
    await microtasksFinished();

    chrome.test.assertTrue(
        mockPlugin.findMessage('annotationRedo') !== undefined);
    chrome.test.assertFalse(undoButton.disabled);
    chrome.test.assertTrue(redoButton.disabled);

    // After redo, draw a stroke and undo it after. The undo button and redo
    // button should both be enabled.
    mockPlugin.clearMessages();
    finishInkStroke(controller);
    undoButton.click();
    await microtasksFinished();

    chrome.test.assertTrue(
        mockPlugin.findMessage('annotationUndo') !== undefined);
    chrome.test.assertFalse(undoButton.disabled);
    chrome.test.assertFalse(redoButton.disabled);

    // Draw another stroke, overriding the stroke that could've been redone. The
    // undo button should be enabled.
    finishInkStroke(controller);
    await microtasksFinished();

    chrome.test.assertFalse(undoButton.disabled);
    chrome.test.assertTrue(redoButton.disabled);

    viewerToolbar.resetStrokesForTesting();
    chrome.test.succeed();
  },
  // Test that the undo and redo buttons are disabled when a text form field is
  // focused.
  async function testUndoRedoButtonsDisabledOnFormFieldFocus() {
    mockPlugin.clearMessages();

    chrome.test.assertTrue(viewerToolbar.annotationMode);

    // Exit annotation mode, since form fields can only be focused outside of
    // annotation mode.
    viewerToolbar.toggleAnnotation();
    await microtasksFinished();
    chrome.test.assertFalse(viewerToolbar.annotationMode);

    const undoButton =
        getRequiredElement<HTMLButtonElement>(viewerToolbar, '#undo');
    const redoButton =
        getRequiredElement<HTMLButtonElement>(viewerToolbar, '#redo');

    // Draw two strokes and undo, so that both undo and redo buttons are
    // enabled.
    finishInkStroke(controller);
    finishInkStroke(controller);
    await microtasksFinished();

    undoButton.click();
    await microtasksFinished();

    chrome.test.assertTrue(
        mockPlugin.findMessage('annotationUndo') !== undefined);
    chrome.test.assertFalse(undoButton.disabled);
    chrome.test.assertFalse(redoButton.disabled);

    mockPlugin.clearMessages();

    // Simulate focusing on a text form field. Both buttons should be disabled.
    mockPlugin.dispatchEvent(new MessageEvent(
        'message', {data: {type: 'formFocusChange', focused: 'text'}}));
    await microtasksFinished();

    chrome.test.assertTrue(undoButton.disabled);
    chrome.test.assertTrue(redoButton.disabled);

    // Simulate focusing on a non-text form field. Both buttons should be
    // enabled.
    mockPlugin.dispatchEvent(new MessageEvent(
        'message', {data: {type: 'formFocusChange', focused: 'non-text'}}));
    await microtasksFinished();

    chrome.test.assertFalse(undoButton.disabled);
    chrome.test.assertFalse(redoButton.disabled);

    // Simulate removing focus from the form. Both buttons should be enabled.
    mockPlugin.dispatchEvent(new MessageEvent(
        'message', {data: {type: 'formFocusChange', focused: 'none'}}));
    await microtasksFinished();

    chrome.test.assertFalse(undoButton.disabled);
    chrome.test.assertFalse(redoButton.disabled);

    viewerToolbar.resetStrokesForTesting();
    chrome.test.succeed();
  },
  // Test the behavior of the undo redo keyboard shortcuts.
  async function testUndoRedoKeyboardShortcuts() {
    mockPlugin.clearMessages();

    chrome.test.assertFalse(viewerToolbar.annotationMode);

    // Enable annotation mode.
    viewerToolbar.toggleAnnotation();
    await microtasksFinished();
    chrome.test.assertTrue(viewerToolbar.annotationMode);

    finishInkStroke(controller);

    // Undo shortcut.
    keyDownOn(viewerToolbar, 0, getUndoRedoModifier(), 'z');

    chrome.test.assertTrue(
        mockPlugin.findMessage('annotationUndo') !== undefined);

    mockPlugin.clearMessages();

    // Redo shortcut.
    keyDownOn(viewerToolbar, 0, getUndoRedoModifier(), 'y');

    chrome.test.assertTrue(
        mockPlugin.findMessage('annotationRedo') !== undefined);

    viewerToolbar.resetStrokesForTesting();
    chrome.test.succeed();
  },
  // Test that the undo and redo keyboard shortcuts are disabled when a text
  // form field is focused.
  async function testUndoRedoShortcutsDisabledOnFormFieldFocus() {
    mockPlugin.clearMessages();

    chrome.test.assertTrue(viewerToolbar.annotationMode);

    // Draw two strokes and undo, so that both undo and redo buttons are
    // enabled.
    finishInkStroke(controller);
    finishInkStroke(controller);
    await microtasksFinished();

    getRequiredElement<HTMLButtonElement>(viewerToolbar, '#undo').click();

    // Exit annotation mode, since form fields can only be focused outside of
    // annotation mode.
    viewerToolbar.toggleAnnotation();
    await microtasksFinished();
    chrome.test.assertFalse(viewerToolbar.annotationMode);

    mockPlugin.clearMessages();

    // Simulate focusing on a text form field. Both shortcuts should be
    // disabled.
    mockPlugin.dispatchEvent(new MessageEvent(
        'message', {data: {type: 'formFocusChange', focused: 'text'}}));
    await microtasksFinished();

    keyDownOn(viewerToolbar, 0, getUndoRedoModifier(), 'z');
    keyDownOn(viewerToolbar, 0, getUndoRedoModifier(), 'y');

    chrome.test.assertTrue(
        mockPlugin.findMessage('annotationUndo') === undefined);
    chrome.test.assertTrue(
        mockPlugin.findMessage('annotationRedo') === undefined);

    // Simulate focusing on a non-text form field. Both shortcuts should be
    // enabled.
    mockPlugin.dispatchEvent(new MessageEvent(
        'message', {data: {type: 'formFocusChange', focused: 'non-text'}}));
    await microtasksFinished();

    keyDownOn(viewerToolbar, 0, getUndoRedoModifier(), 'z');
    keyDownOn(viewerToolbar, 0, getUndoRedoModifier(), 'y');

    chrome.test.assertTrue(
        mockPlugin.findMessage('annotationUndo') !== undefined);
    chrome.test.assertTrue(
        mockPlugin.findMessage('annotationRedo') !== undefined);

    mockPlugin.clearMessages();

    // Simulate removing focus from the form. Both shortcuts should be enabled.
    mockPlugin.dispatchEvent(new MessageEvent(
        'message', {data: {type: 'formFocusChange', focused: 'none'}}));
    await microtasksFinished();

    keyDownOn(viewerToolbar, 0, getUndoRedoModifier(), 'z');
    keyDownOn(viewerToolbar, 0, getUndoRedoModifier(), 'y');

    chrome.test.assertTrue(
        mockPlugin.findMessage('annotationUndo') !== undefined);
    chrome.test.assertTrue(
        mockPlugin.findMessage('annotationRedo') !== undefined);

    viewerToolbar.resetStrokesForTesting();
    chrome.test.succeed();
  },
]);