chromium/chrome/browser/resources/chromeos/accessibility/accessibility_common/magnifier/magnifier_test.js

// 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.

GEN_INCLUDE(['../../common/testing/e2e_test_base.js']);
GEN_INCLUDE(['../../common/testing/mock_accessibility_private.js']);

/**
 * Magnifier feature using accessibility common extension browser tests.
 */
MagnifierE2ETest = class extends E2ETestBase {
  constructor() {
    super();
    window.RoleType = chrome.automation.RoleType;
  }

  async getNextMagnifierBounds() {
    return new Promise((resolve) => {
      const listener = (magnifierBounds) => {
        chrome.accessibilityPrivate.onMagnifierBoundsChanged.removeListener(
            listener);
        resolve(magnifierBounds);
      };
      chrome.accessibilityPrivate.onMagnifierBoundsChanged.addListener(
          listener);
    });
  }

  /** @override */
  testGenCppIncludes() {
    super.testGenCppIncludes();
    GEN(`
#include "chrome/browser/ash/accessibility/magnification_manager.h"
#include "ui/accessibility/accessibility_features.h"
    `);
  }

  /** @override */
  testGenPreamble() {
    super.testGenPreamble();
    GEN(`
    base::OnceClosure load_cb =
        base::BindOnce(&ash::MagnificationManager::SetMagnifierEnabled,
            base::Unretained(ash::MagnificationManager::Get()),
            true);
      `);
    super.testGenPreambleCommon('kAccessibilityCommonExtensionId');
  }

  /** @override */
  get featureList() {
    return {
      enabled: [
        'features::kAccessibilityMagnifierFollowsChromeVox',
        'features::kAccessibilityMagnifierFollowsSts',
      ],
    };
  }
};

// Flaky: http://crbug.com/1171635
AX_TEST_F(
    'MagnifierE2ETest', 'DISABLED_MovesScreenMagnifierToFocusedElement',
    async function() {
      const site = `
        <button id="apple">Apple</button><br />
        <button id="banana" style="margin-top: 400px">Banana</button>
      `;
      const root = await this.runWithLoadedTree(site);
      const magnifier = accessibilityCommon.getMagnifierForTest();
      magnifier.setIsInitializingForTest(false);

      const apple = root.find({attributes: {name: 'Apple'}});
      const banana = root.find({attributes: {name: 'Banana'}});

      // Focus and move magnifier to apple.
      apple.focus();

      // Verify magnifier bounds contains apple, but not banana.
      let bounds = await this.getNextMagnifierBounds();
      assertTrue(RectUtil.contains(bounds, apple.location));
      assertFalse(RectUtil.contains(bounds, banana.location));

      // Focus and move magnifier to banana.
      banana.focus();

      // Verify magnifier bounds contains banana, but not apple.
      bounds = await this.getNextMagnifierBounds();
      assertFalse(RectUtil.contains(bounds, apple.location));
      assertTrue(RectUtil.contains(bounds, banana.location));
    });

// Disabled - flaky: https://crbug.com/1145612
AX_TEST_F(
    'MagnifierE2ETest', 'DISABLED_MovesDockedMagnifierToActiveDescendant',
    async function() {
      const site = `
    <div role="group" id="parent" aria-activedescendant="apple">
      <div id="apple" role="treeitem">Apple</div>
      <div id="banana" role="treeitem">Banana</div>
    </div>
    <script>
      const parent = document.getElementById('parent');
      parent.addEventListener('click', function() {
        parent.setAttribute('aria-activedescendant', 'banana');
      });
      </script>
  `;
      const root = await this.runWithLoadedTree(site);
      // Enable docked magnifier.
      await new Promise((resolve) => {
        chrome.accessibilityFeatures.dockedMagnifier.set(
            {value: true}, resolve);
      });

      // Validate magnifier wants to move to root.
      const rootLocation = await getNextMagnifierLocation();
      assertTrue(RectUtil.equal(rootLocation, root.location));

      // Click parent to change active descendant from apple to banana.
      const parent = root.find({role: RoleType.GROUP});
      parent.doDefault();

      // Register and wait for rect from magnifier.
      const rect = await getNextMagnifierLocation();

      // Validate rect from magnifier is rect of banana.
      const bananaNode = root.find({
        role: RoleType.TREE_ITEM,
        attributes: {name: 'Banana'},
      });
      assertTrue(RectUtil.equal(rect, bananaNode.location));
    });

