@testgorilla/tgo-ai-interview-test 2.0.0 → 2.0.3

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.
Files changed (36) hide show
  1. package/{src/assets → assets}/i18n/en.json +4 -1
  2. package/fesm2022/testgorilla-tgo-ai-interview-test.mjs +503 -0
  3. package/fesm2022/testgorilla-tgo-ai-interview-test.mjs.map +1 -0
  4. package/lib/components/ai-interview-test/ai-interview-test.component.d.ts +45 -0
  5. package/lib/components/interview-stream/interview-stream.component.d.ts +49 -0
  6. package/lib/components/interview-video/interview-video.component.d.ts +16 -0
  7. package/lib/models/index.d.ts +3 -0
  8. package/lib/models/question-component.d.ts +5 -0
  9. package/lib/models/translations.d.ts +1 -0
  10. package/package.json +29 -12
  11. package/.eslintrc.json +0 -46
  12. package/jest.config.ts +0 -29
  13. package/ng-package.json +0 -16
  14. package/project.json +0 -48
  15. package/src/lib/components/ai-interview-test/ai-interview-test.component.html +0 -42
  16. package/src/lib/components/ai-interview-test/ai-interview-test.component.scss +0 -167
  17. package/src/lib/components/ai-interview-test/ai-interview-test.component.spec.ts +0 -212
  18. package/src/lib/components/ai-interview-test/ai-interview-test.component.ts +0 -192
  19. package/src/lib/components/interview-stream/interview-stream.component.html +0 -9
  20. package/src/lib/components/interview-stream/interview-stream.component.scss +0 -5
  21. package/src/lib/components/interview-stream/interview-stream.component.spec.ts +0 -259
  22. package/src/lib/components/interview-stream/interview-stream.component.ts +0 -320
  23. package/src/lib/components/interview-video/interview-video.component.html +0 -8
  24. package/src/lib/components/interview-video/interview-video.component.scss +0 -7
  25. package/src/lib/components/interview-video/interview-video.component.spec.ts +0 -140
  26. package/src/lib/components/interview-video/interview-video.component.ts +0 -67
  27. package/src/lib/models/index.ts +0 -13
  28. package/src/lib/models/question-component.ts +0 -13
  29. package/src/lib/models/translations.ts +0 -3
  30. package/src/test-setup.ts +0 -76
  31. package/tsconfig.json +0 -20
  32. package/tsconfig.lib.json +0 -20
  33. package/tsconfig.lib.prod.json +0 -11
  34. package/tsconfig.spec.json +0 -17
  35. /package/{src/index.ts → index.d.ts} +0 -0
  36. /package/{src/lib/components/index.ts → lib/components/index.d.ts} +0 -0
