1/*
2 * Copyright (C) 2022 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 {NO_ERRORS_SCHEMA} from '@angular/core';
17import {ComponentFixture, TestBed} from '@angular/core/testing';
18import {MatButtonModule} from '@angular/material/button';
19import {MatCardModule} from '@angular/material/card';
20import {MatDividerModule} from '@angular/material/divider';
21import {MatIconModule} from '@angular/material/icon';
22import {MatListModule} from '@angular/material/list';
23import {MatProgressBarModule} from '@angular/material/progress-bar';
24import {MatSnackBar, MatSnackBarModule} from '@angular/material/snack-bar';
25import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
26import {assertDefined} from 'common/assert_utils';
27import {InMemoryStorage} from 'common/in_memory_storage';
28import {PersistentStoreProxy} from 'common/persistent_store_proxy';
29import {
30  TraceConfigurationMap,
31  TRACES,
32} from 'trace_collection/trace_collection_utils';
33import {AdbProxyComponent} from './adb_proxy_component';
34import {CollectTracesComponent} from './collect_traces_component';
35import {LoadProgressComponent} from './load_progress_component';
36import {TraceConfigComponent} from './trace_config_component';
37import {WebAdbComponent} from './web_adb_component';
38
39describe('CollectTracesComponent', () => {
40  let fixture: ComponentFixture<CollectTracesComponent>;
41  let component: CollectTracesComponent;
42  let htmlElement: HTMLElement;
43
44  beforeEach(async () => {
45    await TestBed.configureTestingModule({
46      imports: [
47        MatIconModule,
48        MatCardModule,
49        MatListModule,
50        MatButtonModule,
51        MatDividerModule,
52        MatProgressBarModule,
53        BrowserAnimationsModule,
54        MatSnackBarModule,
55      ],
56      providers: [MatSnackBar],
57      declarations: [
58        CollectTracesComponent,
59        AdbProxyComponent,
60        WebAdbComponent,
61        TraceConfigComponent,
62        LoadProgressComponent,
63      ],
64      schemas: [NO_ERRORS_SCHEMA],
65    }).compileComponents();
66    fixture = TestBed.createComponent(CollectTracesComponent);
67    component = fixture.componentInstance;
68    htmlElement = fixture.nativeElement;
69    component.isAdbProxy = true;
70    component.storage = new InMemoryStorage();
71    component.traceConfig = PersistentStoreProxy.new<TraceConfigurationMap>(
72      'TracingSettings',
73      TRACES['default'],
74      component.storage,
75    );
76    component.dumpConfig = PersistentStoreProxy.new<TraceConfigurationMap>(
77      'DumpSettings',
78      {
79        window_dump: {
80          name: 'Window Manager',
81          run: true,
82          config: undefined,
83        },
84        layers_dump: {
85          name: 'Surface Flinger',
86          run: true,
87          config: undefined,
88        },
89      },
90      component.storage,
91    );
92    fixture.detectChanges();
93  });
94
95  it('can be created', () => {
96    expect(component).toBeTruthy();
97  });
98
99  it('renders the expected card title', () => {
100    const title = assertDefined(htmlElement.querySelector('.title'));
101    expect(title.innerHTML).toContain('Collect Traces');
102  });
103
104  it('displays connecting message', () => {
105    assertDefined(component.connect).isConnectingState = jasmine
106      .createSpy()
107      .and.returnValue(true);
108    fixture.detectChanges();
109
110    const connectingMessage = assertDefined(
111      htmlElement.querySelector('.connecting-message'),
112    );
113    expect(connectingMessage.innerHTML).toContain('Connecting...');
114  });
115
116  it('displays adb set up', () => {
117    assertDefined(component.connect).adbSuccess = jasmine
118      .createSpy()
119      .and.returnValue(false);
120    fixture.detectChanges();
121
122    const setUpAdbEl = assertDefined(htmlElement.querySelector('.set-up-adb'));
123    expect(setUpAdbEl.querySelector('.proxy-tab')).toBeTruthy();
124  });
125
126  it('displays adb proxy element', () => {
127    assertDefined(component.connect).adbSuccess = jasmine
128      .createSpy()
129      .and.returnValue(false);
130    component.isAdbProxy = true;
131    fixture.detectChanges();
132
133    expect(htmlElement.querySelector('adb-proxy')).toBeTruthy();
134  });
135
136  it('displays no connected devices', () => {
137    const connect = assertDefined(component.connect);
138    connect.isDevicesState = jasmine.createSpy().and.returnValue(true);
139    connect.devices = jasmine.createSpy().and.returnValue({});
140    fixture.detectChanges();
141
142    const el = assertDefined(htmlElement.querySelector('.devices-connecting'));
143    expect(el.innerHTML).toContain('No devices detected');
144  });
145
146  it('displays connected authorised devices', () => {
147    const connect = assertDefined(component.connect);
148    connect.isDevicesState = jasmine.createSpy().and.returnValue(true);
149    connect.devices = jasmine
150      .createSpy()
151      .and.returnValue({'35562': {model: 'Pixel 6', authorised: true}});
152    fixture.detectChanges();
153
154    const el = assertDefined(htmlElement.querySelector('.devices-connecting'));
155    expect(el.innerHTML).toContain('Pixel 6');
156    expect(el.innerHTML).toContain('smartphone');
157  });
158
159  it('displays connected unauthorised devices', () => {
160    const connect = assertDefined(component.connect);
161    connect.isDevicesState = jasmine.createSpy().and.returnValue(true);
162    connect.devices = jasmine
163      .createSpy()
164      .and.returnValue({'35562': {model: 'Pixel 6', authorised: false}});
165    fixture.detectChanges();
166
167    const el = assertDefined(htmlElement.querySelector('.devices-connecting'));
168    expect(el.innerHTML).toContain('unauthorised');
169    expect(el.innerHTML).toContain('screen_lock_portrait');
170  });
171
172  it('auto detects changes in devices', async () => {
173    const connect = assertDefined(component.connect);
174    connect.isDevicesState = jasmine.createSpy().and.returnValue(true);
175    fixture.detectChanges();
176
177    const el = assertDefined(htmlElement.querySelector('.devices-connecting'));
178    expect(el.textContent).toContain('No devices detected');
179
180    connect.devices = jasmine
181      .createSpy()
182      .and.returnValue({'35562': {model: 'Pixel 6', authorised: true}});
183
184    await fixture.whenStable();
185    expect(el.textContent).toContain(
186      'Select a device: smartphone  Pixel 6 (35562)',
187    );
188  });
189
190  it('displays trace collection config elements', () => {
191    const connect = assertDefined(component.connect);
192    connect.isStartTraceState = jasmine.createSpy().and.returnValue(true);
193    const mock = {model: 'Pixel 6', authorised: true};
194    connect.devices = jasmine.createSpy().and.returnValue({'35562': mock});
195    connect.selectedDevice = jasmine.createSpy().and.returnValue(mock);
196    connect.selectedDeviceId = jasmine.createSpy().and.returnValue('35562');
197    fixture.detectChanges();
198
199    const el = assertDefined(
200      htmlElement.querySelector('.trace-collection-config'),
201    );
202    expect(el.innerHTML).toContain('smartphone');
203    expect(el.innerHTML).toContain('Pixel 6');
204    expect(el.innerHTML).toContain('35562');
205
206    const traceSection = htmlElement.querySelector('.trace-section');
207    expect(traceSection).toBeTruthy();
208
209    const dumpSection = htmlElement.querySelector('.dump-section');
210    expect(dumpSection).toBeTruthy();
211  });
212
213  it('start trace button works as expected', () => {
214    const connect = assertDefined(component.connect);
215    connect.isStartTraceState = jasmine.createSpy().and.returnValue(true);
216    const mock = {model: 'Pixel 6', authorised: true};
217    connect.devices = jasmine.createSpy().and.returnValue({'35562': mock});
218    connect.selectedDevice = jasmine.createSpy().and.returnValue(mock);
219    fixture.detectChanges();
220
221    const spy = spyOn(connect, 'startTrace');
222    const start = assertDefined(
223      htmlElement.querySelector('.start-btn button'),
224    ) as HTMLButtonElement;
225    start.click();
226    expect(spy).toHaveBeenCalled();
227  });
228
229  it('dump state button works as expected', () => {
230    const connect = assertDefined(component.connect);
231    connect.isStartTraceState = jasmine.createSpy().and.returnValue(true);
232    const mock = {model: 'Pixel 6', authorised: true};
233    connect.devices = jasmine.createSpy().and.returnValue({'35562': mock});
234    connect.selectedDevice = jasmine.createSpy().and.returnValue(mock);
235    fixture.detectChanges();
236
237    const spy = spyOn(connect, 'dumpState');
238    const dump = assertDefined(
239      htmlElement.querySelector('.dump-btn button'),
240    ) as HTMLButtonElement;
241    dump.click();
242    expect(spy).toHaveBeenCalled();
243  });
244
245  it('change device button works as expected', () => {
246    const connect = assertDefined(component.connect);
247    connect.isStartTraceState = jasmine.createSpy().and.returnValue(true);
248    const mock = {model: 'Pixel 6', authorised: true};
249    connect.devices = jasmine.createSpy().and.returnValue({'35562': mock});
250    connect.selectedDevice = jasmine.createSpy().and.returnValue(mock);
251    fixture.detectChanges();
252
253    const spy = spyOn(connect, 'resetLastDevice');
254    const change = assertDefined(
255      htmlElement.querySelector('.change-btn'),
256    ) as HTMLButtonElement;
257    change.click();
258    expect(spy).toHaveBeenCalled();
259  });
260
261  it('displays unknown error message', () => {
262    const connect = assertDefined(component.connect);
263    connect.isErrorState = jasmine.createSpy().and.returnValue(true);
264    fixture.detectChanges();
265
266    const testErrorMessage = 'bad things are happening';
267    assertDefined(connect.proxy).errorText = testErrorMessage;
268    fixture.detectChanges();
269
270    const el = assertDefined(htmlElement.querySelector('.unknown-error'));
271    expect(el.innerHTML).toContain('Error:');
272    expect(el.innerHTML).toContain(testErrorMessage);
273
274    const spy = spyOn(connect, 'restart').and.callThrough();
275    const retryButton = assertDefined(
276      htmlElement.querySelector('.retry-btn'),
277    ) as HTMLButtonElement;
278    retryButton.click();
279    expect(spy).toHaveBeenCalled();
280  });
281
282  it('displays starting trace elements', () => {
283    assertDefined(component.connect).isStartingTraceState = jasmine
284      .createSpy()
285      .and.returnValue(true);
286    fixture.detectChanges();
287
288    const el = assertDefined(htmlElement.querySelector('.starting-trace'));
289    const progress = assertDefined(el.querySelector('load-progress'));
290    expect(progress.innerHTML).toContain('Starting trace...');
291
292    const endButton = assertDefined(
293      el.querySelector('.end-btn button'),
294    ) as HTMLButtonElement;
295    expect(endButton.disabled).toBeTrue();
296  });
297
298  it('displays end tracing elements', () => {
299    const connect = assertDefined(component.connect);
300    connect.isEndTraceState = jasmine.createSpy().and.returnValue(true);
301    fixture.detectChanges();
302
303    const el = assertDefined(htmlElement.querySelector('.end-tracing'));
304    const progress = assertDefined(el.querySelector('load-progress'));
305    expect(progress.innerHTML).toContain('Tracing...');
306    expect(progress.innerHTML).toContain('cable');
307
308    const spy = spyOn(connect, 'endTrace');
309    const endButton = assertDefined(
310      el.querySelector('.end-btn button'),
311    ) as HTMLButtonElement;
312    expect(endButton.disabled).toBeFalse();
313    endButton.click();
314    expect(spy).toHaveBeenCalled();
315  });
316
317  it('displays loading data elements', () => {
318    assertDefined(component.connect).isLoadDataState = jasmine
319      .createSpy()
320      .and.returnValue(true);
321    fixture.detectChanges();
322
323    const el = assertDefined(htmlElement.querySelector('.load-data'));
324    const progress = assertDefined(el.querySelector('load-progress'));
325    expect(progress.innerHTML).toContain('Fetching...');
326
327    const endButton = assertDefined(
328      el.querySelector('.end-btn button'),
329    ) as HTMLButtonElement;
330    expect(endButton.disabled).toBeTrue();
331  });
332});
333