• 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 */
16import {Component, CUSTOM_ELEMENTS_SCHEMA, ViewChild} from '@angular/core';
17import {
18  ComponentFixture,
19  ComponentFixtureAutoDetect,
20  TestBed,
21} from '@angular/core/testing';
22import {MatIconModule} from '@angular/material/icon';
23import {MatTooltipModule} from '@angular/material/tooltip';
24import {assertDefined} from 'common/assert_utils';
25import {HierarchyTreeBuilder} from 'test/unit/hierarchy_tree_builder';
26import {RectShowState} from 'viewers/common/rect_show_state';
27import {UiHierarchyTreeNode} from 'viewers/common/ui_hierarchy_tree_node';
28import {ViewerEvents} from 'viewers/common/viewer_events';
29import {HierarchyTreeNodeDataViewComponent} from './hierarchy_tree_node_data_view_component';
30import {PropertyTreeNodeDataViewComponent} from './property_tree_node_data_view_component';
31import {TreeComponent} from './tree_component';
32import {TreeNodeComponent} from './tree_node_component';
33
34describe('TreeComponent', () => {
35  let fixture: ComponentFixture<TestHostComponent>;
36  let component: TestHostComponent;
37  let htmlElement: HTMLElement;
38
39  beforeEach(async () => {
40    await TestBed.configureTestingModule({
41      providers: [{provide: ComponentFixtureAutoDetect, useValue: true}],
42      declarations: [
43        TreeComponent,
44        TestHostComponent,
45        TreeNodeComponent,
46        HierarchyTreeNodeDataViewComponent,
47        PropertyTreeNodeDataViewComponent,
48      ],
49      imports: [MatTooltipModule, MatIconModule],
50      schemas: [CUSTOM_ELEMENTS_SCHEMA],
51    }).compileComponents();
52    fixture = TestBed.createComponent(TestHostComponent);
53    component = fixture.componentInstance;
54    htmlElement = fixture.nativeElement;
55    fixture.detectChanges();
56  });
57
58  it('can be created', () => {
59    expect(component).toBeTruthy();
60  });
61
62  it('shows node', () => {
63    const treeNode = htmlElement.querySelector('tree-node');
64    expect(treeNode).toBeTruthy();
65  });
66
67  it('can identify if a parent node has a selected child', () => {
68    const treeComponent = assertDefined(component.treeComponent);
69    expect(treeComponent.hasSelectedChild()).toBeFalse();
70    component.highlightedItem = '3 Child3';
71    fixture.detectChanges();
72    expect(treeComponent.hasSelectedChild()).toBeTrue();
73  });
74
75  it('highlights node upon click', () => {
76    const treeNode = assertDefined(htmlElement.querySelector('tree-node'));
77
78    const spy = spyOn(
79      assertDefined(component.treeComponent).highlightedChange,
80      'emit',
81    );
82    (treeNode as HTMLButtonElement).dispatchEvent(
83      new MouseEvent('click', {detail: 1}),
84    );
85    fixture.detectChanges();
86    expect(spy).toHaveBeenCalled();
87  });
88
89  it('toggles tree upon node double click', () => {
90    const treeComponent = assertDefined(component.treeComponent);
91    const treeNode = assertDefined(htmlElement.querySelector('tree-node'));
92
93    const currLocalExpandedState = treeComponent.localExpandedState;
94    (treeNode as HTMLButtonElement).dispatchEvent(
95      new MouseEvent('click', {detail: 2}),
96    );
97    fixture.detectChanges();
98    expect(!currLocalExpandedState).toEqual(treeComponent.localExpandedState);
99  });
100
101  it('does not toggle tree in flat mode on double click', () => {
102    const treeComponent = assertDefined(component.treeComponent);
103    component.isFlattened = true;
104    fixture.detectChanges();
105    const treeNode = assertDefined(htmlElement.querySelector('tree-node'));
106
107    const currLocalExpandedState = treeComponent.localExpandedState;
108    (treeNode as HTMLButtonElement).dispatchEvent(
109      new MouseEvent('click', {detail: 2}),
110    );
111    fixture.detectChanges();
112    expect(currLocalExpandedState).toEqual(treeComponent.localExpandedState);
113  });
114
115  it('scrolls selected node only if not in view', () => {
116    const treeComponent = assertDefined(component.treeComponent);
117    const treeNode = assertDefined(
118      treeComponent.elementRef.nativeElement.querySelector(`#nodeChild79`),
119    );
120
121    component.highlightedItem = 'Root node';
122    fixture.detectChanges();
123
124    const spy = spyOn(treeNode, 'scrollIntoView').and.callThrough();
125    component.highlightedItem = '79 Child79';
126    fixture.detectChanges();
127    expect(spy).toHaveBeenCalledTimes(1);
128
129    component.highlightedItem = '78 Child78';
130    fixture.detectChanges();
131    expect(spy).toHaveBeenCalledTimes(1);
132  });
133
134  it('sets initial expanded state to true by default', () => {
135    fixture.detectChanges();
136    expect(assertDefined(component.treeComponent).isExpanded()).toBeTrue();
137  });
138
139  it('sets initial expanded state to false if collapse state exists in store', () => {
140    component.useStoredExpandedState = true;
141    const treeComponent = assertDefined(component.treeComponent);
142    // tree expanded by default
143    fixture.detectChanges();
144    expect(treeComponent.isExpanded()).toBeTrue();
145
146    // tree collapsed
147    treeComponent.toggleTree();
148    fixture.detectChanges();
149    expect(treeComponent.isExpanded()).toBeFalse();
150
151    // tree collapsed state retained
152    component.tree = makeTree();
153    fixture.detectChanges();
154    expect(treeComponent.isExpanded()).toBeFalse();
155  });
156
157  it('renders show state button if applicable', () => {
158    expect(htmlElement.querySelector('.toggle-rect-show-state-btn')).toBeNull();
159
160    component.rectIdToShowState = new Map([
161      [component.tree.id, RectShowState.HIDE],
162    ]);
163    fixture.detectChanges();
164    expect(
165      assertDefined(htmlElement.querySelector('.toggle-rect-show-state-btn'))
166        .textContent,
167    ).toContain('visibility_off');
168
169    component.rectIdToShowState.set(component.tree.id, RectShowState.SHOW);
170    fixture.detectChanges();
171    expect(
172      assertDefined(htmlElement.querySelector('.toggle-rect-show-state-btn'))
173        .textContent,
174    ).toContain('visibility');
175  });
176
177  it('handles show state button click', () => {
178    component.rectIdToShowState = new Map([
179      [component.tree.id, RectShowState.HIDE],
180    ]);
181    fixture.detectChanges();
182    const button = assertDefined(
183      htmlElement.querySelector('.toggle-rect-show-state-btn'),
184    ) as HTMLElement;
185    expect(button.textContent).toContain('visibility_off');
186
187    let id: string | undefined;
188    let state: RectShowState | undefined;
189    htmlElement.addEventListener(ViewerEvents.RectShowStateChange, (event) => {
190      id = (event as CustomEvent).detail.rectId;
191      state = (event as CustomEvent).detail.state;
192    });
193    button.click();
194    fixture.detectChanges();
195    expect(id).toEqual(component.tree.id);
196    expect(state).toEqual(RectShowState.SHOW);
197  });
198
199  function makeTree() {
200    const children = [];
201    for (let i = 0; i < 80; i++) {
202      children.push({id: i, name: `Child${i}`});
203    }
204    return UiHierarchyTreeNode.from(
205      new HierarchyTreeBuilder()
206        .setId('RootNode')
207        .setName('Root node')
208        .setChildren(children)
209        .build(),
210    );
211  }
212
213  @Component({
214    selector: 'host-component',
215    template: `
216    <div class="tree-wrapper">
217      <tree-view
218        [node]="tree"
219        [isFlattened]="isFlattened"
220        [isPinned]="false"
221        [highlightedItem]="highlightedItem"
222        [useStoredExpandedState]="useStoredExpandedState"
223        [itemsClickable]="true"
224        [rectIdToShowState]="rectIdToShowState"></tree-view>
225    </div>
226    `,
227    styles: [
228      `
229      .tree-wrapper {
230        height: 500px;
231        overflow: auto;
232      }
233    `,
234    ],
235  })
236  class TestHostComponent {
237    tree: UiHierarchyTreeNode;
238    highlightedItem = '';
239    isFlattened = false;
240    useStoredExpandedState = false;
241    rectIdToShowState: Map<string, RectShowState> | undefined;
242
243    constructor() {
244      this.tree = makeTree();
245    }
246
247    @ViewChild(TreeComponent)
248    treeComponent: TreeComponent | undefined;
249  }
250});
251