// Flaky: http://crbug.com/1171750
AX_TEST_F(
    'MagnifierE2ETest', 'DISABLED_MovesScreenMagnifierToActiveDescendant',
    async function() {
      const site = `
    <span tabindex="1">Top</span>
    <div id="group" role="group" style="width: 200px"
        aria-activedescendant="apple">
      <div id="apple" role="treeitem">Apple</div>
      <div id="banana" role="treeitem" style="margin-top: 400px">Banana</div>
    </div>
    <script>
      const group = document.getElementById('group');
      group.addEventListener('click', function() {
        group.setAttribute('aria-activedescendant', 'banana');
      });
    </script>
  `;
      const root = await this.runWithLoadedTree(site);
      const magnifier = accessibilityCommon.getMagnifierForTest();
      magnifier.setIsInitializingForTest(false);

      const top = root.find({attributes: {name: 'Top'}});
      const banana = root.find({attributes: {name: 'Banana'}});
      const group = root.find({role: RoleType.GROUP});

      // Focus and move magnifier to top.
      top.focus();

      // Verify magnifier bounds don't contain banana.
      let bounds = await this.getNextMagnifierBounds();
      assertFalse(RectUtil.contains(bounds, banana.location));

      // Click group to change active descendant to banana.
      group.doDefault();

      // Verify magnifier bounds contain banana.
      bounds = await this.getNextMagnifierBounds();
      assertTrue(RectUtil.contains(bounds, banana.location));
    });

TEST_F(
    'MagnifierE2ETest', 'MovesFullscreenMagnifierSelectionEvent', function() {
      this.runWithLoadedDesktop(async function(desktop) {
        const magnifier = accessibilityCommon.getMagnifierForTest();
        magnifier.setIsInitializingForTest(false);

        const moveMenuSelectionAssertBounds = async (targetBounds) => {
          // Send arrow up key.
          chrome.accessibilityPrivate.sendSyntheticKeyEvent({
            type:
                chrome.accessibilityPrivate.SyntheticKeyboardEventType.KEYDOWN,
            keyCode: KeyCode.UP,
          });

          // Verify new magnifier bounds include |targetBounds|.
          await new Promise((resolve) => {
            const boundsChangedListener = (newBounds) => {
              if (RectUtil.contains(newBounds, targetBounds)) {
                chrome.accessibilityPrivate.onMagnifierBoundsChanged
                    .removeListener(boundsChangedListener);
                resolve();
              }
            };
            chrome.accessibilityPrivate.onMagnifierBoundsChanged.addListener(
                boundsChangedListener);
          });
        };

        // Trigger Chrome menu.
        chrome.accessibilityPrivate.sendSyntheticKeyEvent({
          type: chrome.accessibilityPrivate.SyntheticKeyboardEventType.KEYDOWN,
          keyCode: KeyCode.E,
          modifiers: {alt: true},
        });

        // Wait for Chrome menu to open.
        await new Promise(
            (resolve) => desktop.addEventListener(
                chrome.automation.EventType.MENU_START, resolve, false));

        // Move menu selection to end of menu, and await new magnifier bounds.
        await moveMenuSelectionAssertBounds({
          left: 650,
          top: 450,
          width: 0,
          height: 0,
        });
      });
    });

