Files
react-native-devtools-frontend/test/interactions/data_grid_controller/data_grid_controller_test.ts
T
Jack Franklin 5b1ab68316 [DataGrid] focusable cell logic improvements
This CL is a mix of changes within the DataGrid that improve the logic
around focusable cells. Prior to this CL the DataGrid stored one
value, which was the cell that the user would select if they tabbed
into the DataGrid. That got confusing as we used that value to also
represent the currently active cell that the user has clicked on.

This CL splits those concerns up into two properties, and improves how
we track when the user is or isn't focusing within the data-grid. It
also moves some event handlers from `click` to `focus` for the table
cells which is just a nice tidy up.

Bug: 1211346
Change-Id: If6449f5381134892705f7f42f24b226f8dbf170a
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2935284
Commit-Queue: Jack Franklin <jacktfranklin@chromium.org>
Reviewed-by: Paul Lewis <aerotwist@chromium.org>
2021-06-22 12:10:10 +00:00

216 lines
8.3 KiB
TypeScript

// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {assert} from 'chai';
import type * as puppeteer from 'puppeteer';
import {getDataGrid, getDataGridController, getInnerTextOfDataGridCells} from '../../e2e/helpers/datagrid-helpers.js';
import {$, $$, $textContent, click, waitFor, waitForFunction} from '../../shared/helper.js';
import {describe, it} from '../../shared/mocha-extensions.js';
import {loadComponentDocExample, preloadForCodeCoverage} from '../helpers/shared.js';
import {platform} from '../../shared/helper.js';
function platformSpecificTextForSubMenuEntryItem(text: string): string {
/**
* On Mac the context menu adds the ▶ icon to the sub menu entry points in the
* context menu, but on Linux/Windows it uses an image. So if we're running on
* Mac, we append the search text with the icon, else we do not.
*/
return platform === 'mac' ? `${text}▶` : text;
}
async function assertTopLevelContextMenuItemsText(expectedOptions: string[]): Promise<void> {
const contextMenu = await $('.soft-context-menu');
if (!contextMenu) {
assert.fail('Could not find context menu.');
}
const allItems = await $$('.soft-context-menu > .soft-context-menu-item');
const allItemsText = await Promise.all(allItems.map(item => item.evaluate(div => div.textContent)));
assert.deepEqual(allItemsText, expectedOptions);
}
async function assertSubMenuItemsText(subMenuText: string, expectedOptions: string[]): Promise<void> {
const subMenuEntryItem = await findSubMenuEntryItem(subMenuText);
if (!subMenuEntryItem) {
const allItems = await $$('.soft-context-menu > .soft-context-menu-item');
const allItemsText = await Promise.all(allItems.map(item => item.evaluate(div => div.textContent)));
assert.fail(`Could not find "${subMenuText}" option on context menu. Found items: ${allItemsText.join(' | ')}`);
return;
}
await subMenuEntryItem.hover();
await waitForFunction(async () => {
const menus = await $$('.soft-context-menu');
// Wait for the main menu + the sub menu to be in the DOM
return menus.length === 2;
});
const allMenus = await $$('.soft-context-menu');
// Each submenu is rendered as a separate context menu and is appended to
// the DOM after the main context menu, hence the array index.
const subMenuElement = allMenus[1];
if (!subMenuElement) {
assert.fail(`Could not find sub menu for ${subMenuText}`);
}
const subMenuItems = await $$('.soft-context-menu-item', subMenuElement);
const subMenuItemsText = await Promise.all(subMenuItems.map(item => item.evaluate(div => div.textContent)));
assert.deepEqual(subMenuItemsText, expectedOptions);
}
async function activateContextMenuOnColumnHeader(headerText: string) {
const dataGridController = await getDataGridController();
const dataGrid = await getDataGrid(dataGridController);
const headerCell = await $textContent(headerText, dataGrid);
if (!headerCell) {
assert.fail(`Could not find header cell with text ${headerText}`);
}
await click(headerCell, {
clickOptions: {
button: 'right',
},
});
return headerCell;
}
async function findSubMenuEntryItem(text: string): Promise<puppeteer.ElementHandle<Element>> {
const textToSearchFor = platformSpecificTextForSubMenuEntryItem(text);
const matchingElement = await $textContent(textToSearchFor);
if (!matchingElement) {
const allItems = await $$('.soft-context-menu > .soft-context-menu-item');
const allItemsText = await Promise.all(allItems.map(item => item.evaluate(div => div.textContent)));
assert.fail(`Could not find "${text}" option on context menu. Found items: ${allItemsText.join(' | ')}`);
}
return matchingElement;
}
async function activateContextMenuOnBodyCell(cellText: string) {
const dataGridController = await getDataGridController();
const dataGrid = await getDataGrid(dataGridController);
const headerCell = await $textContent(cellText, dataGrid);
if (!headerCell) {
assert.fail(`Could not find body cell with text ${cellText}`);
}
await click(headerCell, {
clickOptions: {
button: 'right',
},
});
return headerCell;
}
async function waitForFirstBodyCellText(cellText: string) {
await waitForFunction(async () => {
const dataGrid = await getDataGrid();
const firstBodyCell = await $('tbody td', dataGrid);
const text = firstBodyCell && await firstBodyCell.evaluate(cell => (cell as HTMLElement).innerText);
return text === cellText;
});
}
describe('data grid controller', () => {
preloadForCodeCoverage('data_grid_controller/basic.html');
it('lets the user right click on a header to show the context menu', async () => {
await loadComponentDocExample('data_grid_controller/basic.html');
await activateContextMenuOnColumnHeader('Key');
const contextMenu = await $('.soft-context-menu');
assert.isNotNull(contextMenu);
await assertTopLevelContextMenuItemsText(
['Value', platformSpecificTextForSubMenuEntryItem('Sort By'), 'Reset Columns']);
});
it('lists the hideable columns in the context menu and lets the user click to toggle the visibility', async () => {
await loadComponentDocExample('data_grid_controller/basic.html');
await activateContextMenuOnColumnHeader('Key');
const contextMenu = await $('.soft-context-menu');
assert.isNotNull(contextMenu);
const valueColumnOption = await $('[aria-label="Value, checked"]');
if (!valueColumnOption) {
assert.fail('Could not find Value column in context menu.');
}
await click(valueColumnOption);
const dataGrid = await getDataGrid();
await waitForFunction(async () => {
const hiddenCells = await $$('tbody td.hidden', dataGrid);
return hiddenCells.length === 3;
});
const renderedText = await getInnerTextOfDataGridCells(dataGrid, 3);
assert.deepEqual(
[
['Bravo'],
['Alpha'],
['Charlie'],
],
renderedText);
});
it('lists sortable columns in a sub-menu and lets the user click to sort', async () => {
await loadComponentDocExample('data_grid_controller/basic.html');
await activateContextMenuOnColumnHeader('Key');
const contextMenu = await $('.soft-context-menu');
if (!contextMenu) {
assert.fail('Could not find context menu.');
}
const sortBy = await findSubMenuEntryItem('Sort By');
await sortBy.hover();
const keyColumnSort = await waitFor('[aria-label="Key"]');
await keyColumnSort.click();
await waitForFirstBodyCellText('Alpha');
const dataGrid = await getDataGrid();
const renderedText = await getInnerTextOfDataGridCells(dataGrid, 3);
assert.deepEqual(
[
['Alpha', 'Letter A'],
['Bravo', 'Letter B'],
['Charlie', 'Letter C'],
],
renderedText);
});
it('lets the user click on a column header to sort it', async () => {
await loadComponentDocExample('data_grid_controller/basic.html');
const keyHeaderCell = await waitFor('th[data-grid-header-cell="key"]');
await keyHeaderCell.click();
await waitForFirstBodyCellText('Alpha');
const dataGrid = await getDataGrid();
const renderedText = await getInnerTextOfDataGridCells(dataGrid, 3);
assert.deepEqual(
[
['Alpha', 'Letter A'],
['Bravo', 'Letter B'],
['Charlie', 'Letter C'],
],
renderedText);
});
it('lists sort by and header options when right clicking on a body row', async () => {
await loadComponentDocExample('data_grid_controller/basic.html');
await activateContextMenuOnBodyCell('Bravo');
await assertTopLevelContextMenuItemsText([
platformSpecificTextForSubMenuEntryItem('Sort By'),
platformSpecificTextForSubMenuEntryItem('Header Options'),
]);
await assertSubMenuItemsText('Header Options', ['Value', 'Reset Columns']);
await assertSubMenuItemsText('Sort By', ['Key', 'Value']);
});
it('allows the parent to add custom context menu items', async () => {
await loadComponentDocExample('data_grid_controller/custom-context-menu-items.html');
await activateContextMenuOnBodyCell('Bravo');
await assertTopLevelContextMenuItemsText([
platformSpecificTextForSubMenuEntryItem('Sort By'),
platformSpecificTextForSubMenuEntryItem('Header Options'),
'Hello World',
]);
});
});