@flamingo-stack/openframe-frontend-core 0.0.581-snapshot.20260901141954 → 0.0.581

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 (105) hide show
  1. package/dist/{chunk-CR7GPMCM.js → chunk-3GXNCJZI.js} +3 -3
  2. package/dist/{chunk-A5VD6R7N.cjs → chunk-6U45VX5J.cjs} +43 -43
  3. package/dist/{chunk-A5VD6R7N.cjs.map → chunk-6U45VX5J.cjs.map} +1 -1
  4. package/dist/{chunk-SVQVAGZK.js → chunk-7CP5KY5W.js} +3 -3
  5. package/dist/{chunk-WQ236GO3.cjs → chunk-AIALG25H.cjs} +69 -69
  6. package/dist/{chunk-WQ236GO3.cjs.map → chunk-AIALG25H.cjs.map} +1 -1
  7. package/dist/{chunk-ALSLHGBH.cjs → chunk-BTKMTVN5.cjs} +4 -4
  8. package/dist/{chunk-ALSLHGBH.cjs.map → chunk-BTKMTVN5.cjs.map} +1 -1
  9. package/dist/{chunk-FYVL6A4Z.cjs → chunk-DSPQVZFE.cjs} +24 -24
  10. package/dist/{chunk-FYVL6A4Z.cjs.map → chunk-DSPQVZFE.cjs.map} +1 -1
  11. package/dist/{chunk-HQF7VE7B.js → chunk-ESWXXWCL.js} +2 -2
  12. package/dist/{chunk-FRWQ67UM.js → chunk-EZVABFEO.js} +2 -2
  13. package/dist/{chunk-6EUYXDV3.js → chunk-GAHKWML7.js} +6 -6
  14. package/dist/{chunk-L4E2TOSF.cjs → chunk-GMQEHDN2.cjs} +5 -5
  15. package/dist/{chunk-L4E2TOSF.cjs.map → chunk-GMQEHDN2.cjs.map} +1 -1
  16. package/dist/{chunk-K5SCFOD5.cjs → chunk-GUC4JK6O.cjs} +9 -9
  17. package/dist/{chunk-K5SCFOD5.cjs.map → chunk-GUC4JK6O.cjs.map} +1 -1
  18. package/dist/{chunk-U2ZRM3YB.js → chunk-GVM37TRU.js} +4 -4
  19. package/dist/{chunk-F227VMPL.js → chunk-HJVKPG55.js} +15 -125
  20. package/dist/{chunk-F227VMPL.js.map → chunk-HJVKPG55.js.map} +1 -1
  21. package/dist/{chunk-7HHRJTIY.js → chunk-HVSZYUGP.js} +5 -5
  22. package/dist/{chunk-KBOBPKMU.js → chunk-IHFQ5VVR.js} +5 -5
  23. package/dist/{chunk-A3SVEK7A.js → chunk-JW53WPB5.js} +3 -3
  24. package/dist/{chunk-646JEAY4.js → chunk-KAEVE7W2.js} +2 -2
  25. package/dist/{chunk-R3J2ERH2.cjs → chunk-KNBXZ22O.cjs} +3 -3
  26. package/dist/{chunk-R3J2ERH2.cjs.map → chunk-KNBXZ22O.cjs.map} +1 -1
  27. package/dist/{chunk-BUOYMIFJ.js → chunk-LWDIDSCF.js} +3 -3
  28. package/dist/{chunk-ORQQZQBB.cjs → chunk-M4KQWGEL.cjs} +32 -32
  29. package/dist/{chunk-ORQQZQBB.cjs.map → chunk-M4KQWGEL.cjs.map} +1 -1
  30. package/dist/{chunk-SKGMU7TZ.js → chunk-OKA3DJJ3.js} +2 -2
  31. package/dist/{chunk-DQVM4FTL.cjs → chunk-PBRXT7TD.cjs} +10 -10
  32. package/dist/{chunk-DQVM4FTL.cjs.map → chunk-PBRXT7TD.cjs.map} +1 -1
  33. package/dist/{chunk-4KWAHIYA.js → chunk-R5JVRFSW.js} +3 -3
  34. package/dist/{chunk-BQPCCBGO.cjs → chunk-SEZ25E22.cjs} +10 -10
  35. package/dist/{chunk-BQPCCBGO.cjs.map → chunk-SEZ25E22.cjs.map} +1 -1
  36. package/dist/{chunk-DGZPX2G3.cjs → chunk-U3W2ZVN4.cjs} +159 -159
  37. package/dist/{chunk-DGZPX2G3.cjs.map → chunk-U3W2ZVN4.cjs.map} +1 -1
  38. package/dist/{chunk-KAOXOYRT.cjs → chunk-UKHM2S6I.cjs} +15 -15
  39. package/dist/{chunk-KAOXOYRT.cjs.map → chunk-UKHM2S6I.cjs.map} +1 -1
  40. package/dist/{chunk-J5I3TKAH.cjs → chunk-VDRCKHML.cjs} +3 -3
  41. package/dist/{chunk-J5I3TKAH.cjs.map → chunk-VDRCKHML.cjs.map} +1 -1
  42. package/dist/{chunk-GD3OAM4T.js → chunk-VXUUSLD5.js} +2 -2
  43. package/dist/{chunk-T32XVOFP.cjs → chunk-W2TXSZTM.cjs} +21 -21
  44. package/dist/{chunk-T32XVOFP.cjs.map → chunk-W2TXSZTM.cjs.map} +1 -1
  45. package/dist/{chunk-RMOUILHT.cjs → chunk-WHFEG6UU.cjs} +22 -22
  46. package/dist/{chunk-RMOUILHT.cjs.map → chunk-WHFEG6UU.cjs.map} +1 -1
  47. package/dist/{chunk-LOJ5KQ6B.cjs → chunk-YU2UJPAT.cjs} +356 -466
  48. package/dist/chunk-YU2UJPAT.cjs.map +1 -0
  49. package/dist/{chunk-QDSVTIO5.js → chunk-ZEZMGXYV.js} +5 -5
  50. package/dist/components/case-studies/index.cjs +9 -9
  51. package/dist/components/case-studies/index.js +3 -3
  52. package/dist/components/chat/index.cjs +2 -2
  53. package/dist/components/chat/index.js +1 -1
  54. package/dist/components/contact/index.cjs +4 -4
  55. package/dist/components/contact/index.js +3 -3
  56. package/dist/components/docs/index.cjs +7 -7
  57. package/dist/components/docs/index.js +6 -6
  58. package/dist/components/embeds/index.cjs +4 -4
  59. package/dist/components/embeds/index.js +3 -3
  60. package/dist/components/faq/index.cjs +4 -4
  61. package/dist/components/faq/index.js +3 -3
  62. package/dist/components/features/index.cjs +3 -5
  63. package/dist/components/features/index.cjs.map +1 -1
  64. package/dist/components/features/index.js +2 -4
  65. package/dist/components/features/video.d.ts +0 -12
  66. package/dist/components/features/video.d.ts.map +1 -1
  67. package/dist/components/help-center-pages/index.cjs +23 -23
  68. package/dist/components/help-center-pages/index.js +14 -14
  69. package/dist/components/index.cjs +121 -123
  70. package/dist/components/index.cjs.map +1 -1
  71. package/dist/components/index.js +13 -15
  72. package/dist/components/index.js.map +1 -1
  73. package/dist/components/meeting-scheduler/index.cjs +21 -21
  74. package/dist/components/meeting-scheduler/index.js +2 -2
  75. package/dist/components/navigation/index.cjs +6 -6
  76. package/dist/components/navigation/index.js +5 -5
  77. package/dist/components/onboarding-guides/index.cjs +6 -6
  78. package/dist/components/onboarding-guides/index.js +5 -5
  79. package/dist/components/tickets/index.cjs +7 -7
  80. package/dist/components/tickets/index.js +6 -6
  81. package/dist/components/ui/index.cjs +3 -3
  82. package/dist/components/ui/index.js +2 -2
  83. package/dist/index.cjs +7 -9
  84. package/dist/index.cjs.map +1 -1
  85. package/dist/index.js +6 -8
  86. package/dist/index.js.map +1 -1
  87. package/package.json +1 -1
  88. package/src/components/features/video.tsx +16 -198
  89. package/dist/chunk-LOJ5KQ6B.cjs.map +0 -1
  90. package/src/components/features/__tests__/video-playback-failure.test.tsx +0 -250
  91. /package/dist/{chunk-CR7GPMCM.js.map → chunk-3GXNCJZI.js.map} +0 -0
  92. /package/dist/{chunk-SVQVAGZK.js.map → chunk-7CP5KY5W.js.map} +0 -0
  93. /package/dist/{chunk-HQF7VE7B.js.map → chunk-ESWXXWCL.js.map} +0 -0
  94. /package/dist/{chunk-FRWQ67UM.js.map → chunk-EZVABFEO.js.map} +0 -0
  95. /package/dist/{chunk-6EUYXDV3.js.map → chunk-GAHKWML7.js.map} +0 -0
  96. /package/dist/{chunk-U2ZRM3YB.js.map → chunk-GVM37TRU.js.map} +0 -0
  97. /package/dist/{chunk-7HHRJTIY.js.map → chunk-HVSZYUGP.js.map} +0 -0
  98. /package/dist/{chunk-KBOBPKMU.js.map → chunk-IHFQ5VVR.js.map} +0 -0
  99. /package/dist/{chunk-A3SVEK7A.js.map → chunk-JW53WPB5.js.map} +0 -0
  100. /package/dist/{chunk-646JEAY4.js.map → chunk-KAEVE7W2.js.map} +0 -0
  101. /package/dist/{chunk-BUOYMIFJ.js.map → chunk-LWDIDSCF.js.map} +0 -0
  102. /package/dist/{chunk-SKGMU7TZ.js.map → chunk-OKA3DJJ3.js.map} +0 -0
  103. /package/dist/{chunk-4KWAHIYA.js.map → chunk-R5JVRFSW.js.map} +0 -0
  104. /package/dist/{chunk-GD3OAM4T.js.map → chunk-VXUUSLD5.js.map} +0 -0
  105. /package/dist/{chunk-QDSVTIO5.js.map → chunk-ZEZMGXYV.js.map} +0 -0
