chromium/chrome/browser/resources/chromeos/accessibility/accessibility_common/facegaze/scroll_mode_controller_test.js

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

GEN_INCLUDE(['facegaze_test_base.js']);

/** FazeGaze ScrollModeController tests. */
FaceGazeScrollModeControllerTest = class extends FaceGazeTestBase {
  /** @override */
  testGenPreamble() {
    super.testGenPreamble();
    super.testGenPreambleCommon(
        /*extensionIdName=*/ 'kAccessibilityCommonExtensionId',
        /*failOnConsoleError=*/ true);
  }

  /** @override */
  async setUpDeferred() {
    await super.setUpDeferred();

    // The coordinates for the center of the screen.
    this.center_ = {x: 600, y: 400};

    const config = new Config()
                       .withMouseLocation(this.center_)
                       .withBufferSize(1)
                       .withCursorControlEnabled(true);
    await this.startFacegazeWithConfigAndForeheadLocation_(config, 0.1, 0.2);
  }

  toggleScrollMode() {
    this.getFaceGaze().mouseController_.toggleScrollMode();
  }

  /** @return {!ScrollModeController} */
  getScrollModeController() {
    return this.getFaceGaze().mouseController_.scrollModeController_;
  }
};

AX_TEST_F('FaceGazeScrollModeControllerTest', 'Active', async function() {
  const controller = this.getScrollModeController();
  assertNotNullNorUndefined(controller);
  assertNullOrUndefined(controller.mouseLocation_);
  assertNullOrUndefined(controller.center_);
  assertFalse(controller.active());

  this.toggleScrollMode();
  assertTrue(controller.active());
  assertNotNullNorUndefined(controller.mouseLocation_);
  assertNotNullNorUndefined(controller.center_);

  this.toggleScrollMode();
  assertFalse(controller.active());
  assertNullOrUndefined(controller.mouseLocation_);
  assertNullOrUndefined(controller.center_);
});

AX_TEST_F('FaceGazeScrollModeControllerTest', 'CenterPoint', async function() {
  const controller = this.getScrollModeController();
  this.toggleScrollMode();
  // Ensure the controller's center point is properly initialized.
  assertEquals(this.center_.x, controller.center_.x);
  assertEquals(this.center_.y, controller.center_.y);
});

AX_TEST_F(
    'FaceGazeScrollModeControllerTest', 'GetDirectionUndefined',
    async function() {
      const controller = this.getScrollModeController();

      // We should not scroll if ScrollModeController is inactive.
      assertFalse(controller.active());
      assertNullOrUndefined(controller.getDirection_({x: 1000, y: 1000}));

      this.toggleScrollMode();
      assertTrue(controller.active());

      // We should not scroll if the new location is within the threshold value,
      // e.g. if the point is on or within a circle that is centered at
      // controller.center_ and has radius of
      // ScrollModeController.DELTA_THRESHOLD.

      const pointsWithinCircle = [
        {
          x: this.center_.x,
          y: this.center_.y,
        },
        {
          x: this.center_.x + ScrollModeController.DELTA_THRESHOLD / 2,
          y: this.center_.y + ScrollModeController.DELTA_THRESHOLD / 2,
        },
      ];

      const pointsOnCircle = [
        {
          x: this.center_.x + ScrollModeController.DELTA_THRESHOLD,
          y: this.center_.y,
        },
        {
          x: this.center_.x - ScrollModeController.DELTA_THRESHOLD,
          y: this.center_.y,
        },
        {
          x: this.center_.x,
          y: this.center_.y + ScrollModeController.DELTA_THRESHOLD,
        },
        {
          x: this.center_.x,
          y: this.center_.y - ScrollModeController.DELTA_THRESHOLD,
        },
      ];

      // Test points that are outside of the circle, but are at ambiguous angles
      // of the circle, e.g. 45 degrees, 135 degrees, 225 degrees, and 315
      // degrees.

      // Use additional delta to ensure that points lie outside of the circle.
      const additionalDelta = ScrollModeController.DELTA_THRESHOLD * 2;
      const pointsAtAmbiguousAngles = [
        {
          x: this.center_.x + additionalDelta,
          y: this.center_.y + additionalDelta,
        },
        {
          x: this.center_.x + additionalDelta,
          y: this.center_.y - additionalDelta,
        },
        {
          x: this.center_.x - additionalDelta,
          y: this.center_.y + additionalDelta,
        },
        {
          x: this.center_.x - additionalDelta,
          y: this.center_.y - additionalDelta,
        },
      ];

      for (const point of pointsWithinCircle.concat(pointsOnCircle)
               .concat(pointsAtAmbiguousAngles)) {
        assertNullOrUndefined(
            controller.getDirection_(point),
            `Expected point (${point.x}, ${
                point.y}) to yield an undefined direction`);
      }

      this.toggleScrollMode();
      // We should not scroll if ScrollModeController is inactive.
      assertFalse(controller.active());
      assertNullOrUndefined(controller.getDirection_({x: 1000, y: 1000}));
    });

