chromium/chrome/test/data/webui/cr_elements/cr_ripple_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 'chrome://resources/cr_elements/cr_ripple/cr_ripple.js';

import type {CrRippleElement} from 'chrome://resources/cr_elements/cr_ripple/cr_ripple.js';
import {assertEquals, assertFalse, assertNotReached, assertTrue} from 'chrome://webui-test/chai_assert.js';

suite('CrRipple', function() {
  let ripple: CrRippleElement;

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

  function pointerdown() {
    ripple.parentElement!.dispatchEvent(
        new PointerEvent('pointerdown', {pointerId: 1}));
  }

  function pointerup() {
    ripple.parentElement!.dispatchEvent(
        new PointerEvent('pointerup', {pointerId: 1}));
  }

  function keydown(key: string) {
    ripple.parentElement!.dispatchEvent(
        new KeyboardEvent('keydown', {key, cancelable: true}));
  }

  function keyup(key: string) {
    ripple.parentElement!.dispatchEvent(new KeyboardEvent('keyup', {key}));
  }

  function assertRipplesShown(count: number, triggerFn: () => void) {
    return new Promise<void>(resolve => {
      const addedNodes = [];
      const removedNodes = [];

      const observer = new MutationObserver((records: MutationRecord[]) => {
        for (const record of records) {
          if (record.type !== 'childList') {
            continue;
          }

          for (const node of [...record.addedNodes, ...record.removedNodes]) {
            assertEquals(Node.ELEMENT_NODE, node.nodeType);
            assertTrue((node as Element).classList.contains('ripple'));
          }

          addedNodes.push(...record.addedNodes);
          removedNodes.push(...record.removedNodes);

          if (removedNodes.length === count) {
            assertEquals(count, addedNodes.length);
            resolve();
          }
        }
      });
      observer.observe(ripple.shadowRoot!, {childList: true});
      triggerFn();
    });
  }

  function assertRipplesNotShown(triggerFn: () => void) {
    return new Promise<void>(resolve => {
      const observer = new MutationObserver((records: MutationRecord[]) => {
        for (const record of records) {
          if (record.type !== 'childList') {
            continue;
          }

          assertNotReached('Unexpected ripple shown');
        }
      });
      observer.observe(ripple.shadowRoot!, {childList: true});

      // Yield to ensure that any unexpected ripples have a chance to surface.
      window.setTimeout(() => {
        resolve();
      }, 1);

      triggerFn();
    });
  }

  test('DefaultState', function() {
    assertFalse(ripple.noink);
    assertFalse(ripple.holdDown);
    assertFalse(ripple.recenters);
  });

  test('RippleShown_EnterKey', function() {
    return assertRipplesShown(1, () => {
      keydown('Enter');
    });
  });

  test('RippleShown_SpaceKey', function() {
    return assertRipplesShown(1, () => {
      keydown(' ');
      keyup(' ');
    });
  });

  test('RippleShown_PointerEvents', function() {
    return assertRipplesShown(1, () => {
      pointerdown();
      pointerup();
    });
  });

  test('uiDownAction_Single', function() {
    return assertRipplesShown(1, () => {
      ripple.uiDownAction();
      ripple.uiUpAction();
    });
  });

  test('uiDownAction_Multiple', function() {
    return assertRipplesShown(3, () => {
      ripple.uiDownAction();
      ripple.uiDownAction();
      ripple.uiDownAction();
      ripple.uiUpAction();
      ripple.uiUpAction();
      ripple.uiUpAction();
    });
  });

  test('showAndHoldDown', function() {
    return assertRipplesShown(1, async () => {
      ripple.showAndHoldDown();
      await ripple.updateComplete;
      ripple.clear();
    });
  });

  test('noink', async function() {
    ripple.noink = true;
    await assertRipplesNotShown(() => {
      keydown('Enter');
    });

    await assertRipplesNotShown(() => {
      keydown(' ');
      keyup(' ');
    });

    await assertRipplesNotShown(() => {
      pointerdown();
      pointerup();
    });

    return assertRipplesNotShown(() => {
      ripple.uiDownAction();
      ripple.uiUpAction();
    });
  });

  test('RippleNotShown_EnterKeyDefaultPrevenetd', function() {
    ripple.parentElement!.addEventListener('keydown', e => {
      e.preventDefault();
    }, {once: true, capture: true});

    return assertRipplesNotShown(() => {
      keydown('Enter');
    });
  });
});