chromium/chrome/test/data/webui/chromeos/cr_focus_row_behavior_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 {FocusRowBehavior} from 'chrome://resources/ash/common/focus_row_behavior.js';
import {getDeepActiveElement} from 'chrome://resources/js/util.js';
import {down, pressAndReleaseKeyOn, up} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js';
import {html, PolymerElement, mixinBehaviors} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';

import {eventToPromise} from 'chrome://webui-test/test_util.js';
import {assertFalse, assertTrue, assertEquals} from 'chrome://webui-test/chai_assert.js';
import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';

// clang-format on

class ButtonThreeElement extends PolymerElement {
  static get is() {
    return 'button-three';
  }

  static get template() {
    return html`
      <button>
        fake button three
      </button>
    `;
  }

  getFocusableElement() {
    return this.shadowRoot!.querySelector('button');
  }
}
customElements.define(ButtonThreeElement.is, ButtonThreeElement);

const TestElementBase = mixinBehaviors([FocusRowBehavior], PolymerElement) as
    {new (): PolymerElement & FocusRowBehavior};

interface TestFocusRowBehaviorElement {
  $: {
    control: HTMLElement,
    controlTwo: HTMLElement,
  };
}

class TestFocusRowBehaviorElement extends TestElementBase {
  static get is() {
    return 'test-focus-row-behavior-element';
  }

  static get template() {
    return html`
      <div id="container" focus-row-container>
        <span>fake text</span>
        <button id="control" focus-row-control focus-type='fake-btn'>
          fake button
        </button>
        <button id="controlTwo" focus-row-control focus-type='fake-btn-two'>
          fake button two
        </button>
        <button-three focus-row-control focus-type='fake-btn-three'>
        </button-three>
      </div>
    `;
  }

  focusCallCount: number = 0;

  override focus() {
    this.focusCallCount++;
  }
}
customElements.define(
    TestFocusRowBehaviorElement.is, TestFocusRowBehaviorElement);

declare global {
  interface HTMLElementTagNameMap {
    'test-focus-row-behavior-element': TestFocusRowBehaviorElement;
  }
}


suite('cr-focus-row-behavior-test', function() {
  let testElement: TestFocusRowBehaviorElement;

  setup(async function() {
    document.body.innerHTML = window.trustedTypes!.emptyHTML;

    testElement = document.createElement('test-focus-row-behavior-element');
    document.body.appendChild(testElement);

    // Block so that FocusRowBehavior.attached can run.
    await waitAfterNextRender(testElement);
    // Wait one more time to ensure that async setup in FocusRowBehavior has
    // executed.
    await waitAfterNextRender(testElement);
  });

  test('ID is not overriden when index is set', function() {
    assertFalse(testElement.hasAttribute('id'));
    assertFalse(testElement.hasAttribute('aria-rowindex'));
    testElement.id = 'test-id';
    assertTrue(testElement.hasAttribute('id'));
    assertEquals('test-id', testElement.id);
    assertFalse(testElement.hasAttribute('aria-rowindex'));
    testElement.focusRowIndex = 5;  // Arbitrary index.
    assertTrue(testElement.hasAttribute('id'));
    assertEquals('test-id', testElement.id);
    assertTrue(testElement.hasAttribute('aria-rowindex'));
  });

  test('ID and aria-rowindex are only set when index is set', function() {
    assertFalse(testElement.hasAttribute('id'));
    assertFalse(testElement.hasAttribute('aria-rowindex'));
    testElement.focusRowIndex = 5;  // Arbitrary index.
    assertTrue(testElement.hasAttribute('id'));
    assertTrue(testElement.hasAttribute('aria-rowindex'));
  });

  test('item passes focus to first focusable child', function() {
    let focused = false;
    testElement.$.control.addEventListener('focus', function() {
      focused = true;
    });
    testElement.dispatchEvent(new CustomEvent('focus'));
    assertTrue(focused);
  });

  test('will focus a similar item that was last focused', function() {
    const lastButton = document.createElement('button');
    lastButton.setAttribute('focus-type', 'fake-btn-two');
    testElement.lastFocused = lastButton;

    let focused = false;
    testElement.$.controlTwo.addEventListener('focus', function() {
      focused = true;
    });
    testElement.dispatchEvent(new CustomEvent('focus'));
    assertTrue(focused);
  });

  test('mouse clicks on the row does not focus the controls', function() {
    let focused = false;
    testElement.$.control.addEventListener('focus', function() {
      focused = true;
    });
    down(testElement);
    up(testElement);
    testElement.click();
    // iron-list is responsible for firing 'focus' after taps, but is not used
    // in the test, so its necessary to manually fire 'focus' after tap.
    testElement.dispatchEvent(new CustomEvent('focus'));
    assertFalse(focused);
  });

  test('when focus-override is defined, returned element gains focus', () => {
    const lastButton = document.createElement('button');
    lastButton.setAttribute('focus-type', 'fake-btn-three');
    testElement.lastFocused = lastButton;

    const wait = eventToPromise('focus', testElement);
    testElement.dispatchEvent(new CustomEvent('focus'));
    return wait.then(() => {
      const button = getDeepActiveElement();
      assertTrue(!!button);
      assertEquals('fake button three', button.textContent!.trim());
    });
  });

  test('when shift+tab pressed on first control, focus on container', () => {
    const first = testElement.$.control;
    const second = testElement.$.controlTwo;
    pressAndReleaseKeyOn(first, 0, 'shift', 'Tab');
    assertEquals(1, testElement.focusCallCount);
    pressAndReleaseKeyOn(second, 0, 'shift', 'Tab');
    assertEquals(1, testElement.focusCallCount);

    // Simulate updating a row with same first control.
    testElement.dispatchEvent(new CustomEvent('dom-change'));
    pressAndReleaseKeyOn(first, 0, 'shift', 'Tab');
    assertEquals(2, testElement.focusCallCount);
    pressAndReleaseKeyOn(second, 0, 'shift', 'Tab');
    assertEquals(2, testElement.focusCallCount);

    // Simulate updating row with different first control.
    first.remove();
    testElement.dispatchEvent(new CustomEvent('dom-change'));
    pressAndReleaseKeyOn(second, 0, 'shift', 'Tab');
    assertEquals(3, testElement.focusCallCount);
  });
});