1/* 2 * Copyright (C) 2023 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 {ClipboardModule} from '@angular/cdk/clipboard'; 17import { 18 CdkVirtualScrollViewport, 19 ScrollingModule, 20} from '@angular/cdk/scrolling'; 21import { 22 ComponentFixture, 23 ComponentFixtureAutoDetect, 24 TestBed, 25} from '@angular/core/testing'; 26import {FormsModule} from '@angular/forms'; 27import {MatButtonModule} from '@angular/material/button'; 28import {MatDividerModule} from '@angular/material/divider'; 29import {MatFormFieldModule} from '@angular/material/form-field'; 30import {MatIconModule} from '@angular/material/icon'; 31import {MatInputModule} from '@angular/material/input'; 32import {MatSelectModule} from '@angular/material/select'; 33import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; 34import {assertDefined} from 'common/assert_utils'; 35import {TimestampConverterUtils} from 'common/time/test_utils'; 36import {PropertyTreeBuilder} from 'test/unit/property_tree_builder'; 37import {TraceBuilder} from 'test/unit/trace_builder'; 38import {UnitTestUtils} from 'test/unit/utils'; 39import {Trace, TraceEntry} from 'trace/trace'; 40import {TraceType} from 'trace/trace_type'; 41import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; 42import {LogSelectFilter} from 'viewers/common/log_filters'; 43import {executeScrollComponentTests} from 'viewers/common/scroll_component_tests'; 44import {LogHeader} from 'viewers/common/ui_data_log'; 45import {CollapsedSectionsComponent} from 'viewers/components/collapsed_sections_component'; 46import {CollapsibleSectionTitleComponent} from 'viewers/components/collapsible_section_title_component'; 47import {LogComponent} from 'viewers/components/log_component'; 48import {PropertiesComponent} from 'viewers/components/properties_component'; 49import {PropertyTreeNodeDataViewComponent} from 'viewers/components/property_tree_node_data_view_component'; 50import {SearchBoxComponent} from 'viewers/components/search_box_component'; 51import {SelectWithFilterComponent} from 'viewers/components/select_with_filter_component'; 52import {TreeComponent} from 'viewers/components/tree_component'; 53import {TreeNodeComponent} from 'viewers/components/tree_node_component'; 54import {TransitionsScrollDirective} from './scroll_strategy/transitions_scroll_directive'; 55import {TransitionsEntry, UiData} from './ui_data'; 56import {ViewerTransitionsComponent} from './viewer_transitions_component'; 57 58describe('ViewerTransitionsComponent', () => { 59 const testSpec = {name: 'Test Column', cssClass: 'test-class'}; 60 const testField = {spec: testSpec, value: 'VALUE'}; 61 let transitionTree: PropertyTreeNode; 62 let trace: Trace<PropertyTreeNode>; 63 let entry: TraceEntry<PropertyTreeNode>; 64 let fixture: ComponentFixture<ViewerTransitionsComponent>; 65 let component: ViewerTransitionsComponent; 66 let htmlElement: HTMLElement; 67 68 beforeAll(() => { 69 transitionTree = new PropertyTreeBuilder() 70 .setIsRoot(true) 71 .setRootId('TransitionTraceEntry') 72 .setName('transition') 73 .build(); 74 trace = new TraceBuilder<PropertyTreeNode>() 75 .setType(TraceType.TRANSITION) 76 .setEntries([transitionTree]) 77 .setTimestamps([TimestampConverterUtils.makeElapsedTimestamp(20n)]) 78 .build(); 79 entry = trace.getEntry(0); 80 }); 81 82 describe('Main component', () => { 83 beforeEach(async () => { 84 await setUpTestEnvironment(); 85 }); 86 87 it('can be created', () => { 88 expect(component).toBeTruthy(); 89 }); 90 91 it('renders headers with filters', () => { 92 expect( 93 htmlElement.querySelector( 94 `.headers .filter.${testSpec.cssClass.split(' ')[0]}`, 95 ), 96 ).toBeTruthy(); 97 }); 98 99 it('renders entries with field values and no trace timestamp', () => { 100 expect(htmlElement.querySelector('.scroll')).toBeTruthy(); 101 const entry = assertDefined( 102 htmlElement.querySelector( 103 `.scroll .entry .${testSpec.cssClass.split(' ')[0]}`, 104 ), 105 ); 106 expect(entry.textContent).toContain('VALUE'); 107 expect(htmlElement.querySelector('.scroll .entry .time')).toBeNull(); 108 }); 109 110 it('hides go to current time button', () => { 111 expect(htmlElement.querySelector('.go-to-current-time')).toBeNull(); 112 }); 113 114 it('renders properties', () => { 115 expect(htmlElement.querySelector('.properties-view')).toBeTruthy(); 116 }); 117 118 it('shows message when no transition is selected', () => { 119 assertDefined(component.inputData).propertiesTree = undefined; 120 fixture.detectChanges(); 121 expect( 122 htmlElement.querySelector('.properties-view .placeholder-text') 123 ?.innerHTML, 124 ).toContain('No current or selected transition'); 125 }); 126 127 it('creates collapsed sections with no buttons', () => { 128 UnitTestUtils.checkNoCollapsedSectionButtons(htmlElement); 129 }); 130 131 it('handles properties section collapse/expand', () => { 132 UnitTestUtils.checkSectionCollapseAndExpand( 133 htmlElement, 134 fixture, 135 '.properties-view', 136 'SELECTED TRANSITION', 137 ); 138 }); 139 }); 140 141 describe('Scroll component', () => { 142 executeScrollComponentTests(setUpTestEnvironment); 143 }); 144 145 async function setUpTestEnvironment(): Promise< 146 [ 147 ComponentFixture<ViewerTransitionsComponent>, 148 HTMLElement, 149 CdkVirtualScrollViewport, 150 ] 151 > { 152 await TestBed.configureTestingModule({ 153 providers: [{provide: ComponentFixtureAutoDetect, useValue: true}], 154 imports: [ 155 MatDividerModule, 156 ScrollingModule, 157 MatIconModule, 158 ClipboardModule, 159 MatFormFieldModule, 160 MatButtonModule, 161 MatInputModule, 162 BrowserAnimationsModule, 163 FormsModule, 164 MatSelectModule, 165 ], 166 declarations: [ 167 ViewerTransitionsComponent, 168 TreeComponent, 169 TreeNodeComponent, 170 PropertyTreeNodeDataViewComponent, 171 PropertiesComponent, 172 CollapsedSectionsComponent, 173 CollapsibleSectionTitleComponent, 174 LogComponent, 175 SearchBoxComponent, 176 TransitionsScrollDirective, 177 SelectWithFilterComponent, 178 ], 179 }).compileComponents(); 180 fixture = TestBed.createComponent(ViewerTransitionsComponent); 181 component = fixture.componentInstance; 182 htmlElement = fixture.nativeElement; 183 component.inputData = makeUiData(); 184 fixture.detectChanges(); 185 const viewport = assertDefined(component.logComponent?.scrollComponent); 186 return [fixture, htmlElement, viewport]; 187 } 188 189 function makeUiData(): UiData { 190 const transitions = []; 191 for (let i = 0; i < 200; i++) { 192 transitions.push(createMockTransition(entry, i)); 193 } 194 const uiData = UiData.createEmpty(); 195 uiData.headers = [new LogHeader(testSpec, new LogSelectFilter([]))]; 196 uiData.entries = transitions; 197 uiData.selectedIndex = 0; 198 return uiData; 199 } 200 201 function createMockTransition( 202 entry: TraceEntry<PropertyTreeNode>, 203 i: number, 204 ): TransitionsEntry { 205 return new TransitionsEntry( 206 entry, 207 [ 208 testField, 209 testField, 210 testField, 211 testField, 212 testField, 213 testField, 214 {spec: testSpec, value: i % 2 === 0 ? 'VALUE' : 'VALUE'.repeat(40)}, 215 ], 216 transitionTree, 217 ); 218 } 219}); 220