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 */ 16 17import {assertDefined} from 'common/assert_utils'; 18import {Rect} from 'common/rect'; 19import {TracePositionUpdate} from 'messaging/winscope_event'; 20import {TimestampConverterUtils} from 'test/unit/timestamp_converter_utils'; 21import {TreeNodeUtils} from 'test/unit/tree_node_utils'; 22import { 23 AbstractHierarchyViewerPresenter, 24 NotifyHierarchyViewCallbackType, 25} from 'viewers/common/abstract_hierarchy_viewer_presenter'; 26import {VISIBLE_CHIP} from 'viewers/common/chip'; 27import {DiffType} from 'viewers/common/diff_type'; 28import {RectShowState} from 'viewers/common/rect_show_state'; 29import {UiHierarchyTreeNode} from 'viewers/common/ui_hierarchy_tree_node'; 30import {UiTreeUtils} from 'viewers/common/ui_tree_utils'; 31import {UserOptions} from 'viewers/common/user_options'; 32import {UiDataHierarchy} from './ui_data_hierarchy'; 33 34export abstract class AbstractHierarchyViewerPresenterTest { 35 execute() { 36 describe('AbstractHierarchyViewerPresenter', () => { 37 let uiData: UiDataHierarchy; 38 let presenter: AbstractHierarchyViewerPresenter; 39 beforeAll(async () => { 40 await this.setUpTestEnvironment(); 41 }); 42 43 beforeEach(() => { 44 presenter = this.createPresenter((newData) => { 45 uiData = newData; 46 }); 47 }); 48 49 it('is robust to empty trace', async () => { 50 const notifyViewCallback = (newData: UiDataHierarchy) => { 51 uiData = newData; 52 }; 53 const presenter = 54 this.createPresenterWithEmptyTrace(notifyViewCallback); 55 56 const positionUpdateWithoutTraceEntry = 57 TracePositionUpdate.fromTimestamp( 58 TimestampConverterUtils.makeRealTimestamp(0n), 59 ); 60 await presenter.onAppEvent(positionUpdateWithoutTraceEntry); 61 62 expect(Object.keys(uiData.hierarchyUserOptions).length).toBeGreaterThan( 63 0, 64 ); 65 expect( 66 Object.keys(uiData.propertiesUserOptions).length, 67 ).toBeGreaterThan(0); 68 expect(uiData.hierarchyTrees).toBeFalsy(); 69 if (this.shouldExecuteRectTests) { 70 expect( 71 Object.keys(assertDefined(uiData?.rectsUserOptions)).length, 72 ).toBeGreaterThan(0); 73 } 74 }); 75 76 it('processes trace position updates', async () => { 77 await assertDefined(presenter).onAppEvent( 78 assertDefined(this.getPositionUpdate()), 79 ); 80 81 expect(uiData.highlightedItem?.length).toEqual(0); 82 expect(Object.keys(uiData.hierarchyUserOptions).length).toBeGreaterThan( 83 0, 84 ); 85 expect( 86 Object.keys(uiData.propertiesUserOptions).length, 87 ).toBeGreaterThan(0); 88 assertDefined(uiData.hierarchyTrees).forEach((tree) => { 89 expect(tree.getAllChildren().length > 0).toBeTrue(); 90 }); 91 if (this.shouldExecuteRectTests) { 92 expect( 93 Object.keys(assertDefined(uiData.rectsUserOptions)).length, 94 ).toBeGreaterThan(0); 95 expect(uiData.rectsToDraw?.length).toBeGreaterThan(0); 96 expect(uiData.displays?.length).toBeGreaterThan(0); 97 } 98 }); 99 100 if (this.shouldExecuteShowDiffTests) { 101 it('disables show diff and generates non-diff tree if no prev entry available', async () => { 102 await presenter.onAppEvent(this.getPositionUpdate()); 103 104 const hierarchyOpts = assertDefined(uiData.hierarchyUserOptions); 105 expect(hierarchyOpts['showDiff'].isUnavailable).toBeTrue(); 106 107 const propertyOpts = assertDefined(uiData.propertiesUserOptions); 108 expect(propertyOpts['showDiff'].isUnavailable).toBeTrue(); 109 110 assertDefined(uiData.hierarchyTrees).forEach((tree) => { 111 expect(tree.getAllChildren().length > 0).toBeTrue(); 112 }); 113 }); 114 } 115 116 it('updates pinned items', () => { 117 expect(uiData.pinnedItems).toEqual([]); 118 119 const pinnedItem = TreeNodeUtils.makeUiHierarchyNode({ 120 id: 'TestItem 4', 121 name: 'FirstPinnedItem', 122 }); 123 124 presenter.onPinnedItemChange(pinnedItem); 125 expect(uiData.pinnedItems).toContain(pinnedItem); 126 }); 127 128 it('updates highlighted property', () => { 129 expect(uiData.highlightedProperty).toEqual(''); 130 const id = '4'; 131 presenter.onHighlightedPropertyChange(id); 132 expect(uiData.highlightedProperty).toEqual(id); 133 }); 134 135 it('filters hierarchy tree by visibility', async () => { 136 const userOptions: UserOptions = { 137 showOnlyVisible: { 138 name: 'Show only', 139 chip: VISIBLE_CHIP, 140 enabled: false, 141 }, 142 flat: { 143 name: 'Flat', 144 enabled: true, 145 }, 146 }; 147 148 await presenter.onAppEvent(this.getPositionUpdate()); 149 await presenter.onHierarchyUserOptionsChange(userOptions); 150 151 expect(this.getTotalHierarchyChildren(uiData)).toEqual( 152 this.getExpectedChildrenBeforeVisibilityFilter(), 153 ); 154 155 userOptions['showOnlyVisible'].enabled = true; 156 await presenter.onHierarchyUserOptionsChange(userOptions); 157 expect(this.getTotalHierarchyChildren(uiData)).toEqual( 158 this.getExpectedChildrenAfterVisibilityFilter(), 159 ); 160 }); 161 162 if (this.shouldExecuteFlatTreeTest) { 163 it('flattens hierarchy tree', async () => { 164 //change flat view to true 165 const userOptions: UserOptions = { 166 showDiff: { 167 name: 'Show diff', 168 enabled: false, 169 }, 170 simplifyNames: { 171 name: 'Simplify names', 172 enabled: false, 173 }, 174 showOnlyVisible: { 175 name: 'Show only', 176 chip: VISIBLE_CHIP, 177 enabled: false, 178 }, 179 flat: { 180 name: 'Flat', 181 enabled: true, 182 }, 183 }; 184 185 await presenter.onAppEvent(this.getPositionUpdate()); 186 expect(this.getTotalHierarchyChildren(uiData)).toEqual( 187 this.getExpectedChildrenBeforeFlatFilter!(), 188 ); 189 190 await presenter.onHierarchyUserOptionsChange(userOptions); 191 expect(uiData.hierarchyUserOptions).toEqual(userOptions); 192 expect(this.getTotalHierarchyChildren(uiData)).toEqual( 193 this.getExpectedChildrenAfterFlatFilter!(), 194 ); 195 assertDefined(uiData.hierarchyTrees).forEach((tree) => { 196 tree.getAllChildren().forEach((child) => { 197 expect(child.getAllChildren().length).toEqual(0); 198 }); 199 }); 200 }); 201 } 202 203 if (this.shouldExecuteSimplifyNamesTest) { 204 it('simplifies names in hierarchy tree', async () => { 205 const longName = assertDefined(this.treeNodeLongName); 206 const shortName = assertDefined(this.treeNodeShortName); 207 //change flat view to true 208 const userOptions: UserOptions = { 209 showDiff: { 210 name: 'Show diff', 211 enabled: false, 212 }, 213 simplifyNames: { 214 name: 'Simplify names', 215 enabled: false, 216 }, 217 showOnlyVisible: { 218 name: 'Show only', 219 chip: VISIBLE_CHIP, 220 enabled: false, 221 }, 222 flat: { 223 name: 'Flat', 224 enabled: false, 225 }, 226 }; 227 228 await presenter.onAppEvent(this.getPositionUpdate()); 229 let nodeWithLongName = assertDefined( 230 assertDefined(uiData.hierarchyTrees)[0].findDfs( 231 UiTreeUtils.makeIdFilter(longName), 232 ), 233 ); 234 expect(nodeWithLongName.getDisplayName()).toEqual(shortName); 235 236 await presenter.onHierarchyUserOptionsChange(userOptions); 237 expect(uiData.hierarchyUserOptions).toEqual(userOptions); 238 nodeWithLongName = assertDefined( 239 assertDefined(uiData.hierarchyTrees)[0].findDfs( 240 UiTreeUtils.makeIdFilter(longName), 241 ), 242 ); 243 expect(longName).toContain(nodeWithLongName.getDisplayName()); 244 }); 245 } 246 247 it('filters hierarchy tree by search string', async () => { 248 const userOptions: UserOptions = { 249 showDiff: { 250 name: 'Show diff', 251 enabled: false, 252 }, 253 simplifyNames: { 254 name: 'Simplify names', 255 enabled: true, 256 }, 257 showOnlyVisible: { 258 name: 'Show only', 259 chip: VISIBLE_CHIP, 260 enabled: false, 261 }, 262 flat: { 263 name: 'Flat', 264 enabled: true, 265 }, 266 }; 267 await presenter.onAppEvent(this.getPositionUpdate()); 268 await presenter.onHierarchyUserOptionsChange(userOptions); 269 expect(this.getTotalHierarchyChildren(uiData)).toEqual( 270 this.getExpectedHierarchyChildrenBeforeStringFilter(), 271 ); 272 273 await presenter.onHierarchyFilterChange(this.hierarchyFilterString); 274 expect(this.getTotalHierarchyChildren(uiData)).toEqual( 275 this.expectedHierarchyChildrenAfterStringFilter, 276 ); 277 }); 278 279 it('sets properties tree and associated ui data from tree node', async () => { 280 await presenter.onAppEvent(this.getPositionUpdate()); 281 282 const selectedTree = this.getSelectedTree(); 283 await presenter.onHighlightedNodeChange(selectedTree); 284 const propertiesTree = assertDefined(uiData.propertiesTree); 285 expect(propertiesTree.id).toContain(selectedTree.id); 286 expect(propertiesTree.getAllChildren().length).toEqual( 287 this.numberOfNonDefaultProperties, 288 ); 289 if (this.executeSpecializedChecksForPropertiesFromNode) { 290 this.executeSpecializedChecksForPropertiesFromNode(uiData); 291 } 292 }); 293 294 it('after highlighting a node, updates properties tree on position update', async () => { 295 await presenter.onAppEvent(this.getPositionUpdate()); 296 await presenter.onHighlightedNodeChange( 297 this.getSelectedTreeAfterPositionUpdate(), 298 ); 299 this.executeChecksForPropertiesTreeAfterPositionUpdate(uiData); 300 301 const secondUpdate = this.getSecondPositionUpdate(); 302 if (secondUpdate) { 303 await presenter.onAppEvent(secondUpdate); 304 assertDefined( 305 this.executeChecksForPropertiesTreeAfterSecondPositionUpdate, 306 )(uiData); 307 } 308 }); 309 310 if (this.shouldExecuteShowDiffTests) { 311 it('updates properties tree to show diffs', async () => { 312 //change flat view to true 313 const userOptions: UserOptions = { 314 showDiff: { 315 name: 'Show diff', 316 enabled: true, 317 }, 318 }; 319 320 await presenter.onAppEvent(this.getShowDiffPositionUpdate()); 321 await presenter.onHighlightedNodeChange(this.getSelectedTree()); 322 const propertyName = assertDefined(this.propertyWithDiff); 323 expect( 324 assertDefined( 325 uiData.propertiesTree?.getChildByName(propertyName), 326 ).getDiff(), 327 ).toEqual(DiffType.NONE); 328 329 await presenter.onPropertiesUserOptionsChange(userOptions); 330 expect(uiData.propertiesUserOptions).toEqual(userOptions); 331 expect( 332 assertDefined( 333 uiData.propertiesTree?.getChildByName(propertyName), 334 ).getDiff(), 335 ).toEqual(assertDefined(this.expectedPropertyDiffType)); 336 }); 337 } 338 339 it('shows/hides defaults', async () => { 340 const userOptions: UserOptions = { 341 showDiff: { 342 name: 'Show diff', 343 enabled: true, 344 }, 345 showDefaults: { 346 name: 'Show defaults', 347 enabled: true, 348 }, 349 }; 350 351 await presenter.onAppEvent(this.getPositionUpdate()); 352 await presenter.onHighlightedNodeChange(this.getSelectedTree()); 353 expect( 354 assertDefined(uiData.propertiesTree).getAllChildren().length, 355 ).toEqual(this.numberOfNonDefaultProperties); 356 357 await presenter.onPropertiesUserOptionsChange(userOptions); 358 expect(uiData.propertiesUserOptions).toEqual(userOptions); 359 expect( 360 assertDefined(uiData.propertiesTree).getAllChildren().length, 361 ).toEqual( 362 this.numberOfNonDefaultProperties + this.numberOfDefaultProperties, 363 ); 364 }); 365 366 it('filters properties tree', async () => { 367 await presenter.onAppEvent(this.getPositionUpdate()); 368 await presenter.onHighlightedNodeChange(this.getSelectedTree()); 369 expect( 370 assertDefined(uiData.propertiesTree).getAllChildren().length, 371 ).toEqual(this.numberOfNonDefaultProperties); 372 373 await presenter.onPropertiesFilterChange(this.propertiesFilterString); 374 expect( 375 assertDefined(uiData.propertiesTree).getAllChildren().length, 376 ).toEqual(this.numberOfFilteredProperties); 377 }); 378 379 if (this.shouldExecuteRectTests) { 380 const totalRects = assertDefined(this.expectedTotalRects); 381 const visibleRects = assertDefined(this.expectedVisibleRects); 382 383 it('creates input data for rects view', async () => { 384 await presenter.onAppEvent(this.getPositionUpdate()); 385 const rectsToDraw = assertDefined(uiData.rectsToDraw); 386 const expectedFirstRect = assertDefined(this.expectedFirstRect); 387 expect(rectsToDraw[0].x).toEqual(expectedFirstRect.x); 388 expect(rectsToDraw[0].y).toEqual(expectedFirstRect.y); 389 expect(rectsToDraw[0].w).toEqual(expectedFirstRect.w); 390 expect(rectsToDraw[0].h).toEqual(expectedFirstRect.h); 391 this.checkRectUiData(uiData, totalRects, totalRects, totalRects); 392 }); 393 394 it('filters rects by visibility', async () => { 395 const userOptions: UserOptions = { 396 showOnlyVisible: { 397 name: 'Show only', 398 chip: VISIBLE_CHIP, 399 enabled: false, 400 }, 401 }; 402 403 await presenter.onAppEvent(this.getPositionUpdate()); 404 presenter.onRectsUserOptionsChange(userOptions); 405 expect(uiData.rectsUserOptions).toEqual(userOptions); 406 this.checkRectUiData(uiData, totalRects, totalRects, totalRects); 407 408 userOptions['showOnlyVisible'].enabled = true; 409 presenter.onRectsUserOptionsChange(userOptions); 410 this.checkRectUiData(uiData, visibleRects, totalRects, visibleRects); 411 }); 412 413 it('filters rects by show/hide state', async () => { 414 const userOptions: UserOptions = { 415 ignoreNonHidden: { 416 name: 'Ignore', 417 icon: 'visibility', 418 enabled: true, 419 }, 420 }; 421 presenter.onRectsUserOptionsChange(userOptions); 422 await presenter.onAppEvent(this.getPositionUpdate()); 423 this.checkRectUiData(uiData, totalRects, totalRects, totalRects); 424 425 await presenter.onRectShowStateChange( 426 assertDefined(uiData.rectsToDraw)[0].id, 427 RectShowState.HIDE, 428 ); 429 this.checkRectUiData(uiData, totalRects, totalRects, totalRects - 1); 430 431 userOptions['ignoreNonHidden'].enabled = false; 432 presenter.onRectsUserOptionsChange(userOptions); 433 this.checkRectUiData( 434 uiData, 435 totalRects - 1, 436 totalRects, 437 totalRects - 1, 438 ); 439 }); 440 441 it('handles both visibility and show/hide state in rects', async () => { 442 const userOptions: UserOptions = { 443 ignoreNonHidden: { 444 name: 'Ignore', 445 icon: 'visibility', 446 enabled: true, 447 }, 448 showOnlyVisible: { 449 name: 'Show only', 450 chip: VISIBLE_CHIP, 451 enabled: false, 452 }, 453 }; 454 presenter.onRectsUserOptionsChange(userOptions); 455 await presenter.onAppEvent(this.getPositionUpdate()); 456 this.checkRectUiData(uiData, totalRects, totalRects, totalRects); 457 458 await presenter.onRectShowStateChange( 459 assertDefined(uiData.rectsToDraw)[0].id, 460 RectShowState.HIDE, 461 ); 462 this.checkRectUiData(uiData, totalRects, totalRects, totalRects - 1); 463 464 userOptions['ignoreNonHidden'].enabled = false; 465 presenter.onRectsUserOptionsChange(userOptions); 466 this.checkRectUiData( 467 uiData, 468 totalRects - 1, 469 totalRects, 470 totalRects - 1, 471 ); 472 473 userOptions['showOnlyVisible'].enabled = true; 474 presenter.onRectsUserOptionsChange(userOptions); 475 this.checkRectUiData( 476 uiData, 477 visibleRects - 1, 478 totalRects, 479 visibleRects - 1, 480 ); 481 482 userOptions['ignoreNonHidden'].enabled = true; 483 presenter.onRectsUserOptionsChange(userOptions); 484 this.checkRectUiData( 485 uiData, 486 visibleRects, 487 totalRects, 488 visibleRects - 1, 489 ); 490 }); 491 492 it('sets properties tree and associated ui data from rect', async () => { 493 await presenter.onAppEvent(this.getPositionUpdate()); 494 495 const rect = assertDefined(uiData.rectsToDraw?.at(2)); 496 await presenter.onHighlightedIdChange(rect.id); 497 const propertiesTree = assertDefined(uiData.propertiesTree); 498 expect(propertiesTree.id).toEqual(rect.id); 499 expect(propertiesTree.getAllChildren().length).toBeGreaterThan(0); 500 501 if (this.executeSpecializedChecksForPropertiesFromRect) { 502 this.executeSpecializedChecksForPropertiesFromRect(uiData); 503 } 504 }); 505 506 it('after highlighting a rect, updates properties tree on position update', async () => { 507 await presenter.onAppEvent(this.getPositionUpdate()); 508 await presenter.onHighlightedIdChange( 509 this.getSelectedTreeAfterPositionUpdate().id, 510 ); 511 this.executeChecksForPropertiesTreeAfterPositionUpdate(uiData); 512 513 const secondUpdate = this.getSecondPositionUpdate(); 514 if (secondUpdate) { 515 await presenter.onAppEvent(secondUpdate); 516 assertDefined( 517 this.executeChecksForPropertiesTreeAfterSecondPositionUpdate, 518 )(uiData); 519 } 520 }); 521 } 522 }); 523 524 if (this.executeSpecializedTests) { 525 this.executeSpecializedTests(); 526 } 527 } 528 529 private getTotalHierarchyChildren(uiData: UiDataHierarchy) { 530 const children = assertDefined(uiData.hierarchyTrees).reduce( 531 (tot, tree) => (tot += tree.getAllChildren().length), 532 0, 533 ); 534 return children; 535 } 536 537 private checkRectUiData( 538 uiData: UiDataHierarchy, 539 rectsToDraw: number, 540 allRects: number, 541 shownRects: number, 542 ) { 543 expect(assertDefined(uiData.rectsToDraw).length).toEqual(rectsToDraw); 544 const showStates = Array.from( 545 assertDefined(uiData.rectIdToShowState).values(), 546 ); 547 expect(showStates.length).toEqual(allRects); 548 expect(showStates.filter((s) => s === RectShowState.SHOW).length).toEqual( 549 shownRects, 550 ); 551 } 552 553 abstract readonly shouldExecuteFlatTreeTest: boolean; 554 abstract readonly shouldExecuteRectTests: boolean; 555 abstract readonly shouldExecuteShowDiffTests: boolean; 556 abstract readonly shouldExecuteSimplifyNamesTest: boolean; 557 abstract readonly numberOfDefaultProperties: number; 558 abstract readonly numberOfNonDefaultProperties: number; 559 abstract readonly propertiesFilterString: string; 560 abstract readonly numberOfFilteredProperties: number; 561 abstract readonly hierarchyFilterString: string; 562 abstract readonly expectedHierarchyChildrenAfterStringFilter: number; 563 564 readonly expectedFirstRect?: Rect; 565 readonly expectedTotalRects?: number; 566 readonly expectedVisibleRects?: number; 567 readonly treeNodeLongName?: string; 568 readonly treeNodeShortName?: string; 569 readonly propertyWithDiff?: string; 570 readonly expectedPropertyDiffType?: DiffType; 571 572 abstract setUpTestEnvironment(): Promise<void>; 573 abstract createPresenter( 574 callback: NotifyHierarchyViewCallbackType, 575 ): AbstractHierarchyViewerPresenter; 576 abstract createPresenterWithEmptyTrace( 577 callback: NotifyHierarchyViewCallbackType, 578 ): AbstractHierarchyViewerPresenter; 579 abstract getPositionUpdate(): TracePositionUpdate; 580 abstract getSecondPositionUpdate(): TracePositionUpdate | undefined; 581 abstract getShowDiffPositionUpdate(): TracePositionUpdate; 582 abstract getSelectedTree(): UiHierarchyTreeNode; 583 abstract getSelectedTreeAfterPositionUpdate(): UiHierarchyTreeNode; 584 585 abstract getExpectedChildrenBeforeVisibilityFilter(): number; 586 abstract getExpectedChildrenAfterVisibilityFilter(): number; 587 abstract getExpectedHierarchyChildrenBeforeStringFilter(): number; 588 abstract executeChecksForPropertiesTreeAfterPositionUpdate( 589 uiData: UiDataHierarchy, 590 ): void; 591 592 getExpectedChildrenBeforeFlatFilter?(): number; 593 getExpectedChildrenAfterFlatFilter?(): number; 594 executeSpecializedChecksForPropertiesFromNode?(uiData: UiDataHierarchy): void; 595 executeChecksForPropertiesTreeAfterSecondPositionUpdate?( 596 uiData: UiDataHierarchy, 597 ): void; 598 executeSpecializedChecksForPropertiesFromRect?(uiData: UiDataHierarchy): void; 599 executeSpecializedTests?(): void; 600} 601