import { vi } from "vitest"
export class MockHTMLVideoElement {
public currentTime = 0
public duration = 100
public paused = true
public playbackRate = 1
public volume = 1
public muted = false
public src = ""
public videoWidth = 1920
public videoHeight = 1080
public readyState = 4 // HAVE_ENOUGH_DATA
public networkState = 2 // NETWORK_LOADED
public play = vi.fn().mockResolvedValue(undefined)
public pause = vi.fn()
public load = vi.fn()
public canPlayType = vi.fn().mockReturnValue("probably")
public addEventListener = vi.fn()
public removeEventListener = vi.fn()
public dispatchEvent = vi.fn()
private eventListeners = new Map void>>()
constructor() {
this.addEventListener.mockImplementation((event: string, handler: (...args: any[]) => void) => {
if (!this.eventListeners.has(event)) {
this.eventListeners.set(event, [])
}
this.eventListeners.get(event)!.push(handler)
})
this.removeEventListener.mockImplementation((event: string, handler: (...args: any[]) => void) => {
const handlers = this.eventListeners.get(event)
if (handlers) {
const index = handlers.indexOf(handler)
if (index > -1) {
handlers.splice(index, 1)
}
}
})
}
// Simulate video events
public simulateTimeUpdate(time: number) {
this.currentTime = time
this.dispatchEvent(new Event("timeupdate"))
this.triggerEvent("timeupdate")
}
public simulateLoadedMetadata(duration = 100, width = 1920, height = 1080) {
this.duration = duration
this.videoWidth = width
this.videoHeight = height
this.readyState = 1 // HAVE_METADATA
this.dispatchEvent(new Event("loadedmetadata"))
this.triggerEvent("loadedmetadata")
}
public simulateLoadedData() {
this.readyState = 2 // HAVE_CURRENT_DATA
this.dispatchEvent(new Event("loadeddata"))
this.triggerEvent("loadeddata")
}
public simulateCanPlay() {
this.readyState = 3 // HAVE_FUTURE_DATA
this.dispatchEvent(new Event("canplay"))
this.triggerEvent("canplay")
}
public simulateCanPlayThrough() {
this.readyState = 4 // HAVE_ENOUGH_DATA
this.dispatchEvent(new Event("canplaythrough"))
this.triggerEvent("canplaythrough")
}
public simulatePlay() {
this.paused = false
this.dispatchEvent(new Event("play"))
this.triggerEvent("play")
}
public simulatePause() {
this.paused = true
this.dispatchEvent(new Event("pause"))
this.triggerEvent("pause")
}
public simulateEnded() {
this.paused = true
this.currentTime = this.duration
this.dispatchEvent(new Event("ended"))
this.triggerEvent("ended")
}
public simulateError(error: any = { code: 4, message: "MEDIA_ELEMENT_ERROR" }) {
const errorEvent = new Event("error")
;(errorEvent as any).error = error
this.dispatchEvent(errorEvent)
this.triggerEvent("error", errorEvent)
}
private triggerEvent(eventType: string, event?: Event) {
const handlers = this.eventListeners.get(eventType)
if (handlers) {
handlers.forEach((handler) => handler(event || new Event(eventType)))
}
}
}
export class MockHTMLAudioElement extends MockHTMLVideoElement {
constructor() {
super()
this.videoWidth = 0
this.videoHeight = 0
}
}
export function setupVideoMocks() {
// Mock HTMLVideoElement
Object.defineProperty(window.HTMLVideoElement.prototype, "play", {
writable: true,
value: vi.fn().mockResolvedValue(undefined),
})
Object.defineProperty(window.HTMLVideoElement.prototype, "pause", {
writable: true,
value: vi.fn(),
})
Object.defineProperty(window.HTMLVideoElement.prototype, "load", {
writable: true,
value: vi.fn(),
})
// Mock HTMLMediaElement for compatibility
Object.defineProperty(window.HTMLMediaElement.prototype, "play", {
writable: true,
value: vi.fn().mockResolvedValue(undefined),
})
Object.defineProperty(window.HTMLMediaElement.prototype, "pause", {
writable: true,
value: vi.fn(),
})
Object.defineProperty(window.HTMLMediaElement.prototype, "load", {
writable: true,
value: vi.fn(),
})
// Mock constructor
global.HTMLVideoElement = MockHTMLVideoElement as any
global.HTMLAudioElement = MockHTMLAudioElement as any
}
// Web Audio API mocks
export function setupAudioMocks() {
global.AudioContext = vi.fn().mockImplementation(() => ({
createMediaElementSource: vi.fn().mockReturnValue({
connect: vi.fn(),
disconnect: vi.fn(),
}),
createMediaStreamDestination: vi.fn().mockReturnValue({
stream: new MediaStream(),
}),
createGain: vi.fn().mockReturnValue({
gain: { value: 1 },
connect: vi.fn(),
disconnect: vi.fn(),
}),
createAnalyser: vi.fn().mockReturnValue({
frequencyBinCount: 1024,
getByteFrequencyData: vi.fn(),
getByteTimeDomainData: vi.fn(),
getFloatTimeDomainData: vi.fn(),
getFloatFrequencyData: vi.fn(),
connect: vi.fn(),
disconnect: vi.fn(),
}),
createDynamicsCompressor: vi.fn().mockReturnValue({
threshold: { value: -24 },
knee: { value: 30 },
ratio: { value: 12 },
attack: { value: 0.003 },
release: { value: 0.25 },
connect: vi.fn(),
disconnect: vi.fn(),
}),
createBiquadFilter: vi.fn().mockReturnValue({
type: "lowpass",
frequency: { value: 350 },
Q: { value: 1 },
gain: { value: 0 },
connect: vi.fn(),
disconnect: vi.fn(),
}),
createConvolver: vi.fn().mockReturnValue({
buffer: null,
normalize: true,
connect: vi.fn(),
disconnect: vi.fn(),
}),
createDelay: vi.fn().mockReturnValue({
delayTime: { value: 0 },
connect: vi.fn(),
disconnect: vi.fn(),
}),
createOscillator: vi.fn().mockReturnValue({
type: "sine",
frequency: { value: 440 },
start: vi.fn(),
stop: vi.fn(),
connect: vi.fn(),
disconnect: vi.fn(),
}),
createWaveShaper: vi.fn().mockReturnValue({
curve: null,
oversample: "none",
connect: vi.fn(),
disconnect: vi.fn(),
}),
destination: {},
close: vi.fn().mockResolvedValue(undefined),
resume: vi.fn().mockResolvedValue(undefined),
suspend: vi.fn().mockResolvedValue(undefined),
state: "running",
sampleRate: 44100,
}))
// MediaRecorder mock
const MockMediaRecorder = vi.fn().mockImplementation(() => ({
start: vi.fn(),
stop: vi.fn(),
pause: vi.fn(),
resume: vi.fn(),
state: "inactive",
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})) as any
MockMediaRecorder.isTypeSupported = vi.fn().mockReturnValue(true)
global.MediaRecorder = MockMediaRecorder
// MediaStream mock
global.MediaStream = vi.fn().mockImplementation(() => ({
getTracks: vi.fn().mockReturnValue([]),
getAudioTracks: vi.fn().mockReturnValue([]),
getVideoTracks: vi.fn().mockReturnValue([]),
addTrack: vi.fn(),
removeTrack: vi.fn(),
clone: vi.fn(),
active: true,
id: "mock-stream-id",
}))
// Mock navigator.mediaDevices for getUserMedia, getDisplayMedia, enumerateDevices
Object.defineProperty(navigator, "mediaDevices", {
writable: true,
value: {
getUserMedia: vi.fn().mockResolvedValue(new MediaStream()),
getDisplayMedia: vi.fn().mockResolvedValue(new MediaStream()),
enumerateDevices: vi.fn().mockResolvedValue([
{
deviceId: "default",
groupId: "group1",
kind: "audioinput",
label: "Default - Microphone (Built-in)",
},
{
deviceId: "default",
groupId: "group2",
kind: "videoinput",
label: "Default - Camera (Built-in)",
},
]),
},
})
}
// Helper to create a mock video element with specific properties
export function createMockVideo(props: Partial = {}) {
const video = new MockHTMLVideoElement()
Object.assign(video, props)
return video
}
// Helper to create a mock audio element
export function createMockAudio(props: Partial = {}) {
const audio = new MockHTMLAudioElement()
Object.assign(audio, props)
return audio
}
// Helper to reset media mocks
export function resetMediaMocks() {
vi.clearAllMocks()
setupVideoMocks()
setupAudioMocks()
}