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 {ComponentFixture, TestBed} from '@angular/core/testing'; 17import {MatCardModule} from '@angular/material/card'; 18import {MatIconModule} from '@angular/material/icon'; 19import {MatListModule} from '@angular/material/list'; 20import {MatProgressBarModule} from '@angular/material/progress-bar'; 21import {MatSnackBar, MatSnackBarModule} from '@angular/material/snack-bar'; 22import {MatTooltipModule} from '@angular/material/tooltip'; 23import {FilesSource} from 'app/files_source'; 24import {TracePipeline} from 'app/trace_pipeline'; 25import {assertDefined} from 'common/assert_utils'; 26import {UserNotificationsListenerStub} from 'messaging/user_notifications_listener_stub'; 27import {UnitTestUtils} from 'test/unit/utils'; 28import {LoadProgressComponent} from './load_progress_component'; 29import {UploadTracesComponent} from './upload_traces_component'; 30 31describe('UploadTracesComponent', () => { 32 let fixture: ComponentFixture<UploadTracesComponent>; 33 let component: UploadTracesComponent; 34 let htmlElement: HTMLElement; 35 let validSfFile: File; 36 let validWmFile: File; 37 38 beforeEach(async () => { 39 await TestBed.configureTestingModule({ 40 imports: [ 41 MatCardModule, 42 MatSnackBarModule, 43 MatListModule, 44 MatIconModule, 45 MatProgressBarModule, 46 MatTooltipModule, 47 ], 48 providers: [MatSnackBar], 49 declarations: [UploadTracesComponent, LoadProgressComponent], 50 }).compileComponents(); 51 fixture = TestBed.createComponent(UploadTracesComponent); 52 component = fixture.componentInstance; 53 htmlElement = fixture.nativeElement; 54 component.tracePipeline = new TracePipeline(); 55 validSfFile = await UnitTestUtils.getFixtureFile( 56 'traces/elapsed_and_real_timestamp/SurfaceFlinger.pb', 57 ); 58 validWmFile = await UnitTestUtils.getFixtureFile( 59 'traces/elapsed_and_real_timestamp/WindowManager.pb', 60 ); 61 fixture.detectChanges(); 62 }); 63 64 it('can be created', () => { 65 expect(component).toBeTruthy(); 66 }); 67 68 it('renders the expected card title', () => { 69 expect(htmlElement.querySelector('.title')?.innerHTML).toContain( 70 'Upload Traces', 71 ); 72 }); 73 74 it('handles file upload via drag and drop', () => { 75 const spy = spyOn(component.filesUploaded, 'emit'); 76 const dropbox = assertDefined(htmlElement.querySelector('.drop-box')); 77 78 const dataTransfer = new DataTransfer(); 79 dataTransfer.items.add(validSfFile); 80 dropbox?.dispatchEvent(new DragEvent('drop', {dataTransfer})); 81 fixture.detectChanges(); 82 expect(spy).toHaveBeenCalledWith(Array.from(dataTransfer.files)); 83 }); 84 85 it('displays load progress bar', () => { 86 component.isLoadingFiles = true; 87 fixture.detectChanges(); 88 assertDefined(htmlElement.querySelector('load-progress')); 89 }); 90 91 it('can display uploaded traces', async () => { 92 await loadFiles([validSfFile]); 93 fixture.detectChanges(); 94 assertDefined(htmlElement.querySelector('.uploaded-files')); 95 assertDefined(htmlElement.querySelector('.trace-actions-container')); 96 }); 97 98 it('can remove one of two uploaded traces', async () => { 99 await loadFiles([validSfFile, validWmFile]); 100 fixture.detectChanges(); 101 expect(component.tracePipeline.getTraces().getSize()).toBe(2); 102 103 const spy = spyOn(component, 'onOperationFinished'); 104 const removeButton = assertDefined( 105 htmlElement.querySelector('.uploaded-files button'), 106 ); 107 (removeButton as HTMLButtonElement).click(); 108 fixture.detectChanges(); 109 assertDefined(htmlElement.querySelector('.uploaded-files')); 110 expect(spy).toHaveBeenCalled(); 111 expect(component.tracePipeline.getTraces().getSize()).toBe(1); 112 }); 113 114 it('handles removal of the only uploaded trace', async () => { 115 await loadFiles([validSfFile]); 116 fixture.detectChanges(); 117 118 const spy = spyOn(component, 'onOperationFinished'); 119 const removeButton = assertDefined( 120 htmlElement.querySelector('.uploaded-files button'), 121 ); 122 (removeButton as HTMLButtonElement).click(); 123 fixture.detectChanges(); 124 assertDefined(htmlElement.querySelector('.drop-info')); 125 expect(spy).toHaveBeenCalled(); 126 expect(component.tracePipeline.getTraces().getSize()).toBe(0); 127 }); 128 129 it('can remove all uploaded traces', async () => { 130 await loadFiles([validSfFile, validWmFile]); 131 fixture.detectChanges(); 132 expect(component.tracePipeline.getTraces().getSize()).toBe(2); 133 134 const spy = spyOn(component, 'onOperationFinished'); 135 const clearAllButton = assertDefined( 136 htmlElement.querySelector('.clear-all-btn'), 137 ); 138 (clearAllButton as HTMLButtonElement).click(); 139 fixture.detectChanges(); 140 assertDefined(htmlElement.querySelector('.drop-info')); 141 expect(spy).toHaveBeenCalled(); 142 expect(component.tracePipeline.getTraces().getSize()).toBe(0); 143 }); 144 145 it('can triggers view traces event', async () => { 146 await loadFiles([validSfFile]); 147 fixture.detectChanges(); 148 149 const spy = spyOn(component.viewTracesButtonClick, 'emit'); 150 const viewTracesButton = assertDefined( 151 htmlElement.querySelector('.load-btn'), 152 ); 153 (viewTracesButton as HTMLButtonElement).click(); 154 fixture.detectChanges(); 155 expect(spy).toHaveBeenCalled(); 156 }); 157 158 it('disables view traces button unless files with viewers uploaded', async () => { 159 const validEventlogFile = await UnitTestUtils.getFixtureFile( 160 'traces/eventlog.winscope', 161 ); 162 await loadFiles([validEventlogFile]); 163 fixture.detectChanges(); 164 165 const viewTracesButton = assertDefined( 166 htmlElement.querySelector('.load-btn'), 167 ); 168 expect((viewTracesButton as HTMLButtonElement).disabled).toBeTrue(); 169 170 await loadFiles([validSfFile]); 171 fixture.detectChanges(); 172 expect((viewTracesButton as HTMLButtonElement).disabled).toBeFalse(); 173 }); 174 175 it('shows warning elements for traces without visualization', async () => { 176 const shellTransitionFile = await UnitTestUtils.getFixtureFile( 177 'traces/elapsed_and_real_timestamp/shell_transition_trace.pb', 178 ); 179 await loadFiles([shellTransitionFile]); 180 fixture.detectChanges(); 181 182 expect(htmlElement.querySelector('.warning-icon')).toBeTruthy(); 183 const viewTracesButton = assertDefined( 184 htmlElement.querySelector('.load-btn'), 185 ); 186 expect((viewTracesButton as HTMLButtonElement).disabled).toBeTrue(); 187 }); 188 189 it('shows info elements for traces with upload info for the user', async () => { 190 const shellTransitionFile = await UnitTestUtils.getFixtureFile( 191 'traces/eventlog.winscope', 192 ); 193 await loadFiles([shellTransitionFile]); 194 fixture.detectChanges(); 195 196 expect(htmlElement.querySelector('.info-icon')).toBeTruthy(); 197 }); 198 199 async function loadFiles(files: File[]) { 200 component.tracePipeline.clear(); 201 await component.tracePipeline.loadFiles( 202 files, 203 FilesSource.TEST, 204 new UserNotificationsListenerStub(), 205 undefined, 206 ); 207 } 208}); 209