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 {CommonModule} from '@angular/common'; 17import { 18 ComponentFixture, 19 ComponentFixtureAutoDetect, 20 TestBed, 21} from '@angular/core/testing'; 22import {FormsModule} from '@angular/forms'; 23import {MatButtonModule} from '@angular/material/button'; 24import {MatDividerModule} from '@angular/material/divider'; 25import {MatFormFieldModule} from '@angular/material/form-field'; 26import {MatIconModule} from '@angular/material/icon'; 27import {MatInputModule} from '@angular/material/input'; 28import {MatTooltipModule} from '@angular/material/tooltip'; 29import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; 30import {assertDefined} from 'common/assert_utils'; 31import {PersistentStore} from 'common/persistent_store'; 32import {HierarchyTreeBuilder} from 'test/unit/hierarchy_tree_builder'; 33import {TraceType} from 'trace/trace_type'; 34import {UiHierarchyTreeNode} from 'viewers/common/ui_hierarchy_tree_node'; 35import {ViewerEvents} from 'viewers/common/viewer_events'; 36import {HierarchyTreeNodeDataViewComponent} from 'viewers/components/hierarchy_tree_node_data_view_component'; 37import {TreeComponent} from 'viewers/components/tree_component'; 38import {TreeNodeComponent} from 'viewers/components/tree_node_component'; 39import {CollapsibleSectionTitleComponent} from './collapsible_section_title_component'; 40import {HierarchyComponent} from './hierarchy_component'; 41import {UserOptionsComponent} from './user_options_component'; 42 43describe('HierarchyComponent', () => { 44 let fixture: ComponentFixture<HierarchyComponent>; 45 let component: HierarchyComponent; 46 let htmlElement: HTMLElement; 47 48 beforeEach(async () => { 49 await TestBed.configureTestingModule({ 50 providers: [{provide: ComponentFixtureAutoDetect, useValue: true}], 51 declarations: [ 52 HierarchyComponent, 53 TreeComponent, 54 TreeNodeComponent, 55 HierarchyTreeNodeDataViewComponent, 56 CollapsibleSectionTitleComponent, 57 UserOptionsComponent, 58 ], 59 imports: [ 60 CommonModule, 61 MatButtonModule, 62 MatDividerModule, 63 MatInputModule, 64 MatFormFieldModule, 65 BrowserAnimationsModule, 66 FormsModule, 67 MatIconModule, 68 MatTooltipModule, 69 ], 70 }).compileComponents(); 71 72 fixture = TestBed.createComponent(HierarchyComponent); 73 component = fixture.componentInstance; 74 htmlElement = fixture.nativeElement; 75 76 component.tree = UiHierarchyTreeNode.from( 77 new HierarchyTreeBuilder() 78 .setId('RootNode1') 79 .setName('Root node') 80 .setChildren([{id: 'Child1', name: 'Child node'}]) 81 .build(), 82 ); 83 84 component.store = new PersistentStore(); 85 component.userOptions = { 86 showDiff: { 87 name: 'Show diff', 88 enabled: false, 89 isUnavailable: false, 90 }, 91 }; 92 component.dependencies = [TraceType.SURFACE_FLINGER]; 93 94 fixture.detectChanges(); 95 }); 96 97 it('can be created', () => { 98 expect(component).toBeTruthy(); 99 }); 100 101 it('renders title', () => { 102 const title = htmlElement.querySelector('.hierarchy-title'); 103 expect(title).toBeTruthy(); 104 }); 105 106 it('renders view controls', () => { 107 const viewControls = htmlElement.querySelector('.view-controls'); 108 expect(viewControls).toBeTruthy(); 109 const button = htmlElement.querySelector('.view-controls .user-option'); 110 expect(button).toBeTruthy(); //renders at least one view control option 111 }); 112 113 it('renders initial tree elements', () => { 114 const treeView = htmlElement.querySelector('tree-view'); 115 expect(treeView).toBeTruthy(); 116 expect(assertDefined(treeView).innerHTML).toContain('Root node'); 117 expect(assertDefined(treeView).innerHTML).toContain('Child node'); 118 }); 119 120 it('renders subtrees', () => { 121 component.subtrees = [ 122 UiHierarchyTreeNode.from( 123 new HierarchyTreeBuilder().setId('subtree').setName('subtree').build(), 124 ), 125 ]; 126 fixture.detectChanges(); 127 const subtree = assertDefined( 128 htmlElement.querySelector('.tree-wrapper .subtrees tree-view'), 129 ); 130 expect(assertDefined(subtree).innerHTML).toContain('subtree'); 131 }); 132 133 it('renders pinned nodes', () => { 134 const pinnedNodesDiv = htmlElement.querySelector('.pinned-items'); 135 expect(pinnedNodesDiv).toBeFalsy(); 136 137 component.pinnedItems = [assertDefined(component.tree)]; 138 fixture.detectChanges(); 139 const pinnedNodeEl = htmlElement.querySelector('.pinned-items tree-node'); 140 expect(pinnedNodeEl).toBeTruthy(); 141 }); 142 143 it('handles pinned node click', () => { 144 const node = assertDefined(component.tree); 145 component.pinnedItems = [node]; 146 fixture.detectChanges(); 147 148 let highlightedItem: UiHierarchyTreeNode | undefined; 149 htmlElement.addEventListener( 150 ViewerEvents.HighlightedNodeChange, 151 (event) => { 152 highlightedItem = (event as CustomEvent).detail.node; 153 }, 154 ); 155 156 const pinnedNodeEl = assertDefined( 157 htmlElement.querySelector('.pinned-items tree-node'), 158 ); 159 160 (pinnedNodeEl as HTMLButtonElement).click(); 161 fixture.detectChanges(); 162 expect(highlightedItem).toEqual(node); 163 }); 164 165 it('handles pinned item change from tree', () => { 166 let pinnedItem: UiHierarchyTreeNode | undefined; 167 htmlElement.addEventListener( 168 ViewerEvents.HierarchyPinnedChange, 169 (event) => { 170 pinnedItem = (event as CustomEvent).detail.pinnedItem; 171 }, 172 ); 173 const child = assertDefined(component.tree?.getChildByName('Child node')); 174 component.pinnedItems = [child]; 175 fixture.detectChanges(); 176 177 const pinButton = assertDefined( 178 htmlElement.querySelector('.pinned-items tree-node .pin-node-btn'), 179 ); 180 (pinButton as HTMLButtonElement).click(); 181 fixture.detectChanges(); 182 183 expect(pinnedItem).toEqual(child); 184 }); 185 186 it('handles change in filter', () => { 187 let filterString: string | undefined; 188 htmlElement.addEventListener( 189 ViewerEvents.HierarchyFilterChange, 190 (event) => { 191 filterString = (event as CustomEvent).detail.filterString; 192 }, 193 ); 194 const inputEl = assertDefined( 195 htmlElement.querySelector('.title-section input'), 196 ) as HTMLInputElement; 197 198 inputEl.value = 'Root'; 199 inputEl.dispatchEvent(new Event('input')); 200 fixture.detectChanges(); 201 expect(filterString).toBe('Root'); 202 }); 203 204 it('handles collapse button click', () => { 205 const spy = spyOn(component.collapseButtonClicked, 'emit'); 206 const collapseButton = assertDefined( 207 htmlElement.querySelector('collapsible-section-title button'), 208 ) as HTMLButtonElement; 209 collapseButton.click(); 210 fixture.detectChanges(); 211 expect(spy).toHaveBeenCalled(); 212 }); 213}); 214