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.d 15 */ 16 17import {assertDefined} from 'common/assert_utils'; 18import {InMemoryStorage} from 'common/store/in_memory_storage'; 19import {Store} from 'common/store/store'; 20import {TracePositionUpdate} from 'messaging/winscope_event'; 21import {TraceBuilder} from 'test/unit/trace_builder'; 22import {TreeNodeUtils} from 'test/unit/tree_node_utils'; 23import {UserNotifierChecker} from 'test/unit/user_notifier_checker'; 24import {UnitTestUtils} from 'test/unit/utils'; 25import {Traces} from 'trace/traces'; 26import {ImeTraceType, TraceType} from 'trace/trace_type'; 27import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node'; 28import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; 29import {ImeUiData} from 'viewers/common/ime_ui_data'; 30import {PresenterInputMethodClients} from 'viewers/viewer_input_method_clients/presenter_input_method_clients'; 31import {PresenterInputMethodManagerService} from 'viewers/viewer_input_method_manager_service/presenter_input_method_manager_service'; 32import {PresenterInputMethodService} from 'viewers/viewer_input_method_service/presenter_input_method_service'; 33import {NotifyHierarchyViewCallbackType} from './abstract_hierarchy_viewer_presenter'; 34import {AbstractHierarchyViewerPresenterTest} from './abstract_hierarchy_viewer_presenter_test'; 35import {AbstractPresenterInputMethod} from './abstract_presenter_input_method'; 36import {VISIBLE_CHIP} from './chip'; 37import {UiDataHierarchy} from './ui_data_hierarchy'; 38import {UiHierarchyTreeNode} from './ui_hierarchy_tree_node'; 39import {UiPropertyTreeNode} from './ui_property_tree_node'; 40import {ViewerEvents} from './viewer_events'; 41 42export abstract class AbstractPresenterInputMethodTest extends AbstractHierarchyViewerPresenterTest<ImeUiData> { 43 private traces: Traces | undefined; 44 private positionUpdate: TracePositionUpdate | undefined; 45 private secondPositionUpdate: TracePositionUpdate | undefined; 46 private selectedTree: UiHierarchyTreeNode | undefined; 47 private entries: Map<TraceType, HierarchyTreeNode> | undefined; 48 49 override readonly shouldExecuteRectTests = false; 50 override readonly shouldExecuteSimplifyNamesTest = false; 51 override readonly keepCalculatedPropertiesInChild = false; 52 override readonly keepCalculatedPropertiesInRoot = false; 53 override readonly expectedHierarchyOpts = { 54 showOnlyVisible: { 55 name: 'Show only', 56 chip: VISIBLE_CHIP, 57 enabled: false, 58 }, 59 simplifyNames: { 60 name: 'Simplify names', 61 enabled: true, 62 }, 63 flat: { 64 name: 'Flat', 65 enabled: false, 66 }, 67 }; 68 override readonly expectedPropertiesOpts = { 69 showDefaults: { 70 name: 'Show defaults', 71 enabled: false, 72 tooltip: `If checked, shows the value of all properties. 73Otherwise, hides all properties whose value is 74the default for its data type.`, 75 }, 76 }; 77 78 override async setUpTestEnvironment(): Promise<void> { 79 let secondEntries: Map<TraceType, HierarchyTreeNode>; 80 [this.entries, secondEntries] = await UnitTestUtils.getImeTraceEntries(); 81 this.traces = new Traces(); 82 const traceEntries = [assertDefined(this.entries.get(this.imeTraceType))]; 83 const secondEntry = secondEntries.get(this.imeTraceType); 84 if (secondEntry) { 85 traceEntries.push(secondEntry); 86 } 87 88 const trace = new TraceBuilder<HierarchyTreeNode>() 89 .setType(this.imeTraceType) 90 .setEntries(traceEntries) 91 .setFrame(0, 0) 92 .build(); 93 this.traces.addTrace(trace); 94 95 const sfEntry = this.entries.get(TraceType.SURFACE_FLINGER); 96 if (sfEntry) { 97 this.traces.addTrace( 98 new TraceBuilder<HierarchyTreeNode>() 99 .setType(TraceType.SURFACE_FLINGER) 100 .setEntries([sfEntry]) 101 .setFrame(0, 0) 102 .build(), 103 ); 104 } 105 106 const wmEntry = this.entries.get(TraceType.WINDOW_MANAGER); 107 if (wmEntry) { 108 this.traces.addTrace( 109 new TraceBuilder<HierarchyTreeNode>() 110 .setType(TraceType.WINDOW_MANAGER) 111 .setEntries([wmEntry]) 112 .setFrame(0, 0) 113 .build(), 114 ); 115 } 116 117 const entry = trace.getEntry(0); 118 this.positionUpdate = TracePositionUpdate.fromTraceEntry(entry); 119 this.secondPositionUpdate = secondEntry 120 ? TracePositionUpdate.fromTraceEntry(trace.getEntry(1)) 121 : undefined; 122 123 this.selectedTree = UiHierarchyTreeNode.from(this.getSelectedNode()); 124 } 125 126 override createPresenterWithEmptyTrace( 127 callback: NotifyHierarchyViewCallbackType<ImeUiData>, 128 ): AbstractPresenterInputMethod { 129 const trace = UnitTestUtils.makeEmptyTrace(this.imeTraceType); 130 const traces = new Traces(); 131 traces.addTrace(trace); 132 return new this.PresenterInputMethod( 133 trace, 134 traces, 135 new InMemoryStorage(), 136 callback, 137 ); 138 } 139 140 override createPresenter( 141 callback: NotifyHierarchyViewCallbackType<ImeUiData>, 142 storage: Store, 143 ): AbstractPresenterInputMethod { 144 const traces = assertDefined(this.traces); 145 const trace = assertDefined(traces.getTrace(this.imeTraceType)); 146 return new this.PresenterInputMethod(trace, traces, storage, callback); 147 } 148 149 override getPositionUpdate(): TracePositionUpdate { 150 return assertDefined(this.positionUpdate); 151 } 152 153 override getSecondPositionUpdate(): TracePositionUpdate | undefined { 154 return this.secondPositionUpdate; 155 } 156 157 override getSelectedTree(): UiHierarchyTreeNode { 158 return assertDefined(this.selectedTree); 159 } 160 161 override getSelectedTreeAfterPositionUpdate(): UiHierarchyTreeNode { 162 return assertDefined(this.selectedTree); 163 } 164 165 override executePropertiesChecksAfterPositionUpdate(uiData: UiDataHierarchy) { 166 const trees = assertDefined(uiData.hierarchyTrees); 167 expect(trees.length).toEqual(this.numberOfNestedChildren); 168 } 169 170 override executePropertiesChecksAfterSecondPositionUpdate( 171 uiData: UiDataHierarchy, 172 ) { 173 const trees = assertDefined(uiData.hierarchyTrees); 174 expect(trees.length).toEqual(1); 175 } 176 177 override executeSpecializedTests() { 178 describe('AbstractPresenterInputMethod', () => { 179 let presenter: AbstractPresenterInputMethod; 180 let uiData: ImeUiData; 181 let traces: Traces; 182 let entries: Map<TraceType, HierarchyTreeNode>; 183 let Presenter: 184 | typeof PresenterInputMethodClients 185 | typeof PresenterInputMethodService 186 | typeof PresenterInputMethodManagerService; 187 let imeTraceType: ImeTraceType; 188 let userNotifierChecker: UserNotifierChecker; 189 190 beforeAll(async () => { 191 jasmine.addCustomEqualityTester(TreeNodeUtils.treeNodeEqualityTester); 192 userNotifierChecker = new UserNotifierChecker(); 193 Presenter = this.PresenterInputMethod; 194 imeTraceType = this.imeTraceType; 195 await this.setUpTestEnvironment(); 196 entries = assertDefined(this.entries); 197 await loadTraces(); 198 }); 199 200 afterEach(() => { 201 userNotifierChecker.expectNone(); 202 userNotifierChecker.reset(); 203 }); 204 205 it('adds event listeners', async () => { 206 setUpPresenter([imeTraceType]); 207 const element = document.createElement('div'); 208 presenter.addEventListeners(element); 209 210 const spy: jasmine.Spy = spyOn( 211 presenter, 212 'onAdditionalPropertySelected', 213 ); 214 const selectedItem = { 215 name: '', 216 treeNode: TreeNodeUtils.makePropertyNode('', '', null), 217 }; 218 element.dispatchEvent( 219 new CustomEvent(ViewerEvents.AdditionalPropertySelected, { 220 detail: {selectedItem}, 221 }), 222 ); 223 expect(spy).toHaveBeenCalledWith(selectedItem); 224 }); 225 226 it('is robust to traces without SF', async () => { 227 setUpPresenter([imeTraceType, TraceType.WINDOW_MANAGER]); 228 await presenter.onAppEvent(this.getPositionUpdate()); 229 expect(uiData.hierarchyUserOptions).toBeTruthy(); 230 expect(uiData.propertiesUserOptions).toBeTruthy(); 231 expect(uiData.hierarchyTrees).toBeDefined(); 232 }); 233 234 it('is robust to traces without WM', async () => { 235 setUpPresenter([imeTraceType, TraceType.SURFACE_FLINGER]); 236 await presenter.onAppEvent(this.getPositionUpdate()); 237 expect(uiData.hierarchyUserOptions).toBeTruthy(); 238 expect(uiData.propertiesUserOptions).toBeTruthy(); 239 expect(uiData.hierarchyTrees).toBeDefined(); 240 }); 241 242 it('is robust to traces without WM and SF', async () => { 243 setUpPresenter([imeTraceType]); 244 await presenter.onAppEvent(this.getPositionUpdate()); 245 expect(uiData.hierarchyUserOptions).toBeTruthy(); 246 expect(uiData.propertiesUserOptions).toBeTruthy(); 247 expect(uiData.hierarchyTrees).toBeDefined(); 248 }); 249 250 it('can set new additional properties tree and associated ui data from hierarchy tree node', async () => { 251 setUpPresenter([imeTraceType, TraceType.WINDOW_MANAGER]); 252 expect(uiData.propertiesTree).toBeUndefined(); 253 await presenter.onAppEvent(this.getPositionUpdate()); 254 await presenter.onAdditionalPropertySelected({ 255 name: 'Test Tree', 256 treeNode: this.getSelectedTree(), 257 }); 258 expect(assertDefined(uiData.propertiesTree).getDisplayName()).toEqual( 259 'Test Tree', 260 ); 261 expect(uiData.highlightedItem).toEqual(this.getSelectedTree().id); 262 }); 263 264 it('can set new properties tree and associated ui data from id', async () => { 265 setUpPresenter([imeTraceType, TraceType.WINDOW_MANAGER]); 266 expect(uiData.propertiesTree).toBeUndefined(); 267 await presenter.onAppEvent(this.getPositionUpdate()); 268 269 const selectedTree = this.getSelectedTree(); 270 await presenter.onHighlightedIdChange(selectedTree.id); 271 const propertiesTree = assertDefined(uiData.propertiesTree); 272 expect(propertiesTree.getDisplayName()).toEqual(selectedTree.name); 273 expect(uiData.highlightedItem).toEqual(this.getSelectedTree().id); 274 275 await presenter.onHighlightedIdChange(selectedTree.id); 276 expect(uiData.propertiesTree).toEqual(propertiesTree); 277 expect(uiData.highlightedItem).toEqual(''); 278 }); 279 280 if (this.getPropertiesTree) { 281 it('can set new additional properties tree and associated ui data from property tree node', async () => { 282 const selectedPropertyTree = assertDefined(this.getPropertiesTree)(); 283 if (!selectedPropertyTree) { 284 return; 285 } 286 setUpPresenter([imeTraceType]); 287 expect(uiData.propertiesTree).toBeUndefined(); 288 await presenter.onAppEvent(this.getPositionUpdate()); 289 await presenter.onAdditionalPropertySelected({ 290 name: 'Additional Properties Tree', 291 treeNode: selectedPropertyTree, 292 }); 293 const propertiesTree = assertDefined(uiData.propertiesTree); 294 expect(propertiesTree.getDisplayName()).toEqual( 295 'Additional Properties Tree', 296 ); 297 expect(propertiesTree).toEqual( 298 UiPropertyTreeNode.from(selectedPropertyTree), 299 ); 300 expect(uiData.highlightedItem).toEqual(selectedPropertyTree.id); 301 302 // clears additional property tree selection 303 const selectedTree = this.getSelectedTree(); 304 await presenter.onHighlightedIdChange(selectedTree.id); 305 expect(uiData.propertiesTree?.getDisplayName()).toEqual( 306 selectedTree.name, 307 ); 308 }); 309 } 310 311 function setUpPresenter(traceTypes: TraceType[]) { 312 traceTypes.forEach((traceType) => { 313 const trace = new TraceBuilder<HierarchyTreeNode>() 314 .setType(traceType) 315 .setEntries([assertDefined(entries.get(traceType))]) 316 .setFrame(0, 0) 317 .build(); 318 319 assertDefined(traces).addTrace(trace); 320 }); 321 presenter = createPresenter(traces); 322 } 323 324 function createPresenter(traces: Traces): AbstractPresenterInputMethod { 325 const callback = (newData: ImeUiData) => { 326 uiData = newData; 327 }; 328 const trace = assertDefined(traces.getTrace(imeTraceType)); 329 return new Presenter( 330 trace, 331 traces, 332 new InMemoryStorage(), 333 callback as NotifyHierarchyViewCallbackType<ImeUiData>, 334 ); 335 } 336 337 async function loadTraces() { 338 traces = new Traces(); 339 entries = (await UnitTestUtils.getImeTraceEntries())[0]; 340 } 341 }); 342 } 343 344 protected getPropertiesTree?(): PropertyTreeNode; 345 protected abstract getSelectedNode(): HierarchyTreeNode; 346 347 protected abstract readonly numberOfNestedChildren: number; 348 protected abstract readonly PresenterInputMethod: 349 | typeof PresenterInputMethodClients 350 | typeof PresenterInputMethodService 351 | typeof PresenterInputMethodManagerService; 352 protected abstract readonly imeTraceType: ImeTraceType; 353} 354