chromium/chrome/test/data/webui/cr_elements/cr_tree_test.ts

// Copyright 2022 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/cr_elements/cr_tree/cr_tree_item.js';
import 'chrome://resources/cr_elements/cr_tree/cr_tree.js';

import type {CrTreeElement} from 'chrome://resources/cr_elements/cr_tree/cr_tree.js';
import type {CrTreeItemElement} from 'chrome://resources/cr_elements/cr_tree/cr_tree_item.js';
import { SELECTED_ATTR} from 'chrome://resources/cr_elements/cr_tree/cr_tree_item.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-tree', function() {
  let tree: CrTreeElement;
  let root: CrTreeItemElement;
  let foo: CrTreeItemElement;
  let bar: CrTreeItemElement;
  let baz: CrTreeItemElement;

  setup(() => {
    document.body.innerHTML = window.trustedTypes!.emptyHTML;
    tree = document.createElement('cr-tree');
    document.body.appendChild(tree);

    root = document.createElement('cr-tree-item');
    root.label = 'Root';
    foo = document.createElement('cr-tree-item');
    foo.label = 'Foo';
    bar = document.createElement('cr-tree-item');
    bar.label = 'Bar';
    baz = document.createElement('cr-tree-item');
    baz.label = 'Baz';

    bar.add(baz);
    root.add(foo);
    root.add(bar);
    tree.add(root);
  });

  // Check tree structure is created correctly.
  test('items', () => {
    assertTrue(tree.hasChildren);
    assertEquals(1, tree.items.length);
    assertEquals(root, tree.items[0]);
    assertEquals(tree, root.tree);

    assertTrue(root.hasChildren);
    assertEquals(2, root.items.length);
    assertEquals(foo, root.items[0]);
    assertEquals(bar, root.items[1]);
    assertEquals(tree, root.tree);
    assertEquals(tree, root.parentItem);

    assertFalse(foo.hasChildren);
    assertEquals(0, foo.items.length);
    assertEquals(tree, foo.tree);
    assertEquals(root, foo.parentItem);

    assertTrue(bar.hasChildren);
    assertEquals(1, bar.items.length);
    assertEquals(baz, bar.items[0]);
    assertEquals(tree, bar.tree);
    assertEquals(root, bar.parentItem);

    assertFalse(baz.hasChildren);
    assertEquals(0, baz.items.length);
    assertEquals(tree, baz.tree);
    assertEquals(bar, baz.parentItem);
  });

  // Verify selecting an item expands its ancestors.
  test('selection', async () => {
    assertFalse(root.expanded);
    assertFalse(bar.expanded);
    assertFalse(foo.hasAttribute(SELECTED_ATTR));
    const whenExpand = eventToPromise('cr-tree-item-expand', tree);
    tree.selectedItem = foo;
    await whenExpand;
    assertTrue(foo.hasAttribute(SELECTED_ATTR));
    assertFalse(root.hasAttribute(SELECTED_ATTR));
    assertTrue(root.expanded);
    assertFalse(bar.hasAttribute(SELECTED_ATTR));
    assertFalse(bar.expanded);
  });

  test('collapse/expand with arrow keys', async () => {
    // Expand root
    tree.selectedItem = root;
    let whenExpand = eventToPromise('cr-tree-item-expand', tree);
    tree.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowRight'}));
    await whenExpand;
    assertTrue(root.expanded);
    assertFalse(bar.expanded);

    // Select bar
    tree.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowDown'}));
    tree.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowDown'}));
    assertEquals(bar, tree.selectedItem);

    // Expand bar
    whenExpand = eventToPromise('cr-tree-item-expand', tree);
    tree.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowRight'}));
    await whenExpand;
    assertTrue(root.expanded);
    assertTrue(bar.expanded);

    // Re-select root
    tree.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowUp'}));
    tree.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowUp'}));
    assertEquals(root, tree.selectedItem);

    // Collapse root
    const whenCollapse = eventToPromise('cr-tree-item-collapse', tree);
    tree.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowLeft'}));
    await whenCollapse;
    assertFalse(root.expanded);
    assertTrue(bar.expanded);
  });

  test('removal', async () => {
    const whenExpand = eventToPromise('cr-tree-item-expand', tree);
    tree.selectedItem = baz;
    await whenExpand;
    assertTrue(bar.hasChildren);
    assertEquals(1, bar.items.length);
    assertFalse(bar.hasAttribute(SELECTED_ATTR));
    assertTrue(baz.hasAttribute(SELECTED_ATTR));
    const whenChange = eventToPromise('cr-tree-change', tree);
    bar.removeTreeItem(baz);
    await whenChange;
    assertEquals(bar, tree.selectedItem);
    assertTrue(bar.hasAttribute(SELECTED_ATTR));
    assertFalse(bar.hasChildren);
    assertEquals(0, bar.items.length);
  });

  test('expand on icon click', async () => {
    tree.selectedItem = root;
    assertFalse(root.expanded);
    let whenExpand = eventToPromise('cr-tree-item-expand', tree);
    const expand = root.shadowRoot!.querySelector<HTMLElement>('.expand-icon');
    assertTrue(!!expand);
    expand.click();
    await whenExpand;

    assertTrue(root.expanded);
    assertFalse(bar.expanded);
    whenExpand = eventToPromise('cr-tree-item-expand', tree);
    const barExpand =
        bar.shadowRoot!.querySelector<HTMLElement>('.expand-icon');
    assertTrue(!!barExpand);
    barExpand.click();

    assertTrue(root.expanded);
    assertTrue(bar.expanded);
    // Selection isn't impacted by clicking the expand icon.
    assertEquals(root, tree.selectedItem);
  });
});