@open-kingdom/shared-backend-integration-test-doubles 0.0.2-17 → 0.0.2-18

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 (99) hide show
  1. package/README.md +75 -5
  2. package/dist/gcs/gcs-error-mode.d.ts +161 -0
  3. package/dist/gcs/gcs-error-mode.d.ts.map +1 -0
  4. package/dist/gcs/gcs-error-mode.js +256 -0
  5. package/dist/gcs/gcs-error-mode.js.map +1 -0
  6. package/dist/gcs/gcs-error-simulation.interceptor.d.ts +21 -0
  7. package/dist/gcs/gcs-error-simulation.interceptor.d.ts.map +1 -0
  8. package/dist/gcs/gcs-error-simulation.interceptor.js +40 -0
  9. package/dist/gcs/gcs-error-simulation.interceptor.js.map +1 -0
  10. package/dist/gcs/gcs-twin.d.ts +1 -0
  11. package/dist/gcs/gcs-twin.d.ts.map +1 -1
  12. package/dist/gcs/gcs-twin.js +19 -1
  13. package/dist/gcs/gcs-twin.js.map +1 -1
  14. package/dist/gcs/index.d.ts +3 -0
  15. package/dist/gcs/index.d.ts.map +1 -1
  16. package/dist/gcs/index.js +2 -0
  17. package/dist/gcs/index.js.map +1 -1
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +4 -0
  21. package/dist/index.js.map +1 -1
  22. package/dist/interceptor/http-helper.d.ts +37 -0
  23. package/dist/interceptor/http-helper.d.ts.map +1 -0
  24. package/dist/interceptor/http-helper.js +109 -0
  25. package/dist/interceptor/http-helper.js.map +1 -0
  26. package/dist/interceptor/index.d.ts +4 -0
  27. package/dist/interceptor/index.d.ts.map +1 -0
  28. package/dist/interceptor/index.js +5 -0
  29. package/dist/interceptor/index.js.map +1 -0
  30. package/dist/interceptor/node-interceptor.d.ts +40 -0
  31. package/dist/interceptor/node-interceptor.d.ts.map +1 -0
  32. package/dist/interceptor/node-interceptor.js +188 -0
  33. package/dist/interceptor/node-interceptor.js.map +1 -0
  34. package/dist/interceptor/routing-table.config.d.ts +3 -0
  35. package/dist/interceptor/routing-table.config.d.ts.map +1 -0
  36. package/dist/interceptor/routing-table.config.js +25 -0
  37. package/dist/interceptor/routing-table.config.js.map +1 -0
  38. package/dist/interceptor/routing-table.d.ts +15 -0
  39. package/dist/interceptor/routing-table.d.ts.map +1 -0
  40. package/dist/interceptor/routing-table.js +44 -0
  41. package/dist/interceptor/routing-table.js.map +1 -0
  42. package/dist/shared/constants.d.ts +4 -0
  43. package/dist/shared/constants.d.ts.map +1 -1
  44. package/dist/shared/constants.js +3 -1
  45. package/dist/shared/constants.js.map +1 -1
  46. package/dist/youtube/index.d.ts +15 -0
  47. package/dist/youtube/index.d.ts.map +1 -0
  48. package/dist/youtube/index.js +11 -0
  49. package/dist/youtube/index.js.map +1 -0
  50. package/dist/youtube/msw-handlers.d.ts +40 -0
  51. package/dist/youtube/msw-handlers.d.ts.map +1 -0
  52. package/dist/youtube/msw-handlers.js +45 -0
  53. package/dist/youtube/msw-handlers.js.map +1 -0
  54. package/dist/youtube/player-shim.d.ts +3 -0
  55. package/dist/youtube/player-shim.d.ts.map +1 -0
  56. package/dist/youtube/player-shim.js +296 -0
  57. package/dist/youtube/player-shim.js.map +1 -0
  58. package/dist/youtube/search-fixtures.d.ts +29 -0
  59. package/dist/youtube/search-fixtures.d.ts.map +1 -0
  60. package/dist/youtube/search-fixtures.js +392 -0
  61. package/dist/youtube/search-fixtures.js.map +1 -0
  62. package/dist/youtube/search-response.d.ts +51 -0
  63. package/dist/youtube/search-response.d.ts.map +1 -0
  64. package/dist/youtube/search-response.js +53 -0
  65. package/dist/youtube/search-response.js.map +1 -0
  66. package/dist/youtube/youtube-error-mode.d.ts +36 -0
  67. package/dist/youtube/youtube-error-mode.d.ts.map +1 -0
  68. package/dist/youtube/youtube-error-mode.js +87 -0
  69. package/dist/youtube/youtube-error-mode.js.map +1 -0
  70. package/dist/youtube/youtube-twin.config.d.ts +13 -0
  71. package/dist/youtube/youtube-twin.config.d.ts.map +1 -0
  72. package/dist/youtube/youtube-twin.config.js +32 -0
  73. package/dist/youtube/youtube-twin.config.js.map +1 -0
  74. package/dist/youtube/youtube-twin.d.ts +14 -0
  75. package/dist/youtube/youtube-twin.d.ts.map +1 -0
  76. package/dist/youtube/youtube-twin.js +162 -0
  77. package/dist/youtube/youtube-twin.js.map +1 -0
  78. package/package.json +15 -2
  79. package/test-assets/youtube/sample.mp4 +0 -0
  80. package/test-assets/youtube/thumbnails/thumbnail-01.jpg +0 -0
  81. package/test-assets/youtube/thumbnails/thumbnail-02.jpg +0 -0
  82. package/test-assets/youtube/thumbnails/thumbnail-03.jpg +0 -0
  83. package/test-assets/youtube/thumbnails/thumbnail-04.jpg +0 -0
  84. package/test-assets/youtube/thumbnails/thumbnail-05.jpg +0 -0
  85. package/test-assets/youtube/thumbnails/thumbnail-06.jpg +0 -0
  86. package/test-assets/youtube/thumbnails/thumbnail-07.jpg +0 -0
  87. package/test-assets/youtube/thumbnails/thumbnail-08.jpg +0 -0
  88. package/test-assets/youtube/thumbnails/thumbnail-09.jpg +0 -0
  89. package/test-assets/youtube/thumbnails/thumbnail-10.jpg +0 -0
  90. package/test-assets/youtube/thumbnails/thumbnail-11.jpg +0 -0
  91. package/test-assets/youtube/thumbnails/thumbnail-12.jpg +0 -0
  92. package/test-assets/youtube/thumbnails/thumbnail-13.jpg +0 -0
  93. package/test-assets/youtube/thumbnails/thumbnail-14.jpg +0 -0
  94. package/test-assets/youtube/thumbnails/thumbnail-15.jpg +0 -0
  95. package/test-assets/youtube/thumbnails/thumbnail-16.jpg +0 -0
  96. package/test-assets/youtube/thumbnails/thumbnail-17.jpg +0 -0
  97. package/test-assets/youtube/thumbnails/thumbnail-18.jpg +0 -0
  98. package/test-assets/youtube/thumbnails/thumbnail-19.jpg +0 -0
  99. package/test-assets/youtube/thumbnails/thumbnail-20.jpg +0 -0
