@testgorilla/tgo-ai-interview-test 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +46 -0
- package/README.md +91 -0
- package/jest.config.ts +29 -0
- package/ng-package.json +16 -0
- package/package.json +25 -0
- package/project.json +37 -0
- package/src/assets/i18n/en.json +19 -0
- package/src/index.ts +3 -0
- package/src/lib/components/ai-interview-test/ai-interview-test.component.html +42 -0
- package/src/lib/components/ai-interview-test/ai-interview-test.component.scss +167 -0
- package/src/lib/components/ai-interview-test/ai-interview-test.component.spec.ts +211 -0
- package/src/lib/components/ai-interview-test/ai-interview-test.component.ts +193 -0
- package/src/lib/components/index.ts +3 -0
- package/src/lib/components/interview-stream/interview-stream.component.html +9 -0
- package/src/lib/components/interview-stream/interview-stream.component.scss +5 -0
- package/src/lib/components/interview-stream/interview-stream.component.spec.ts +285 -0
- package/src/lib/components/interview-stream/interview-stream.component.ts +321 -0
- package/src/lib/components/interview-video/interview-video.component.html +8 -0
- package/src/lib/components/interview-video/interview-video.component.scss +7 -0
- package/src/lib/components/interview-video/interview-video.component.spec.ts +140 -0
- package/src/lib/components/interview-video/interview-video.component.ts +68 -0
- package/src/lib/models/index.ts +13 -0
- package/src/lib/models/question-component.ts +13 -0
- package/src/lib/models/translations.ts +3 -0
- package/src/test-setup.ts +28 -0
- package/tsconfig.json +17 -0
- package/tsconfig.lib.json +15 -0
- package/tsconfig.lib.prod.json +10 -0
- package/tsconfig.spec.json +13 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { InterviewStreamComponent } from '../interview-stream/interview-stream.component';
|
|
2
|
+
import { firstValueFrom, Observable, takeUntil } from 'rxjs';
|
|
3
|
+
import { transition, style, animate, trigger } from '@angular/animations';
|
|
4
|
+
import {
|
|
5
|
+
ElementRef,
|
|
6
|
+
OnDestroy,
|
|
7
|
+
signal,
|
|
8
|
+
ViewChild,
|
|
9
|
+
Component,
|
|
10
|
+
EventEmitter,
|
|
11
|
+
Input,
|
|
12
|
+
Output,
|
|
13
|
+
inject,
|
|
14
|
+
OnInit,
|
|
15
|
+
Inject,
|
|
16
|
+
ChangeDetectorRef,
|
|
17
|
+
} from '@angular/core';
|
|
18
|
+
import { Subject } from 'rxjs';
|
|
19
|
+
import {
|
|
20
|
+
ButtonComponentModule,
|
|
21
|
+
DialogService,
|
|
22
|
+
IconComponentModule,
|
|
23
|
+
} from '@testgorilla/tgo-ui';
|
|
24
|
+
import { CommonModule } from '@angular/common';
|
|
25
|
+
import {
|
|
26
|
+
TRANSLOCO_SCOPE,
|
|
27
|
+
TranslocoModule,
|
|
28
|
+
TranslocoScope,
|
|
29
|
+
TranslocoService,
|
|
30
|
+
} from '@ngneat/transloco';
|
|
31
|
+
import {
|
|
32
|
+
AudioAnimationComponent,
|
|
33
|
+
Question,
|
|
34
|
+
TestResultRead,
|
|
35
|
+
SelectedMediaDevices,
|
|
36
|
+
ISubmissionState,
|
|
37
|
+
ROOT_TRANSLATIONS_SCOPE,
|
|
38
|
+
MediaService,
|
|
39
|
+
ThemeService,
|
|
40
|
+
TranslocoLazyModuleUtils,
|
|
41
|
+
getAvailableLangs,
|
|
42
|
+
} from '@testgorilla/tgo-test-shared';
|
|
43
|
+
import { IQuestionDataContract } from '../../models';
|
|
44
|
+
|
|
45
|
+
@Component({
|
|
46
|
+
selector: 'tgo-ai-interview-test',
|
|
47
|
+
templateUrl: './ai-interview-test.component.html',
|
|
48
|
+
styleUrl: './ai-interview-test.component.scss',
|
|
49
|
+
animations: [
|
|
50
|
+
trigger('fadeInFadeOut', [
|
|
51
|
+
transition(':enter', [
|
|
52
|
+
style({ opacity: 0 }),
|
|
53
|
+
animate('600ms', style({ opacity: 1 })),
|
|
54
|
+
]),
|
|
55
|
+
transition(':leave', [animate('600ms', style({ opacity: 0 }))]),
|
|
56
|
+
]),
|
|
57
|
+
],
|
|
58
|
+
standalone: true,
|
|
59
|
+
imports: [
|
|
60
|
+
TranslocoModule,
|
|
61
|
+
ButtonComponentModule,
|
|
62
|
+
IconComponentModule,
|
|
63
|
+
CommonModule,
|
|
64
|
+
AudioAnimationComponent,
|
|
65
|
+
InterviewStreamComponent,
|
|
66
|
+
],
|
|
67
|
+
providers: [
|
|
68
|
+
TranslocoLazyModuleUtils.getScopeProvider(
|
|
69
|
+
'tgo-ai-interview-test',
|
|
70
|
+
getAvailableLangs(),
|
|
71
|
+
ROOT_TRANSLATIONS_SCOPE,
|
|
72
|
+
(lang: string) => import(`../../../assets/i18n/${lang}.json`)
|
|
73
|
+
),
|
|
74
|
+
DialogService,
|
|
75
|
+
ThemeService,
|
|
76
|
+
],
|
|
77
|
+
})
|
|
78
|
+
export class AiInterviewTestComponent
|
|
79
|
+
implements OnInit, OnDestroy, IQuestionDataContract
|
|
80
|
+
{
|
|
81
|
+
@ViewChild('video') videoElement?: ElementRef<HTMLVideoElement>;
|
|
82
|
+
@ViewChild('audio') audioElement?: ElementRef<HTMLAudioElement>;
|
|
83
|
+
@Input({ required: true }) question!: Question;
|
|
84
|
+
@Input({ required: true }) test!: TestResultRead;
|
|
85
|
+
@Input() isFirstQuestion?: boolean = false;
|
|
86
|
+
@Input() selectedMediaDevices?: SelectedMediaDevices;
|
|
87
|
+
@Input() conversationUrl?: string;
|
|
88
|
+
@Input() mediaAccessChanged?: Observable<SelectedMediaDevices> | undefined;
|
|
89
|
+
|
|
90
|
+
@Output() submissionStateChanged =
|
|
91
|
+
new EventEmitter<ISubmissionState | null>();
|
|
92
|
+
@Output() loadingStateChanged: EventEmitter<boolean> =
|
|
93
|
+
new EventEmitter<boolean>();
|
|
94
|
+
@Output() requestMediaAccess: EventEmitter<void> = new EventEmitter<void>();
|
|
95
|
+
|
|
96
|
+
isInterviewInProgress = signal(false);
|
|
97
|
+
candidateVideoStreamReady = signal(false);
|
|
98
|
+
translations: { [key: string]: string } = {};
|
|
99
|
+
hasMediaPermissions = signal(false);
|
|
100
|
+
|
|
101
|
+
private unsubscribe$ = new Subject<void>();
|
|
102
|
+
private mediaService = inject(MediaService);
|
|
103
|
+
private translocoService = inject(TranslocoService);
|
|
104
|
+
private cdr = inject(ChangeDetectorRef);
|
|
105
|
+
private themeService = inject(ThemeService);
|
|
106
|
+
|
|
107
|
+
companyColor = this.themeService.getCompanyColor();
|
|
108
|
+
|
|
109
|
+
constructor(
|
|
110
|
+
@Inject(TRANSLOCO_SCOPE) private translationScope: TranslocoScope
|
|
111
|
+
) {}
|
|
112
|
+
|
|
113
|
+
ngOnInit(): void {
|
|
114
|
+
this.initMediaAccessSubscription();
|
|
115
|
+
this.loadingStateChanged.emit(true);
|
|
116
|
+
this.mediaService.setSelectedMediaDevices(this.selectedMediaDevices);
|
|
117
|
+
void this.checkMediaPermissions();
|
|
118
|
+
|
|
119
|
+
void this.setTranslations();
|
|
120
|
+
void this.initVideoStream();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
ngOnDestroy() {
|
|
124
|
+
this.unsubscribe$.next();
|
|
125
|
+
this.unsubscribe$.complete();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
onVideoLoad() {
|
|
129
|
+
this.candidateVideoStreamReady.set(true);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
interviewStarted() {
|
|
133
|
+
this.isInterviewInProgress.set(true);
|
|
134
|
+
this.loadingStateChanged.emit(false);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
interviewEnded() {
|
|
138
|
+
this.isInterviewInProgress.set(false);
|
|
139
|
+
this.submissionStateChanged.emit({
|
|
140
|
+
text: '',
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async checkMediaPermissions() {
|
|
145
|
+
if (
|
|
146
|
+
!(await this.mediaService.checkPermission({ audio: true, video: false }))
|
|
147
|
+
) {
|
|
148
|
+
this.hasMediaPermissions.set(false);
|
|
149
|
+
this.requestMediaAccess.emit();
|
|
150
|
+
return;
|
|
151
|
+
} else {
|
|
152
|
+
this.hasMediaPermissions.set(true);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private async initVideoStream() {
|
|
157
|
+
try {
|
|
158
|
+
const stream = await this.mediaService.getMediaStream({
|
|
159
|
+
video: true,
|
|
160
|
+
audio: false,
|
|
161
|
+
});
|
|
162
|
+
if (this.videoElement) {
|
|
163
|
+
this.videoElement.nativeElement.srcObject = stream;
|
|
164
|
+
await this.videoElement?.nativeElement.play();
|
|
165
|
+
}
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error('Error initializing video stream:', error);
|
|
168
|
+
this.candidateVideoStreamReady.set(false);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private async setTranslations() {
|
|
173
|
+
this.translations = await firstValueFrom(
|
|
174
|
+
this.translocoService.selectTranslateObject(
|
|
175
|
+
`TEST`,
|
|
176
|
+
{},
|
|
177
|
+
this.translationScope as string
|
|
178
|
+
)
|
|
179
|
+
);
|
|
180
|
+
this.cdr.markForCheck();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private initMediaAccessSubscription() {
|
|
184
|
+
this.mediaAccessChanged
|
|
185
|
+
?.pipe(takeUntil(this.unsubscribe$))
|
|
186
|
+
.subscribe((selectedMediaDevices) => {
|
|
187
|
+
this.mediaService.setSelectedMediaDevices(selectedMediaDevices);
|
|
188
|
+
this.checkMediaPermissions();
|
|
189
|
+
void this.initVideoStream();
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
|
2
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
3
|
+
import {
|
|
4
|
+
DailyEventObjectFatalError,
|
|
5
|
+
DailyEventObjectNoPayload,
|
|
6
|
+
DailyEventObjectParticipant,
|
|
7
|
+
DailyEventObjectParticipantLeft,
|
|
8
|
+
DailyEventObjectParticipants,
|
|
9
|
+
DailyEventObjectTrack,
|
|
10
|
+
DailyParticipant,
|
|
11
|
+
} from '@daily-co/daily-js';
|
|
12
|
+
import { InterviewStreamComponent } from './interview-stream.component';
|
|
13
|
+
|
|
14
|
+
class MediaStreamTrackMock {
|
|
15
|
+
kind: string;
|
|
16
|
+
enabled: boolean;
|
|
17
|
+
id: string;
|
|
18
|
+
constructor(kind = 'video') {
|
|
19
|
+
this.kind = kind;
|
|
20
|
+
this.enabled = true;
|
|
21
|
+
this.id = `${kind}-track-${Math.random().toString(36).substring(2, 15)}`;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Mock Daily.co
|
|
26
|
+
jest.mock('@daily-co/daily-js', () => {
|
|
27
|
+
const mockCallObject = {
|
|
28
|
+
on: jest.fn().mockReturnThis(),
|
|
29
|
+
off: jest.fn().mockReturnThis(),
|
|
30
|
+
join: jest.fn().mockResolvedValue(undefined),
|
|
31
|
+
leave: jest.fn(),
|
|
32
|
+
destroy: jest.fn(),
|
|
33
|
+
stopRecording: jest.fn(),
|
|
34
|
+
setInputDevicesAsync: jest.fn().mockResolvedValue(undefined),
|
|
35
|
+
startRecording: jest.fn(),
|
|
36
|
+
sendAppMessage: jest.fn(),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const mockDailyIframe = {
|
|
40
|
+
getCallInstance: jest.fn().mockReturnValue(null),
|
|
41
|
+
createCallObject: jest.fn().mockReturnValue(mockCallObject),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
__esModule: true,
|
|
46
|
+
default: mockDailyIframe,
|
|
47
|
+
DailyIframe: mockDailyIframe,
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('InterviewStreamComponent', () => {
|
|
52
|
+
let component: InterviewStreamComponent;
|
|
53
|
+
let fixture: ComponentFixture<InterviewStreamComponent>;
|
|
54
|
+
let streamStartSpy: jest.SpyInstance;
|
|
55
|
+
let streamEndSpy: jest.SpyInstance;
|
|
56
|
+
|
|
57
|
+
beforeEach(async () => {
|
|
58
|
+
// Mock MediaStream for InterviewVideoComponent
|
|
59
|
+
class MediaStreamMock {
|
|
60
|
+
tracks: MediaStreamTrack[] = [];
|
|
61
|
+
constructor(initialTracks: MediaStreamTrack[]) {
|
|
62
|
+
this.tracks = [...initialTracks];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
addTrack(track: MediaStreamTrack) {
|
|
66
|
+
this.tracks.push(track);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
removeTrack(track: MediaStreamTrack) {
|
|
70
|
+
const index = this.tracks.findIndex(t => t.id === track.id);
|
|
71
|
+
if (index !== -1) {
|
|
72
|
+
this.tracks.splice(index, 1);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
getVideoTracks() {
|
|
76
|
+
return this.tracks.filter(t => t.kind === 'video');
|
|
77
|
+
}
|
|
78
|
+
getAudioTracks() {
|
|
79
|
+
return this.tracks.filter(t => t.kind === 'audio');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
Object.defineProperty(global, 'MediaStream', {
|
|
84
|
+
writable: true,
|
|
85
|
+
value: jest.fn().mockImplementation(tracks => new MediaStreamMock(tracks)),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
await TestBed.configureTestingModule({
|
|
89
|
+
imports: [InterviewStreamComponent],
|
|
90
|
+
schemas: [NO_ERRORS_SCHEMA],
|
|
91
|
+
}).compileComponents();
|
|
92
|
+
|
|
93
|
+
fixture = TestBed.createComponent(InterviewStreamComponent);
|
|
94
|
+
component = fixture.componentInstance;
|
|
95
|
+
streamStartSpy = jest.spyOn(component.streamStart, 'emit');
|
|
96
|
+
streamEndSpy = jest.spyOn(component.streamEnd, 'emit');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
afterEach(() => {
|
|
100
|
+
fixture.destroy();
|
|
101
|
+
jest.clearAllMocks();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('when component is initialized with conversationUrl', () => {
|
|
105
|
+
beforeEach(() => {
|
|
106
|
+
component.conversationUrl = 'https://example.com/conversation';
|
|
107
|
+
component.ngOnInit();
|
|
108
|
+
fixture.detectChanges();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should initialize the call object', () => {
|
|
112
|
+
expect(component.callObject).toBeDefined();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('when candidateJoinMeeting event is triggered', () => {
|
|
116
|
+
beforeEach(() => {
|
|
117
|
+
component['candidateJoinMeeting']({} as DailyEventObjectParticipants);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should emit streamStart event', () => {
|
|
121
|
+
expect(streamStartSpy).toHaveBeenCalled();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should set candidateJoined to true', () => {
|
|
125
|
+
expect(component.candidateJoined()).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('when candidateLeaveMeeting event is triggered', () => {
|
|
130
|
+
beforeEach(() => {
|
|
131
|
+
component['handleLeftMeeting']({} as DailyEventObjectNoPayload);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should emit streamEnd event', () => {
|
|
135
|
+
expect(streamEndSpy).toHaveBeenCalled();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should set candidateJoined to false', () => {
|
|
139
|
+
expect(component.candidateJoined()).toBe(false);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('when participantJoined event is triggered', () => {
|
|
144
|
+
const testSessionId = '123';
|
|
145
|
+
const testUserName = 'Test User';
|
|
146
|
+
beforeEach(() => {
|
|
147
|
+
component['participantJoined']({
|
|
148
|
+
participant: {
|
|
149
|
+
user_name: testUserName,
|
|
150
|
+
session_id: testSessionId,
|
|
151
|
+
tracks: {},
|
|
152
|
+
} as DailyParticipant,
|
|
153
|
+
} as DailyEventObjectParticipant);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should set avatarParticipant with the participant data', () => {
|
|
157
|
+
expect(component.avatarParticipant?.userName).toEqual(testUserName);
|
|
158
|
+
expect(component.avatarParticipant?.sessionId).toEqual(testSessionId);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('when video ready track-started event is triggered', () => {
|
|
162
|
+
beforeEach(() => {
|
|
163
|
+
const videoTrack = new MediaStreamTrackMock('video') as MediaStreamTrack;
|
|
164
|
+
component.candidateJoined.set(true);
|
|
165
|
+
const event = {
|
|
166
|
+
action: 'track-started',
|
|
167
|
+
participant: {
|
|
168
|
+
user_name: testUserName,
|
|
169
|
+
session_id: testSessionId,
|
|
170
|
+
tracks: {
|
|
171
|
+
video: {
|
|
172
|
+
state: 'playable',
|
|
173
|
+
persistentTrack: videoTrack,
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
} as unknown as DailyParticipant,
|
|
177
|
+
type: 'video',
|
|
178
|
+
} as DailyEventObjectTrack;
|
|
179
|
+
component['handleTrackStartedStopped'](event);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should set videoReady to true for the participant', () => {
|
|
183
|
+
expect(component.avatarParticipant?.videoReady).toBe(true);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should set videoTrack for the participant', () => {
|
|
187
|
+
expect(component.avatarParticipant?.videoTrack).toBeDefined();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should not set audioReady for the participant', () => {
|
|
191
|
+
expect(component.avatarParticipant?.audioReady).toBe(false);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should not set audioTrack for the participant', () => {
|
|
195
|
+
expect(component.avatarParticipant?.audioTrack).toBeUndefined();
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe('when audio ready track-started event is triggered', () => {
|
|
200
|
+
const testSessionId = '123';
|
|
201
|
+
const testUserName = 'Test User';
|
|
202
|
+
beforeEach(() => {
|
|
203
|
+
const audioTrack = new MediaStreamTrackMock('audio') as MediaStreamTrack;
|
|
204
|
+
component.candidateJoined.set(true);
|
|
205
|
+
const event = {
|
|
206
|
+
action: 'track-started',
|
|
207
|
+
participant: {
|
|
208
|
+
user_name: testUserName,
|
|
209
|
+
session_id: testSessionId,
|
|
210
|
+
tracks: {
|
|
211
|
+
audio: {
|
|
212
|
+
state: 'playable',
|
|
213
|
+
persistentTrack: audioTrack,
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
} as unknown as DailyParticipant,
|
|
217
|
+
type: 'audio',
|
|
218
|
+
} as DailyEventObjectTrack;
|
|
219
|
+
component['handleTrackStartedStopped'](event);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('should set audioReady to true for the participant', () => {
|
|
223
|
+
expect(component.avatarParticipant?.audioReady).toBe(true);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should set audioTrack for the participant', () => {
|
|
227
|
+
expect(component.avatarParticipant?.audioTrack).toBeDefined();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should not set videoReady for the participant', () => {
|
|
231
|
+
expect(component.avatarParticipant?.videoReady).toBe(false);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should not set videoTrack for the participant', () => {
|
|
235
|
+
expect(component.avatarParticipant?.videoTrack).toBeUndefined();
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe('when participantLeft event is triggered', () => {
|
|
241
|
+
let callObjectLeaveSpy: jest.SpyInstance;
|
|
242
|
+
beforeEach(() => {
|
|
243
|
+
const testSessionId = '123';
|
|
244
|
+
const testUserName = 'Test User';
|
|
245
|
+
if (component.callObject) {
|
|
246
|
+
callObjectLeaveSpy = jest.spyOn(component.callObject, 'leave');
|
|
247
|
+
}
|
|
248
|
+
component['handleParticipantLeft']({
|
|
249
|
+
participant: {
|
|
250
|
+
user_name: testUserName,
|
|
251
|
+
session_id: testSessionId,
|
|
252
|
+
tracks: {},
|
|
253
|
+
} as DailyParticipant,
|
|
254
|
+
} as DailyEventObjectParticipantLeft);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('should call callObject.leave()', () => {
|
|
258
|
+
expect(callObjectLeaveSpy).toHaveBeenCalled();
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
describe('when handleError event is triggered', () => {
|
|
263
|
+
let callObjectLeaveSpy: jest.SpyInstance;
|
|
264
|
+
let consoleErrorSpy: jest.SpyInstance;
|
|
265
|
+
|
|
266
|
+
beforeEach(() => {
|
|
267
|
+
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
268
|
+
if (component.callObject) {
|
|
269
|
+
callObjectLeaveSpy = jest.spyOn(component.callObject, 'leave');
|
|
270
|
+
}
|
|
271
|
+
component['handleError']({
|
|
272
|
+
errorMsg: 'Test',
|
|
273
|
+
} as DailyEventObjectFatalError);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
afterEach(() => {
|
|
277
|
+
consoleErrorSpy.mockRestore();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('should call callObject.leave()', () => {
|
|
281
|
+
expect(callObjectLeaveSpy).toHaveBeenCalled();
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
});
|