testing.mjs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import { ContentContainerComponentHarness, HarnessPredicate, ComponentHarness, parallel } from '@angular/cdk/testing';
  2. import { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';
  3. /** Harness for interacting with a standard Angular Material tree node. */
  4. class MatTreeNodeHarness extends ContentContainerComponentHarness {
  5. /** The selector of the host element of a `MatTreeNode` instance. */
  6. static hostSelector = '.mat-tree-node, .mat-nested-tree-node';
  7. _toggle = this.locatorForOptional('[matTreeNodeToggle]');
  8. /**
  9. * Gets a `HarnessPredicate` that can be used to search for a tree node with specific attributes.
  10. * @param options Options for narrowing the search
  11. * @return a `HarnessPredicate` configured with the given options.
  12. */
  13. static with(options = {}) {
  14. return getNodePredicate(MatTreeNodeHarness, options);
  15. }
  16. /** Whether the tree node is expanded. */
  17. async isExpanded() {
  18. return coerceBooleanProperty(await (await this.host()).getAttribute('aria-expanded'));
  19. }
  20. /** Whether the tree node is expandable. */
  21. async isExpandable() {
  22. return (await (await this.host()).getAttribute('aria-expanded')) !== null;
  23. }
  24. /** Whether the tree node is disabled. */
  25. async isDisabled() {
  26. return coerceBooleanProperty(await (await this.host()).getProperty('aria-disabled'));
  27. }
  28. /** Gets the level of the tree node. Note that this gets the aria-level and is 1 indexed. */
  29. async getLevel() {
  30. return coerceNumberProperty(await (await this.host()).getAttribute('aria-level'));
  31. }
  32. /** Gets the tree node's text. */
  33. async getText() {
  34. return (await this.host()).text({ exclude: '.mat-tree-node, .mat-nested-tree-node, button' });
  35. }
  36. /** Toggles node between expanded/collapsed. Only works when node is not disabled. */
  37. async toggle() {
  38. const toggle = await this._toggle();
  39. if (toggle) {
  40. return toggle.click();
  41. }
  42. }
  43. /** Expands the node if it is collapsed. Only works when node is not disabled. */
  44. async expand() {
  45. if (!(await this.isExpanded())) {
  46. await this.toggle();
  47. }
  48. }
  49. /** Collapses the node if it is expanded. Only works when node is not disabled. */
  50. async collapse() {
  51. if (await this.isExpanded()) {
  52. await this.toggle();
  53. }
  54. }
  55. }
  56. function getNodePredicate(type, options) {
  57. return new HarnessPredicate(type, options)
  58. .addOption('text', options.text, (harness, text) => HarnessPredicate.stringMatches(harness.getText(), text))
  59. .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled)
  60. .addOption('expanded', options.expanded, async (harness, expanded) => (await harness.isExpanded()) === expanded)
  61. .addOption('level', options.level, async (harness, level) => (await harness.getLevel()) === level);
  62. }
  63. /** Harness for interacting with a standard mat-tree in tests. */
  64. class MatTreeHarness extends ComponentHarness {
  65. /** The selector for the host element of a `MatTableHarness` instance. */
  66. static hostSelector = '.mat-tree';
  67. /**
  68. * Gets a `HarnessPredicate` that can be used to search for a tree with specific attributes.
  69. * @param options Options for narrowing the search
  70. * @return a `HarnessPredicate` configured with the given options.
  71. */
  72. static with(options = {}) {
  73. return new HarnessPredicate(MatTreeHarness, options);
  74. }
  75. /** Gets all of the nodes in the tree. */
  76. async getNodes(filter = {}) {
  77. return this.locatorForAll(MatTreeNodeHarness.with(filter))();
  78. }
  79. /**
  80. * Gets an object representation for the visible tree structure
  81. * If a node is under an unexpanded node it will not be included.
  82. * Eg.
  83. * Tree (all nodes expanded):
  84. * `
  85. * <mat-tree>
  86. * <mat-tree-node>Node 1<mat-tree-node>
  87. * <mat-nested-tree-node>
  88. * Node 2
  89. * <mat-nested-tree-node>
  90. * Node 2.1
  91. * <mat-tree-node>
  92. * Node 2.1.1
  93. * <mat-tree-node>
  94. * <mat-nested-tree-node>
  95. * <mat-tree-node>
  96. * Node 2.2
  97. * <mat-tree-node>
  98. * <mat-nested-tree-node>
  99. * </mat-tree>`
  100. *
  101. * Tree structure:
  102. * {
  103. * children: [
  104. * {
  105. * text: 'Node 1',
  106. * children: [
  107. * {
  108. * text: 'Node 2',
  109. * children: [
  110. * {
  111. * text: 'Node 2.1',
  112. * children: [{text: 'Node 2.1.1'}]
  113. * },
  114. * {text: 'Node 2.2'}
  115. * ]
  116. * }
  117. * ]
  118. * }
  119. * ]
  120. * };
  121. */
  122. async getTreeStructure() {
  123. const nodes = await this.getNodes();
  124. const nodeInformation = await parallel(() => nodes.map(node => {
  125. return parallel(() => [node.getLevel(), node.getText(), node.isExpanded()]);
  126. }));
  127. return this._getTreeStructure(nodeInformation, 1, true);
  128. }
  129. /**
  130. * Recursively collect the structured text of the tree nodes.
  131. * @param nodes A list of tree nodes
  132. * @param level The level of nodes that are being accounted for during this iteration
  133. * @param parentExpanded Whether the parent of the first node in param nodes is expanded
  134. */
  135. _getTreeStructure(nodes, level, parentExpanded) {
  136. const result = {};
  137. for (let i = 0; i < nodes.length; i++) {
  138. const [nodeLevel, text, expanded] = nodes[i];
  139. const nextNodeLevel = nodes[i + 1]?.[0] ?? -1;
  140. // Return the accumulated value for the current level once we reach a shallower level node
  141. if (nodeLevel < level) {
  142. return result;
  143. }
  144. // Skip deeper level nodes during this iteration, they will be picked up in a later iteration
  145. if (nodeLevel > level) {
  146. continue;
  147. }
  148. // Only add to representation if it is visible (parent is expanded)
  149. if (parentExpanded) {
  150. // Collect the data under this node according to the following rules:
  151. // 1. If the next node in the list is a sibling of the current node add it to the child list
  152. // 2. If the next node is a child of the current node, get the sub-tree structure for the
  153. // child and add it under this node
  154. // 3. If the next node has a shallower level, we've reached the end of the child nodes for
  155. // the current parent.
  156. if (nextNodeLevel === level) {
  157. this._addChildToNode(result, { text });
  158. }
  159. else if (nextNodeLevel > level) {
  160. let children = this._getTreeStructure(nodes.slice(i + 1), nextNodeLevel, expanded)?.children;
  161. let child = children ? { text, children } : { text };
  162. this._addChildToNode(result, child);
  163. }
  164. else {
  165. this._addChildToNode(result, { text });
  166. return result;
  167. }
  168. }
  169. }
  170. return result;
  171. }
  172. _addChildToNode(result, child) {
  173. result.children ? result.children.push(child) : (result.children = [child]);
  174. }
  175. }
  176. export { MatTreeHarness, MatTreeNodeHarness };
  177. //# sourceMappingURL=testing.mjs.map