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 */ 16 17import {HierarchyTreeBuilder} from 'test/unit/hierarchy_tree_builder'; 18import {MockStorage} from 'test/unit/mock_storage'; 19import {TraceBuilder} from 'test/unit/trace_builder'; 20import {UnitTestUtils} from 'test/unit/utils'; 21import {Point} from 'trace/flickerlib/common'; 22import {Parser} from 'trace/parser'; 23import {RealTimestamp, TimestampType} from 'trace/timestamp'; 24import {Trace} from 'trace/trace'; 25import {Traces} from 'trace/traces'; 26import {TracePosition} from 'trace/trace_position'; 27import {TraceType} from 'trace/trace_type'; 28import {HierarchyTreeNode} from 'viewers/common/ui_tree_utils'; 29import {UserOptions} from 'viewers/common/user_options'; 30import {Presenter} from 'viewers/viewer_view_capture/presenter'; 31import {UiData} from 'viewers/viewer_view_capture/ui_data'; 32 33describe('PresenterViewCapture', () => { 34 let parser: Parser<object>; 35 let trace: Trace<object>; 36 let uiData: UiData; 37 let presenter: Presenter; 38 let position: TracePosition; 39 let selectedTree: HierarchyTreeNode; 40 41 beforeAll(async () => { 42 parser = await UnitTestUtils.getParser( 43 'traces/elapsed_and_real_timestamp/com.google.android.apps.nexuslauncher_0.vc' 44 ); 45 trace = new TraceBuilder<object>() 46 .setEntries([ 47 parser.getEntry(0, TimestampType.REAL), 48 parser.getEntry(1, TimestampType.REAL), 49 parser.getEntry(2, TimestampType.REAL), 50 ]) 51 .build(); 52 position = TracePosition.fromTraceEntry(trace.getEntry(0)); 53 selectedTree = new HierarchyTreeBuilder() 54 .setName('Name@Id') 55 .setStableId('stableId') 56 .setKind('kind') 57 .setDiffType('diff type') 58 .setId(53) 59 .build(); 60 }); 61 62 beforeEach(async () => { 63 presenter = createPresenter(trace); 64 }); 65 66 it('is robust to empty trace', async () => { 67 const emptyTrace = new TraceBuilder<object>().setEntries([]).build(); 68 const presenter = createPresenter(emptyTrace); 69 70 const positionWithoutTraceEntry = TracePosition.fromTimestamp(new RealTimestamp(0n)); 71 await presenter.onTracePositionUpdate(positionWithoutTraceEntry); 72 expect(uiData.hierarchyUserOptions).toBeTruthy(); 73 expect(uiData.tree).toBeFalsy(); 74 }); 75 76 it('processes trace position updates', async () => { 77 await presenter.onTracePositionUpdate(position); 78 79 expect(uiData.rects.length).toBeGreaterThan(0); 80 expect(uiData.highlightedItems?.length).toEqual(0); 81 const hierarchyOpts = Object.keys(uiData.hierarchyUserOptions); 82 expect(hierarchyOpts).toBeTruthy(); 83 const propertyOpts = Object.keys(uiData.propertiesUserOptions); 84 expect(propertyOpts).toBeTruthy(); 85 expect(Object.keys(uiData.tree!).length > 0).toBeTrue(); 86 }); 87 88 it('creates input data for rects view', async () => { 89 await presenter.onTracePositionUpdate(position); 90 expect(uiData.rects.length).toBeGreaterThan(0); 91 expect(uiData.rects[0].topLeft).toEqual(new Point(0, 0)); 92 expect(uiData.rects[0].bottomRight).toEqual(new Point(1080, 2340)); 93 }); 94 95 it('updates pinned items', async () => { 96 const pinnedItem = new HierarchyTreeBuilder().setName('FirstPinnedItem').setId('id').build(); 97 await presenter.onTracePositionUpdate(position); 98 presenter.updatePinnedItems(pinnedItem); 99 expect(uiData.pinnedItems).toContain(pinnedItem); 100 }); 101 102 it('updates highlighted items', async () => { 103 expect(uiData.highlightedItems).toEqual([]); 104 105 const id = '4'; 106 await presenter.onTracePositionUpdate(position); 107 presenter.updateHighlightedItems(id); 108 expect(uiData.highlightedItems).toContain(id); 109 }); 110 111 it('updates hierarchy tree', async () => { 112 await presenter.onTracePositionUpdate(position); 113 114 expect( 115 // DecorView -> LinearLayout -> FrameLayout -> LauncherRootView -> DragLayer -> Workspace 116 uiData.tree?.children[0].children[1].children[0].children[0].children[1].id 117 ).toEqual('com.android.launcher3.Workspace@251960479'); 118 119 const userOptions: UserOptions = { 120 showDiff: { 121 name: 'Show diff', 122 enabled: true, 123 }, 124 simplifyNames: { 125 name: 'Simplify names', 126 enabled: false, 127 }, 128 onlyVisible: { 129 name: 'Only visible', 130 enabled: true, 131 }, 132 }; 133 presenter.updateHierarchyTree(userOptions); 134 expect(uiData.hierarchyUserOptions).toEqual(userOptions); 135 expect( 136 // DecorView -> LinearLayout -> FrameLayout (before, this was the 2nd child) -> LauncherRootView -> DragLayer -> Workspace if filter works as expected 137 uiData.tree?.children[0].children[0].children[0].children[0].children[1].id 138 ).toEqual('com.android.launcher3.Workspace@251960479'); 139 }); 140 141 it('filters hierarchy tree', async () => { 142 const userOptions: UserOptions = { 143 showDiff: { 144 name: 'Show diff', 145 enabled: false, 146 }, 147 simplifyNames: { 148 name: 'Simplify names', 149 enabled: true, 150 }, 151 onlyVisible: { 152 name: 'Only visible', 153 enabled: false, 154 }, 155 flat: { 156 name: 'Flat', 157 enabled: true, 158 }, 159 }; 160 await presenter.onTracePositionUpdate(position); 161 presenter.updateHierarchyTree(userOptions); 162 presenter.filterHierarchyTree('Workspace'); 163 164 expect( 165 // DecorView -> LinearLayout -> FrameLayout -> LauncherRootView -> DragLayer -> Workspace if filter works as expected 166 uiData.tree?.children[0].children[0].children[0].children[0].children[0].id 167 ).toEqual('com.android.launcher3.Workspace@251960479'); 168 }); 169 170 it('sets properties tree and associated ui data', async () => { 171 await presenter.onTracePositionUpdate(position); 172 presenter.newPropertiesTree(selectedTree); 173 expect(uiData.propertiesTree).toBeTruthy(); 174 }); 175 176 it('updates properties tree', async () => { 177 const userOptions: UserOptions = { 178 showDiff: { 179 name: 'Show diff', 180 enabled: true, 181 }, 182 showDefaults: { 183 name: 'Show defaults', 184 enabled: true, 185 tooltip: ` 186 If checked, shows the value of all properties. 187 Otherwise, hides all properties whose value is 188 the default for its data type. 189 `, 190 }, 191 }; 192 193 await presenter.onTracePositionUpdate(position); 194 presenter.newPropertiesTree(selectedTree); 195 expect(uiData.propertiesTree?.diffType).toBeFalsy(); 196 197 presenter.updatePropertiesTree(userOptions); 198 expect(uiData.propertiesUserOptions).toEqual(userOptions); 199 expect(uiData.propertiesTree?.diffType).toBeTruthy(); 200 }); 201 202 it('filters properties tree', async () => { 203 await presenter.onTracePositionUpdate(position); 204 205 const userOptions: UserOptions = { 206 showDiff: { 207 name: 'Show diff', 208 enabled: true, 209 }, 210 showDefaults: { 211 name: 'Show defaults', 212 enabled: true, 213 tooltip: ` 214 If checked, shows the value of all properties. 215 Otherwise, hides all properties whose value is 216 the default for its data type. 217 `, 218 }, 219 }; 220 presenter.updatePropertiesTree(userOptions); 221 let nonTerminalChildren = uiData.propertiesTree?.children?.filter( 222 (it) => typeof it.propertyKey === 'string' 223 ); 224 expect(nonTerminalChildren?.length).toEqual(24); 225 presenter.filterPropertiesTree('alpha'); 226 227 nonTerminalChildren = uiData.propertiesTree?.children?.filter( 228 (it) => typeof it.propertyKey === 'string' 229 ); 230 expect(nonTerminalChildren?.length).toEqual(1); 231 }); 232 233 const createPresenter = (trace: Trace<object>): Presenter => { 234 const traces = new Traces(); 235 traces.setTrace(TraceType.VIEW_CAPTURE, trace); 236 return new Presenter(traces, new MockStorage(), (newData: UiData) => { 237 uiData = newData; 238 }); 239 }; 240}); 241