xref: /aosp_15_r20/development/tools/winscope/src/viewers/components/properties_component_test.ts (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
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 {ClipboardModule} from '@angular/cdk/clipboard';
17import {CommonModule} from '@angular/common';
18import {
19  ComponentFixture,
20  ComponentFixtureAutoDetect,
21  TestBed,
22} from '@angular/core/testing';
23import {FormsModule, ReactiveFormsModule} from '@angular/forms';
24import {MatButtonModule} from '@angular/material/button';
25import {MatDividerModule} from '@angular/material/divider';
26import {MatFormFieldModule} from '@angular/material/form-field';
27import {MatIconModule} from '@angular/material/icon';
28import {MatInputModule} from '@angular/material/input';
29import {MatTooltipModule} from '@angular/material/tooltip';
30import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
31import {assertDefined} from 'common/assert_utils';
32import {FilterFlag} from 'common/filter_flag';
33import {PersistentStore} from 'common/persistent_store';
34import {PropertyTreeBuilder} from 'test/unit/property_tree_builder';
35import {TraceType} from 'trace/trace_type';
36import {TextFilter} from 'viewers/common/text_filter';
37import {UiPropertyTreeNode} from 'viewers/common/ui_property_tree_node';
38import {ViewerEvents} from 'viewers/common/viewer_events';
39import {CollapsibleSectionTitleComponent} from './collapsible_section_title_component';
40import {PropertiesComponent} from './properties_component';
41import {PropertyTreeNodeDataViewComponent} from './property_tree_node_data_view_component';
42import {SearchBoxComponent} from './search_box_component';
43import {SurfaceFlingerPropertyGroupsComponent} from './surface_flinger_property_groups_component';
44import {TreeComponent} from './tree_component';
45import {TreeNodeComponent} from './tree_node_component';
46import {UserOptionsComponent} from './user_options_component';
47
48describe('PropertiesComponent', () => {
49  let fixture: ComponentFixture<PropertiesComponent>;
50  let component: PropertiesComponent;
51  let htmlElement: HTMLElement;
52
53  beforeEach(async () => {
54    await TestBed.configureTestingModule({
55      providers: [{provide: ComponentFixtureAutoDetect, useValue: true}],
56      declarations: [
57        PropertiesComponent,
58        SurfaceFlingerPropertyGroupsComponent,
59        TreeComponent,
60        TreeNodeComponent,
61        PropertyTreeNodeDataViewComponent,
62        CollapsibleSectionTitleComponent,
63        UserOptionsComponent,
64        SearchBoxComponent,
65      ],
66      imports: [
67        CommonModule,
68        MatInputModule,
69        MatFormFieldModule,
70        MatButtonModule,
71        MatDividerModule,
72        BrowserAnimationsModule,
73        FormsModule,
74        ReactiveFormsModule,
75        MatIconModule,
76        MatTooltipModule,
77        ClipboardModule,
78      ],
79    }).compileComponents();
80
81    fixture = TestBed.createComponent(PropertiesComponent);
82    component = fixture.componentInstance;
83    htmlElement = fixture.nativeElement;
84
85    component.store = new PersistentStore();
86    component.userOptions = {
87      showDiff: {
88        name: 'Show diff',
89        enabled: false,
90        isUnavailable: false,
91      },
92    };
93    component.textFilter = new TextFilter();
94    component.traceType = TraceType.SURFACE_FLINGER;
95
96    fixture.detectChanges();
97  });
98
99  it('can be created', () => {
100    expect(component).toBeTruthy();
101  });
102
103  it('creates title', () => {
104    const title = htmlElement.querySelector('.properties-title');
105    expect(title).toBeTruthy();
106  });
107
108  it('renders view controls', () => {
109    const viewControls = htmlElement.querySelector('.view-controls');
110    expect(viewControls).toBeTruthy();
111    const box = htmlElement.querySelector('.view-controls .user-option');
112    expect(box).toBeTruthy(); //renders at least one view control option
113  });
114
115  it('renders tree in proto dump upon selected item', () => {
116    const tree = new PropertyTreeBuilder()
117      .setRootId('selectedItem')
118      .setName('property')
119      .setValue(null)
120      .build();
121    tree.setIsRoot(true);
122    component.propertiesTree = UiPropertyTreeNode.from(tree);
123    fixture.detectChanges();
124    const treeEl = htmlElement.querySelector('tree-view');
125    expect(treeEl).toBeTruthy();
126  });
127
128  it('renders placeholder text', () => {
129    component.propertiesTree = undefined;
130    component.placeholderText = 'Placeholder text';
131    fixture.detectChanges();
132    expect(
133      htmlElement.querySelector('.placeholder-text')?.textContent,
134    ).toContain('Placeholder text');
135  });
136
137  it('handles node click', () => {
138    const tree = new PropertyTreeBuilder()
139      .setRootId('selectedItem')
140      .setName('property')
141      .setValue(null)
142      .build();
143    tree.setIsRoot(true);
144    component.propertiesTree = UiPropertyTreeNode.from(tree);
145    fixture.detectChanges();
146
147    let highlightedItem: string | undefined;
148    htmlElement.addEventListener(
149      ViewerEvents.HighlightedPropertyChange,
150      (event) => {
151        highlightedItem = (event as CustomEvent).detail.id;
152      },
153    );
154
155    const node = assertDefined(
156      htmlElement.querySelector('tree-node'),
157    ) as HTMLElement;
158    node.click();
159    fixture.detectChanges();
160    expect(highlightedItem).toEqual(tree.id);
161  });
162
163  it('handles change in filter', () => {
164    let textFilter: TextFilter | undefined;
165    htmlElement.addEventListener(
166      ViewerEvents.PropertiesFilterChange,
167      (event) => {
168        textFilter = (event as CustomEvent).detail;
169      },
170    );
171    const inputEl = assertDefined(
172      htmlElement.querySelector<HTMLInputElement>('.title-section input'),
173    );
174    const flagButton = assertDefined(
175      htmlElement.querySelector<HTMLElement>('.search-box button'),
176    );
177    flagButton.click();
178    fixture.detectChanges();
179
180    inputEl.value = 'Root';
181    inputEl.dispatchEvent(new Event('input'));
182    fixture.detectChanges();
183    expect(textFilter).toEqual(new TextFilter('Root', [FilterFlag.MATCH_CASE]));
184  });
185
186  it('handles collapse button click', () => {
187    const spy = spyOn(component.collapseButtonClicked, 'emit');
188    const collapseButton = assertDefined(
189      htmlElement.querySelector('collapsible-section-title button'),
190    ) as HTMLButtonElement;
191    collapseButton.click();
192    fixture.detectChanges();
193    expect(spy).toHaveBeenCalled();
194  });
195});
196