• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {ScrollingModule} from '@angular/cdk/scrolling';
18import {
19  ComponentFixture,
20  ComponentFixtureAutoDetect,
21  TestBed,
22} from '@angular/core/testing';
23import {MatDividerModule} from '@angular/material/divider';
24import {MatIconModule} from '@angular/material/icon';
25import {assertDefined} from 'common/assert_utils';
26import {TracePositionUpdate} from 'messaging/winscope_event';
27import {PropertyTreeBuilder} from 'test/unit/property_tree_builder';
28import {TimestampConverterUtils} from 'test/unit/timestamp_converter_utils';
29import {UnitTestUtils} from 'test/unit/utils';
30import {Parser} from 'trace/parser';
31import {Trace} from 'trace/trace';
32import {Traces} from 'trace/traces';
33import {TracePosition} from 'trace/trace_position';
34import {TraceType} from 'trace/trace_type';
35import {Transition} from 'trace/transition';
36import {TIMESTAMP_NODE_FORMATTER} from 'trace/tree_node/formatters';
37import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
38import {TimestampClickDetail, ViewerEvents} from 'viewers/common/viewer_events';
39import {CollapsedSectionsComponent} from 'viewers/components/collapsed_sections_component';
40import {CollapsibleSectionTitleComponent} from 'viewers/components/collapsible_section_title_component';
41import {PropertiesComponent} from 'viewers/components/properties_component';
42import {PropertyTreeNodeDataViewComponent} from 'viewers/components/property_tree_node_data_view_component';
43import {TreeComponent} from 'viewers/components/tree_component';
44import {TreeNodeComponent} from 'viewers/components/tree_node_component';
45import {Presenter} from './presenter';
46import {UiData} from './ui_data';
47import {ViewerTransitionsComponent} from './viewer_transitions_component';
48
49describe('ViewerTransitionsComponent', () => {
50  let fixture: ComponentFixture<ViewerTransitionsComponent>;
51  let component: ViewerTransitionsComponent;
52  let htmlElement: HTMLElement;
53
54  beforeEach(async () => {
55    await TestBed.configureTestingModule({
56      providers: [{provide: ComponentFixtureAutoDetect, useValue: true}],
57      imports: [MatDividerModule, ScrollingModule, MatIconModule],
58      declarations: [
59        ViewerTransitionsComponent,
60        TreeComponent,
61        TreeNodeComponent,
62        PropertyTreeNodeDataViewComponent,
63        PropertiesComponent,
64        CollapsedSectionsComponent,
65        CollapsibleSectionTitleComponent,
66      ],
67      schemas: [],
68    }).compileComponents();
69
70    fixture = TestBed.createComponent(ViewerTransitionsComponent);
71    component = fixture.componentInstance;
72    htmlElement = fixture.nativeElement;
73
74    component.uiData = makeUiData();
75    fixture.detectChanges();
76  });
77
78  it('can be created', () => {
79    expect(component).toBeTruthy();
80  });
81
82  it('renders entries', () => {
83    expect(htmlElement.querySelector('.scroll')).toBeTruthy();
84
85    const entry = assertDefined(htmlElement.querySelector('.scroll .entry'));
86    expect(entry.innerHTML).toContain('TO_FRONT');
87    expect(entry.innerHTML).toContain('10ns');
88  });
89
90  it('shows message when no transition is selected', () => {
91    expect(
92      htmlElement.querySelector('.properties-view .placeholder-text')
93        ?.innerHTML,
94    ).toContain('No selected transition');
95  });
96
97  it('emits TransitionSelected event on transition clicked', () => {
98    const emitEventSpy = spyOn(component, 'emitEvent');
99
100    const entries = htmlElement.querySelectorAll('.entry.table-row');
101    const entry1 = assertDefined(entries[0]) as HTMLElement;
102    const entry2 = assertDefined(entries[1]) as HTMLElement;
103    const treeView = assertDefined(
104      htmlElement.querySelector('.properties-view'),
105    ) as HTMLElement;
106    expect(
107      assertDefined(treeView.querySelector('.placeholder-text')).textContent,
108    ).toContain('No selected transition');
109
110    expect(emitEventSpy).not.toHaveBeenCalled();
111
112    const id0 = assertDefined(entry1.querySelector('.id')).textContent;
113    expect(id0).toEqual('0');
114    entry1.click();
115    fixture.detectChanges();
116
117    expect(emitEventSpy).toHaveBeenCalled();
118    expect(emitEventSpy).toHaveBeenCalledWith(
119      ViewerEvents.TransitionSelected,
120      jasmine.any(Object),
121    );
122    expect(
123      (emitEventSpy.calls.mostRecent().args[1] as PropertyTreeNode)
124        .getChildByName('id')
125        ?.getValue(),
126    ).toEqual(0);
127
128    const id1 = assertDefined(entry2.querySelector('.id')).textContent;
129    expect(id1).toEqual('1');
130    entry2.click();
131    fixture.detectChanges();
132
133    expect(emitEventSpy).toHaveBeenCalled();
134    expect(emitEventSpy).toHaveBeenCalledWith(
135      ViewerEvents.TransitionSelected,
136      jasmine.any(Object),
137    );
138    expect(
139      (emitEventSpy.calls.mostRecent().args[1] as PropertyTreeNode)
140        .getChildByName('id')
141        ?.getValue(),
142    ).toEqual(1);
143  });
144
145  it('updates tree view on TracePositionUpdate event', async () => {
146    const parser = (await UnitTestUtils.getTracesParser([
147      'traces/elapsed_and_real_timestamp/wm_transition_trace.pb',
148      'traces/elapsed_and_real_timestamp/shell_transition_trace.pb',
149    ])) as Parser<PropertyTreeNode>;
150    const trace = Trace.fromParser(parser);
151    const traces = new Traces();
152    traces.addTrace(trace);
153
154    let treeView = assertDefined(
155      htmlElement.querySelector('.properties-view'),
156    ) as any as HTMLElement;
157    expect(
158      assertDefined(treeView.querySelector('.placeholder-text')).textContent,
159    ).toContain('No selected transition');
160
161    const presenter = new Presenter(trace, traces, (data) => {
162      component.inputData = data;
163    });
164    const selectedTransitionEntry = assertDefined(
165      traces.getTrace(TraceType.TRANSITION)?.getEntry(2),
166    );
167    const selectedTransition = await selectedTransitionEntry.getValue();
168    const selectedTransitionId = assertDefined(
169      selectedTransition.getChildByName('id'),
170    ).getValue();
171    await presenter.onAppEvent(
172      new TracePositionUpdate(
173        TracePosition.fromTraceEntry(selectedTransitionEntry),
174      ),
175    );
176
177    expect(
178      assertDefined(
179        component.uiData.selectedTransition
180          ?.getChildByName('wmData')
181          ?.getChildByName('id'),
182      ).getValue(),
183    ).toEqual(selectedTransitionId);
184
185    fixture.detectChanges();
186
187    treeView = assertDefined(
188      fixture.nativeElement.querySelector('.properties-view'),
189    ) as any as HTMLElement;
190    const textContentWithoutWhitespaces = treeView.textContent?.replace(
191      /(\s|\t|\n)*/g,
192      '',
193    );
194    expect(textContentWithoutWhitespaces).toContain(
195      `id:${selectedTransitionId}`,
196    );
197  });
198
199  it('propagates timestamp on click', () => {
200    let timestamp: string | undefined;
201    let index: number | undefined;
202    htmlElement.addEventListener(ViewerEvents.TimestampClick, (event) => {
203      const detail: TimestampClickDetail = (event as CustomEvent).detail;
204      timestamp = detail.timestamp?.format();
205      index = detail.index;
206    });
207    const sendTimeButton = assertDefined(
208      htmlElement.querySelector('.send-time button'),
209    ) as HTMLButtonElement;
210    sendTimeButton.click();
211
212    expect(index).toBeUndefined();
213    expect(timestamp).toEqual('20ns');
214
215    const dispatchTimeButton = assertDefined(
216      htmlElement.querySelector('.dispatch-time button'),
217    ) as HTMLButtonElement;
218    dispatchTimeButton.click();
219
220    expect(index).toEqual(0);
221    expect(timestamp).toEqual('25ns');
222  });
223
224  it('creates collapsed sections with no buttons', () => {
225    UnitTestUtils.checkNoCollapsedSectionButtons(htmlElement);
226  });
227
228  it('handles properties section collapse/expand', () => {
229    UnitTestUtils.checkSectionCollapseAndExpand(
230      htmlElement,
231      fixture,
232      '.properties-view',
233      'SELECTED TRANSITION',
234    );
235  });
236});
237
238function makeUiData(): UiData {
239  let mockTransitionIdCounter = 0;
240
241  const transitions = [
242    createMockTransition(20, 30, mockTransitionIdCounter++),
243    createMockTransition(42, 50, mockTransitionIdCounter++),
244    createMockTransition(46, 49, mockTransitionIdCounter++),
245    createMockTransition(58, 70, mockTransitionIdCounter++),
246  ];
247
248  return new UiData(transitions, undefined);
249}
250
251function createMockTransition(
252  sendTimeNanos: number,
253  finishTimeNanos: number,
254  id: number,
255): Transition {
256  const transitionTree = new PropertyTreeBuilder()
257    .setIsRoot(true)
258    .setRootId('TransitionTraceEntry')
259    .setName('transition')
260    .setChildren([{name: 'id', value: id}])
261    .build();
262
263  const sendTimeNode = new PropertyTreeBuilder()
264    .setRootId(transitionTree.id)
265    .setName('sendTimeNs')
266    .setValue(
267      TimestampConverterUtils.makeElapsedTimestamp(BigInt(sendTimeNanos)),
268    )
269    .setFormatter(TIMESTAMP_NODE_FORMATTER)
270    .build();
271
272  const dispatchTimeNode = new PropertyTreeBuilder()
273    .setRootId(transitionTree.id)
274    .setName('dispatchTimeNs')
275    .setValue(
276      TimestampConverterUtils.makeElapsedTimestamp(BigInt(sendTimeNanos) + 5n),
277    )
278    .setFormatter(TIMESTAMP_NODE_FORMATTER)
279    .build();
280
281  return {
282    id,
283    type: 'TO_FRONT',
284    sendTime: sendTimeNode,
285    dispatchTime: dispatchTimeNode,
286    duration: (finishTimeNanos - sendTimeNanos).toString() + 'ns',
287    merged: false,
288    aborted: false,
289    played: false,
290    propertiesTree: transitionTree,
291    traceIndex: 0,
292  };
293}
294