AX_TEST_F('MagnifierE2ETest', 'IgnoresRootNodeFocus', async function() {
  await this.runWithLoadedTree('');

  const magnifier = accessibilityCommon.getMagnifierForTest();
  magnifier.setIsInitializingForTest(false);

  chrome.accessibilityPrivate.onMagnifierBoundsChanged.addListener(
      (newBounds) => {
        throw new Error(
            'Magnifier did not ignore focus change on document load - ' +
            'moved to following location: ' + JSON.stringify(newBounds));
      });

  // Wait seven seconds to verify magnifier successfully ignored focus on root
  // node.
  await new Promise((resolve) => setTimeout(resolve, 7000));
});

// TODO(crbug.com/1295685): Test is flaky.
AX_TEST_F(
    'MagnifierE2ETest', 'DISABLED_MagnifierCenterOnPoint', async function() {
      await this.runWithLoadedTree('');
      const magnifier = accessibilityCommon.getMagnifierForTest();
      magnifier.setIsInitializingForTest(false);

      const movePointAssertBounds = async (targetPoint, targetBounds) => {
        // Repeatedly center magnifier on |targetPoint|.
        const id = setInterval(() => {
          chrome.accessibilityPrivate.magnifierCenterOnPoint(targetPoint);
        }, 500);

        // Verify new magnifier bounds include |targetBounds|.
        await new Promise((resolve) => {
          const boundsChangedListener = (newBounds) => {
            if (RectUtil.contains(newBounds, targetBounds)) {
              chrome.accessibilityPrivate.onMagnifierBoundsChanged
                  .removeListener(boundsChangedListener);
              clearInterval(id);
              resolve();
            }
          };
          chrome.accessibilityPrivate.onMagnifierBoundsChanged.addListener(
              boundsChangedListener);
        });
      };

      // Move magnifier to upper left of screen.
      await movePointAssertBounds(
          {x: 100, y: 100}, {left: 100, top: 100, width: 0, height: 0});

      // Move magnifier to lower right of screen.
      await movePointAssertBounds(
          {x: 650, y: 450}, {left: 650, top: 450, width: 0, height: 0});
    });

AX_TEST_F('MagnifierE2ETest', 'OnCaretBoundsChanged', async function() {
  const site = `
    <input type="text" id="input" style="width: 1000px">
    <button id="button">Type words</button>
    <script>
      const input = document.getElementById('input');
      const button = document.getElementById('button');
      button.addEventListener('click', function() {
        input.focus();
        input.value += 'The quick brown fox jumps over the lazy dog.';
      });
    </script>
  `;
  const root = await this.runWithLoadedTree(site);
  const magnifier = accessibilityCommon.getMagnifierForTest();
  magnifier.setIsInitializingForTest(false);
  const button = root.find({attributes: {name: 'Type words'}});
  const input = root.find({role: RoleType.TEXT_FIELD});
  input.doDefault();

  const typeWordsAssertBounds = async (targetBounds) => {
    // Type words in the input field to move the text caret forward.
    const id = setInterval(() => {
      button.doDefault();
    }, 500);

    // Verify new magnifier bounds include |targetBounds|.
    await new Promise((resolve) => {
      const boundsChangedListener = (newBounds) => {
        if (RectUtil.contains(newBounds, targetBounds)) {
          chrome.accessibilityPrivate.onMagnifierBoundsChanged.removeListener(
              boundsChangedListener);
          clearInterval(id);
          resolve();
        }
      };
      chrome.accessibilityPrivate.onMagnifierBoundsChanged.addListener(
          boundsChangedListener);
    });
  };

  // Type words to move text cursor forward, verify magnifier contains caret.
  await typeWordsAssertBounds({left: 400, top: 100, width: 0, height: 0});

  // Additional words to move caret forward, make sure magnifier follows.
  await typeWordsAssertBounds({left: 800, top: 100, width: 0, height: 0});

  // Even more words to move caret forward, make sure magnifier follows.
  await typeWordsAssertBounds({left: 1200, top: 100, width: 0, height: 0});
});