@@ -0,0 +1,296 @@
1
+ export const SHIM_TEMPLATE = `(function() {
2
+ 'use strict';
3
+
4
+ var TWIN_BASE = '__TWIN_BASE_URL__';
5
+
6
+ var PlayerState = {
7
+ UNSTARTED: -1,
8
+ ENDED: 0,
9
+ PLAYING: 1,
10
+ PAUSED: 2,
11
+ BUFFERING: 3,
12
+ CUED: 5
13
+ };
14
+
15
+ function Player(elementIdOrElement, options) {
16
+ options = options || {};
17
+
18
+ // Resolve target container
19
+ this._container = typeof elementIdOrElement === 'string'
20
+ ? document.getElementById(elementIdOrElement)
21
+ : elementIdOrElement;
22
+
23
+ if (!this._container) {
24
+ throw new Error('YT.Player: target element not found: ' + elementIdOrElement);
25
+ }
26
+
27
+ this._options = options;
28
+ this._state = PlayerState.UNSTARTED;
29
+ this._volume = 100;
30
+ this._muted = false;
31
+ this._hasError = false;
32
+ this._errorCode = null;
33
+ this._videoData = {
34
+ video_id: options.videoId || '',
35
+ title: '',
36
+ author: ''
37
+ };
38
+
39
+ // Create HTML5 <video> element
40
+ this._video = document.createElement('video');
41
+ this._video.src = TWIN_BASE + '/test-assets/sample.mp4';
42
+ this._video.width = options.width || 640;
43
+ this._video.height = options.height || 360;
44
+ this._video.controls = !!(options.playerVars && options.playerVars.controls !== 0);
45
+ this._video.playsInline = !!(options.playerVars && options.playerVars.playsinline);
46
+ this._video.style.width = '100%';
47
+ this._video.style.height = '100%';
48
+ this._video.style.backgroundColor = '#000';
49
+ this._video.style.display = 'block';
50
+
51
+ // Override video.play to check error state
52
+ var self = this;
53
+ var originalPlay = this._video.play;
54
+ this._video.play = function() {
55
+ if (self._hasError) {
56
+ return Promise.reject(new Error('Playback blocked due to player error ' + self._errorCode));
57
+ }
58
+ return originalPlay.apply(this, arguments);
59
+ };
60
+
61
+ // Create metadata overlay
62
+ this._overlay = document.createElement('div');
63
+ this._overlay.style.cssText =
64
+ 'position:absolute;bottom:0;left:0;right:0;padding:8px 12px;' +
65
+ 'background:linear-gradient(transparent, rgba(0,0,0,0.8));' +
66
+ 'color:#fff;font-size:14px;font-family:Roboto,Arial,sans-serif;' +
67
+ 'pointer-events:none;z-index:10;';
68
+ this._loadVideoMetadata(options.videoId);
69
+
70
+ // Wrap in positioned container
71
+ this._wrapper = document.createElement('div');
72
+ this._wrapper.style.cssText = 'position:relative;overflow:hidden;width:' + (options.width ? options.width + 'px' : '100%') + ';height:' + (options.height ? options.height + 'px' : '100%') + ';';
73
+ this._wrapper.appendChild(this._video);
74
+ this._wrapper.appendChild(this._overlay);
75
+
76
+ // Replace target element content
77
+ this._container.innerHTML = '';
78
+ this._container.appendChild(this._wrapper);
79
+
80
+ // Wire HTML5 video events -> YT state changes
81
+ this._video.addEventListener('play', function() {
82
+ if (self._hasError) {
83
+ self._video.pause();
84
+ }
85
+ });
86
+ this._video.addEventListener('playing', function() {
87
+ if (self._hasError) {
88
+ self._video.pause();
89
+ return;
90
+ }
91
+ self._state = PlayerState.PLAYING;
92
+ self._fireStateChange();
93
+ });
94
+ this._video.addEventListener('pause', function() {
95
+ if (self._video.ended) return; // 'ended' fires separately
96
+ if (self._hasError) return;
97
+ self._state = PlayerState.PAUSED;
98
+ self._fireStateChange();
99
+ });
100
+ this._video.addEventListener('ended', function() {
101
+ if (self._hasError) return;
102
+ self._state = PlayerState.ENDED;
103
+ self._fireStateChange();
104
+ });
105
+ this._video.addEventListener('waiting', function() {
106
+ if (self._hasError) return;
107
+ self._state = PlayerState.BUFFERING;
108
+ self._fireStateChange();
109
+ });
110
+
111
+ // Fire onReady asynchronously (matches real YT behavior)
112
+ setTimeout(function() {
113
+ if (options.events && typeof options.events.onReady === 'function') {
114
+ options.events.onReady({ target: self });
115
+ }
116
+ }, 100);
117
+
118
+ }
119
+
120
+ Player.prototype._loadVideoMetadata = function(videoId) {
121
+ if (!videoId) {
122
+ this._overlay.textContent = 'YouTube Twin Player';
123
+ return;
124
+ }
125
+
126
+ var self = this;
127
+
128
+ // Check error-mode control endpoint first
129
+ fetch(TWIN_BASE + '/test/youtube/error-mode')
130
+ .then(function(res) { return res.json(); })
131
+ .then(function(data) {
132
+ if (data.active && data.mode && data.mode.startsWith('player-error-')) {
133
+ var code = parseInt(data.mode.split('-')[2], 10);
134
+ setTimeout(function() {
135
+ self._fireError(code);
136
+ }, 200);
137
+ return;
138
+ }
139
+
140
+ // Auto-play if configured and no error
141
+ if (self._options.playerVars && self._options.playerVars.autoplay === 1) {
142
+ self._video.play().catch(function() {});
143
+ }
144
+
145
+ // Fetch actual mock metadata
146
+ fetch(TWIN_BASE + '/youtube/v3/search?q=' + encodeURIComponent(videoId) +
147
+ '&type=video&maxResults=1&key=shim&part=snippet')
148
+ .then(function(res) {
149
+ if (!res.ok) throw new Error('Search failed');
150
+ return res.json();
151
+ })
152
+ .then(function(searchData) {
153
+ if (searchData.items && searchData.items.length > 0) {
154
+ var snippet = searchData.items[0].snippet;
155
+ self._videoData = {
156
+ video_id: videoId,
157
+ title: snippet.title,
158
+ author: snippet.channelTitle
159
+ };
160
+ self._overlay.textContent = snippet.title + ' - ' + snippet.channelTitle;
161
+ } else {
162
+ self._overlay.textContent = 'Video: ' + videoId;
163
+ }
164
+ })
165
+ .catch(function() {
166
+ self._overlay.textContent = 'Video: ' + videoId;
167
+ });
168
+ })
169
+ .catch(function() {
170
+ // Auto-play if configured and endpoint query fails
171
+ if (self._options.playerVars && self._options.playerVars.autoplay === 1) {
172
+ self._video.play().catch(function() {});
173
+ }
174
+ self._overlay.textContent = 'Video: ' + videoId;
175
+ });
176
+ };
177
+
178
+ Player.prototype._fireStateChange = function() {
179
+ if (this._options.events && typeof this._options.events.onStateChange === 'function') {
180
+ this._options.events.onStateChange({ target: this, data: this._state });
181
+ }
182
+ };
183
+
184
+ Player.prototype._fireError = function(code) {
185
+ this._hasError = true;
186
+ this._errorCode = code;
187
+ this._state = PlayerState.UNSTARTED;
188
+
189
+ // Pause and clean up video
190
+ if (this._video) {
191
+ this._video.pause();
192
+ this._video.src = '';
193
+ try {
194
+ this._video.removeAttribute('src');
195
+ this._video.load();
196
+ } catch (e) {}
197
+ }
198
+
199
+ var desc = 'An unknown player error occurred.';
200
+ if (code === 2) desc = 'The request contains an invalid parameter value.';
201
+ else if (code === 5) desc = 'The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.';
202
+ else if (code === 100) desc = 'The video requested was not found (removed or marked as private).';
203
+ else if (code === 101 || code === 150) desc = 'The owner of the requested video does not allow it to be played in embedded players.';
204
+
205
+ if (this._overlay) {
206
+ this._overlay.style.cssText =
207
+ 'position:absolute;top:0;left:0;right:0;bottom:0;padding:24px;' +
208
+ 'background:#151515;color:#f1f1f1;font-size:14px;' +
209
+ 'font-family:Roboto,Arial,sans-serif;display:flex;' +
210
+ 'flex-direction:column;align-items:center;justify-content:center;' +
211
+ 'text-align:center;pointer-events:auto;z-index:10;box-sizing:border-box;';
212
+ this._overlay.innerHTML =
213
+ '<div style="font-size:28px;margin-bottom:8px;">⚠️</div>' +
214
+ '<div style="font-weight:bold;margin-bottom:4px;color:#fff;">An error occurred.</div>' +
215
+ '<div style="font-size:12px;color:#aaa;line-height:1.4;max-width:280px;">' + desc + ' (Error Code: ' + code + ')</div>';
216
+ }
217
+
218
+ if (this._options.events && typeof this._options.events.onError === 'function') {
219
+ this._options.events.onError({ target: this, data: code });
220
+ }
221
+ };
222
+
223
+ // Playback controls
224
+ Player.prototype.playVideo = function() {
225
+ if (this._hasError) return;
226
+ this._video.play().catch(function() {});
227
+ };
228
+ Player.prototype.pauseVideo = function() {
229
+ if (this._hasError) return;
230
+ this._video.pause();
231
+ };
232
+ Player.prototype.stopVideo = function() {
233
+ if (this._hasError) return;
234
+ this._video.pause();
235
+ this._video.currentTime = 0;
236
+ };
237
+ Player.prototype.seekTo = function(seconds) {
238
+ if (this._hasError) return;
239
+ this._video.currentTime = seconds;
240
+ };
241
+
242
+ // Volume controls
243
+ Player.prototype.setVolume = function(vol) {
244
+ this._volume = vol;
245
+ this._video.volume = vol / 100;
246
+ };
247
+ Player.prototype.getVolume = function() { return this._volume; };
248
+ Player.prototype.mute = function() { this._muted = true; this._video.muted = true; };
249
+ Player.prototype.unMute = function() { this._muted = false; this._video.muted = false; };
250
+ Player.prototype.isMuted = function() { return this._muted; };
251
+
252
+ // State queries
253
+ Player.prototype.getPlayerState = function() { return this._state; };
254
+ Player.prototype.getCurrentTime = function() { return this._video.currentTime; };
255
+ Player.prototype.getDuration = function() { return this._video.duration || 0; };
256
+ Player.prototype.getVideoUrl = function() {
257
+ return 'https://www.youtube.com/watch?v=' + (this._options.videoId || 'test');
258
+ };
259
+ Player.prototype.getVideoData = function() { return this._videoData; };
260
+
261
+ // Sizing
262
+ Player.prototype.setSize = function(w, h) {
263
+ this._video.width = w;
264
+ this._video.height = h;
265
+ if (this._wrapper) {
266
+ this._wrapper.style.width = w + 'px';
267
+ this._wrapper.style.height = h + 'px';
268
+ }
269
+ };
270
+
271
+ // Cleanup
272
+ Player.prototype.destroy = function() {
273
+ this._video.pause();
274
+ this._video.src = '';
275
+ this._video.load();
276
+ if (this._container) {
277
+ this._container.innerHTML = '';
278
+ }
279
+ };
280
+
281
+ // Install globals
282
+ window.YT = {
283
+ Player: Player,
284
+ PlayerState: PlayerState
285
+ };
286
+
287
+ // Fire the API-ready callback
288
+ if (typeof window.onYouTubeIframeAPIReady === 'function') {
289
+ setTimeout(function() { window.onYouTubeIframeAPIReady(); }, 0);
290
+ }
291
+ })();`;
292
+ export function generatePlayerShim(twinBaseUrl) {
293
+ return SHIM_TEMPLATE.replace(/__TWIN_BASE_URL__/g, twinBaseUrl);
294
+ }
295
+
296
+ //# sourceMappingURL=player-shim.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/youtube/player-shim.ts"],"sourcesContent":["export const SHIM_TEMPLATE = `(function() {\n 'use strict';\n\n var TWIN_BASE = '__TWIN_BASE_URL__';\n\n var PlayerState = {\n UNSTARTED: -1,\n ENDED: 0,\n PLAYING: 1,\n PAUSED: 2,\n BUFFERING: 3,\n CUED: 5\n };\n\n function Player(elementIdOrElement, options) {\n options = options || {};\n\n // Resolve target container\n this._container = typeof elementIdOrElement === 'string'\n ? document.getElementById(elementIdOrElement)\n : elementIdOrElement;\n\n if (!this._container) {\n throw new Error('YT.Player: target element not found: ' + elementIdOrElement);\n }\n\n this._options = options;\n this._state = PlayerState.UNSTARTED;\n this._volume = 100;\n this._muted = false;\n this._hasError = false;\n this._errorCode = null;\n this._videoData = {\n video_id: options.videoId || '',\n title: '',\n author: ''\n };\n\n // Create HTML5 <video> element\n this._video = document.createElement('video');\n this._video.src = TWIN_BASE + '/test-assets/sample.mp4';\n this._video.width = options.width || 640;\n this._video.height = options.height || 360;\n this._video.controls = !!(options.playerVars && options.playerVars.controls !== 0);\n this._video.playsInline = !!(options.playerVars && options.playerVars.playsinline);\n this._video.style.width = '100%';\n this._video.style.height = '100%';\n this._video.style.backgroundColor = '#000';\n this._video.style.display = 'block';\n\n // Override video.play to check error state\n var self = this;\n var originalPlay = this._video.play;\n this._video.play = function() {\n if (self._hasError) {\n return Promise.reject(new Error('Playback blocked due to player error ' + self._errorCode));\n }\n return originalPlay.apply(this, arguments);\n };\n\n // Create metadata overlay\n this._overlay = document.createElement('div');\n this._overlay.style.cssText =\n 'position:absolute;bottom:0;left:0;right:0;padding:8px 12px;' +\n 'background:linear-gradient(transparent, rgba(0,0,0,0.8));' +\n 'color:#fff;font-size:14px;font-family:Roboto,Arial,sans-serif;' +\n 'pointer-events:none;z-index:10;';\n this._loadVideoMetadata(options.videoId);\n\n // Wrap in positioned container\n this._wrapper = document.createElement('div');\n this._wrapper.style.cssText = 'position:relative;overflow:hidden;width:' + (options.width ? options.width + 'px' : '100%') + ';height:' + (options.height ? options.height + 'px' : '100%') + ';';\n this._wrapper.appendChild(this._video);\n this._wrapper.appendChild(this._overlay);\n\n // Replace target element content\n this._container.innerHTML = '';\n this._container.appendChild(this._wrapper);\n\n // Wire HTML5 video events -> YT state changes\n this._video.addEventListener('play', function() {\n if (self._hasError) {\n self._video.pause();\n }\n });\n this._video.addEventListener('playing', function() {\n if (self._hasError) {\n self._video.pause();\n return;\n }\n self._state = PlayerState.PLAYING;\n self._fireStateChange();\n });\n this._video.addEventListener('pause', function() {\n if (self._video.ended) return; // 'ended' fires separately\n if (self._hasError) return;\n self._state = PlayerState.PAUSED;\n self._fireStateChange();\n });\n this._video.addEventListener('ended', function() {\n if (self._hasError) return;\n self._state = PlayerState.ENDED;\n self._fireStateChange();\n });\n this._video.addEventListener('waiting', function() {\n if (self._hasError) return;\n self._state = PlayerState.BUFFERING;\n self._fireStateChange();\n });\n\n // Fire onReady asynchronously (matches real YT behavior)\n setTimeout(function() {\n if (options.events && typeof options.events.onReady === 'function') {\n options.events.onReady({ target: self });\n }\n }, 100);\n\n }\n\n Player.prototype._loadVideoMetadata = function(videoId) {\n if (!videoId) {\n this._overlay.textContent = 'YouTube Twin Player';\n return;\n }\n\n var self = this;\n\n // Check error-mode control endpoint first\n fetch(TWIN_BASE + '/test/youtube/error-mode')\n .then(function(res) { return res.json(); })\n .then(function(data) {\n if (data.active && data.mode && data.mode.startsWith('player-error-')) {\n var code = parseInt(data.mode.split('-')[2], 10);\n setTimeout(function() {\n self._fireError(code);\n }, 200);\n return;\n }\n\n // Auto-play if configured and no error\n if (self._options.playerVars && self._options.playerVars.autoplay === 1) {\n self._video.play().catch(function() {});\n }\n\n // Fetch actual mock metadata\n fetch(TWIN_BASE + '/youtube/v3/search?q=' + encodeURIComponent(videoId) +\n '&type=video&maxResults=1&key=shim&part=snippet')\n .then(function(res) {\n if (!res.ok) throw new Error('Search failed');\n return res.json();\n })\n .then(function(searchData) {\n if (searchData.items && searchData.items.length > 0) {\n var snippet = searchData.items[0].snippet;\n self._videoData = {\n video_id: videoId,\n title: snippet.title,\n author: snippet.channelTitle\n };\n self._overlay.textContent = snippet.title + ' - ' + snippet.channelTitle;\n } else {\n self._overlay.textContent = 'Video: ' + videoId;\n }\n })\n .catch(function() {\n self._overlay.textContent = 'Video: ' + videoId;\n });\n })\n .catch(function() {\n // Auto-play if configured and endpoint query fails\n if (self._options.playerVars && self._options.playerVars.autoplay === 1) {\n self._video.play().catch(function() {});\n }\n self._overlay.textContent = 'Video: ' + videoId;\n });\n };\n\n Player.prototype._fireStateChange = function() {\n if (this._options.events && typeof this._options.events.onStateChange === 'function') {\n this._options.events.onStateChange({ target: this, data: this._state });\n }\n };\n\n Player.prototype._fireError = function(code) {\n this._hasError = true;\n this._errorCode = code;\n this._state = PlayerState.UNSTARTED;\n\n // Pause and clean up video\n if (this._video) {\n this._video.pause();\n this._video.src = '';\n try {\n this._video.removeAttribute('src');\n this._video.load();\n } catch (e) {}\n }\n\n var desc = 'An unknown player error occurred.';\n if (code === 2) desc = 'The request contains an invalid parameter value.';\n else if (code === 5) desc = 'The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.';\n else if (code === 100) desc = 'The video requested was not found (removed or marked as private).';\n else if (code === 101 || code === 150) desc = 'The owner of the requested video does not allow it to be played in embedded players.';\n\n if (this._overlay) {\n this._overlay.style.cssText =\n 'position:absolute;top:0;left:0;right:0;bottom:0;padding:24px;' +\n 'background:#151515;color:#f1f1f1;font-size:14px;' +\n 'font-family:Roboto,Arial,sans-serif;display:flex;' +\n 'flex-direction:column;align-items:center;justify-content:center;' +\n 'text-align:center;pointer-events:auto;z-index:10;box-sizing:border-box;';\n this._overlay.innerHTML =\n '<div style=\"font-size:28px;margin-bottom:8px;\">⚠️</div>' +\n '<div style=\"font-weight:bold;margin-bottom:4px;color:#fff;\">An error occurred.</div>' +\n '<div style=\"font-size:12px;color:#aaa;line-height:1.4;max-width:280px;\">' + desc + ' (Error Code: ' + code + ')</div>';\n }\n\n if (this._options.events && typeof this._options.events.onError === 'function') {\n this._options.events.onError({ target: this, data: code });\n }\n };\n\n // Playback controls\n Player.prototype.playVideo = function() {\n if (this._hasError) return;\n this._video.play().catch(function() {});\n };\n Player.prototype.pauseVideo = function() {\n if (this._hasError) return;\n this._video.pause();\n };\n Player.prototype.stopVideo = function() {\n if (this._hasError) return;\n this._video.pause();\n this._video.currentTime = 0;\n };\n Player.prototype.seekTo = function(seconds) {\n if (this._hasError) return;\n this._video.currentTime = seconds;\n };\n\n // Volume controls\n Player.prototype.setVolume = function(vol) {\n this._volume = vol;\n this._video.volume = vol / 100;\n };\n Player.prototype.getVolume = function() { return this._volume; };\n Player.prototype.mute = function() { this._muted = true; this._video.muted = true; };\n Player.prototype.unMute = function() { this._muted = false; this._video.muted = false; };\n Player.prototype.isMuted = function() { return this._muted; };\n\n // State queries\n Player.prototype.getPlayerState = function() { return this._state; };\n Player.prototype.getCurrentTime = function() { return this._video.currentTime; };\n Player.prototype.getDuration = function() { return this._video.duration || 0; };\n Player.prototype.getVideoUrl = function() {\n return 'https://www.youtube.com/watch?v=' + (this._options.videoId || 'test');\n };\n Player.prototype.getVideoData = function() { return this._videoData; };\n\n // Sizing\n Player.prototype.setSize = function(w, h) {\n this._video.width = w;\n this._video.height = h;\n if (this._wrapper) {\n this._wrapper.style.width = w + 'px';\n this._wrapper.style.height = h + 'px';\n }\n };\n\n // Cleanup\n Player.prototype.destroy = function() {\n this._video.pause();\n this._video.src = '';\n this._video.load();\n if (this._container) {\n this._container.innerHTML = '';\n }\n };\n\n // Install globals\n window.YT = {\n Player: Player,\n PlayerState: PlayerState\n };\n\n // Fire the API-ready callback\n if (typeof window.onYouTubeIframeAPIReady === 'function') {\n setTimeout(function() { window.onYouTubeIframeAPIReady(); }, 0);\n }\n})();`;\n\nexport function generatePlayerShim(twinBaseUrl: string): string {\n return SHIM_TEMPLATE.replace(/__TWIN_BASE_URL__/g, twinBaseUrl);\n}\n"],"names":["SHIM_TEMPLATE","generatePlayerShim","twinBaseUrl","replace"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA,OAAO,MAAMA,gBAAgB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAkSzB,CAAC,CAAC;AAEP,OAAO,SAASC,mBAAmBC,WAAmB;IACpD,OAAOF,cAAcG,OAAO,CAAC,sBAAsBD;AACrD"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Canned YouTube video search fixtures.
3
+ *
4
+ * Each fixture represents a single video result with realistic metadata.
5
+ * The fixture set spans diverse content categories to support query-aware
6
+ * filtering in tests.
7
+ */
8
+ export interface VideoFixture {
9
+ videoId: string;
10
+ title: string;
11
+ channelTitle: string;
12
+ channelId: string;
13
+ description: string;
14
+ publishedAt: string;
15
+ thumbnailIndex: number;
16
+ duration: string;
17
+ viewCount: string;
18
+ tags: string[];
19
+ }
20
+ export declare const videoFixtures: VideoFixture[];
21
+ /**
22
+ * Search fixtures using query-aware filtering.
23
+ *
24
+ * Matches `q` against title, description, and tags (case-insensitive).
25
+ * If no matches found, returns a general fallback subset.
26
+ * Never returns empty unless the fixture set itself is empty.
27
+ */
28
+ export declare function searchFixtures(query: string, maxResults?: number, fixtures?: VideoFixture[]): VideoFixture[];
29
+ //# sourceMappingURL=search-fixtures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-fixtures.d.ts","sourceRoot":"","sources":["../../src/youtube/search-fixtures.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,eAAO,MAAM,aAAa,EAAE,YAAY,EAmQvC,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,UAAU,SAAK,EACf,QAAQ,GAAE,YAAY,EAAkB,GACvC,YAAY,EAAE,CAyBhB"}