@@ -1,320 +0,0 @@
1
- import {
2
- ChangeDetectorRef,
3
- Component,
4
- EventEmitter,
5
- inject,
6
- Input,
7
- OnDestroy,
8
- OnInit,
9
- Output,
10
- signal,
11
- } from '@angular/core';
12
- import { CommonModule } from '@angular/common';
13
- import DailyIframe, {
14
- DailyCall,
15
- DailyEventObjectParticipant,
16
- DailyParticipant,
17
- DailyEventObjectFatalError,
18
- DailyEventObjectParticipants,
19
- DailyEventObjectNoPayload,
20
- DailyEventObjectParticipantLeft,
21
- DailyEventObjectTrack,
22
- DailyEventObjectAppMessage,
23
- } from '@daily-co/daily-js';
24
- import { SelectedMediaDevices } from '@testgorilla/tgo-test-shared';
25
- import { InterviewVideoComponent } from '../interview-video/interview-video.component';
26
-
27
- export type Participant = {
28
- videoTrack?: MediaStreamTrack | undefined;
29
- audioTrack?: MediaStreamTrack | undefined;
30
- videoReady: boolean;
31
- audioReady: boolean;
32
- userName: string;
33
- local: boolean;
34
- sessionId: string;
35
- };
36
-
37
- const PLAYABLE_STATE = 'playable';
38
- const LOADING_STATE = 'loading';
39
-
40
- interface EventData {
41
- event_type: string;
42
- properties: {
43
- name: string;
44
- arguments: string;
45
- };
46
- }
47
-
48
- @Component({
49
- selector: 'tgo-interview-stream',
50
- templateUrl: './interview-stream.component.html',
51
- styleUrls: ['./interview-stream.component.scss'],
52
- imports: [CommonModule, InterviewVideoComponent]
53
- })
54
- export class InterviewStreamComponent implements OnInit, OnDestroy {
55
- @Input() conversationUrl: string | undefined;
56
- @Input() selectedMediaDevices: SelectedMediaDevices | undefined;
57
- @Input() translations: { [key: string]: string } = {};
58
- @Output() streamStart: EventEmitter<null> = new EventEmitter();
59
- @Output() streamEnd: EventEmitter<null> = new EventEmitter();
60
- @Output() checkMediaPermissions: EventEmitter<null> = new EventEmitter();
61
-
62
- callObject: DailyCall | undefined;
63
- avatarParticipant: Participant | undefined;
64
- candidateJoined = signal(false);
65
- private cdr = inject(ChangeDetectorRef);
66
-
67
- ngOnInit(): void {
68
- void this.setupCall();
69
- }
70
-
71
- ngOnDestroy(): void {
72
- if (!this.callObject) return;
73
- this.leaveCall();
74
- this.callObject
75
- .off('joined-meeting', this.candidateJoinMeeting)
76
- .off('participant-joined', this.participantJoined)
77
- .off('app-message', this.handleNewMessage)
78
- .off('track-started', this.handleTrackStartedStopped)
79
- .off('track-stopped', this.handleTrackStartedStopped)
80
- .off('participant-left', this.handleParticipantLeft)
81
- .off('left-meeting', this.handleLeftMeeting)
82
- .off('error', this.handleError);
83
- }
84
-
85
- private async setupCall() {
86
- this.callObject = DailyIframe.getCallInstance();
87
- if (!this.callObject) {
88
- this.callObject = DailyIframe.createCallObject();
89
- }
90
- if (this.selectedMediaDevices) {
91
- await this.callObject.setInputDevicesAsync({
92
- videoDeviceId: this.selectedMediaDevices.videoDeviceId,
93
- audioDeviceId: this.selectedMediaDevices.audioDeviceId,
94
- });
95
- }
96
- this.callObject.startRecording({
97
- type: 'cloud',
98
- });
99
-
100
- this.callObject
101
- .on('joined-meeting', this.candidateJoinMeeting)
102
- .on('participant-joined', this.participantJoined)
103
- .on('app-message', this.handleNewMessage)
104
- .on('track-started', this.handleTrackStartedStopped)
105
- .on('track-stopped', this.handleTrackStartedStopped)
106
- .on('participant-left', this.handleParticipantLeft)
107
- .on('left-meeting', this.handleLeftMeeting)
108
- .on('error', this.handleError);
109
-
110
- await this.callObject.join({
111
- userName: 'Candidate',
112
- url: this.conversationUrl,
113
- });
114
- }
115
-
116
- formatParticipantObj(participant: DailyParticipant): Participant {
117
- const { video, audio } = participant.tracks;
118
-
119
- const videoTrack = video?.persistentTrack;
120
- const audioTrack = audio?.persistentTrack;
121
- return {
122
- videoTrack: videoTrack,
123
- audioTrack: audioTrack,
124
- videoReady: !!(
125
- videoTrack &&
126
- (video.state === PLAYABLE_STATE || video.state === LOADING_STATE)
127
- ),
128
- audioReady: !!(
129
- audioTrack &&
130
- (audio.state === PLAYABLE_STATE || audio.state === LOADING_STATE)
131
- ),
132
- userName: participant.user_name,
133
- local: participant.local,
134
- sessionId: participant.session_id,
135
- };
136
- }
137
-
138
- updateTrack(participant: DailyParticipant, newTrackType: string): void {
139
- if (
140
- !this.avatarParticipant ||
141
- this.avatarParticipant.sessionId !== participant.session_id
142
- ) {
143
- return;
144
- }
145
- const existingParticipant = this.avatarParticipant as Participant;
146
- const currentParticipantCopy = this.formatParticipantObj(participant);
147
-
148
- if (newTrackType === 'video') {
149
- if (
150
- existingParticipant.videoReady !== currentParticipantCopy.videoReady
151
- ) {
152
- existingParticipant.videoReady = currentParticipantCopy.videoReady;
153
- }
154
-
155
- if (
156
- currentParticipantCopy.videoReady &&
157
- existingParticipant.videoTrack?.id !==
158
- currentParticipantCopy.videoTrack?.id
159
- ) {
160
- existingParticipant.videoTrack = currentParticipantCopy.videoTrack;
161
- }
162
- return;
163
- }
164
-
165
- if (newTrackType === 'audio') {
166
- if (
167
- existingParticipant.audioReady !== currentParticipantCopy.audioReady
168
- ) {
169
- existingParticipant.audioReady = currentParticipantCopy.audioReady;
170
- }
171
-
172
- if (
173
- currentParticipantCopy.audioReady &&
174
- existingParticipant.audioTrack?.id !==
175
- currentParticipantCopy.audioTrack?.id
176
- ) {
177
- existingParticipant.audioTrack = currentParticipantCopy.audioTrack;
178
- }
179
- }
180
- }
181
-
182
- private candidateJoinMeeting = (
183
- event: DailyEventObjectParticipants | undefined
184
- ): void => {
185
- if (!event || !this.callObject) return;
186
- this.candidateJoined.set(true);
187
- this.streamStart.emit();
188
- };
189
-
190
- private participantJoined = (
191
- event: DailyEventObjectParticipant | undefined
192
- ) => {
193
- if (!event) return;
194
- this.avatarParticipant = this.formatParticipantObj(event.participant);
195
- };
196
-
197
- private handleTrackStartedStopped = (
198
- event: DailyEventObjectTrack | undefined
199
- ): void => {
200
- if (!event || !event.participant || !this.candidateJoined()) return;
201
- if (event.action === 'track-stopped') {
202
- this.checkMediaPermissions.emit();
203
- }
204
- this.updateTrack(event.participant, event.type);
205
- this.cdr.detectChanges();
206
- };
207
-
208
- private handleParticipantLeft = (
209
- event: DailyEventObjectParticipantLeft | undefined
210
- ): void => {
211
- if (!event) return;
212
- this.leaveCall();
213
- };
214
-
215
- private handleError = (
216
- event: DailyEventObjectFatalError | undefined
217
- ): void => {
218
- if (!event) return;
219
- console.error('Interview stream error', event);
220
- this.leaveCall();
221
- };
222
-
223
- private handleLeftMeeting = (
224
- event: DailyEventObjectNoPayload | undefined
225
- ): void => {
226
- this.callObject?.stopRecording();
227
- if (!event || !this.callObject) return;
228
- this.candidateJoined.set(false);
229
- this.callObject.destroy();
230
- this.streamEnd.emit();
231
- };
232
-
233
- private leaveCall(): void {
234
- if (!this.callObject) return;
235
- this.callObject.leave();
236
- }
237
-
238
- private handleNewMessage = (
239
- event: DailyEventObjectAppMessage<EventData> | undefined
240
- ): void => {
241
- if (!event) return;
242
- if (event.data.event_type === 'conversation.tool_call') {
243
- this.handleToolCall(event.data.properties);
244
- }
245
- };
246
-
247
- private getConversationId(): string | undefined {
248
- return this.conversationUrl?.replace('https://tavus.daily.co/', '');
249
- }
250
-
251
- /*
252
- This is a test implementation of tool calling.
253
- These events will only be triggered if configured with a Tavus persona. The message content will be further refined by the IP Team.
254
- https://docs.tavus.io/sections/event-schemas/conversation-toolcall
255
- */
256
- private handleToolCall = (properties: {
257
- name: string;
258
- arguments: string;
259
- }): void => {
260
- switch (properties.name) {
261
- case 'next_question':
262
- try {
263
- const args = JSON.parse(properties.arguments);
264
- if (args?.questionsLeft > 0) {
265
- this.sendMessage(this.getToolCallTranslation('NEXT_QUESTION'));
266
- window.setTimeout(() => {
267
- this.sendMessage('Read next question', 'respond');
268
- }, 1000);
269
- } else {
270
- throw new Error('No more questions left');
271
- }
272
- } catch (err) {
273
- console.error(
274
- 'Failed to parse arguments for next_question tool call',
275
- err
276
- );
277
-
278
- this.sendMessage(this.getToolCallTranslation('ALL_FOR_TODAY'));
279
- window.setTimeout(() => {
280
- this.leaveCall();
281
- }, 5000);
282
- }
283
- break;
284
- case 'end_conversation':
285
- window.setTimeout(() => {
286
- this.leaveCall();
287
- }, 5000);
288
- break;
289
- default:
290
- console.warn('Unknown tool call code:', properties);
291
- break;
292
- }
293
- };
294
-
295
- /*
296
- Echo message is a message that avatar reads and candidate can hear
297
- Respond message is a message that avatar reads and candidate cannot hear
298
- */
299
-
300
- private sendMessage(text: string, type: 'echo' | 'respond' = 'echo'): void {
301
- if (!this.callObject) return;
302
-
303
- this.callObject.sendAppMessage({
304
- message_type: 'conversation',
305
- event_type: 'conversation.' + type,
306
- conversation_id: this.getConversationId(),
307
- properties: {
308
- text,
309
- },
310
- });
311
- }
312
-
313
- private getToolCallTranslation(key: string) {
314
- const toolCallTranslations = this.translations['TOOL_CALL'] as unknown as {
315
- [key: string]: string;
316
- };
317
- return toolCallTranslations[key];
318
- }
319
- }
320
-
@@ -1,8 +0,0 @@
1
- @if (videoStream) {
2
- <video autoPlay muted playsInline [srcObject]="videoStream"></video>
3
- } @if (audioStream) {
4
- <audio autoPlay playsInline [srcObject]="audioStream">
5
- <track kind="captions" />
6
- </audio>
7
- }
8
-
@@ -1,7 +0,0 @@
1
- :host {
2
- video {
3
- width: 100%;
4
- height: 100%;
5
- }
6
- }
7
-
@@ -1,140 +0,0 @@
1
- import { NO_ERRORS_SCHEMA } from '@angular/core';
2
- import { ComponentFixture, TestBed } from '@angular/core/testing';
3
- import { InterviewVideoComponent } from './interview-video.component';
4
-
5
- class MediaStreamTrackMock {
6
- kind: string;
7
- enabled: boolean;
8
- id: string;
9
-
10
- constructor(kind = 'video') {
11
- this.kind = kind;
12
- this.enabled = true;
13
- this.id = `${kind}-track-${Math.random().toString(36).substring(2, 15)}`;
14
- }
15
- }
16
-
17
- describe('InterviewVideoComponent', () => {
18
- let component: InterviewVideoComponent;
19
- let fixture: ComponentFixture<InterviewVideoComponent>;
20
- beforeEach(async () => {
21
- class MediaStreamMock {
22
- tracks: MediaStreamTrack[] = [];
23
- constructor(initialTracks: MediaStreamTrack[]) {
24
- this.tracks = [...initialTracks];
25
- }
26
-
27
- addTrack(track: MediaStreamTrack) {
28
- this.tracks.push(track);
29
- }
30
-
31
- removeTrack(track: MediaStreamTrack) {
32
- const index = this.tracks.findIndex((t) => t.id === track.id);
33
- if (index !== -1) {
34
- this.tracks.splice(index, 1);
35
- }
36
- }
37
- getVideoTracks() {
38
- return this.tracks.filter((t) => t.kind === 'video');
39
- }
40
- getAudioTracks() {
41
- return this.tracks.filter((t) => t.kind === 'audio');
42
- }
43
- }
44
-
45
- Object.defineProperty(global, 'MediaStream', {
46
- writable: true,
47
- value: jest
48
- .fn()
49
- .mockImplementation((tracks) => new MediaStreamMock(tracks)),
50
- });
51
-
52
- await TestBed.configureTestingModule({
53
- imports: [InterviewVideoComponent],
54
- schemas: [NO_ERRORS_SCHEMA],
55
- }).compileComponents();
56
-
57
- fixture = TestBed.createComponent(InterviewVideoComponent);
58
- component = fixture.componentInstance;
59
- });
60
-
61
- afterEach(() => {
62
- fixture.destroy();
63
- jest.clearAllMocks();
64
- });
65
-
66
- describe('when component is initialized with videoTrack and audioTrack', () => {
67
- const testVideoTrack = new MediaStreamTrackMock(
68
- 'video'
69
- ) as MediaStreamTrack;
70
- const testAudioTrack = new MediaStreamTrackMock(
71
- 'audio'
72
- ) as MediaStreamTrack;
73
- beforeEach(() => {
74
- component.videoTrack = testVideoTrack;
75
- component.audioTrack = testAudioTrack;
76
- component.ngOnInit();
77
- fixture.detectChanges();
78
- });
79
-
80
- it('should initialize videoStream and audioStream', () => {
81
- expect(component.videoStream).toBeDefined();
82
- expect(component.audioStream).toBeDefined();
83
- });
84
-
85
- it('should add video track to videoStream', () => {
86
- expect(component.videoStream?.getVideoTracks().length).toBe(1);
87
- expect(component.videoStream?.getVideoTracks()[0].id).toBe(
88
- testVideoTrack.id
89
- );
90
- });
91
-
92
- it('should add audio track to audioStream', () => {
93
- expect(component.audioStream?.getAudioTracks().length).toBe(1);
94
- expect(component.audioStream?.getAudioTracks()[0].id).toBe(
95
- testAudioTrack.id
96
- );
97
- });
98
-
99
- describe('when track changes', () => {
100
- const changedVideoTrack = new MediaStreamTrackMock(
101
- 'video'
102
- ) as MediaStreamTrack;
103
- const changedAudioTrack = new MediaStreamTrackMock(
104
- 'audio'
105
- ) as MediaStreamTrack;
106
- beforeEach(() => {
107
- component.ngOnChanges({
108
- videoTrack: {
109
- currentValue: changedVideoTrack,
110
- previousValue: testVideoTrack,
111
- isFirstChange: () => false,
112
- firstChange: false,
113
- },
114
- audioTrack: {
115
- currentValue: changedAudioTrack,
116
- previousValue: testAudioTrack,
117
- isFirstChange: () => false,
118
- firstChange: false,
119
- },
120
- });
121
- fixture.detectChanges();
122
- });
123
-
124
- it('should update videoStream with new video track', () => {
125
- expect(component.videoStream?.getVideoTracks().length).toBe(1);
126
- expect(component.videoStream?.getVideoTracks()[0].id).toBe(
127
- changedVideoTrack.id
128
- );
129
- });
130
-
131
- it('should update audioStream with new audio track', () => {
132
- expect(component.audioStream?.getAudioTracks().length).toBe(1);
133
- expect(component.audioStream?.getAudioTracks()[0].id).toBe(
134
- changedAudioTrack.id
135
- );
136
- });
137
- });
138
- });
139
- });
140
-
@@ -1,67 +0,0 @@
1
- import { Component, Input, OnInit, SimpleChanges, OnChanges } from '@angular/core';
2
- import { CommonModule } from '@angular/common';
3
-
4
- @Component({
5
- selector: 'tgo-interview-video',
6
- templateUrl: './interview-video.component.html',
7
- styleUrls: ['./interview-video.component.scss'],
8
- imports: [CommonModule]
9
- })
10
- export class InterviewVideoComponent implements OnInit, OnChanges {
11
- @Input() videoTrack: MediaStreamTrack | undefined;
12
- @Input() audioTrack: MediaStreamTrack | undefined;
13
- videoStream: MediaStream | undefined;
14
- audioStream: MediaStream | undefined;
15
-
16
- ngOnInit(): void {
17
- if (this.videoTrack) {
18
- this.addVideoStream(this.videoTrack);
19
- }
20
- if (this.audioTrack) {
21
- this.addAudioStream(this.audioTrack);
22
- }
23
- }
24
-
25
- ngOnChanges(changes: SimpleChanges): void {
26
- const { videoTrack, audioTrack } = changes;
27
-
28
- if (videoTrack?.currentValue && !this.videoStream) {
29
- this.addVideoStream(videoTrack.currentValue);
30
- }
31
-
32
- if (audioTrack?.currentValue && !this.audioStream) {
33
- this.addAudioStream(audioTrack.currentValue);
34
- }
35
-
36
- if (videoTrack?.currentValue && this.videoStream) {
37
- this.updateVideoTrack(videoTrack.previousValue, videoTrack.currentValue);
38
- }
39
-
40
- if (audioTrack?.currentValue && this.audioStream) {
41
- this.updateAudioTrack(audioTrack.previousValue, audioTrack.currentValue);
42
- }
43
- }
44
-
45
- addVideoStream(track: MediaStreamTrack) {
46
- this.videoStream = new MediaStream([track]);
47
- }
48
-
49
- addAudioStream(track: MediaStreamTrack) {
50
- this.audioStream = new MediaStream([track]);
51
- }
52
-
53
- updateVideoTrack(oldTrack: MediaStreamTrack, track: MediaStreamTrack) {
54
- if (oldTrack) {
55
- this.videoStream?.removeTrack(oldTrack);
56
- }
57
- this.videoStream?.addTrack(track);
58
- }
59
-
60
- updateAudioTrack(oldTrack: MediaStreamTrack, track: MediaStreamTrack) {
61
- if (oldTrack) {
62
- this.audioStream?.removeTrack(oldTrack);
63
- }
64
- this.audioStream?.addTrack(track);
65
- }
66
- }
67
-
@@ -1,13 +0,0 @@
1
- // Re-export shared models
2
- export {
3
- Question,
4
- TestResultRead,
5
- SelectedMediaDevices,
6
- ISubmissionState,
7
- ROOT_TRANSLATIONS_SCOPE,
8
- } from '@testgorilla/tgo-test-shared';
9
-
10
- // Export library-specific extensions
11
- export * from './question-component';
12
- export * from './translations';
13
-
@@ -1,13 +0,0 @@
1
- import { IQuestionDataContract as BaseIQuestionDataContract } from '@testgorilla/tgo-test-shared';
2
-
3
- // Extend the base interface to add conversationUrl for AI Interview
4
- export interface IQuestionDataContract extends BaseIQuestionDataContract {
5
- conversationUrl?: string;
6
- }
7
-
8
- // Re-export shared interfaces for convenience
9
- export {
10
- SelectedMediaDevices,
11
- ISubmissionState,
12
- } from '@testgorilla/tgo-test-shared';
13
-
@@ -1,3 +0,0 @@
1
- // Re-export from shared
2
- export { ROOT_TRANSLATIONS_SCOPE } from '@testgorilla/tgo-test-shared';
3
-
package/src/test-setup.ts DELETED
@@ -1,76 +0,0 @@
1
- import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
2
-
3
- setupZoneTestEnv();
4
-
5
- // Mock HTMLMediaElement.play() for jsdom
6
- Object.defineProperty(HTMLMediaElement.prototype, 'play', {
7
- writable: true,
8
- value: jest.fn().mockResolvedValue(undefined),
9
- });
10
-
11
- // Mock @daily-co/daily-js
12
- jest.mock('@daily-co/daily-js', () => {
13
- const mockCallObject = {
14
- on: jest.fn().mockReturnThis(),
15
- off: jest.fn().mockReturnThis(),
16
- join: jest.fn().mockResolvedValue(undefined),
17
- leave: jest.fn(),
18
- destroy: jest.fn(),
19
- stopRecording: jest.fn(),
20
- setInputDevicesAsync: jest.fn().mockResolvedValue(undefined),
21
- startRecording: jest.fn(),
22
- sendAppMessage: jest.fn(),
23
- };
24
-
25
- const mockDailyIframe = {
26
- getCallInstance: jest.fn().mockReturnValue(null),
27
- createCallObject: jest.fn().mockReturnValue(mockCallObject),
28
- };
29
-
30
- return {
31
- __esModule: true,
32
- default: mockDailyIframe,
33
- DailyIframe: mockDailyIframe,
34
- };
35
- });
36
-
37
- // Global cleanup for media elements after each test
38
- afterEach(() => {
39
- // Clean up all audio and video elements in the DOM
40
- const audioElements = Array.from(document.querySelectorAll('audio'));
41
- const videoElements = Array.from(document.querySelectorAll('video'));
42
-
43
- [...audioElements, ...videoElements].forEach((element) => {
44
- const mediaElement = element as HTMLMediaElement;
45
- try {
46
- mediaElement.pause();
47
- mediaElement.src = '';
48
- mediaElement.srcObject = null;
49
- // Remove event listeners by cloning
50
- const newElement = mediaElement.cloneNode(false);
51
- mediaElement.parentNode?.replaceChild(newElement, mediaElement);
52
- } catch (error) {
53
- // Ignore errors during cleanup
54
- }
55
- });
56
-
57
- // Clean up MediaStream tracks
58
- if (window.MediaStream) {
59
- // Stop any active MediaStream tracks
60
- const streams = Array.from(document.querySelectorAll('video, audio'));
61
- streams.forEach((element) => {
62
- const mediaElement = element as HTMLMediaElement;
63
- if (mediaElement.srcObject instanceof MediaStream) {
64
- mediaElement.srcObject.getTracks().forEach((track) => {
65
- track.stop();
66
- });
67
- mediaElement.srcObject = null;
68
- }
69
- });
70
- }
71
-
72
- // Revoke any object URLs that might have been created
73
- // Note: This is a best-effort cleanup since we can't track all created URLs
74
- // Components should clean up their own object URLs in ngOnDestroy
75
- });
76
-
package/tsconfig.json DELETED
@@ -1,20 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "es2022",
4
- "paths": {
5
- "@testgorilla/tgo-test-shared": ["./src/shared/index.ts"]
6
- }
7
- },
8
- "files": [],
9
- "include": [],
10
- "references": [
11
- {
12
- "path": "./tsconfig.lib.json"
13
- },
14
- {
15
- "path": "./tsconfig.spec.json"
16
- }
17
- ],
18
- "extends": "../../tsconfig.base.json"
19
- }
20
-
package/tsconfig.lib.json DELETED
@@ -1,20 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "../../dist/out-tsc",
5
- "declaration": true,
6
- "declarationMap": true,
7
- "inlineSources": true,
8
- "baseUrl": "../../",
9
- "moduleResolution": "node",
10
- "paths": {
11
- "@testgorilla/tgo-test-shared": ["packages/tgo-ai-interview-test/src/shared/index.ts"]
12
- }
13
- },
14
- "angularCompilerOptions": {
15
- "preserveWhitespaces": false
16
- },
17
- "exclude": ["src/**/*.spec.ts", "src/test-setup.ts", "jest.config.ts", "src/**/*.test.ts"],
18
- "include": ["src/**/*.ts", "src/shared/**/*.ts"]
19
- }
20
-