AX_TEST_F(
    'FaceGazeScrollModeControllerTest', 'GetDirectionValid', async function() {
      const controller = this.getScrollModeController();
      this.toggleScrollMode();

      // We should scroll if the new location is outside the threshold value,
      // e.g. if the point is outside a circle that is centered at
      // controller.center_ and has radius of
      // ScrollModeController.DELTA_THRESHOLD.

      const additionalDelta = ScrollModeController.DELTA_THRESHOLD * 2;

      const pointsUp = [
        // Creates an angle of 45.1 degrees with the positive x-axis.
        {
          x: this.center_.x + additionalDelta - 1,
          y: this.center_.y - additionalDelta,
        },
        // Creates an angle of 90 degrees with the positive x-axis.
        {
          x: this.center_.x,
          y: this.center_.y - additionalDelta,
        },
        // Creates an angle of 134.8 degrees with the positive x-axis.
        {
          x: this.center_.x - additionalDelta + 1,
          y: this.center_.y - additionalDelta,
        },
      ];

      for (const point of pointsUp) {
        assertEquals(
            this.scrollDirection.UP, controller.getDirection_(point),
            `Expected point (${point.x}, ${
                point.y}) to yield a direction of UP`);
      }

      const pointsDown = [
        // Creates an angle of -45.1 degrees with the positive x-axis.
        {
          x: this.center_.x + additionalDelta - 1,
          y: this.center_.y + additionalDelta,
        },
        // Creates an angle of -90 degrees with the positive x-axis.
        {
          x: this.center_.x,
          y: this.center_.y + additionalDelta,
        },
        // Creates an angle of -134.8 degrees with the positive x-axis.
        {
          x: this.center_.x - additionalDelta + 1,
          y: this.center_.y + additionalDelta,
        },
      ];

      for (const point of pointsDown) {
        assertEquals(
            this.scrollDirection.DOWN, controller.getDirection_(point),
            `Expected point (${point.x}, ${
                point.y}) to yield a direction of DOWN`);
      }

      const pointsLeft = [
        // Creates an angle of 135.1 degrees with the positive x-axis.
        {
          x: this.center_.x - additionalDelta - 1,
          y: this.center_.y - additionalDelta,
        },
        // Creates an angle of 180 degrees with the positive x-axis.
        {
          x: this.center_.x - additionalDelta,
          y: this.center_.y,
        },
        // Creates an angle of -135.1 degrees with the positive x-axis.
        {
          x: this.center_.x - additionalDelta - 1,
          y: this.center_.y + additionalDelta,
        },
      ];

      for (const point of pointsLeft) {
        assertEquals(
            this.scrollDirection.LEFT, controller.getDirection_(point),
            `Expected point (${point.x}, ${
                point.y}) to yield a direction of LEFT`);
      }

      const pointsRight = [
        // Creates an angle of 0 degrees with the positive x-axis.
        {
          x: this.center_.x + additionalDelta,
          y: this.center_.y,
        },
        // Creates an angle of 44.8 degrees with the positive x-axis.
        {
          x: this.center_.x + additionalDelta,
          y: this.center_.y - additionalDelta + 1,
        },
        // Creates an angle of -44.8 degrees with the positive x-axis.
        {
          x: this.center_.x + additionalDelta,
          y: this.center_.y + additionalDelta - 1,
        },
      ];

      for (const point of pointsRight) {
        assertEquals(
            this.scrollDirection.RIGHT, controller.getDirection_(point),
            `Expected point (${point.x}, ${
                point.y}) to yield a direction of RIGHT`);
      }
    });

AX_TEST_F(
    'FaceGazeScrollModeControllerTest', 'CallsApiOnValidPoint',
    async function() {
      const controller = this.getScrollModeController();
      this.toggleScrollMode();
      const point = {
        x: this.center_.x + ScrollModeController.DELTA_THRESHOLD + 1,
        y: this.center_.y,
      };

      controller.scroll(point);
      const mockApi = this.mockAccessibilityPrivate;
      assertEquals(1, mockApi.getScrollAtPointCount());
      // The scroll target is wherever the mouse is when scroll mode is toggled.
      // In this case, it's the center.
      assertEquals(this.center_.x, mockApi.getScrollAtPointTarget().x);
      assertEquals(this.center_.y, mockApi.getScrollAtPointTarget().y);
      assertEquals(
          this.scrollDirection.RIGHT, mockApi.getScrollAtPointDirection());
      assertNotEquals(0, controller.lastScrollTime_);
    });

AX_TEST_F(
    'FaceGazeScrollModeControllerTest', 'SkipsApiOnInvalidPoint',
    async function() {
      const controller = this.getScrollModeController();
      this.toggleScrollMode();
      controller.scroll(this.center_);

      const mockApi = this.mockAccessibilityPrivate;
      assertEquals(0, mockApi.getScrollAtPointCount());
      assertNullOrUndefined(mockApi.getScrollAtPointTarget());
      assertNullOrUndefined(mockApi.getScrollAtPointDirection());
    });