TEST_F('MagnifierE2ETest', 'ScreenMagnifierFocusFollowingPref', function() {
  this.newCallback(async () => {
    // Disable focus following for full screen magnifier, and verify prefs and
    // state.
    await this.setPref(Magnifier.Prefs.SCREEN_MAGNIFIER_FOCUS_FOLLOWING, false);
    magnifier = accessibilityCommon.getMagnifierForTest();
    magnifier.setIsInitializingForTest(false);
    assertEquals(magnifier.type, Magnifier.Type.FULL_SCREEN);
    assertFalse(magnifier.shouldFollowFocus());

    // Enable focus following for full screen magnifier, and verify prefs and
    // state.
    await this.setPref(Magnifier.Prefs.SCREEN_MAGNIFIER_FOCUS_FOLLOWING, true);
    magnifier = accessibilityCommon.getMagnifierForTest();
    magnifier.setIsInitializingForTest(false);
    assertEquals(magnifier.type, Magnifier.Type.FULL_SCREEN);
    assertTrue(magnifier.shouldFollowFocus());
  })();
});

TEST_F(
    'MagnifierE2ETest', 'ScreenMagnifierSelectToSpeakFollowingPref',
    function() {
      this.newCallback(async () => {
        // Disable select to speak following for full screen magnifier, and
        // verify prefs and state.
        await this.setPref(
            Magnifier.Prefs.SCREEN_MAGNIFIER_SELECT_TO_SPEAK_FOCUS_FOLLOWING,
            false);
        magnifier = accessibilityCommon.getMagnifierForTest();
        magnifier.setIsInitializingForTest(false);
        assertEquals(magnifier.type, Magnifier.Type.FULL_SCREEN);
        assertFalse(magnifier.shouldFollowStsFocus());

        // Enable select to speak following for full screen magnifier, and
        // verify prefs and state.
        await this.setPref(
            Magnifier.Prefs.SCREEN_MAGNIFIER_SELECT_TO_SPEAK_FOCUS_FOLLOWING,
            true);
        magnifier = accessibilityCommon.getMagnifierForTest();
        magnifier.setIsInitializingForTest(false);
        assertEquals(magnifier.type, Magnifier.Type.FULL_SCREEN);
        assertTrue(magnifier.shouldFollowStsFocus());
      })();
    });

TEST_F(
    'MagnifierE2ETest', 'FullscreenMagnifierDoesNotFollowStsWhenPrefOff',
    function() {
      this.newCallback(async () => {
        // Disable select to speak following for full screen magnifier, and
        // verify prefs and state.
        await this.setPref(
            Magnifier.Prefs.SCREEN_MAGNIFIER_SELECT_TO_SPEAK_FOCUS_FOLLOWING,
            false);
        magnifier = accessibilityCommon.getMagnifierForTest();
        magnifier.setIsInitializingForTest(false);
        assertEquals(magnifier.type, Magnifier.Type.FULL_SCREEN);
        assertFalse(magnifier.shouldFollowStsFocus());

        let count = 0;
        chrome.accessibilityPrivate.moveMagnifierToRect = () => (count += 1);
        assertEquals(count, 0);
        magnifier.onSelectToSpeakFocusChanged_({
          left: 2,
          top: 4,
          width: 5,
          height: 7,
        });
        assertEquals(count, 0);
      })();
    });