@@ -1,250 +0,0 @@
1
- /**
2
- * Playback-failure handling for the shared `<Video>`.
3
- *
4
- * Two branches: the file player (MuxPlayer) via a fake element that fires the
5
- * media events the watchdog listens to, and the YouTube facade via the
6
- * postMessage channel it subscribes to. Both assert the same contract — a
7
- * failure surfaces a reachable retry AND is reported once.
8
- */
9
- import { render, screen, act, fireEvent } from '@testing-library/react';
10
- import { forwardRef, useImperativeHandle } from 'react';
11
- import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest';
12
-
13
- interface FakeMuxEl {
14
- paused: boolean;
15
- ended: boolean;
16
- currentTime: number;
17
- loadCalls: number;
18
- playCalls: number;
19
- listeners: Record<string, Array<() => void>>;
20
- emit: (type: string) => void;
21
- }
22
-
23
- let muxProps: Record<string, (() => void) | undefined> = {};
24
- let muxEl: FakeMuxEl | null = null;
25
-
26
- vi.mock('@mux/mux-player-react', () => {
27
- const Fake = forwardRef((props: Record<string, unknown>, ref: React.Ref<unknown>) => {
28
- muxProps = props as Record<string, (() => void) | undefined>;
29
- useImperativeHandle(ref, () => {
30
- const listeners: Record<string, Array<() => void>> = {};
31
- const el = {
32
- paused: false,
33
- ended: false,
34
- loadCalls: 0,
35
- playCalls: 0,
36
- muted: false,
37
- volume: 1,
38
- currentTime: 0,
39
- duration: 100,
40
- listeners,
41
- play: () => {
42
- el.playCalls += 1;
43
- el.paused = false;
44
- return Promise.resolve();
45
- },
46
- pause: () => {
47
- el.paused = true;
48
- },
49
- // Mirrors the real element: a reload rewinds to zero.
50
- load: () => {
51
- el.loadCalls += 1;
52
- el.currentTime = 0;
53
- },
54
- addEventListener: (type: string, fn: () => void) => {
55
- (listeners[type] ??= []).push(fn);
56
- },
57
- removeEventListener: (type: string, fn: () => void) => {
58
- listeners[type] = (listeners[type] ?? []).filter(l => l !== fn);
59
- },
60
- emit: (type: string) => {
61
- for (const fn of [...(listeners[type] ?? [])]) fn();
62
- },
63
- };
64
- muxEl = el;
65
- return el;
66
- }, []);
67
- return <div data-testid="mux" />;
68
- });
69
- Fake.displayName = 'FakeMuxPlayer';
70
- return { default: Fake };
71
- });
72
-
73
- import { Video, VIDEO_PLAYBACK_FAILED_EVENT } from '../video';
74
-
75
- const YT_ORIGIN = 'https://www.youtube-nocookie.com';
76
- const ERROR_TEXT = 'This video failed to play.';
77
-
78
- function ytMessage(body: unknown): void {
79
- window.dispatchEvent(new MessageEvent('message', { data: JSON.stringify(body), origin: YT_ORIGIN }));
80
- }
81
-
82
- function failureEvents(spy: { mock: { calls: unknown[][] } }): CustomEvent[] {
83
- return spy.mock.calls
84
- .map(call => call[0])
85
- .filter((e): e is CustomEvent => e instanceof CustomEvent && e.type === VIDEO_PLAYBACK_FAILED_EVENT);
86
- }
87
-
88
- describe('Video — file playback failure handling', () => {
89
- beforeEach(() => {
90
- vi.useFakeTimers();
91
- muxProps = {};
92
- muxEl = null;
93
- });
94
- afterEach(() => {
95
- vi.runOnlyPendingTimers();
96
- vi.useRealTimers();
97
- // Re-spying on a live spy returns it with its calls intact — restore, or
98
- // the next test counts this one's events.
99
- vi.restoreAllMocks();
100
- });
101
-
102
- it('a mid-playback stall surfaces a retry and reports the failure once', () => {
103
- const dispatched = vi.spyOn(window, 'dispatchEvent');
104
- render(<Video kind="file" url="https://example.com/a.m3u8" layout="centered" />);
105
-
106
- // Buffering while playback is expected — the watchdog arms.
107
- act(() => muxProps.onWaiting?.());
108
- expect(screen.queryByText(ERROR_TEXT)).toBeNull();
109
-
110
- // No progress within the window → failure surfaces.
111
- act(() => {
112
- vi.advanceTimersByTime(15_000);
113
- });
114
- expect(screen.getByText(ERROR_TEXT)).toBeInTheDocument();
115
- expect(screen.getByRole('button', { name: 'Try again' })).toBeInTheDocument();
116
-
117
- const events = failureEvents(dispatched);
118
- expect(events).toHaveLength(1);
119
- expect(events[0].detail).toMatchObject({ kind: 'file', reason: 'stall' });
120
- });
121
-
122
- it('Try again reloads and resumes the element, clearing the error', () => {
123
- render(<Video kind="file" url="https://example.com/a.m3u8" layout="centered" />);
124
- act(() => muxProps.onWaiting?.());
125
- act(() => {
126
- vi.advanceTimersByTime(15_000);
127
- });
128
-
129
- fireEvent.click(screen.getByRole('button', { name: 'Try again' }));
130
- expect(muxEl?.loadCalls).toBe(1);
131
- expect(muxEl?.playCalls).toBe(1);
132
- expect(screen.queryByText(ERROR_TEXT)).toBeNull();
133
- });
134
-
135
- it('Try again resumes from where the stall happened, not from the top', () => {
136
- render(<Video kind="file" url="https://example.com/a.m3u8" layout="centered" />);
137
- if (muxEl) muxEl.currentTime = 480;
138
- act(() => muxProps.onWaiting?.());
139
- act(() => {
140
- vi.advanceTimersByTime(15_000);
141
- });
142
-
143
- fireEvent.click(screen.getByRole('button', { name: 'Try again' }));
144
- expect(muxEl?.currentTime).toBe(0); // reload rewound it
145
- act(() => muxEl?.emit('loadedmetadata'));
146
- expect(muxEl?.currentTime).toBe(480);
147
- // One-shot: a later reload must not drag the old position back.
148
- expect(muxEl?.listeners.loadedmetadata).toHaveLength(0);
149
- });
150
-
151
- it('a hard media error surfaces the retry immediately', () => {
152
- render(<Video kind="file" url="https://example.com/a.m3u8" layout="centered" />);
153
- act(() => muxProps.onError?.());
154
- expect(screen.getByText(ERROR_TEXT)).toBeInTheDocument();
155
- });
156
-
157
- it('a stall that self-heals dismisses its own overlay', () => {
158
- render(<Video kind="file" url="https://example.com/a.m3u8" layout="centered" />);
159
- act(() => muxProps.onWaiting?.());
160
- act(() => {
161
- vi.advanceTimersByTime(15_000);
162
- });
163
- expect(screen.getByText(ERROR_TEXT)).toBeInTheDocument();
164
-
165
- act(() => muxProps.onPlaying?.());
166
- expect(screen.queryByText(ERROR_TEXT)).toBeNull();
167
- });
168
-
169
- it('a paused player is never treated as a stall', () => {
170
- render(<Video kind="file" url="https://example.com/a.m3u8" layout="centered" />);
171
- if (muxEl) muxEl.paused = true;
172
- act(() => muxProps.onWaiting?.());
173
- act(() => {
174
- vi.advanceTimersByTime(15_000);
175
- });
176
- expect(screen.queryByText(ERROR_TEXT)).toBeNull();
177
- });
178
-
179
- it('decorative first-frame previews stay silent on error', () => {
180
- render(<Video kind="file" url="https://example.com/a.m3u8" firstFrameOnly />);
181
- act(() => muxProps.onError?.());
182
- expect(screen.queryByText(ERROR_TEXT)).toBeNull();
183
- });
184
- });
185
-
186
- describe('Video — YouTube facade failure handling', () => {
187
- beforeEach(() => {
188
- vi.useFakeTimers();
189
- });
190
- afterEach(() => {
191
- vi.runOnlyPendingTimers();
192
- vi.useRealTimers();
193
- vi.restoreAllMocks();
194
- });
195
-
196
- function activate(): void {
197
- render(<Video kind="youtube" url="dQw4w9WgXcQ" />);
198
- fireEvent.click(screen.getByRole('button', { name: /Play:/ }));
199
- }
200
-
201
- it('an explicit embed error surfaces retry + a YouTube fallback link', () => {
202
- const dispatched = vi.spyOn(window, 'dispatchEvent');
203
- activate();
204
- act(() => ytMessage({ event: 'onError', info: 150 }));
205
-
206
- expect(screen.getByText(ERROR_TEXT)).toBeInTheDocument();
207
- expect(screen.getByRole('link', { name: 'Watch on YouTube' })).toHaveAttribute(
208
- 'href',
209
- 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
210
- );
211
- expect(failureEvents(dispatched)[0].detail).toMatchObject({
212
- kind: 'youtube',
213
- reason: 'error',
214
- errorCode: 150,
215
- });
216
- });
217
-
218
- // Regression guard: a silent embed is NOT a failed one. Safari/iOS block the
219
- // unmuted autoplay and wait for a click inside the iframe, and a broken jsapi
220
- // channel reports nothing at all — a readiness timeout would tear both down.
221
- it('a silent embed is left alone, however long it stays silent', () => {
222
- const dispatched = vi.spyOn(window, 'dispatchEvent');
223
- activate();
224
- act(() => {
225
- vi.advanceTimersByTime(120_000);
226
- });
227
-
228
- expect(screen.queryByText(ERROR_TEXT)).toBeNull();
229
- expect(screen.getByTitle('YouTube Video')).toBeInTheDocument();
230
- expect(failureEvents(dispatched)).toHaveLength(0);
231
- });
232
-
233
- it('reaching PLAYING keeps the embed and never shows the error', () => {
234
- activate();
235
- act(() => ytMessage({ event: 'infoDelivery', info: { playerState: 1 } }));
236
- act(() => {
237
- vi.advanceTimersByTime(12_000);
238
- });
239
- expect(screen.queryByText(ERROR_TEXT)).toBeNull();
240
- });
241
-
242
- it('Try again remounts the embed', () => {
243
- activate();
244
- act(() => ytMessage({ event: 'onError', info: 150 }));
245
- fireEvent.click(screen.getByRole('button', { name: 'Try again' }));
246
- expect(screen.queryByText(ERROR_TEXT)).toBeNull();
247
- expect(screen.queryByRole('link', { name: 'Watch on YouTube' })).toBeNull();
248
- expect(screen.getByTitle('YouTube Video')).toBeInTheDocument();
249
- });
250
- });