diff --git a/front_end/component_docs/tree_outline/BUILD.gn b/front_end/component_docs/tree_outline/BUILD.gn index b2d9cae27c..109420aeea 100644 --- a/front_end/component_docs/tree_outline/BUILD.gn +++ b/front_end/component_docs/tree_outline/BUILD.gn @@ -9,6 +9,7 @@ ts_library("ts") { testonly = true sources = [ "basic.ts", + "custom-renderers.ts", "lazy-children.ts", "sample-data.ts", ] @@ -16,6 +17,7 @@ ts_library("ts") { deps = [ "../../../test/unittests/front_end/helpers", "../../component_helpers:bundle", + "../../third_party/lit-html:bundle", "../../ui/components:bundle", ] } @@ -24,6 +26,7 @@ copy_to_gen("tree_outline") { testonly = true sources = [ "basic.html", + "custom-renderers.html", "lazy-children.html", ] diff --git a/front_end/component_docs/tree_outline/custom-renderers.html b/front_end/component_docs/tree_outline/custom-renderers.html new file mode 100644 index 0000000000..e5b231c8a8 --- /dev/null +++ b/front_end/component_docs/tree_outline/custom-renderers.html @@ -0,0 +1,32 @@ + + + + + + + TreeOutline with custom renderers + + + + + +
+
+ + + + + + + diff --git a/front_end/component_docs/tree_outline/custom-renderers.ts b/front_end/component_docs/tree_outline/custom-renderers.ts new file mode 100644 index 0000000000..0eaf98c4b3 --- /dev/null +++ b/front_end/component_docs/tree_outline/custom-renderers.ts @@ -0,0 +1,70 @@ +// 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 * as FrontendHelpers from '../../../test/unittests/front_end/helpers/EnvironmentHelpers.js'; +import * as ComponentHelpers from '../../component_helpers/component_helpers.js'; +import * as LitHtml from '../../third_party/lit-html/lit-html.js'; +import * as Components from '../../ui/components/components.js'; + +await ComponentHelpers.ComponentServerSetup.setup(); +await FrontendHelpers.initializeGlobalVars(); + +const data: Components.TreeOutline.TreeOutlineData = { + tree: [ + { + key: 'Offices', + renderer: (key: string): LitHtml.TemplateResult => + LitHtml.html`${key.toUpperCase()}`, + children: (): Promise => Promise.resolve([ + { + key: 'Europe', + children: (): Promise => Promise.resolve([ + { + key: 'UK', + children: (): Promise => Promise.resolve([ + { + key: 'LON', + children: (): Promise => + Promise.resolve([{key: '6PS'}, {key: 'CSG'}, {key: 'BEL'}]), + }, + ]), + }, + { + key: 'Germany', + children: (): Promise => Promise.resolve([ + {key: 'MUC'}, + {key: 'BER'}, + ]), + }, + ]), + }, + ]), + }, + { + key: 'Products', + children: (): Promise => Promise.resolve([ + { + key: 'Chrome', + }, + { + key: 'YouTube', + }, + { + key: 'Drive', + }, + { + key: 'Calendar', + }, + ]), + }, + ], +}; + +const component = new Components.TreeOutline.TreeOutline(); +component.data = data; + +document.getElementById('container')?.appendChild(component); +document.getElementById('recursively-expand')?.addEventListener('click', () => { + component.expandRecursively(); +}); diff --git a/front_end/ui/components/TreeOutline.ts b/front_end/ui/components/TreeOutline.ts index 22ed1a1ab2..7c8a23a1b6 100644 --- a/front_end/ui/components/TreeOutline.ts +++ b/front_end/ui/components/TreeOutline.ts @@ -234,6 +234,8 @@ export class TreeOutline extends HTMLElement { }); const ariaExpandedAttribute = LitHtml.Directives.ifDefined(isExpandableNode(node) ? String(nodeIsExpanded) : undefined); + const renderedNodeKey = node.renderer ? node.renderer(node.key) : node.key; + return LitHtml.html`
  • - ${node.key} + ${renderedNodeKey} ${childrenToRender}
  • diff --git a/front_end/ui/components/TreeOutlineUtils.ts b/front_end/ui/components/TreeOutlineUtils.ts index e777a6c1fe..2094871a88 100644 --- a/front_end/ui/components/TreeOutlineUtils.ts +++ b/front_end/ui/components/TreeOutlineUtils.ts @@ -6,6 +6,7 @@ import * as LitHtml from '../../third_party/lit-html/lit-html.js'; interface BaseTreeNode { key: string; + renderer?: (key: string) => LitHtml.TemplateResult; } export interface TreeNodeWithChildren extends BaseTreeNode { diff --git a/test/unittests/front_end/helpers/DOMHelpers.ts b/test/unittests/front_end/helpers/DOMHelpers.ts index 7791f6b83a..efb758d63c 100644 --- a/test/unittests/front_end/helpers/DOMHelpers.ts +++ b/test/unittests/front_end/helpers/DOMHelpers.ts @@ -189,3 +189,13 @@ export async function doubleRaf() { export async function raf() { return new Promise(resolve => requestAnimationFrame(resolve)); } + +/** + * It's useful to use innerHTML in the tests to have full confidence in the + * renderer output, but LitHtml uses comment nodes to split dynamic from + * static parts of a template, and we don't want our tests full of noise + * from those. + */ +export function stripLitHtmlCommentNodes(text: string) { + return text.replaceAll('', ''); +} diff --git a/test/unittests/front_end/ui/components/DataGrid_test.ts b/test/unittests/front_end/ui/components/DataGrid_test.ts index 3297a4f69a..9509610bee 100644 --- a/test/unittests/front_end/ui/components/DataGrid_test.ts +++ b/test/unittests/front_end/ui/components/DataGrid_test.ts @@ -6,7 +6,7 @@ import * as Platform from '../../../../../front_end/platform/platform.js'; import * as Coordinator from '../../../../../front_end/render_coordinator/render_coordinator.js'; import * as LitHtml from '../../../../../front_end/third_party/lit-html/lit-html.js'; import * as UIComponents from '../../../../../front_end/ui/components/components.js'; -import {assertElement, assertShadowRoot, dispatchClickEvent, dispatchKeyDownEvent, getEventPromise, renderElementIntoDOM} from '../../helpers/DOMHelpers.js'; +import {assertElement, assertShadowRoot, dispatchClickEvent, dispatchKeyDownEvent, getEventPromise, renderElementIntoDOM, stripLitHtmlCommentNodes} from '../../helpers/DOMHelpers.js'; import {withMutations} from '../../helpers/MutationHelpers.js'; import {assertCurrentFocusedCellIs, emulateUserFocusingCellAt, emulateUserKeyboardNavigation, focusTableCell, getAllRows, getCellByIndexes, getFocusableCell, getHeaderCellForColumnId, getHeaderCells, getValuesOfAllBodyRows, getValuesOfBodyRowByAriaIndex} from './DataGridHelpers.js'; @@ -60,14 +60,6 @@ Object.freeze(columns); Object.freeze(columnsWithNoneSortable); Object.freeze(rows); -/** - * It's useful to use innerHTML in the tests to have full confidence in the - * renderer output, but LitHtml uses comment nodes to split dynamic from - * static parts of a template, and we don't want our tests full of noise - * from those. - */ -const stripLitHtmlCommentNodes = (text: string) => text.replaceAll('', ''); - const renderDataGrid = (data: Partial): UIComponents.DataGrid.DataGrid => { const component = new UIComponents.DataGrid.DataGrid(); component.data = { diff --git a/test/unittests/front_end/ui/components/TreeOutline_test.ts b/test/unittests/front_end/ui/components/TreeOutline_test.ts index 9659f61dd6..a631f5ada2 100644 --- a/test/unittests/front_end/ui/components/TreeOutline_test.ts +++ b/test/unittests/front_end/ui/components/TreeOutline_test.ts @@ -3,8 +3,9 @@ // found in the LICENSE file. import * as Coordinator from '../../../../../front_end/render_coordinator/render_coordinator.js'; +import * as LitHtml from '../../../../../front_end/third_party/lit-html/lit-html.js'; import * as UIComponents from '../../../../../front_end/ui/components/components.js'; -import {assertElement, assertShadowRoot, dispatchClickEvent, dispatchKeyDownEvent, renderElementIntoDOM} from '../../helpers/DOMHelpers.js'; +import {assertElement, assertShadowRoot, dispatchClickEvent, dispatchKeyDownEvent, renderElementIntoDOM, stripLitHtmlCommentNodes} from '../../helpers/DOMHelpers.js'; const coordinator = Coordinator.RenderCoordinator.RenderCoordinator.instance(); const {assert} = chai; @@ -135,8 +136,7 @@ function treeNodeKeyText(node: HTMLLIElement) { if (!keyNode) { throw new Error('Found tree node without a key within it.'); } - // Trim to remove whitespace that made it into the DOM that we don't care about. - return (keyNode.textContent || '').trim(); + return keyNode.getAttribute('data-node-key') || ''; } function getVisibleTreeNodeByText(shadowRoot: ShadowRoot, text: string): HTMLLIElement { @@ -307,6 +307,36 @@ describe('TreeOutline', () => { ]); }); + it('allows a node to have a custom renderer', async () => { + const tinyTree: UIComponents.TreeOutlineUtils.TreeNode[] = [{ + key: 'Offices', + renderer: key => LitHtml.html`

    ${key.toUpperCase()}

    `, + children: () => Promise.resolve([ + { + key: 'EMEA', + }, + { + key: 'USA', + }, + { + key: 'APAC', + }, + ]), + }]; + + const {component, shadowRoot} = await renderTreeOutline({ + tree: tinyTree, + }); + + await component.expandRecursively(Number.POSITIVE_INFINITY); + await coordinator.done(); + const officeNode = getVisibleTreeNodeByText(shadowRoot, 'Offices'); + const key = officeNode.querySelector('[data-node-key]'); + assertElement(key, HTMLElement); + const renderedKey = stripLitHtmlCommentNodes(key.innerHTML); + assert.strictEqual(renderedKey, '

    OFFICES

    '); + }); + describe('navigating with keyboard', () => { it('defaults to the first root node as active', async () => { const {shadowRoot} = await renderTreeOutline({