145 lines
4.7 KiB
TypeScript
145 lines
4.7 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.dev/license
|
|
*/
|
|
|
|
import {ComponentHarness, HarnessPredicate, parallel} from '@angular/cdk/testing';
|
|
import {MatTreeNodeHarness} from './node-harness';
|
|
import {TreeHarnessFilters, TreeNodeHarnessFilters} from './tree-harness-filters';
|
|
|
|
export type TextTree = {
|
|
text?: string;
|
|
children?: TextTree[];
|
|
};
|
|
|
|
/** Harness for interacting with a standard mat-tree in tests. */
|
|
export class MatTreeHarness extends ComponentHarness {
|
|
/** The selector for the host element of a `MatTableHarness` instance. */
|
|
static hostSelector = '.mat-tree';
|
|
|
|
/**
|
|
* Gets a `HarnessPredicate` that can be used to search for a tree with specific attributes.
|
|
* @param options Options for narrowing the search
|
|
* @return a `HarnessPredicate` configured with the given options.
|
|
*/
|
|
static with(options: TreeHarnessFilters = {}): HarnessPredicate<MatTreeHarness> {
|
|
return new HarnessPredicate(MatTreeHarness, options);
|
|
}
|
|
|
|
/** Gets all of the nodes in the tree. */
|
|
async getNodes(filter: TreeNodeHarnessFilters = {}): Promise<MatTreeNodeHarness[]> {
|
|
return this.locatorForAll(MatTreeNodeHarness.with(filter))();
|
|
}
|
|
|
|
/**
|
|
* Gets an object representation for the visible tree structure
|
|
* If a node is under an unexpanded node it will not be included.
|
|
* Eg.
|
|
* Tree (all nodes expanded):
|
|
* `
|
|
* <mat-tree>
|
|
* <mat-tree-node>Node 1<mat-tree-node>
|
|
* <mat-nested-tree-node>
|
|
* Node 2
|
|
* <mat-nested-tree-node>
|
|
* Node 2.1
|
|
* <mat-tree-node>
|
|
* Node 2.1.1
|
|
* <mat-tree-node>
|
|
* <mat-nested-tree-node>
|
|
* <mat-tree-node>
|
|
* Node 2.2
|
|
* <mat-tree-node>
|
|
* <mat-nested-tree-node>
|
|
* </mat-tree>`
|
|
*
|
|
* Tree structure:
|
|
* {
|
|
* children: [
|
|
* {
|
|
* text: 'Node 1',
|
|
* children: [
|
|
* {
|
|
* text: 'Node 2',
|
|
* children: [
|
|
* {
|
|
* text: 'Node 2.1',
|
|
* children: [{text: 'Node 2.1.1'}]
|
|
* },
|
|
* {text: 'Node 2.2'}
|
|
* ]
|
|
* }
|
|
* ]
|
|
* }
|
|
* ]
|
|
* };
|
|
*/
|
|
async getTreeStructure(): Promise<TextTree> {
|
|
const nodes = await this.getNodes();
|
|
const nodeInformation = await parallel(() =>
|
|
nodes.map(node => {
|
|
return parallel(() => [node.getLevel(), node.getText(), node.isExpanded()]);
|
|
}),
|
|
);
|
|
return this._getTreeStructure(nodeInformation, 1, true);
|
|
}
|
|
|
|
/**
|
|
* Recursively collect the structured text of the tree nodes.
|
|
* @param nodes A list of tree nodes
|
|
* @param level The level of nodes that are being accounted for during this iteration
|
|
* @param parentExpanded Whether the parent of the first node in param nodes is expanded
|
|
*/
|
|
private _getTreeStructure(
|
|
nodes: [number, string, boolean][],
|
|
level: number,
|
|
parentExpanded: boolean,
|
|
): TextTree {
|
|
const result: TextTree = {};
|
|
for (let i = 0; i < nodes.length; i++) {
|
|
const [nodeLevel, text, expanded] = nodes[i];
|
|
const nextNodeLevel = nodes[i + 1]?.[0] ?? -1;
|
|
|
|
// Return the accumulated value for the current level once we reach a shallower level node
|
|
if (nodeLevel < level) {
|
|
return result;
|
|
}
|
|
// Skip deeper level nodes during this iteration, they will be picked up in a later iteration
|
|
if (nodeLevel > level) {
|
|
continue;
|
|
}
|
|
// Only add to representation if it is visible (parent is expanded)
|
|
if (parentExpanded) {
|
|
// Collect the data under this node according to the following rules:
|
|
// 1. If the next node in the list is a sibling of the current node add it to the child list
|
|
// 2. If the next node is a child of the current node, get the sub-tree structure for the
|
|
// child and add it under this node
|
|
// 3. If the next node has a shallower level, we've reached the end of the child nodes for
|
|
// the current parent.
|
|
if (nextNodeLevel === level) {
|
|
this._addChildToNode(result, {text});
|
|
} else if (nextNodeLevel > level) {
|
|
let children = this._getTreeStructure(
|
|
nodes.slice(i + 1),
|
|
nextNodeLevel,
|
|
expanded,
|
|
)?.children;
|
|
let child = children ? {text, children} : {text};
|
|
this._addChildToNode(result, child);
|
|
} else {
|
|
this._addChildToNode(result, {text});
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private _addChildToNode(result: TextTree, child: TextTree) {
|
|
result.children ? result.children.push(child) : (result.children = [child]);
|
|
}
|
|
}
|