@flamingo-stack/openframe-frontend-core 0.0.581 → 0.0.582-1960.5386.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/dist/{chunk-R5JVRFSW.js → chunk-4KWAHIYA.js} +3 -3
- package/dist/{chunk-KAEVE7W2.js → chunk-646JEAY4.js} +2 -2
- package/dist/{chunk-GAHKWML7.js → chunk-6EUYXDV3.js} +6 -6
- package/dist/{chunk-HVSZYUGP.js → chunk-7HHRJTIY.js} +5 -5
- package/dist/{chunk-JW53WPB5.js → chunk-A3SVEK7A.js} +3 -3
- package/dist/{chunk-6U45VX5J.cjs → chunk-A5VD6R7N.cjs} +43 -43
- package/dist/{chunk-6U45VX5J.cjs.map → chunk-A5VD6R7N.cjs.map} +1 -1
- package/dist/{chunk-BTKMTVN5.cjs → chunk-ALSLHGBH.cjs} +4 -4
- package/dist/{chunk-BTKMTVN5.cjs.map → chunk-ALSLHGBH.cjs.map} +1 -1
- package/dist/{chunk-SEZ25E22.cjs → chunk-BQPCCBGO.cjs} +10 -10
- package/dist/{chunk-SEZ25E22.cjs.map → chunk-BQPCCBGO.cjs.map} +1 -1
- package/dist/{chunk-LWDIDSCF.js → chunk-BUOYMIFJ.js} +3 -3
- package/dist/{chunk-3GXNCJZI.js → chunk-CR7GPMCM.js} +3 -3
- package/dist/{chunk-U3W2ZVN4.cjs → chunk-DGZPX2G3.cjs} +159 -159
- package/dist/{chunk-U3W2ZVN4.cjs.map → chunk-DGZPX2G3.cjs.map} +1 -1
- package/dist/{chunk-PBRXT7TD.cjs → chunk-DQVM4FTL.cjs} +10 -10
- package/dist/{chunk-PBRXT7TD.cjs.map → chunk-DQVM4FTL.cjs.map} +1 -1
- package/dist/{chunk-HJVKPG55.js → chunk-F227VMPL.js} +125 -15
- package/dist/{chunk-HJVKPG55.js.map → chunk-F227VMPL.js.map} +1 -1
- package/dist/{chunk-EZVABFEO.js → chunk-FRWQ67UM.js} +2 -2
- package/dist/{chunk-DSPQVZFE.cjs → chunk-FYVL6A4Z.cjs} +24 -24
- package/dist/{chunk-DSPQVZFE.cjs.map → chunk-FYVL6A4Z.cjs.map} +1 -1
- package/dist/{chunk-VXUUSLD5.js → chunk-GD3OAM4T.js} +2 -2
- package/dist/{chunk-ESWXXWCL.js → chunk-HQF7VE7B.js} +2 -2
- package/dist/{chunk-VDRCKHML.cjs → chunk-J5I3TKAH.cjs} +3 -3
- package/dist/{chunk-VDRCKHML.cjs.map → chunk-J5I3TKAH.cjs.map} +1 -1
- package/dist/{chunk-GUC4JK6O.cjs → chunk-K5SCFOD5.cjs} +9 -9
- package/dist/{chunk-GUC4JK6O.cjs.map → chunk-K5SCFOD5.cjs.map} +1 -1
- package/dist/{chunk-UKHM2S6I.cjs → chunk-KAOXOYRT.cjs} +15 -15
- package/dist/{chunk-UKHM2S6I.cjs.map → chunk-KAOXOYRT.cjs.map} +1 -1
- package/dist/{chunk-IHFQ5VVR.js → chunk-KBOBPKMU.js} +5 -5
- package/dist/{chunk-GMQEHDN2.cjs → chunk-L4E2TOSF.cjs} +5 -5
- package/dist/{chunk-GMQEHDN2.cjs.map → chunk-L4E2TOSF.cjs.map} +1 -1
- package/dist/{chunk-YU2UJPAT.cjs → chunk-LOJ5KQ6B.cjs} +466 -356
- package/dist/chunk-LOJ5KQ6B.cjs.map +1 -0
- package/dist/{chunk-M4KQWGEL.cjs → chunk-ORQQZQBB.cjs} +32 -32
- package/dist/{chunk-M4KQWGEL.cjs.map → chunk-ORQQZQBB.cjs.map} +1 -1
- package/dist/{chunk-ZEZMGXYV.js → chunk-QDSVTIO5.js} +5 -5
- package/dist/{chunk-KNBXZ22O.cjs → chunk-R3J2ERH2.cjs} +3 -3
- package/dist/{chunk-KNBXZ22O.cjs.map → chunk-R3J2ERH2.cjs.map} +1 -1
- package/dist/{chunk-WHFEG6UU.cjs → chunk-RMOUILHT.cjs} +22 -22
- package/dist/{chunk-WHFEG6UU.cjs.map → chunk-RMOUILHT.cjs.map} +1 -1
- package/dist/{chunk-OKA3DJJ3.js → chunk-SKGMU7TZ.js} +2 -2
- package/dist/{chunk-7CP5KY5W.js → chunk-SVQVAGZK.js} +3 -3
- package/dist/{chunk-W2TXSZTM.cjs → chunk-T32XVOFP.cjs} +21 -21
- package/dist/{chunk-W2TXSZTM.cjs.map → chunk-T32XVOFP.cjs.map} +1 -1
- package/dist/{chunk-GVM37TRU.js → chunk-U2ZRM3YB.js} +4 -4
- package/dist/{chunk-AIALG25H.cjs → chunk-WQ236GO3.cjs} +69 -69
- package/dist/{chunk-AIALG25H.cjs.map → chunk-WQ236GO3.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +9 -9
- package/dist/components/case-studies/index.js +3 -3
- package/dist/components/chat/index.cjs +2 -2
- package/dist/components/chat/index.js +1 -1
- package/dist/components/contact/index.cjs +4 -4
- package/dist/components/contact/index.js +3 -3
- package/dist/components/docs/index.cjs +7 -7
- package/dist/components/docs/index.js +6 -6
- package/dist/components/embeds/index.cjs +4 -4
- package/dist/components/embeds/index.js +3 -3
- package/dist/components/faq/index.cjs +4 -4
- package/dist/components/faq/index.js +3 -3
- package/dist/components/features/index.cjs +5 -3
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.js +4 -2
- package/dist/components/features/video.d.ts +12 -0
- package/dist/components/features/video.d.ts.map +1 -1
- package/dist/components/help-center-pages/index.cjs +23 -23
- package/dist/components/help-center-pages/index.js +14 -14
- package/dist/components/index.cjs +123 -121
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +15 -13
- package/dist/components/index.js.map +1 -1
- package/dist/components/meeting-scheduler/index.cjs +21 -21
- package/dist/components/meeting-scheduler/index.js +2 -2
- package/dist/components/navigation/index.cjs +6 -6
- package/dist/components/navigation/index.js +5 -5
- package/dist/components/onboarding-guides/index.cjs +6 -6
- package/dist/components/onboarding-guides/index.js +5 -5
- package/dist/components/tickets/index.cjs +7 -7
- package/dist/components/tickets/index.js +6 -6
- package/dist/components/ui/index.cjs +3 -3
- package/dist/components/ui/index.js +2 -2
- package/dist/index.cjs +9 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +8 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/features/__tests__/video-playback-failure.test.tsx +250 -0
- package/src/components/features/video.tsx +198 -16
- package/dist/chunk-YU2UJPAT.cjs.map +0 -1
- /package/dist/{chunk-R5JVRFSW.js.map → chunk-4KWAHIYA.js.map} +0 -0
- /package/dist/{chunk-KAEVE7W2.js.map → chunk-646JEAY4.js.map} +0 -0
- /package/dist/{chunk-GAHKWML7.js.map → chunk-6EUYXDV3.js.map} +0 -0
- /package/dist/{chunk-HVSZYUGP.js.map → chunk-7HHRJTIY.js.map} +0 -0
- /package/dist/{chunk-JW53WPB5.js.map → chunk-A3SVEK7A.js.map} +0 -0
- /package/dist/{chunk-LWDIDSCF.js.map → chunk-BUOYMIFJ.js.map} +0 -0
- /package/dist/{chunk-3GXNCJZI.js.map → chunk-CR7GPMCM.js.map} +0 -0
- /package/dist/{chunk-EZVABFEO.js.map → chunk-FRWQ67UM.js.map} +0 -0
- /package/dist/{chunk-VXUUSLD5.js.map → chunk-GD3OAM4T.js.map} +0 -0
- /package/dist/{chunk-ESWXXWCL.js.map → chunk-HQF7VE7B.js.map} +0 -0
- /package/dist/{chunk-IHFQ5VVR.js.map → chunk-KBOBPKMU.js.map} +0 -0
- /package/dist/{chunk-ZEZMGXYV.js.map → chunk-QDSVTIO5.js.map} +0 -0
- /package/dist/{chunk-OKA3DJJ3.js.map → chunk-SKGMU7TZ.js.map} +0 -0
- /package/dist/{chunk-7CP5KY5W.js.map → chunk-SVQVAGZK.js.map} +0 -0
- /package/dist/{chunk-GVM37TRU.js.map → chunk-U2ZRM3YB.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
SoftwareInfo,
|
|
10
10
|
SoftwareSourceBadge,
|
|
11
11
|
ToolBadge
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-CR7GPMCM.js";
|
|
13
13
|
import {
|
|
14
14
|
AppHeader,
|
|
15
15
|
AppLayout,
|
|
@@ -28,14 +28,14 @@ import {
|
|
|
28
28
|
SlidingSidebar,
|
|
29
29
|
TicketAlertsButton,
|
|
30
30
|
TopNavigation
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-U2ZRM3YB.js";
|
|
32
32
|
import {
|
|
33
33
|
MobileNavigationDropdown,
|
|
34
34
|
MultiLevelNavigation,
|
|
35
35
|
StickySectionNav,
|
|
36
36
|
useSectionNavigation
|
|
37
|
-
} from "./chunk-
|
|
38
|
-
import "./chunk-
|
|
37
|
+
} from "./chunk-646JEAY4.js";
|
|
38
|
+
import "./chunk-4KWAHIYA.js";
|
|
39
39
|
import {
|
|
40
40
|
ADMIN_APPROVAL_REQUEST_CONTEXT_TYPE,
|
|
41
41
|
AIEnrichButton,
|
|
@@ -577,7 +577,7 @@ import {
|
|
|
577
577
|
withFirstTouchAttribution,
|
|
578
578
|
withWalkthroughOpenParam,
|
|
579
579
|
writeDismissCookie
|
|
580
|
-
} from "./chunk-
|
|
580
|
+
} from "./chunk-GD3OAM4T.js";
|
|
581
581
|
import "./chunk-L2SLFCXP.js";
|
|
582
582
|
import "./chunk-BJXDHKUX.js";
|
|
583
583
|
import {
|
|
@@ -892,6 +892,7 @@ import {
|
|
|
892
892
|
TooltipProvider,
|
|
893
893
|
TooltipTrigger,
|
|
894
894
|
UnarchiveChatModal,
|
|
895
|
+
VIDEO_PLAYBACK_FAILED_EVENT,
|
|
895
896
|
Video,
|
|
896
897
|
VideoBiteCard,
|
|
897
898
|
VideoBitesStrip,
|
|
@@ -1054,7 +1055,7 @@ import {
|
|
|
1054
1055
|
useVideoOriginPreconnect,
|
|
1055
1056
|
useVideoWarmup,
|
|
1056
1057
|
usesCanonicalStatusStyle
|
|
1057
|
-
} from "./chunk-
|
|
1058
|
+
} from "./chunk-F227VMPL.js";
|
|
1058
1059
|
import {
|
|
1059
1060
|
HEADING_EMOJI_RE,
|
|
1060
1061
|
HUB_HEADER_OFFSET_PX,
|
|
@@ -2504,6 +2505,7 @@ export {
|
|
|
2504
2505
|
UserIcon,
|
|
2505
2506
|
UsersGroupIcon,
|
|
2506
2507
|
UsersIcon,
|
|
2508
|
+
VIDEO_PLAYBACK_FAILED_EVENT,
|
|
2507
2509
|
VendorDirectoryIcon,
|
|
2508
2510
|
VendorShowcaseMainIcon,
|
|
2509
2511
|
VendorsIcon,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/logs-list.tsx","../src/components/log-severity-dot.tsx","../src/types/logs.types.ts","../src/assets/index.ts"],"sourcesContent":["import { type FC, forwardRef, useRef } from 'react';\nimport type { LogsListProps, LogEntry } from '../types/logs.types';\nimport { cn } from '../utils/cn';\nimport { LogSeverityDot } from './log-severity-dot';\nimport { ToolIcon } from './tool-icon';\n\nconst formatTimestamp = (timestamp: string | Date): string => {\n const date = timestamp instanceof Date ? timestamp : new Date(timestamp);\n\n // UTC getters so the timestamp is identical on server (UTC) and client\n // (local) — otherwise React #418 hydration mismatch.\n const year = date.getUTCFullYear();\n const month = String(date.getUTCMonth() + 1).padStart(2, '0');\n const day = String(date.getUTCDate()).padStart(2, '0');\n const hours = String(date.getUTCHours()).padStart(2, '0');\n const minutes = String(date.getUTCMinutes()).padStart(2, '0');\n\n return `${year}/${month}/${day},${hours}:${minutes}`;\n};\n\nconst LogCard: FC<{\n log: LogEntry;\n isLast: boolean;\n showConnector: boolean;\n onClick?: () => void;\n}> = ({ log, isLast, showConnector, onClick }) => {\n return (\n <div className=\"relative\">\n <div\n className={cn(\n 'relative box-border flex w-full items-start gap-3 rounded px-1 py-2',\n 'cursor-pointer transition-colors hover:bg-ods-bg-hover/50',\n )}\n onClick={onClick}\n role=\"button\"\n tabIndex={0}\n onKeyDown={e => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClick?.();\n }\n }}\n >\n <LogSeverityDot severity={log.severity} size=\"md\" />\n\n <div className=\"min-w-0 flex-1 space-y-1\">\n <p className=\"text-ods-text-primary text-h6\">{log.title}</p>\n <div className=\"flex items-center gap-2\">\n <p className=\"text-ods-text-secondary text-h5\">{formatTimestamp(log.timestamp)}</p>\n {log.toolType && <ToolIcon toolType={log.toolType} size={16} />}\n </div>\n </div>\n </div>\n\n {showConnector && !isLast && (\n <div\n className=\"absolute left-[15px] w-[2px] bg-ods-border\"\n style={{\n top: '28px',\n bottom: '-10px',\n }}\n aria-hidden=\"true\"\n />\n )}\n </div>\n );\n};\n\nexport const LogsList = forwardRef<HTMLDivElement, LogsListProps>(\n (\n {\n logs,\n maxHeight = '400px',\n showConnector = true,\n onLogClick,\n loading = false,\n emptyMessage = 'No logs to display',\n className,\n },\n ref,\n ) => {\n const containerRef = useRef<HTMLDivElement>(null);\n\n const isFullHeight = maxHeight === '100%';\n\n const getContainerStyles = () => {\n if (isFullHeight) return undefined;\n return { maxHeight, minHeight: '200px' };\n };\n\n const getContainerClasses = () => {\n if (isFullHeight) return 'h-full';\n return '';\n };\n\n if (loading) {\n return (\n <div\n ref={ref}\n className={cn(\n 'rounded-lg border border-ods-border bg-ods-card p-4',\n 'flex items-center justify-center',\n getContainerClasses(),\n className,\n )}\n style={getContainerStyles()}\n >\n <div className=\"text-ods-text-muted text-h6\">Loading logs...</div>\n </div>\n );\n }\n\n if (logs.length === 0) {\n return (\n <div\n ref={ref}\n className={cn(\n 'rounded-lg border border-ods-border bg-ods-card p-4',\n 'flex items-center justify-center',\n getContainerClasses(),\n className,\n )}\n style={getContainerStyles()}\n >\n <div className=\"text-ods-text-muted text-h6\">{emptyMessage}</div>\n </div>\n );\n }\n\n return (\n <div\n ref={ref}\n className={cn(\n 'relative rounded-lg border border-ods-border bg-ods-card',\n 'flex flex-col overflow-hidden',\n getContainerClasses(),\n className,\n )}\n style={getContainerStyles()}\n >\n <div ref={containerRef} className=\"flex-1 overflow-y-auto px-4 py-3\">\n {logs.map((log, index) => (\n <LogCard\n key={log.id}\n log={log}\n isLast={index === logs.length - 1}\n showConnector={showConnector}\n onClick={() => onLogClick?.(log)}\n />\n ))}\n </div>\n </div>\n );\n },\n);\n\nLogsList.displayName = 'LogsList';\n","import { forwardRef } from 'react';\nimport { type LogSeverityDotProps, LOG_SEVERITY_COLORS, LOG_SEVERITY_LABELS } from '../types/logs.types';\nimport { cn } from '../utils/cn';\n\nexport const LogSeverityDot = forwardRef<HTMLDivElement, LogSeverityDotProps>(\n ({ severity, size = 'md', className }, ref) => {\n const sizeClasses = {\n sm: 'w-4 h-4',\n md: 'w-6 h-6',\n lg: 'w-8 h-8',\n };\n\n const dotSizeClasses = {\n sm: 'w-2 h-2',\n md: 'w-3 h-3',\n lg: 'w-4 h-4',\n };\n\n return (\n <div\n ref={ref}\n className={cn('relative flex shrink-0 items-center justify-center', sizeClasses[size], className)}\n aria-label={`${LOG_SEVERITY_LABELS[severity]} severity`}\n >\n <div\n className={cn('rounded-full', dotSizeClasses[size])}\n style={{ backgroundColor: LOG_SEVERITY_COLORS[severity] }}\n />\n </div>\n );\n },\n);\n\nLogSeverityDot.displayName = 'LogSeverityDot';\n","import type { ToolType } from './tool.types';\n\nexport type LogSeverity = 'DEBUG' | 'INFO' | 'WARNING' | 'ERROR' | 'CRITICAL';\n\nexport interface LogEntry {\n id: string;\n ingestDay: string;\n eventType: string;\n severity: LogSeverity;\n title: string;\n timestamp: string | Date;\n toolType?: ToolType;\n toolIcon?: string;\n message?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface LogsListProps {\n logs: LogEntry[];\n maxHeight?: string | number;\n showConnector?: boolean;\n onLogClick?: (log: LogEntry) => void;\n loading?: boolean;\n emptyMessage?: string;\n className?: string;\n}\n\nexport interface LogSeverityDotProps {\n severity: LogSeverity;\n size?: 'sm' | 'md' | 'lg';\n className?: string;\n}\n\nexport const LOG_SEVERITY_COLORS: Record<LogSeverity, string> = {\n DEBUG: '#888888',\n INFO: '#888888',\n WARNING: '#e1b32f',\n ERROR: '#f36666',\n CRITICAL: '#b43b3b',\n};\n\nexport const LOG_SEVERITY_LABELS: Record<LogSeverity, string> = {\n DEBUG: 'Debug',\n INFO: 'Info',\n WARNING: 'Warning',\n ERROR: 'Error',\n CRITICAL: 'Critical',\n};\n","// Assets exports\n// Font files and other static assets\n// Platform utilities will be available when needed\n\nexport const assets = {} as const;\nexport * from './fae-avatar';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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","names":["forwardRef","jsx","forwardRef"]}
|
|
1
|
+
{"version":3,"sources":["../src/components/logs-list.tsx","../src/components/log-severity-dot.tsx","../src/types/logs.types.ts","../src/assets/index.ts"],"sourcesContent":["import { type FC, forwardRef, useRef } from 'react';\nimport type { LogsListProps, LogEntry } from '../types/logs.types';\nimport { cn } from '../utils/cn';\nimport { LogSeverityDot } from './log-severity-dot';\nimport { ToolIcon } from './tool-icon';\n\nconst formatTimestamp = (timestamp: string | Date): string => {\n const date = timestamp instanceof Date ? timestamp : new Date(timestamp);\n\n // UTC getters so the timestamp is identical on server (UTC) and client\n // (local) — otherwise React #418 hydration mismatch.\n const year = date.getUTCFullYear();\n const month = String(date.getUTCMonth() + 1).padStart(2, '0');\n const day = String(date.getUTCDate()).padStart(2, '0');\n const hours = String(date.getUTCHours()).padStart(2, '0');\n const minutes = String(date.getUTCMinutes()).padStart(2, '0');\n\n return `${year}/${month}/${day},${hours}:${minutes}`;\n};\n\nconst LogCard: FC<{\n log: LogEntry;\n isLast: boolean;\n showConnector: boolean;\n onClick?: () => void;\n}> = ({ log, isLast, showConnector, onClick }) => {\n return (\n <div className=\"relative\">\n <div\n className={cn(\n 'relative box-border flex w-full items-start gap-3 rounded px-1 py-2',\n 'cursor-pointer transition-colors hover:bg-ods-bg-hover/50',\n )}\n onClick={onClick}\n role=\"button\"\n tabIndex={0}\n onKeyDown={e => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClick?.();\n }\n }}\n >\n <LogSeverityDot severity={log.severity} size=\"md\" />\n\n <div className=\"min-w-0 flex-1 space-y-1\">\n <p className=\"text-ods-text-primary text-h6\">{log.title}</p>\n <div className=\"flex items-center gap-2\">\n <p className=\"text-ods-text-secondary text-h5\">{formatTimestamp(log.timestamp)}</p>\n {log.toolType && <ToolIcon toolType={log.toolType} size={16} />}\n </div>\n </div>\n </div>\n\n {showConnector && !isLast && (\n <div\n className=\"absolute left-[15px] w-[2px] bg-ods-border\"\n style={{\n top: '28px',\n bottom: '-10px',\n }}\n aria-hidden=\"true\"\n />\n )}\n </div>\n );\n};\n\nexport const LogsList = forwardRef<HTMLDivElement, LogsListProps>(\n (\n {\n logs,\n maxHeight = '400px',\n showConnector = true,\n onLogClick,\n loading = false,\n emptyMessage = 'No logs to display',\n className,\n },\n ref,\n ) => {\n const containerRef = useRef<HTMLDivElement>(null);\n\n const isFullHeight = maxHeight === '100%';\n\n const getContainerStyles = () => {\n if (isFullHeight) return undefined;\n return { maxHeight, minHeight: '200px' };\n };\n\n const getContainerClasses = () => {\n if (isFullHeight) return 'h-full';\n return '';\n };\n\n if (loading) {\n return (\n <div\n ref={ref}\n className={cn(\n 'rounded-lg border border-ods-border bg-ods-card p-4',\n 'flex items-center justify-center',\n getContainerClasses(),\n className,\n )}\n style={getContainerStyles()}\n >\n <div className=\"text-ods-text-muted text-h6\">Loading logs...</div>\n </div>\n );\n }\n\n if (logs.length === 0) {\n return (\n <div\n ref={ref}\n className={cn(\n 'rounded-lg border border-ods-border bg-ods-card p-4',\n 'flex items-center justify-center',\n getContainerClasses(),\n className,\n )}\n style={getContainerStyles()}\n >\n <div className=\"text-ods-text-muted text-h6\">{emptyMessage}</div>\n </div>\n );\n }\n\n return (\n <div\n ref={ref}\n className={cn(\n 'relative rounded-lg border border-ods-border bg-ods-card',\n 'flex flex-col overflow-hidden',\n getContainerClasses(),\n className,\n )}\n style={getContainerStyles()}\n >\n <div ref={containerRef} className=\"flex-1 overflow-y-auto px-4 py-3\">\n {logs.map((log, index) => (\n <LogCard\n key={log.id}\n log={log}\n isLast={index === logs.length - 1}\n showConnector={showConnector}\n onClick={() => onLogClick?.(log)}\n />\n ))}\n </div>\n </div>\n );\n },\n);\n\nLogsList.displayName = 'LogsList';\n","import { forwardRef } from 'react';\nimport { type LogSeverityDotProps, LOG_SEVERITY_COLORS, LOG_SEVERITY_LABELS } from '../types/logs.types';\nimport { cn } from '../utils/cn';\n\nexport const LogSeverityDot = forwardRef<HTMLDivElement, LogSeverityDotProps>(\n ({ severity, size = 'md', className }, ref) => {\n const sizeClasses = {\n sm: 'w-4 h-4',\n md: 'w-6 h-6',\n lg: 'w-8 h-8',\n };\n\n const dotSizeClasses = {\n sm: 'w-2 h-2',\n md: 'w-3 h-3',\n lg: 'w-4 h-4',\n };\n\n return (\n <div\n ref={ref}\n className={cn('relative flex shrink-0 items-center justify-center', sizeClasses[size], className)}\n aria-label={`${LOG_SEVERITY_LABELS[severity]} severity`}\n >\n <div\n className={cn('rounded-full', dotSizeClasses[size])}\n style={{ backgroundColor: LOG_SEVERITY_COLORS[severity] }}\n />\n </div>\n );\n },\n);\n\nLogSeverityDot.displayName = 'LogSeverityDot';\n","import type { ToolType } from './tool.types';\n\nexport type LogSeverity = 'DEBUG' | 'INFO' | 'WARNING' | 'ERROR' | 'CRITICAL';\n\nexport interface LogEntry {\n id: string;\n ingestDay: string;\n eventType: string;\n severity: LogSeverity;\n title: string;\n timestamp: string | Date;\n toolType?: ToolType;\n toolIcon?: string;\n message?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface LogsListProps {\n logs: LogEntry[];\n maxHeight?: string | number;\n showConnector?: boolean;\n onLogClick?: (log: LogEntry) => void;\n loading?: boolean;\n emptyMessage?: string;\n className?: string;\n}\n\nexport interface LogSeverityDotProps {\n severity: LogSeverity;\n size?: 'sm' | 'md' | 'lg';\n className?: string;\n}\n\nexport const LOG_SEVERITY_COLORS: Record<LogSeverity, string> = {\n DEBUG: '#888888',\n INFO: '#888888',\n WARNING: '#e1b32f',\n ERROR: '#f36666',\n CRITICAL: '#b43b3b',\n};\n\nexport const LOG_SEVERITY_LABELS: Record<LogSeverity, string> = {\n DEBUG: 'Debug',\n INFO: 'Info',\n WARNING: 'Warning',\n ERROR: 'Error',\n CRITICAL: 'Critical',\n};\n","// Assets exports\n// Font files and other static assets\n// Platform utilities will be available when needed\n\nexport const assets = {} as const;\nexport * from './fae-avatar';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAkB,cAAAA,aAAY,cAAc;;;ACA5C,SAAS,kBAAkB;;;ACiCpB,IAAM,sBAAmD;AAAA,EAC9D,OAAO;AAAA,EACP,MAAM;AAAA,EACN,SAAS;AAAA,EACT,OAAO;AAAA,EACP,UAAU;AACZ;AAEO,IAAM,sBAAmD;AAAA,EAC9D,OAAO;AAAA,EACP,MAAM;AAAA,EACN,SAAS;AAAA,EACT,OAAO;AAAA,EACP,UAAU;AACZ;;;ADvBQ;AApBD,IAAM,iBAAiB;AAAA,EAC5B,CAAC,EAAE,UAAU,OAAO,MAAM,UAAU,GAAG,QAAQ;AAC7C,UAAM,cAAc;AAAA,MAClB,IAAI;AAAA,MACJ,IAAI;AAAA,MACJ,IAAI;AAAA,IACN;AAEA,UAAM,iBAAiB;AAAA,MACrB,IAAI;AAAA,MACJ,IAAI;AAAA,MACJ,IAAI;AAAA,IACN;AAEA,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,WAAW,GAAG,sDAAsD,YAAY,IAAI,GAAG,SAAS;AAAA,QAChG,cAAY,GAAG,oBAAoB,QAAQ,CAAC;AAAA,QAE5C;AAAA,UAAC;AAAA;AAAA,YACC,WAAW,GAAG,gBAAgB,eAAe,IAAI,CAAC;AAAA,YAClD,OAAO,EAAE,iBAAiB,oBAAoB,QAAQ,EAAE;AAAA;AAAA,QAC1D;AAAA;AAAA,IACF;AAAA,EAEJ;AACF;AAEA,eAAe,cAAc;;;ADUrB,gBAAAC,MAIE,YAJF;AArCR,IAAM,kBAAkB,CAAC,cAAqC;AAC5D,QAAM,OAAO,qBAAqB,OAAO,YAAY,IAAI,KAAK,SAAS;AAIvE,QAAM,OAAO,KAAK,eAAe;AACjC,QAAM,QAAQ,OAAO,KAAK,YAAY,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG;AAC5D,QAAM,MAAM,OAAO,KAAK,WAAW,CAAC,EAAE,SAAS,GAAG,GAAG;AACrD,QAAM,QAAQ,OAAO,KAAK,YAAY,CAAC,EAAE,SAAS,GAAG,GAAG;AACxD,QAAM,UAAU,OAAO,KAAK,cAAc,CAAC,EAAE,SAAS,GAAG,GAAG;AAE5D,SAAO,GAAG,IAAI,IAAI,KAAK,IAAI,GAAG,IAAI,KAAK,IAAI,OAAO;AACpD;AAEA,IAAM,UAKD,CAAC,EAAE,KAAK,QAAQ,eAAe,QAAQ,MAAM;AAChD,SACE,qBAAC,SAAI,WAAU,YACb;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,WAAW;AAAA,UACT;AAAA,UACA;AAAA,QACF;AAAA,QACA;AAAA,QACA,MAAK;AAAA,QACL,UAAU;AAAA,QACV,WAAW,OAAK;AACd,cAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;AACtC,cAAE,eAAe;AACjB,sBAAU;AAAA,UACZ;AAAA,QACF;AAAA,QAEA;AAAA,0BAAAA,KAAC,kBAAe,UAAU,IAAI,UAAU,MAAK,MAAK;AAAA,UAElD,qBAAC,SAAI,WAAU,4BACb;AAAA,4BAAAA,KAAC,OAAE,WAAU,iCAAiC,cAAI,OAAM;AAAA,YACxD,qBAAC,SAAI,WAAU,2BACb;AAAA,8BAAAA,KAAC,OAAE,WAAU,mCAAmC,0BAAgB,IAAI,SAAS,GAAE;AAAA,cAC9E,IAAI,YAAY,gBAAAA,KAAC,YAAS,UAAU,IAAI,UAAU,MAAM,IAAI;AAAA,eAC/D;AAAA,aACF;AAAA;AAAA;AAAA,IACF;AAAA,IAEC,iBAAiB,CAAC,UACjB,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,OAAO;AAAA,UACL,KAAK;AAAA,UACL,QAAQ;AAAA,QACV;AAAA,QACA,eAAY;AAAA;AAAA,IACd;AAAA,KAEJ;AAEJ;AAEO,IAAM,WAAWC;AAAA,EACtB,CACE;AAAA,IACE;AAAA,IACA,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB;AAAA,IACA,UAAU;AAAA,IACV,eAAe;AAAA,IACf;AAAA,EACF,GACA,QACG;AACH,UAAM,eAAe,OAAuB,IAAI;AAEhD,UAAM,eAAe,cAAc;AAEnC,UAAM,qBAAqB,MAAM;AAC/B,UAAI,aAAc,QAAO;AACzB,aAAO,EAAE,WAAW,WAAW,QAAQ;AAAA,IACzC;AAEA,UAAM,sBAAsB,MAAM;AAChC,UAAI,aAAc,QAAO;AACzB,aAAO;AAAA,IACT;AAEA,QAAI,SAAS;AACX,aACE,gBAAAD;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,WAAW;AAAA,YACT;AAAA,YACA;AAAA,YACA,oBAAoB;AAAA,YACpB;AAAA,UACF;AAAA,UACA,OAAO,mBAAmB;AAAA,UAE1B,0BAAAA,KAAC,SAAI,WAAU,+BAA8B,6BAAe;AAAA;AAAA,MAC9D;AAAA,IAEJ;AAEA,QAAI,KAAK,WAAW,GAAG;AACrB,aACE,gBAAAA;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,WAAW;AAAA,YACT;AAAA,YACA;AAAA,YACA,oBAAoB;AAAA,YACpB;AAAA,UACF;AAAA,UACA,OAAO,mBAAmB;AAAA,UAE1B,0BAAAA,KAAC,SAAI,WAAU,+BAA+B,wBAAa;AAAA;AAAA,MAC7D;AAAA,IAEJ;AAEA,WACE,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA,oBAAoB;AAAA,UACpB;AAAA,QACF;AAAA,QACA,OAAO,mBAAmB;AAAA,QAE1B,0BAAAA,KAAC,SAAI,KAAK,cAAc,WAAU,oCAC/B,eAAK,IAAI,CAAC,KAAK,UACd,gBAAAA;AAAA,UAAC;AAAA;AAAA,YAEC;AAAA,YACA,QAAQ,UAAU,KAAK,SAAS;AAAA,YAChC;AAAA,YACA,SAAS,MAAM,aAAa,GAAG;AAAA;AAAA,UAJ1B,IAAI;AAAA,QAKX,CACD,GACH;AAAA;AAAA,IACF;AAAA,EAEJ;AACF;AAEA,SAAS,cAAc;;;AGxJhB,IAAM,SAAS,CAAC;","names":["forwardRef","jsx","forwardRef"]}
|
package/package.json
CHANGED
|
@@ -0,0 +1,250 @@
|
|
|
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
|
+
});
|