chromium/chrome/test/data/webui/cr_elements/cr_focus_row_mixin_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 {FocusRowMixin} from 'chrome://resources/cr_elements/focus_row_mixin.js';
import {getDeepActiveElement} from 'chrome://resources/js/util.js';
import {down, up} from 'chrome://webui-test/mouse_mock_interactions.js';
import {pressAndReleaseKeyOn} from 'chrome://webui-test/keyboard_mock_interactions.js';
import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {eventToPromise} from 'chrome://webui-test/test_util.js';
import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
import {assertFalse, assertTrue, assertEquals} from 'chrome://webui-test/chai_assert.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 = FocusRowMixin(PolymerElement);

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

class TestFocusRowMixinElement extends TestElementBase {
  static get is() {
    return 'test-focus-row-mixin-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(TestFocusRowMixinElement.is, TestFocusRowMixinElement);

declare global {
  interface HTMLElementTagNameMap {
    'test-focus-row-mixin-element': TestFocusRowMixinElement;
  }
}


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

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

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

    // Block so that FocusRowMixin.connectedCallback can run.
    await waitAfterNextRender(testElement);
    // Wait one more time to ensure that async setup in FocusRowMixin 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',
      async () => {
        const lastButton = document.createElement('button');
        lastButton.setAttribute('focus-type', 'fake-btn-three');
        testElement.lastFocused = lastButton;

        const whenFocus = eventToPromise('focus', testElement);
        testElement.dispatchEvent(new CustomEvent('focus'));
        await whenFocus;
        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);
  });
});