TEST_F(
    'MagnifierE2ETest', 'FullscreenMagnifierFollowsStsWhenPrefOn', function() {
      this.newCallback(async () => {
        // Disable select to speak following for full screen magnifier, and
        // verify prefs and state.
        await this.setPref(
            Magnifier.Prefs.SCREEN_MAGNIFIER_SELECT_TO_SPEAK_FOCUS_FOLLOWING,
            true);
        magnifier = accessibilityCommon.getMagnifierForTest();
        magnifier.setIsInitializingForTest(false);
        assertEquals(magnifier.type, Magnifier.Type.FULL_SCREEN);
        assertTrue(magnifier.shouldFollowStsFocus());

        let count = 0;
        chrome.accessibilityPrivate.moveMagnifierToRect = () => (count += 1);
        magnifier.lastMouseMovedTime_ = undefined;
        assertEquals(count, 0);
        magnifier.onSelectToSpeakFocusChanged_({
          left: 2,
          top: 4,
          width: 5,
          height: 7,
        });
        assertEquals(count, 1);
      })();
    });

TEST_F('MagnifierE2ETest', 'ScreenMagnifierChromeVoxFollowingPref', function() {
  this.newCallback(async () => {
    // Disable ChromeVox following for full screen magnifier, and
    // verify prefs and state.
    await this.setPref(
        Magnifier.Prefs.SCREEN_MAGNIFIER_CHROMEVOX_FOCUS_FOLLOWING, false);
    magnifier = accessibilityCommon.getMagnifierForTest();
    magnifier.setIsInitializingForTest(false);
    assertEquals(magnifier.type, Magnifier.Type.FULL_SCREEN);
    assertFalse(magnifier.shouldFollowChromeVoxFocus());

    // Enable ChromeVox following for full screen magnifier, and
    // verify prefs and state.
    await this.setPref(
        Magnifier.Prefs.SCREEN_MAGNIFIER_CHROMEVOX_FOCUS_FOLLOWING, true);
    magnifier = accessibilityCommon.getMagnifierForTest();
    magnifier.setIsInitializingForTest(false);
    assertEquals(magnifier.type, Magnifier.Type.FULL_SCREEN);
    assertTrue(magnifier.shouldFollowChromeVoxFocus());
  })();
});

TEST_F(
    'MagnifierE2ETest', 'ScreenMagnifierChromeVoxDoesNotFollowWhenPrefOff',
    function() {
      this.newCallback(async () => {
        // Disable ChromeVox following for full screen magnifier, and
        // verify prefs and state.
        await this.setPref(
            Magnifier.Prefs.SCREEN_MAGNIFIER_CHROMEVOX_FOCUS_FOLLOWING, false);
        magnifier = accessibilityCommon.getMagnifierForTest();
        magnifier.setIsInitializingForTest(false);
        assertEquals(magnifier.type, Magnifier.Type.FULL_SCREEN);
        assertFalse(magnifier.shouldFollowChromeVoxFocus());

        let count = 0;
        chrome.accessibilityPrivate.moveMagnifierToRect = () => (count += 1);
        assertEquals(count, 0);
        magnifier.onChromeVoxFocusChanged_({
          left: 2,
          top: 4,
          width: 5,
          height: 7,
        });
        assertEquals(count, 0);
      })();
    });

TEST_F(
    'MagnifierE2ETest', 'ScreenMagnifierChromeVoxFollowsWhenPrefOn',
    function() {
      this.newCallback(async () => {
        // Disable ChromeVox following for full screen magnifier, and
        // verify prefs and state.
        await this.setPref(
            Magnifier.Prefs.SCREEN_MAGNIFIER_CHROMEVOX_FOCUS_FOLLOWING, true);
        magnifier = accessibilityCommon.getMagnifierForTest();
        magnifier.setIsInitializingForTest(false);
        assertEquals(magnifier.type, Magnifier.Type.FULL_SCREEN);
        assertTrue(magnifier.shouldFollowChromeVoxFocus());

        let count = 0;
        chrome.accessibilityPrivate.moveMagnifierToRect = () => (count += 1);
        magnifier.lastMouseMovedTime_ = undefined;
        assertEquals(count, 0);
        magnifier.onChromeVoxFocusChanged_({
          left: 2,
          top: 4,
          width: 5,
          height: 7,
        });
        assertEquals(count, 1);
      })();
    });