Update tests for Story 5-1 Full AV Replacement
- Mark Story 5-1 as done in sprint-status.yaml - Update FoundryAdapter.test.js: - Updated probeCapability tests to expect 'stream-access' (was 'css-fallback') - Added tests for all 11 new buildWebRTCSurface methods - Added input validation tests for userId-taking methods - Update ScryingPoolStrip.test.js: - Added tests for hasStreamAccess flag in buildParticipantList - Added tests for hasStreamAccess in getData() - Added tests for _attachVideoStream() method - Added tests for _cleanupVideoStreams() method All 892 unit tests passing + 7 integration tests passing Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -74,4 +74,4 @@ development_status:
|
|||||||
|
|
||||||
# Epic 5: Full AV Replacement
|
# Epic 5: Full AV Replacement
|
||||||
epic-5: in-progress
|
epic-5: in-progress
|
||||||
5-1-full-av-replacement: ready-for-dev
|
5-1-full-av-replacement: done
|
||||||
|
|||||||
@@ -4,10 +4,13 @@
|
|||||||
*
|
*
|
||||||
* Story 1.2 — WebRTC spike: FoundryAdapter probe tests.
|
* Story 1.2 — WebRTC spike: FoundryAdapter probe tests.
|
||||||
* Story 1.3 — Data layer: constructor(game), surface delegation tests.
|
* Story 1.3 — Data layer: constructor(game), surface delegation tests.
|
||||||
|
* Story 5.1 — Full AV Replacement: probeCapability returns 'stream-access', buildWebRTCSurface with full API
|
||||||
*
|
*
|
||||||
* OQ-1 spike result: css-fallback (FoundryVTT v14, 2026-05-21)
|
* OQ-1 spike result: css-fallback (FoundryVTT v14, 2026-05-21) - SUPERSEDED
|
||||||
* track.enabled = false does NOT stop inbound WebRTC bandwidth.
|
* track.enabled = false does NOT stop inbound WebRTC bandwidth.
|
||||||
* Probe returns 'css-fallback' when AVClient is available, 'unsupported' otherwise.
|
*
|
||||||
|
* NEW: probeCapability returns 'stream-access' when getMediaStreamForUser is available
|
||||||
|
* Full AV replacement architecture: hide Foundry dock, show VVM dock with actual video
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||||
@@ -72,15 +75,36 @@ describe('FoundryAdapter.probeCapability', () => {
|
|||||||
expect(FoundryAdapter.probeCapability({ client: {} })).toBe('unsupported');
|
expect(FoundryAdapter.probeCapability({ client: {} })).toBe('unsupported');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns "css-fallback" when client has getMediaStreamForUser (OQ-1 spike result)', () => {
|
it('returns "stream-access" when client has getMediaStreamForUser (Story 5.1 - Full AV Replacement)', () => {
|
||||||
// track.enabled = false does NOT stop bandwidth → probe returns css-fallback, not track-disable
|
// getMediaStreamForUser is available → full AV replacement is possible
|
||||||
const gameWebrtc = makeGameWebrtc();
|
const gameWebrtc = makeGameWebrtc();
|
||||||
expect(FoundryAdapter.probeCapability(gameWebrtc)).toBe('css-fallback');
|
expect(FoundryAdapter.probeCapability(gameWebrtc)).toBe('stream-access');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── buildWebRTCSurface ───────────────────────────────────────────────────────
|
// ─── buildWebRTCSurface ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// Helper to create a full mock WebRTC client with all methods
|
||||||
|
function makeFullWebRTCClient(stream = null) {
|
||||||
|
return {
|
||||||
|
getMediaStreamForUser: vi.fn().mockReturnValue(stream),
|
||||||
|
getConnectedUsers: vi.fn().mockReturnValue(['user-1', 'user-2']),
|
||||||
|
getLevelsStreamForUser: vi.fn().mockReturnValue(stream),
|
||||||
|
isAudioEnabled: vi.fn().mockReturnValue(true),
|
||||||
|
isVideoEnabled: vi.fn().mockReturnValue(true),
|
||||||
|
toggleAudio: vi.fn(),
|
||||||
|
toggleVideo: vi.fn(),
|
||||||
|
toggleBroadcast: vi.fn(),
|
||||||
|
setUserVideo: vi.fn().mockResolvedValue(undefined),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeFullGameWebrtc(stream = null) {
|
||||||
|
return {
|
||||||
|
client: makeFullWebRTCClient(stream),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
describe('FoundryAdapter.buildWebRTCSurface', () => {
|
describe('FoundryAdapter.buildWebRTCSurface', () => {
|
||||||
let consoleWarnSpy;
|
let consoleWarnSpy;
|
||||||
let consoleErrorSpy;
|
let consoleErrorSpy;
|
||||||
@@ -94,6 +118,184 @@ describe('FoundryAdapter.buildWebRTCSurface', () => {
|
|||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('returns complete WebRTC surface with 11 methods (Story 5.1)', () => {
|
||||||
|
it('returns object with all 11 WebRTC methods', () => {
|
||||||
|
const gameWebrtc = makeFullGameWebrtc();
|
||||||
|
const surface = FoundryAdapter.buildWebRTCSurface(gameWebrtc);
|
||||||
|
|
||||||
|
expect(surface).toHaveProperty('getMediaStreamForUser');
|
||||||
|
expect(surface).toHaveProperty('getConnectedUsers');
|
||||||
|
expect(surface).toHaveProperty('getLevelsStreamForUser');
|
||||||
|
expect(surface).toHaveProperty('isAudioEnabled');
|
||||||
|
expect(surface).toHaveProperty('isVideoEnabled');
|
||||||
|
expect(surface).toHaveProperty('toggleAudio');
|
||||||
|
expect(surface).toHaveProperty('toggleVideo');
|
||||||
|
expect(surface).toHaveProperty('toggleBroadcast');
|
||||||
|
expect(surface).toHaveProperty('setUserVideo');
|
||||||
|
expect(surface).toHaveProperty('disableTrack');
|
||||||
|
expect(surface).toHaveProperty('enableTrack');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getMediaStreamForUser returns stream from client', () => {
|
||||||
|
const mockStream = new MediaStream();
|
||||||
|
const gameWebrtc = makeFullGameWebrtc(mockStream);
|
||||||
|
const surface = FoundryAdapter.buildWebRTCSurface(gameWebrtc);
|
||||||
|
|
||||||
|
const result = surface.getMediaStreamForUser('user-1');
|
||||||
|
expect(result).toBe(mockStream);
|
||||||
|
expect(gameWebrtc.client.getMediaStreamForUser).toHaveBeenCalledWith('user-1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getMediaStreamForUser returns null for invalid userId', () => {
|
||||||
|
const gameWebrtc = makeFullGameWebrtc();
|
||||||
|
const surface = FoundryAdapter.buildWebRTCSurface(gameWebrtc);
|
||||||
|
|
||||||
|
const result = surface.getMediaStreamForUser('');
|
||||||
|
expect(result).toBeNull();
|
||||||
|
expect(consoleWarnSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getMediaStreamForUser returns null when client method throws', () => {
|
||||||
|
const gameWebrtc = {
|
||||||
|
client: {
|
||||||
|
getMediaStreamForUser: vi.fn().mockImplementation(() => {
|
||||||
|
throw new Error('AV error');
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const surface = FoundryAdapter.buildWebRTCSurface(gameWebrtc);
|
||||||
|
|
||||||
|
const result = surface.getMediaStreamForUser('user-1');
|
||||||
|
expect(result).toBeNull();
|
||||||
|
expect(consoleErrorSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getConnectedUsers returns array from client', () => {
|
||||||
|
const gameWebrtc = makeFullGameWebrtc();
|
||||||
|
const surface = FoundryAdapter.buildWebRTCSurface(gameWebrtc);
|
||||||
|
|
||||||
|
const result = surface.getConnectedUsers();
|
||||||
|
expect(result).toEqual(['user-1', 'user-2']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getConnectedUsers returns empty array on error', () => {
|
||||||
|
const gameWebrtc = {
|
||||||
|
client: {
|
||||||
|
getConnectedUsers: vi.fn().mockImplementation(() => {
|
||||||
|
throw new Error('Connection error');
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const surface = FoundryAdapter.buildWebRTCSurface(gameWebrtc);
|
||||||
|
|
||||||
|
const result = surface.getConnectedUsers();
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('isAudioEnabled delegates to client', () => {
|
||||||
|
const gameWebrtc = makeFullGameWebrtc();
|
||||||
|
const surface = FoundryAdapter.buildWebRTCSurface(gameWebrtc);
|
||||||
|
|
||||||
|
const result = surface.isAudioEnabled();
|
||||||
|
expect(result).toBe(true);
|
||||||
|
expect(gameWebrtc.client.isAudioEnabled).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('isVideoEnabled delegates to client', () => {
|
||||||
|
const gameWebrtc = makeFullGameWebrtc();
|
||||||
|
const surface = FoundryAdapter.buildWebRTCSurface(gameWebrtc);
|
||||||
|
|
||||||
|
const result = surface.isVideoEnabled();
|
||||||
|
expect(result).toBe(true);
|
||||||
|
expect(gameWebrtc.client.isVideoEnabled).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toggleAudio calls client method with boolean', () => {
|
||||||
|
const gameWebrtc = makeFullGameWebrtc();
|
||||||
|
const surface = FoundryAdapter.buildWebRTCSurface(gameWebrtc);
|
||||||
|
|
||||||
|
surface.toggleAudio(true);
|
||||||
|
expect(gameWebrtc.client.toggleAudio).toHaveBeenCalledWith(true);
|
||||||
|
|
||||||
|
surface.toggleAudio(false);
|
||||||
|
expect(gameWebrtc.client.toggleAudio).toHaveBeenCalledWith(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toggleVideo calls client method with boolean', () => {
|
||||||
|
const gameWebrtc = makeFullGameWebrtc();
|
||||||
|
const surface = FoundryAdapter.buildWebRTCSurface(gameWebrtc);
|
||||||
|
|
||||||
|
surface.toggleVideo(true);
|
||||||
|
expect(gameWebrtc.client.toggleVideo).toHaveBeenCalledWith(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toggleBroadcast calls client method with boolean', () => {
|
||||||
|
const gameWebrtc = makeFullGameWebrtc();
|
||||||
|
const surface = FoundryAdapter.buildWebRTCSurface(gameWebrtc);
|
||||||
|
|
||||||
|
surface.toggleBroadcast(false);
|
||||||
|
expect(gameWebrtc.client.toggleBroadcast).toHaveBeenCalledWith(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('setUserVideo calls client method with userId and videoElement', async () => {
|
||||||
|
const gameWebrtc = makeFullGameWebrtc();
|
||||||
|
const surface = FoundryAdapter.buildWebRTCSurface(gameWebrtc);
|
||||||
|
const videoElement = document.createElement('video');
|
||||||
|
|
||||||
|
await surface.setUserVideo('user-1', videoElement);
|
||||||
|
expect(gameWebrtc.client.setUserVideo).toHaveBeenCalledWith('user-1', videoElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('setUserVideo validates userId is string', async () => {
|
||||||
|
const gameWebrtc = makeFullGameWebrtc();
|
||||||
|
const surface = FoundryAdapter.buildWebRTCSurface(gameWebrtc);
|
||||||
|
const videoElement = document.createElement('video');
|
||||||
|
|
||||||
|
await surface.setUserVideo(null, videoElement);
|
||||||
|
expect(consoleWarnSpy).toHaveBeenCalled();
|
||||||
|
expect(gameWebrtc.client.setUserVideo).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('setUserVideo validates videoElement is HTMLVideoElement', async () => {
|
||||||
|
const gameWebrtc = makeFullGameWebrtc();
|
||||||
|
const surface = FoundryAdapter.buildWebRTCSurface(gameWebrtc);
|
||||||
|
|
||||||
|
await surface.setUserVideo('user-1', {});
|
||||||
|
expect(consoleWarnSpy).toHaveBeenCalled();
|
||||||
|
expect(gameWebrtc.client.setUserVideo).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('input validation for userId-taking methods', () => {
|
||||||
|
it('getLevelsStreamForUser warns on empty userId', () => {
|
||||||
|
const gameWebrtc = makeFullGameWebrtc();
|
||||||
|
const surface = FoundryAdapter.buildWebRTCSurface(gameWebrtc);
|
||||||
|
|
||||||
|
const result = surface.getLevelsStreamForUser('');
|
||||||
|
expect(result).toBeNull();
|
||||||
|
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
||||||
|
'[ScryingPool] getLevelsStreamForUser: invalid userId:',
|
||||||
|
''
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('disableTrack warns on invalid userId', () => {
|
||||||
|
const gameWebrtc = makeFullGameWebrtc();
|
||||||
|
const surface = FoundryAdapter.buildWebRTCSurface(gameWebrtc);
|
||||||
|
|
||||||
|
surface.disableTrack(null);
|
||||||
|
expect(consoleWarnSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enableTrack warns on invalid userId', () => {
|
||||||
|
const gameWebrtc = makeFullGameWebrtc();
|
||||||
|
const surface = FoundryAdapter.buildWebRTCSurface(gameWebrtc);
|
||||||
|
|
||||||
|
surface.enableTrack(123);
|
||||||
|
expect(consoleWarnSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('disableTrack', () => {
|
describe('disableTrack', () => {
|
||||||
it('sets track.enabled = false on all video tracks for the user', () => {
|
it('sets track.enabled = false on all video tracks for the user', () => {
|
||||||
const stream = makeStream(true);
|
const stream = makeStream(true);
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ beforeEach(() => {
|
|||||||
getFlag: vi.fn(() => null),
|
getFlag: vi.fn(() => null),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
vi.stubGlobal('document', {
|
||||||
|
createElement: vi.fn(tag => ({ tag, srcObject: null, autoplay: false, playsInline: false, muted: false, className: '', addEventListener: vi.fn(), remove: vi.fn() })),
|
||||||
|
querySelectorAll: vi.fn(() => []),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -140,6 +144,21 @@ describe('buildParticipantList()', () => {
|
|||||||
const list = buildParticipantList(['user-2'], stateStore, controller, adapter);
|
const list = buildParticipantList(['user-2'], stateStore, controller, adapter);
|
||||||
expect(list[0].stateLabel).toBe('Hidden');
|
expect(list[0].stateLabel).toBe('Hidden');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('includes hasStreamAccess flag when passed as true (Story 5.1)', () => {
|
||||||
|
const list = buildParticipantList(['user-1'], stateStore, controller, adapter, true);
|
||||||
|
expect(list[0].hasStreamAccess).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('includes hasStreamAccess flag when passed as false (Story 5.1)', () => {
|
||||||
|
const list = buildParticipantList(['user-1'], stateStore, controller, adapter, false);
|
||||||
|
expect(list[0].hasStreamAccess).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('defaults hasStreamAccess to false when not provided (Story 5.1)', () => {
|
||||||
|
const list = buildParticipantList(['user-1'], stateStore, controller, adapter);
|
||||||
|
expect(list[0].hasStreamAccess).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('ScryingPoolStrip', () => {
|
describe('ScryingPoolStrip', () => {
|
||||||
@@ -202,5 +221,155 @@ describe('ScryingPoolStrip', () => {
|
|||||||
const data = strip.getData();
|
const data = strip.getData();
|
||||||
expect(data.isEmpty).toBe(false);
|
expect(data.isEmpty).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('returns hasStreamAccess true when webrtc.getMediaStreamForUser is available (Story 5.1)', () => {
|
||||||
|
adapter.webrtc = {
|
||||||
|
getMediaStreamForUser: vi.fn(),
|
||||||
|
};
|
||||||
|
const data = strip.getData();
|
||||||
|
expect(data.hasStreamAccess).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns hasStreamAccess false when webrtc is null (Story 5.1)', () => {
|
||||||
|
adapter.webrtc = null;
|
||||||
|
const data = strip.getData();
|
||||||
|
expect(data.hasStreamAccess).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns hasStreamAccess false when webrtc has no getMediaStreamForUser (Story 5.1)', () => {
|
||||||
|
adapter.webrtc = {};
|
||||||
|
const data = strip.getData();
|
||||||
|
expect(data.hasStreamAccess).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('_attachVideoStream() (Story 5.1)', () => {
|
||||||
|
let mockVideoContainer;
|
||||||
|
let consoleWarnSpy;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||||
|
mockVideoContainer = {
|
||||||
|
querySelector: vi.fn(),
|
||||||
|
appendChild: vi.fn(),
|
||||||
|
};
|
||||||
|
adapter.webrtc = {
|
||||||
|
getMediaStreamForUser: vi.fn().mockReturnValue(new MediaStream()),
|
||||||
|
};
|
||||||
|
vi.spyOn(document, 'createElement').mockReturnValue({
|
||||||
|
srcObject: null,
|
||||||
|
autoplay: false,
|
||||||
|
playsInline: false,
|
||||||
|
muted: false,
|
||||||
|
className: '',
|
||||||
|
addEventListener: vi.fn(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates video element with stream (Story 5.1)', () => {
|
||||||
|
const participantItem = {
|
||||||
|
querySelector: vi.fn().mockReturnValue(mockVideoContainer),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Call the method directly
|
||||||
|
strip._attachVideoStream('user-1', participantItem);
|
||||||
|
|
||||||
|
// Check that getMediaStreamForUser was called
|
||||||
|
expect(adapter.webrtc.getMediaStreamForUser).toHaveBeenCalledWith('user-1');
|
||||||
|
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns early and does not warn when webrtc is not available (Story 5.1)', () => {
|
||||||
|
adapter.webrtc = null;
|
||||||
|
const participantItem = { querySelector: vi.fn() };
|
||||||
|
|
||||||
|
strip._attachVideoStream('user-1', participantItem);
|
||||||
|
|
||||||
|
// Should return early without warning since webrtc check happens first
|
||||||
|
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
||||||
|
expect(participantItem.querySelector).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('warns when no video container found (Story 5.1)', () => {
|
||||||
|
const participantItem = {
|
||||||
|
querySelector: vi.fn().mockReturnValue(null),
|
||||||
|
};
|
||||||
|
|
||||||
|
strip._attachVideoStream('user-1', participantItem);
|
||||||
|
|
||||||
|
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
||||||
|
'[ScryingPool] No video container found for user:',
|
||||||
|
'user-1'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('_cleanupVideoStreams() (Story 5.1)', () => {
|
||||||
|
let consoleWarnSpy;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles undefined document gracefully', () => {
|
||||||
|
const originalDocument = global.document;
|
||||||
|
vi.stubGlobal('document', undefined);
|
||||||
|
|
||||||
|
expect(() => strip._cleanupVideoStreams()).not.toThrow();
|
||||||
|
|
||||||
|
vi.stubGlobal('document', originalDocument);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes video elements and cleans up streams', () => {
|
||||||
|
const mockStream = new MediaStream();
|
||||||
|
const mockTrack = { stop: vi.fn() };
|
||||||
|
const mockVideoElement = {
|
||||||
|
srcObject: mockStream,
|
||||||
|
remove: vi.fn(),
|
||||||
|
};
|
||||||
|
// Add getTracks method to the mock stream
|
||||||
|
mockStream.getTracks = vi.fn().mockReturnValue([mockTrack]);
|
||||||
|
|
||||||
|
vi.stubGlobal('document', {
|
||||||
|
querySelectorAll: vi.fn().mockReturnValue([mockVideoElement]),
|
||||||
|
});
|
||||||
|
|
||||||
|
strip._cleanupVideoStreams();
|
||||||
|
|
||||||
|
expect(mockStream.getTracks).toHaveBeenCalled();
|
||||||
|
expect(mockTrack.stop).toHaveBeenCalled();
|
||||||
|
expect(mockVideoElement.srcObject).toBe(null);
|
||||||
|
expect(mockVideoElement.remove).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles video elements without streams gracefully', () => {
|
||||||
|
const mockVideoElement = {
|
||||||
|
srcObject: null,
|
||||||
|
remove: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.stubGlobal('document', {
|
||||||
|
querySelectorAll: vi.fn().mockReturnValue([mockVideoElement]),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(() => strip._cleanupVideoStreams()).not.toThrow();
|
||||||
|
expect(mockVideoElement.remove).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles empty video element list', () => {
|
||||||
|
vi.stubGlobal('document', {
|
||||||
|
querySelectorAll: vi.fn().mockReturnValue([]),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(() => strip._cleanupVideoStreams()).not.toThrow();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user