chromium/chrome/browser/resources/chromeos/accessibility/chromevox/background/chromevox_range_test.js

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

// Include test fixture.
GEN_INCLUDE(['../testing/chromevox_e2e_test_base.js']);
GEN_INCLUDE(['../../common/testing/documents.js']);

/** Test fixture for ChromeVoxRange. */
ChromeVoxRangeTest = class extends ChromeVoxE2ETest {
  /** @override */
  async setUpDeferred() {
    await super.setUpDeferred();
    globalThis.RoleType = chrome.automation.RoleType;
  }
};

AX_TEST_F('ChromeVoxRangeTest', 'Observer', async function() {
  const root =
      await this.runWithLoadedTree(Documents.button + Documents.slider);
  const button = root.find({role: RoleType.BUTTON});
  assertNotNullNorUndefined(button);
  const slider = root.find({role: RoleType.SLIDER});
  assertNotNullNorUndefined(slider);

  const observer = new class extends ChromeVoxRangeObserver {
    onCurrentRangeChanged(range, opt_fromEditing) {
      this.capturedRange = range;
      this.capturedFromEditing = opt_fromEditing;
    }
  }
  ();
  ChromeVoxRange.addObserver(observer);

  // Test when range changes not from editing.
  const buttonRange = CursorRange.fromNode(button);
  ChromeVoxRange.set(buttonRange);
  assertEquals(buttonRange, observer.capturedRange);
  assertUndefined(observer.capturedFromEditing);

  // Test when range change is from editing.
  const sliderRange = CursorRange.fromNode(slider);
  ChromeVoxRange.set(sliderRange, true);
  assertEquals(sliderRange, observer.capturedRange);
  assertTrue(observer.capturedFromEditing);

  // Test removeObserver.
  ChromeVoxRange.removeObserver(observer);
  delete observer.capturedRange;
  ChromeVoxRange.set(buttonRange);
  assertUndefined(observer.capturedRange);
});

AX_TEST_F(
    'ChromeVoxRangeTest', 'GetCurrentRangeWithoutRecovery', async function() {
      const root = await this.runWithLoadedTree('');
      ChromeVoxRange.instance.current_ = CursorRange.fromNode(root);
      ChromeVoxRange.instance.current_.isValid = () => false;

      assertNotNullNorUndefined(
          ChromeVoxRange.getCurrentRangeWithoutRecovery());
    });

AX_TEST_F('ChromeVoxRangeTest', 'Current', async function() {
  const root = await this.runWithLoadedTree('');
  ChromeVoxRange.instance.current_ = null;
  assertEquals(null, ChromeVoxRange.current, 'First');

  ChromeVoxRange.instance.current_ = CursorRange.fromNode(root);
  assertEquals(
      ChromeVoxRange.instance.current_, ChromeVoxRange.current, 'Second');

  ChromeVoxRange.instance.current_.isValid = () => false;
  assertEquals(null, ChromeVoxRange.current, 'Third');
});

AX_TEST_F('ChromeVoxRangeTest', 'Set', async function() {
  const root = await this.runWithLoadedTree(Documents.button);
  const rootRange = CursorRange.fromNode(root);
  const button = root.find({role: RoleType.BUTTON});
  assertNotNullNorUndefined(button);
  const buttonRange = CursorRange.fromNode(button);
  const desktopRange = CursorRange.fromNode(this.desktop_);

  let hasThawed;
  ChromeVox.braille.thaw = () => hasThawed = true;
  let lastFocusBounds;
  FocusBounds.set = bounds => lastFocusBounds = bounds;

  const reset = () => {
    hasThawed = false;
    lastFocusBounds = null;
    ChromeVoxRange.instance.current_ = null;
    ChromeVoxRange.instance.previous_ = rootRange;
    ChromeVoxState.position = {};
  };

  // When setting to null and the previous is already null, it returns early.
  reset();
  ChromeVoxRange.instance.current_ = null;
  ChromeVoxRange.set(null);
  assertTrue(hasThawed);
  assertTrue(lastFocusBounds instanceof Array);
  assertEquals(0, lastFocusBounds.length);
  assertNotNullNorUndefined(ChromeVoxRange.instance.previous_);
  assertEquals(0, Object.keys(ChromeVoxState.position).length);

  // When the new range is not valid, it returns early.
  reset();
  const invalidRange = CursorRange.fromNode(root);
  invalidRange.isValid = () => false;
  ChromeVoxRange.set(invalidRange);
  assertTrue(hasThawed);
  assertTrue(lastFocusBounds instanceof Array);
  assertEquals(0, lastFocusBounds.length);
  assertEquals(0, Object.keys(ChromeVoxState.position).length);

  // When the new range is null, set the previous and current and then return.
  reset();
  ChromeVoxRange.instance.current_ = buttonRange;
  ChromeVoxRange.set(null);
  assertTrue(hasThawed);
  assertTrue(lastFocusBounds instanceof Array);
  assertEquals(0, lastFocusBounds.length);
  assertEquals(buttonRange, ChromeVoxRange.instance.previous_);
  assertEquals(null, ChromeVoxRange.instance.current_);
  assertEquals(0, Object.keys(ChromeVoxState.position).length);

  // When the node's root is the desktop, don't set a position.
  reset();
  ChromeVoxRange.set(desktopRange);
  assertTrue(hasThawed);
  assertEquals(null, ChromeVoxRange.instance.previous_);
  assertEquals(desktopRange, ChromeVoxRange.instance.current_);
  assertEquals(0, Object.keys(ChromeVoxState.position).length);

  // Check that the position is set when the focus is in a webpage.
  reset();
  ChromeVoxRange.set(buttonRange);
  assertTrue(hasThawed);
  assertEquals(null, ChromeVoxRange.instance.previous_);
  assertEquals(buttonRange, ChromeVoxRange.instance.current_);
  assertEquals(1, Object.keys(ChromeVoxState.position).length);
});

TEST_F('ChromeVoxRangeTest', 'MaybeResetFromFocus', async function() {
  const root = await this.runWithLoadedTree(Documents.button);
  const button = root.find({role: RoleType.BUTTON});

  let focusNode = null;
  chrome.automation.getFocus = callback => callback(focusNode);

  // If there's no focused node, the range is set to null.
  ChromeVoxRange.instance.current_ = CursorRange.fromNode(button);
  ChromeVoxRange.maybeResetFromFocus();
  assertEquals(null, ChromeVoxRange.instance.current_);

  // If the current node is nod valid and there's a current focus, set the
  // current range to be the focus.
  const invalidRange = CursorRange.fromNode(button);
  invalidRange.isValid = () => false;
  ChromeVoxRange.instance.current_ = invalidRange;
  focusNode = root;
  ChromeVoxRange.maybeResetFromFocus();
  assertEquals(focusNode, ChromeVoxRange.instance.current_);

  // If talkback is enabled, clear the range.
  ChromeVoxState.instance.talkBackEnabled = true;
  focusNode = this.desktop_.find({role: RoleType.CLIENT});
  assertNotNullNorUndefined(focusNode);
  ChromeVoxRange.maybeResetFromFocus();
  assertEquals(null, ChromeVoxRange.instance.current_);
});