-
Notifications
You must be signed in to change notification settings - Fork 6.8k
/
Copy pathfake-youtube-player.ts
123 lines (110 loc) · 3.46 KB
/
fake-youtube-player.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
// A re-creation of YT.PlayerState since enum values cannot be bound to the window object.
const playerState = {
UNSTARTED: -1,
ENDED: 0,
PLAYING: 1,
PAUSED: 2,
BUFFERING: 3,
CUED: 5,
};
// Re-creation of `YT.ModestBranding` since it was changed
// to a plain enum which we can't reference in tests.
const modestBranding = {
Full: 0,
Modest: 1,
};
interface FakeYtNamespace {
playerCtorSpy: jasmine.Spy;
playerSpy: jasmine.SpyObj<YT.Player>;
events: Required<YT.Events>;
namespace: typeof YT;
}
type ListenersStore<EventName extends keyof YT.Events> = {[E in EventName]?: Set<Listener<E>>};
type Listener<EventName extends keyof YT.Events> = NonNullable<YT.Events[EventName]>;
type ListenerArg<EventName extends keyof YT.Events> =
Listener<EventName> extends YT.PlayerEventHandler<infer T> ? T : never;
export function createFakeYtNamespace(): FakeYtNamespace {
const playerSpy: jasmine.SpyObj<YT.Player> = jasmine.createSpyObj('Player', [
'getPlayerState',
'destroy',
'cueVideoById',
'loadVideoById',
'pauseVideo',
'stopVideo',
'seekTo',
'isMuted',
'mute',
'unMute',
'getVolume',
'getPlaybackRate',
'getAvailablePlaybackRates',
'getVideoLoadedFraction',
'getPlayerState',
'getCurrentTime',
'getPlaybackQuality',
'getAvailableQualityLevels',
'getDuration',
'getVideoUrl',
'getVideoEmbedCode',
'playVideo',
'setSize',
'setVolume',
'setPlaybackQuality',
'setPlaybackRate',
'addEventListener',
'removeEventListener',
]);
let playerConfig: YT.PlayerOptions | undefined;
const boundListeners: ListenersStore<keyof YT.Events> = {};
const playerCtorSpy = jasmine.createSpy('Player Constructor');
// The spy target function cannot be an arrow-function as this breaks when created through `new`.
playerCtorSpy.and.callFake(function (_el: Element, config: YT.PlayerOptions) {
playerConfig = config;
return playerSpy;
});
playerSpy.addEventListener.and.callFake((name, listener) => {
const store: ListenersStore<typeof name> = boundListeners;
if (!store[name]) {
store[name] = new Set();
}
store[name].add(listener);
});
playerSpy.removeEventListener.and.callFake((name, listener) => {
boundListeners[name]?.delete(listener);
});
function eventHandlerFactory<EventName extends keyof YT.Events>(name: EventName) {
return (arg = {} as ListenerArg<EventName>) => {
if (!playerConfig) {
throw new Error(`Player not initialized before ${name} called`);
}
boundListeners[name]?.forEach(callback =>
(callback as (arg: ListenerArg<EventName>) => void)(arg),
);
};
}
const events: Required<YT.Events> = {
onReady: eventHandlerFactory('onReady'),
onStateChange: eventHandlerFactory('onStateChange'),
onPlaybackQualityChange: eventHandlerFactory('onPlaybackQualityChange'),
onPlaybackRateChange: eventHandlerFactory('onPlaybackRateChange'),
onError: eventHandlerFactory('onError'),
onApiChange: eventHandlerFactory('onApiChange'),
};
return {
playerCtorSpy,
playerSpy,
events,
namespace: {
'Player': playerCtorSpy as unknown as typeof YT.Player,
'PlayerState': playerState,
'ModestBranding': modestBranding,
} as typeof YT,
};
}