chromium/chrome/test/data/webui/chromeos/ash_common/cr_elements/cr_toggle_test.ts

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

// clang-format off
import 'chrome://resources/ash/common/cr_elements/cr_toggle/cr_toggle.js';

import {getTrustedHTML} from 'chrome://resources/js/static_types.js';
import {CrToggleElement, MOVE_THRESHOLD_PX} from 'chrome://resources/ash/common/cr_elements/cr_toggle/cr_toggle.js';
import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {eventToPromise} from 'chrome://webui-test/test_util.js';
// clang-format on

suite('cr-toggle', function() {
  let toggle: CrToggleElement;

  setup(function() {
    document.body.innerHTML = window.trustedTypes!.emptyHTML;
    toggle = document.createElement('cr-toggle');
    document.body.appendChild(toggle);
    assertNotChecked();
  });

  function assertChecked() {
    assertTrue(toggle.checked);
    assertTrue(toggle.hasAttribute('checked'));
    assertEquals('true', toggle.getAttribute('aria-pressed'));
    // Asserting that the toggle button has actually moved.
    assertTrue(getComputedStyle(toggle.$.knob).transform.includes('matrix'));
  }

  function assertNotChecked() {
    assertFalse(toggle.checked);
    assertEquals(null, toggle.getAttribute('checked'));
    assertEquals('false', toggle.getAttribute('aria-pressed'));
    // Asserting that the toggle button has not moved.
    assertEquals('none', getComputedStyle(toggle.$.knob).transform);
  }

  function assertDisabled() {
    assertTrue(toggle.disabled);
    assertEquals('-1', toggle.getAttribute('tabindex'));
    assertTrue(toggle.hasAttribute('disabled'));
    assertEquals('true', toggle.getAttribute('aria-disabled'));
  }

  function assertNotDisabled() {
    assertFalse(toggle.disabled);
    assertEquals('0', toggle.getAttribute('tabindex'));
    assertFalse(toggle.hasAttribute('disabled'));
    assertEquals('false', toggle.getAttribute('aria-disabled'));
  }

  /**
   * Simulates dragging the toggle button left/right.
   * @param moveDirection -1 for left, 1 for right, 0 when no pointermove event
   *     should be simulated.
   * @param diff The move amount in pixels. Only relevant if moveDirection is
   *     non-zero.
   */
  function triggerPointerDownMoveUpTapSequence(
      moveDirection: -1|0|1, diff?: number) {
    const computedStyles = window.getComputedStyle(toggle);
    if (computedStyles.getPropertyValue('pointer-events') === 'none') {
      return;
    }

    // Simulate events in the same order they are fired by the browser.
    // Need to provide a valid |pointerId| for setPointerCapture() to not throw
    // an error.
    const xStart = 100;
    toggle.dispatchEvent(
        new PointerEvent('pointerdown', {pointerId: 1, clientX: xStart}));
    let xEnd = xStart;
    if (moveDirection) {
      xEnd = moveDirection > 0 ? xStart + diff! : xStart - diff!;
      toggle.dispatchEvent(
          new PointerEvent('pointermove', {pointerId: 1, clientX: xEnd}));
    }
    toggle.dispatchEvent(
        new PointerEvent('pointerup', {pointerId: 1, clientX: xEnd}));
    toggle.click();
  }

  // Check if setting checked in HTML works, has to use a separate element to
  // ensure that we are testing brand new state.
  test('initiallyCheckedWorks', function() {
    document.body.innerHTML = getTrustedHTML`<cr-toggle checked></cr-toggle> `;
    toggle = (document.querySelector('cr-toggle'))!;
    assertChecked();
  });

  // Test that the control is toggled when the |checked| attribute is
  // programmatically changed.
  test('ToggleByAttribute', function() {
    eventToPromise('change', toggle).then(function() {
      // Should not fire 'change' event when state is changed programmatically.
      // Only user interaction should result in 'change' event.
      assertFalse(true);
    });

    toggle.checked = true;
    assertChecked();

    toggle.checked = false;
    assertNotChecked();
  });

  // Test that the control is toggled when the user taps on it (no movement
  // between pointerdown and pointerup).
  test('ToggleByPointerTap', function() {
    let whenChanged = eventToPromise('change', toggle);
    triggerPointerDownMoveUpTapSequence(0 /* no pointermove */);
    return whenChanged
        .then(function() {
          assertChecked();
          whenChanged = eventToPromise('change', toggle);
          triggerPointerDownMoveUpTapSequence(0 /* no pointermove */);
          return whenChanged;
        })
        .then(function() {
          assertNotChecked();
        });
  });

  // Test that the control is toggled if the user moves the pointer by a
  // MOVE_THRESHOLD_PX pixels accidentally (shaky hands) in any direction.
  test('ToggleByShakyPointerTap', function() {
    let whenChanged = eventToPromise('change', toggle);
    triggerPointerDownMoveUpTapSequence(1 /* right */, MOVE_THRESHOLD_PX - 1);
    return whenChanged
        .then(function() {
          assertChecked();
          whenChanged = eventToPromise('change', toggle);
          triggerPointerDownMoveUpTapSequence(
              1 /* right */, MOVE_THRESHOLD_PX - 1);
          return whenChanged;
        })
        .then(function() {
          assertNotChecked();
        });
  });

  // Test that the control is toggled when the user moves the pointer while
  // holding down.
  test('ToggleByPointerMove', function() {
    let whenChanged = eventToPromise('change', toggle);
    triggerPointerDownMoveUpTapSequence(1 /* right */, MOVE_THRESHOLD_PX);
    return whenChanged
        .then(function() {
          assertChecked();
          whenChanged = eventToPromise('change', toggle);
          triggerPointerDownMoveUpTapSequence(-1 /* left */, MOVE_THRESHOLD_PX);
          return whenChanged;
        })
        .then(function() {
          assertNotChecked();
          whenChanged = eventToPromise('change', toggle);

          // Test simple tapping after having dragged.
          triggerPointerDownMoveUpTapSequence(0 /* no pointermove */);
          return whenChanged;
        })
        .then(function() {
          assertChecked();
        });
  });

  // Test that the control is toggled when the user presses the 'Enter' or
  // 'Space' key.
  test('ToggleByKey', () => {
    assertNotChecked();
    toggle.dispatchEvent(
        new KeyboardEvent('keydown', {key: 'Enter', repeat: true}));
    assertNotChecked();
    toggle.dispatchEvent(new KeyboardEvent('keydown', {key: ' '}));
    assertNotChecked();
    toggle.dispatchEvent(
        new KeyboardEvent('keydown', {key: ' ', repeat: true}));
    assertNotChecked();
    toggle.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter'}));
    assertChecked();
    toggle.dispatchEvent(new KeyboardEvent('keyup', {key: ' '}));
    assertNotChecked();
  });

  // Test that the control is not affected by user interaction when disabled.
  test('ToggleWhenDisabled', function() {
    assertNotDisabled();
    toggle.disabled = true;
    assertDisabled();

    triggerPointerDownMoveUpTapSequence(0 /* no pointermove */);
    assertNotChecked();
    assertDisabled();

    toggle.disabled = false;
    triggerPointerDownMoveUpTapSequence(0 /* no pointermove */);
    assertChecked();
  });

  // Test that the control works as expected when the click() method is called.
  test('ToggleWhenWithClick', function() {
    assertNotDisabled();
    assertNotChecked();

    // State should change because control is enabled.
    toggle.click();
    assertChecked();

    // State should *not* change because control is disabled.
    toggle.disabled = true;
    assertDisabled();
    toggle.click();
    assertChecked();
  });
});