• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import {Clipboard, ClipboardModule} from '@angular/cdk/clipboard';
18import {Component, ViewChild} from '@angular/core';
19import {ComponentFixture, TestBed} from '@angular/core/testing';
20import {MatIconModule} from '@angular/material/icon';
21import {MatTooltipModule} from '@angular/material/tooltip';
22import {assertDefined} from 'common/assert_utils';
23import {HierarchyTreeBuilder} from 'test/unit/hierarchy_tree_builder';
24import {PropertyTreeBuilder} from 'test/unit/property_tree_builder';
25import {DEFAULT_PROPERTY_FORMATTER} from 'trace/tree_node/formatters';
26import {DiffType} from 'viewers/common/diff_type';
27import {UiHierarchyTreeNode} from 'viewers/common/ui_hierarchy_tree_node';
28import {UiPropertyTreeNode} from 'viewers/common/ui_property_tree_node';
29import {HierarchyTreeNodeDataViewComponent} from './hierarchy_tree_node_data_view_component';
30import {PropertyTreeNodeDataViewComponent} from './property_tree_node_data_view_component';
31import {TreeNodeComponent} from './tree_node_component';
32
33describe('TreeNodeComponent', () => {
34  let fixture: ComponentFixture<TestHostComponent>;
35  let component: TestHostComponent;
36  let htmlElement: HTMLElement;
37  let mockCopyText: jasmine.Spy;
38
39  const propertiesTree = UiPropertyTreeNode.from(
40    new PropertyTreeBuilder()
41      .setRootId('test')
42      .setName('property tree')
43      .setChildren([
44        {name: 'key1', value: 'value1', formatter: DEFAULT_PROPERTY_FORMATTER},
45        {name: 'key2', children: [{name: 'key3'}]},
46      ])
47      .build(),
48  );
49  propertiesTree.setIsRoot(true);
50
51  beforeEach(async () => {
52    mockCopyText = jasmine.createSpy();
53    await TestBed.configureTestingModule({
54      providers: [{provide: Clipboard, useValue: {copy: mockCopyText}}],
55      declarations: [
56        TreeNodeComponent,
57        HierarchyTreeNodeDataViewComponent,
58        PropertyTreeNodeDataViewComponent,
59        TestHostComponent,
60      ],
61      imports: [MatIconModule, MatTooltipModule, ClipboardModule],
62    }).compileComponents();
63    fixture = TestBed.createComponent(TestHostComponent);
64    component = fixture.componentInstance;
65    htmlElement = fixture.nativeElement;
66    fixture.detectChanges();
67  });
68
69  it('can be created', () => {
70    expect(component).toBeTruthy();
71  });
72
73  it('can generate hierarchy data view component', () => {
74    const treeNodeDataView = htmlElement.querySelector(
75      'hierarchy-tree-node-data-view',
76    );
77    expect(treeNodeDataView).toBeTruthy();
78    expect(
79      htmlElement.querySelector('property-tree-node-data-view'),
80    ).toBeNull();
81  });
82
83  it('can generate property data view component', () => {
84    component.node = propertiesTree;
85    fixture.detectChanges();
86    const treeNodeDataView = htmlElement.querySelector(
87      'property-tree-node-data-view',
88    );
89    expect(treeNodeDataView).toBeTruthy();
90    expect(
91      htmlElement.querySelector('hierarchy-tree-node-data-view'),
92    ).toBeNull();
93  });
94
95  it('can trigger tree toggle on click of chevron', () => {
96    const treeNodeComponent = assertDefined(component.treeNodeComponent);
97    treeNodeComponent.showChevron = jasmine.createSpy().and.returnValue(true);
98    fixture.detectChanges();
99
100    const spy = spyOn(treeNodeComponent.toggleTreeChange, 'emit');
101    const toggleButton = assertDefined(
102      htmlElement.querySelector<HTMLElement>('.toggle-tree-btn'),
103    );
104    toggleButton.click();
105    expect(spy).toHaveBeenCalled();
106  });
107
108  it('can trigger tree expansion on click of expand tree button', () => {
109    const spy = spyOn(
110      assertDefined(component.treeNodeComponent).expandTreeChange,
111      'emit',
112    );
113    const expandButton = assertDefined(
114      htmlElement.querySelector<HTMLElement>('.expand-tree-btn'),
115    );
116    expandButton.click();
117    expect(spy).toHaveBeenCalled();
118  });
119
120  it('can trigger tree expansion if node is selected and not in pinned section', () => {
121    const spy = spyOn(
122      assertDefined(component.treeNodeComponent).expandTreeChange,
123      'emit',
124    );
125    component.isInPinnedSection = true;
126    component.isSelected = true;
127    fixture.detectChanges();
128    expect(spy).not.toHaveBeenCalled();
129
130    component.isSelected = false;
131    component.isInPinnedSection = false;
132    fixture.detectChanges();
133    component.isSelected = true;
134    fixture.detectChanges();
135    expect(spy).toHaveBeenCalledTimes(1);
136  });
137
138  it('assigns diff css classes to expand tree button', () => {
139    const expandButton = assertDefined(
140      htmlElement.querySelector<HTMLElement>('.expand-tree-btn'),
141    );
142    expect(expandButton.className).toEqual('icon-button expand-tree-btn');
143    component.node = UiHierarchyTreeNode.from(
144      new HierarchyTreeBuilder()
145        .setId('LayerTraceEntry')
146        .setName('Added Diff')
147        .setChildren([
148          {id: 1, name: 'Child 1', children: [{id: 2, name: 'Child 2'}]},
149        ])
150        .build(),
151    );
152    component.node.getChildByName('Child 1')?.setDiff(DiffType.ADDED);
153    fixture.detectChanges();
154    expect(expandButton.className).toEqual('icon-button expand-tree-btn added');
155
156    component.node = UiHierarchyTreeNode.from(
157      new HierarchyTreeBuilder()
158        .setId('LayerTraceEntry')
159        .setName('Added Diff')
160        .setChildren([
161          {id: 1, name: 'Child 1', children: [{id: 2, name: 'Child 2'}]},
162        ])
163        .build(),
164    );
165    const child1 = assertDefined(component.node.getChildByName('Child 1'));
166    child1.setDiff(DiffType.ADDED);
167    child1.getChildByName('Child 2')?.setDiff(DiffType.DELETED);
168    fixture.detectChanges();
169    expect(expandButton.className).toEqual(
170      'icon-button expand-tree-btn modified',
171    );
172  });
173
174  it('pins node on click', () => {
175    const treeNodeComponent = assertDefined(component.treeNodeComponent);
176    treeNodeComponent.showPinNodeIcon = jasmine
177      .createSpy()
178      .and.returnValue(true);
179    fixture.detectChanges();
180
181    const spy = spyOn(treeNodeComponent.pinNodeChange, 'emit');
182    const pinNodeButton = assertDefined(
183      htmlElement.querySelector<HTMLElement>('.pin-node-btn'),
184    );
185    pinNodeButton.click();
186    expect(spy).toHaveBeenCalledWith(component.node as UiHierarchyTreeNode);
187  });
188
189  it('can trigger rect show state toggle on click of icon', () => {
190    const treeNodeComponent = assertDefined(component.treeNodeComponent);
191    treeNodeComponent.showStateIcon = 'visibility';
192    fixture.detectChanges();
193
194    const spy = spyOn(treeNodeComponent.rectShowStateChange, 'emit');
195    const showStateButton = assertDefined(
196      htmlElement.querySelector<HTMLElement>('.toggle-rect-show-state-btn'),
197    );
198    showStateButton.click();
199    expect(spy).toHaveBeenCalled();
200  });
201
202  it('does not show copy button for hierarchy tree', () => {
203    expect(htmlElement.querySelector('.icon-wrapper-copy')).toBeNull();
204  });
205
206  it('does not show copy button for property tree node that is not leaf or root', () => {
207    component.node = assertDefined(propertiesTree.getChildByName('key2'));
208    fixture.detectChanges();
209    expect(htmlElement.querySelector('.icon-wrapper-copy')).toBeNull();
210  });
211
212  it('copies node name for root of property tree node', () => {
213    component.node = propertiesTree;
214    fixture.detectChanges();
215    const copyButton = assertDefined(
216      htmlElement.querySelector<HTMLElement>('.icon-wrapper-copy button'),
217    );
218    copyButton.click();
219    fixture.detectChanges();
220    expect(mockCopyText).toHaveBeenCalledWith(propertiesTree.name);
221  });
222
223  it('copies property name and value for leaf node', () => {
224    component.node = assertDefined(propertiesTree.getChildByName('key1'));
225    component.isLeaf = true;
226    fixture.detectChanges();
227    const copyButton = assertDefined(
228      htmlElement.querySelector<HTMLElement>('.icon-wrapper-copy button'),
229    );
230    copyButton.click();
231    fixture.detectChanges();
232    expect(mockCopyText).toHaveBeenCalledWith('key1: value1');
233  });
234
235  @Component({
236    selector: 'host-component',
237    template: `
238      <tree-node
239        [node]="node"
240        [isExpanded]="isExpanded"
241        [isPinned]="false"
242        [isInPinnedSection]="isInPinnedSection"
243        [isSelected]="isSelected"
244        [isLeaf]="isLeaf"></tree-node>
245    `,
246  })
247  class TestHostComponent {
248    node: UiHierarchyTreeNode | UiPropertyTreeNode = UiHierarchyTreeNode.from(
249      new HierarchyTreeBuilder()
250        .setId('LayerTraceEntry')
251        .setName('4')
252        .setChildren([{id: 1, name: 'Child 1'}])
253        .build(),
254    );
255
256    isSelected = false;
257    isLeaf = false;
258    isExpanded = false;
259    isInPinnedSection = false;
260
261    @ViewChild(TreeNodeComponent)
262    treeNodeComponent: TreeNodeComponent | undefined;
263  }
264});
265