• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
16
17import {CommonModule} from '@angular/common';
18import {HttpClientModule} from '@angular/common/http';
19import {Component, CUSTOM_ELEMENTS_SCHEMA, ViewChild} from '@angular/core';
20import {ComponentFixture, TestBed} from '@angular/core/testing';
21import {MatButtonModule} from '@angular/material/button';
22import {MatDividerModule} from '@angular/material/divider';
23import {MatFormFieldModule} from '@angular/material/form-field';
24import {MatIconModule} from '@angular/material/icon';
25import {MatSelectModule} from '@angular/material/select';
26import {MatSliderModule} from '@angular/material/slider';
27import {MatTooltipModule} from '@angular/material/tooltip';
28import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
29import {assertDefined} from 'common/assert_utils';
30import {PersistentStore} from 'common/persistent_store';
31import {TraceType} from 'trace/trace_type';
32import {VISIBLE_CHIP} from 'viewers/common/chip';
33import {DisplayIdentifier} from 'viewers/common/display_identifier';
34import {ViewerEvents} from 'viewers/common/viewer_events';
35import {CollapsibleSectionTitleComponent} from 'viewers/components/collapsible_section_title_component';
36import {RectsComponent} from 'viewers/components/rects/rects_component';
37import {UiRect} from 'viewers/components/rects/types2d';
38import {UserOptionsComponent} from 'viewers/components/user_options_component';
39import {Canvas} from './canvas';
40import {ColorType, ShadingMode} from './types3d';
41import {UiRectBuilder} from './ui_rect_builder';
42
43describe('RectsComponent', () => {
44  let component: TestHostComponent;
45  let fixture: ComponentFixture<TestHostComponent>;
46  let htmlElement: HTMLElement;
47
48  beforeEach(async () => {
49    localStorage.clear();
50
51    await TestBed.configureTestingModule({
52      imports: [
53        CommonModule,
54        MatDividerModule,
55        MatSliderModule,
56        MatButtonModule,
57        MatTooltipModule,
58        MatIconModule,
59        HttpClientModule,
60        MatSelectModule,
61        BrowserAnimationsModule,
62        MatFormFieldModule,
63      ],
64      declarations: [
65        TestHostComponent,
66        RectsComponent,
67        CollapsibleSectionTitleComponent,
68        UserOptionsComponent,
69      ],
70      schemas: [CUSTOM_ELEMENTS_SCHEMA],
71    }).compileComponents();
72
73    fixture = TestBed.createComponent(TestHostComponent);
74    component = fixture.componentInstance;
75    htmlElement = fixture.nativeElement;
76  });
77
78  it('can be created', () => {
79    expect(component).toBeTruthy();
80  });
81
82  it('renders rotation slider', () => {
83    const slider = htmlElement.querySelector('mat-slider.slider-rotation');
84    expect(slider).toBeTruthy();
85  });
86
87  it('renders separation slider', () => {
88    const slider = htmlElement.querySelector('mat-slider.slider-spacing');
89    expect(slider).toBeTruthy();
90  });
91
92  it('renders canvas', () => {
93    const rectsCanvas = htmlElement.querySelector('.large-rects-canvas');
94    expect(rectsCanvas).toBeTruthy();
95  });
96
97  it('draws scene when input data changes', async () => {
98    fixture.detectChanges();
99    spyOn(Canvas.prototype, 'draw').and.callThrough();
100
101    const inputRect = makeRectWithGroupId(0);
102
103    expect(Canvas.prototype.draw).toHaveBeenCalledTimes(0);
104    component.rects = [inputRect];
105    fixture.detectChanges();
106    expect(Canvas.prototype.draw).toHaveBeenCalledTimes(1);
107    component.rects = [inputRect];
108    fixture.detectChanges();
109    expect(Canvas.prototype.draw).toHaveBeenCalledTimes(2);
110  });
111
112  it('draws scene when rotation slider changes', () => {
113    fixture.detectChanges();
114    spyOn(Canvas.prototype, 'draw').and.callThrough();
115    const slider = assertDefined(htmlElement.querySelector('.slider-rotation'));
116
117    expect(Canvas.prototype.draw).toHaveBeenCalledTimes(0);
118
119    slider.dispatchEvent(new MouseEvent('mousedown'));
120    expect(Canvas.prototype.draw).toHaveBeenCalledTimes(1);
121  });
122
123  it('draws scene when spacing slider changes', () => {
124    fixture.detectChanges();
125    spyOn(Canvas.prototype, 'draw').and.callThrough();
126    const slider = assertDefined(htmlElement.querySelector('.slider-spacing'));
127
128    expect(Canvas.prototype.draw).toHaveBeenCalledTimes(0);
129
130    slider.dispatchEvent(new MouseEvent('mousedown'));
131    expect(Canvas.prototype.draw).toHaveBeenCalledTimes(1);
132  });
133
134  it('unfocuses spacing slider on click', () => {
135    fixture.detectChanges();
136    const spacingSlider = assertDefined(
137      htmlElement.querySelector('.slider-spacing'),
138    );
139    checkSliderUnfocusesOnClick(spacingSlider, 0.02);
140  });
141
142  it('unfocuses rotation slider on click', () => {
143    fixture.detectChanges();
144    const rotationSlider = assertDefined(
145      htmlElement.querySelector('.slider-rotation'),
146    );
147    checkSliderUnfocusesOnClick(rotationSlider, 1);
148  });
149
150  it('renders display selector', () => {
151    component.displays = [
152      {displayId: 0, groupId: 0, name: 'Display 0'},
153      {displayId: 1, groupId: 1, name: 'Display 1'},
154      {displayId: 2, groupId: 2, name: 'Display 2'},
155    ];
156    fixture.detectChanges();
157    checkSelectedDisplay('Display 0');
158  });
159
160  it('handles display change', () => {
161    component.displays = [
162      {displayId: 0, groupId: 0, name: 'Display 0'},
163      {displayId: 1, groupId: 1, name: 'Display 1'},
164      {displayId: 2, groupId: 2, name: 'Display 2'},
165    ];
166    fixture.detectChanges();
167    checkSelectedDisplay('Display 0');
168
169    const trigger = assertDefined(
170      htmlElement.querySelector('.displays-section .mat-select-trigger'),
171    );
172    (trigger as HTMLElement).click();
173    fixture.detectChanges();
174
175    let groupId = 0;
176    htmlElement.addEventListener(ViewerEvents.RectGroupIdChange, (event) => {
177      groupId = (event as CustomEvent).detail.groupId;
178    });
179
180    const option = document
181      .querySelectorAll('.mat-select-panel .mat-option')
182      .item(1);
183    (option as HTMLElement).click();
184    fixture.detectChanges();
185    checkSelectedDisplay('Display 1');
186    expect(groupId).toEqual(1);
187  });
188
189  it('tracks selected display', () => {
190    component.displays = [
191      {displayId: 10, groupId: 0, name: 'Display 0'},
192      {displayId: 20, groupId: 1, name: 'Display 1'},
193    ];
194    fixture.detectChanges();
195    checkSelectedDisplay('Display 0');
196
197    component.displays = [
198      {displayId: 20, groupId: 2, name: 'Display 1'},
199      {displayId: 10, groupId: 1, name: 'Display 0'},
200    ];
201    fixture.detectChanges();
202    checkSelectedDisplay('Display 0');
203  });
204
205  it('updates scene on separation slider change', () => {
206    const inputRect = makeRectWithGroupId(0);
207    component.rects = [inputRect, inputRect];
208    const spy = spyOn(Canvas.prototype, 'draw').and.callThrough();
209    fixture.detectChanges();
210    updateSeparationSlider();
211
212    expect(spy).toHaveBeenCalledTimes(2);
213    const sceneBefore = assertDefined(spy.calls.first().args.at(0));
214    const sceneAfter = assertDefined(spy.calls.mostRecent().args.at(0));
215
216    expect(sceneBefore.rects[1].topLeft.z).toEqual(5);
217    expect(sceneAfter.rects[1].topLeft.z).toEqual(0.3);
218  });
219
220  it('updates scene on rotation slider change', () => {
221    const inputRect = makeRectWithGroupId(0);
222    component.rects = [inputRect];
223    const spy = spyOn(Canvas.prototype, 'draw').and.callThrough();
224    fixture.detectChanges();
225    updateRotationSlider();
226
227    expect(spy).toHaveBeenCalledTimes(2);
228    const sceneBefore = assertDefined(spy.calls.first().args.at(0));
229    const sceneAfter = assertDefined(spy.calls.mostRecent().args.at(0));
230
231    expect(sceneBefore.camera.rotationFactor).toEqual(1);
232    expect(sceneAfter.camera.rotationFactor).toEqual(0.5);
233  });
234
235  it('updates scene on shading mode change', () => {
236    const inputRect = makeRectWithGroupId(0);
237    component.rects = [inputRect];
238    const spy = spyOn(Canvas.prototype, 'draw').and.callThrough();
239    fixture.detectChanges();
240
241    updateShadingMode(ShadingMode.GRADIENT, ShadingMode.WIRE_FRAME);
242    updateShadingMode(ShadingMode.WIRE_FRAME, ShadingMode.OPACITY);
243
244    expect(spy).toHaveBeenCalledTimes(3);
245    const sceneGradient = assertDefined(spy.calls.first().args.at(0));
246    const sceneWireFrame = assertDefined(spy.calls.argsFor(1).at(0));
247    const sceneOpacity = assertDefined(spy.calls.mostRecent().args.at(0));
248
249    expect(sceneGradient.rects[0].colorType).toEqual(ColorType.VISIBLE);
250    expect(sceneGradient.rects[0].darkFactor).toEqual(1);
251
252    expect(sceneWireFrame.rects[0].colorType).toEqual(ColorType.EMPTY);
253    expect(sceneWireFrame.rects[0].darkFactor).toEqual(1);
254
255    expect(sceneOpacity.rects[0].colorType).toEqual(
256      ColorType.VISIBLE_WITH_OPACITY,
257    );
258    expect(sceneOpacity.rects[0].darkFactor).toEqual(0.5);
259  });
260
261  it('uses stored rects view settings', () => {
262    fixture.detectChanges();
263
264    updateSeparationSlider();
265    updateShadingMode(ShadingMode.GRADIENT, ShadingMode.WIRE_FRAME);
266
267    const newFixture = TestBed.createComponent(TestHostComponent);
268    newFixture.detectChanges();
269    const newRectsComponent = assertDefined(
270      newFixture.componentInstance.rectsComponent,
271    );
272    expect(newRectsComponent.getZSpacingFactor()).toEqual(0.06);
273    expect(newRectsComponent.getShadingMode()).toEqual(ShadingMode.WIRE_FRAME);
274  });
275
276  it('defaults initial selection to first display with non-display rects and groupId 0', () => {
277    const inputRect = makeRectWithGroupId(0);
278    component.rects = [inputRect];
279    component.displays = [
280      {displayId: 10, groupId: 1, name: 'Display 0'},
281      {displayId: 20, groupId: 0, name: 'Display 1'},
282    ];
283    fixture.detectChanges();
284    checkSelectedDisplay('Display 1');
285  });
286
287  it('defaults initial selection to first display with non-display rects and groupId non-zero', () => {
288    const inputRect = makeRectWithGroupId(1);
289    component.rects = [inputRect];
290    component.displays = [
291      {displayId: 10, groupId: 0, name: 'Display 0'},
292      {displayId: 20, groupId: 1, name: 'Display 1'},
293    ];
294    fixture.detectChanges();
295    checkSelectedDisplay('Display 1');
296  });
297
298  it('draws mini rects with non-present group id', () => {
299    fixture.detectChanges();
300    const inputRect = makeRectWithGroupId(0);
301    const miniRect = makeRectWithGroupId(2);
302    component.rects = [inputRect];
303    component.displays = [{displayId: 10, groupId: 0, name: 'Display 0'}];
304    component.miniRects = [miniRect];
305    const spy = spyOn(Canvas.prototype, 'draw').and.callThrough();
306    fixture.detectChanges();
307    expect(spy).toHaveBeenCalledTimes(2);
308    expect(
309      spy.calls
310        .all()
311        .forEach((call) => expect(call.args[0].rects.length).toEqual(1)),
312    );
313  });
314
315  it('draws mini rects with default spacing, rotation and shading mode', () => {
316    fixture.detectChanges();
317
318    updateSeparationSlider();
319    updateRotationSlider();
320    updateShadingMode(ShadingMode.GRADIENT, ShadingMode.WIRE_FRAME);
321
322    const inputRect = makeRectWithGroupId(0);
323    component.rects = [inputRect, inputRect];
324    component.displays = [{displayId: 10, groupId: 0, name: 'Display 0'}];
325    component.miniRects = [inputRect, inputRect];
326    const spy = spyOn(Canvas.prototype, 'draw').and.callThrough();
327    fixture.detectChanges();
328    expect(spy).toHaveBeenCalledTimes(2);
329
330    const largeRectsScene = assertDefined(spy.calls.first().args.at(0));
331    const miniRectsScene = assertDefined(spy.calls.mostRecent().args.at(0));
332
333    expect(largeRectsScene.camera.rotationFactor).toEqual(0.5);
334    expect(miniRectsScene.camera.rotationFactor).toEqual(1);
335
336    expect(largeRectsScene.rects[0].colorType).toEqual(ColorType.EMPTY);
337    expect(miniRectsScene.rects[0].colorType).toEqual(ColorType.VISIBLE);
338
339    expect(largeRectsScene.rects[1].topLeft.z).toEqual(0.3);
340    expect(miniRectsScene.rects[1].topLeft.z).toEqual(5);
341  });
342
343  it('handles collapse button click', () => {
344    fixture.detectChanges();
345    const spy = spyOn(
346      assertDefined(component.rectsComponent).collapseButtonClicked,
347      'emit',
348    );
349    const collapseButton = assertDefined(
350      htmlElement.querySelector('collapsible-section-title button'),
351    ) as HTMLButtonElement;
352    collapseButton.click();
353    fixture.detectChanges();
354    expect(spy).toHaveBeenCalled();
355  });
356
357  function checkSelectedDisplay(selectedDisplay: string) {
358    const displaySelect = assertDefined(
359      htmlElement.querySelector('.displays-select'),
360    );
361    expect(displaySelect.innerHTML).toContain(selectedDisplay);
362  }
363
364  function findAndClickElement(selector: string) {
365    const el = assertDefined(
366      htmlElement.querySelector(selector),
367    ) as HTMLElement;
368    el.click();
369    fixture.detectChanges();
370  }
371
372  function checkSliderUnfocusesOnClick(slider: Element, expectedValue: number) {
373    const rectsComponent = assertDefined(component.rectsComponent);
374    slider.dispatchEvent(new MouseEvent('mousedown'));
375    expect(rectsComponent.getZSpacingFactor()).toEqual(expectedValue);
376    htmlElement.dispatchEvent(
377      new KeyboardEvent('keydown', {key: 'ArrowRight'}),
378    );
379    expect(rectsComponent.getZSpacingFactor()).toEqual(expectedValue);
380    htmlElement.dispatchEvent(new KeyboardEvent('keydown', {key: 'ArrowLeft'}));
381    expect(rectsComponent.getZSpacingFactor()).toEqual(expectedValue);
382  }
383
384  function updateSeparationSlider() {
385    const rectsComponent = assertDefined(component.rectsComponent);
386    expect(rectsComponent.getZSpacingFactor()).toEqual(1);
387    rectsComponent.onSeparationSliderChange(0.06);
388    fixture.detectChanges();
389    expect(rectsComponent.getZSpacingFactor()).toEqual(0.06);
390  }
391
392  function updateRotationSlider() {
393    const rectsComponent = assertDefined(component.rectsComponent);
394    rectsComponent.onRotationSliderChange(0.5);
395    fixture.detectChanges();
396  }
397
398  function updateShadingMode(before: ShadingMode, after: ShadingMode) {
399    const rectsComponent = assertDefined(component.rectsComponent);
400    expect(rectsComponent.getShadingMode()).toEqual(before);
401    findAndClickElement('.right-btn-container button.shading-mode');
402    expect(rectsComponent.getShadingMode()).toEqual(after);
403  }
404
405  function makeRectWithGroupId(groupId: number, isVisible = true): UiRect {
406    return new UiRectBuilder()
407      .setX(0)
408      .setY(0)
409      .setWidth(1)
410      .setHeight(1)
411      .setLabel('rectangle1')
412      .setTransform({
413        dsdx: 1,
414        dsdy: 0,
415        dtdx: 0,
416        dtdy: 1,
417        tx: 0,
418        ty: 0,
419      })
420      .setIsVisible(isVisible)
421      .setIsDisplay(false)
422      .setId('test-id-1234')
423      .setGroupId(groupId)
424      .setIsVirtual(false)
425      .setIsClickable(false)
426      .setCornerRadius(0)
427      .setDepth(0)
428      .setOpacity(0.5)
429      .build();
430  }
431
432  @Component({
433    selector: 'host-component',
434    template: `
435      <rects-view
436        title="TestRectsView"
437        [store]="store"
438        [rects]="rects"
439        [isStackBased]="isStackBased"
440        [displays]="displays"
441        [miniRects]="miniRects"
442        [shadingModes]="shadingModes"
443        [userOptions]="userOptions"
444        [dependencies]="dependencies"></rects-view>
445    `,
446  })
447  class TestHostComponent {
448    store = new PersistentStore();
449    rects: UiRect[] = [];
450    displays: DisplayIdentifier[] = [];
451    miniRects: UiRect[] = [];
452    isStackBased = false;
453    shadingModes = [
454      ShadingMode.GRADIENT,
455      ShadingMode.WIRE_FRAME,
456      ShadingMode.OPACITY,
457    ];
458    userOptions = {
459      showOnlyVisible: {
460        name: 'Show only',
461        chip: VISIBLE_CHIP,
462        enabled: false,
463      },
464    };
465    dependencies = [TraceType.SURFACE_FLINGER];
466
467    @ViewChild(RectsComponent)
468    rectsComponent: RectsComponent | undefined;
469  }
470});
471