Skip to content

Commit a072dca

Browse files
refactor(tests): reorganize mocks into service subdirectories
Groups mock infrastructure by the service being mocked, making it easier to find the right file when working with a specific integration: mocks/empirica/ MockPlayer, MockGame, MockStage, MockEmpiricaProvider, hooks.js, global-hooks.js mocks/daily/ MockDailyProvider, hooks.jsx mocks/sentry/ mock.js mocks/utils/ console-capture.js Old flat files are converted to thin re-exports so all existing explicit import paths in test files continue to work without changes. Vite config aliases updated to point directly to the canonical new locations. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent cb24864 commit a072dca

21 files changed

Lines changed: 1403 additions & 1887 deletions
Lines changed: 2 additions & 238 deletions
Original file line numberDiff line numberDiff line change
@@ -1,238 +1,2 @@
1-
import React, { createContext, useMemo, useState, useEffect, useRef } from 'react';
2-
3-
/**
4-
* Mock Daily.co Context for component tests
5-
*
6-
* Provides mocked Daily.co state that the aliased Daily hooks read from.
7-
* Use this for tests that need to control video/audio track states
8-
* without actually connecting to Daily.co.
9-
*
10-
* ## Test-Accessible Globals (exposed on window)
11-
*
12-
* ### window.mockCallObject
13-
* The mock call object with a real EventEmitter. Use to fire Daily events:
14-
* await page.evaluate(() => window.mockCallObject.emit('participant-joined', {
15-
* participant: { session_id: 'daily-p1', tracks: {} }
16-
* }));
17-
* Track updateParticipants calls:
18-
* const calls = await page.evaluate(() => window.mockCallObject._updateParticipantsCalls);
19-
*
20-
* Set participant userData for fast position mapping (issue #1187):
21-
* await page.evaluate(() => {
22-
* window.mockCallObject._participants = {
23-
* 'daily-p1': { session_id: 'daily-p1', userData: { position: 1 }, tracks: {} }
24-
* };
25-
* });
26-
*
27-
* ### window.mockDailySetLocalSessionId(id)
28-
* Update the local session ID mid-test to simulate reconnection:
29-
* await page.evaluate(() => window.mockDailySetLocalSessionId('daily-p0-v2'));
30-
*
31-
* ### window.mockDailyDeviceOverrides
32-
* Override device functions BEFORE mount to test error scenarios:
33-
* await page.evaluate(() => {
34-
* window.mockDailyDeviceOverrides = {
35-
* setSpeaker: () => Promise.reject(new DOMException('NotAllowedError', 'NotAllowedError')),
36-
* };
37-
* });
38-
* The device functions read this at call-time, so overrides work even after mount.
39-
*/
40-
export const MockDailyContext = createContext(null);
41-
42-
/**
43-
* Minimal EventEmitter for MockCallObject.
44-
* Supports on/off/emit — same interface as Daily's call object.
45-
*/
46-
class MockEventEmitter {
47-
constructor() {
48-
this._handlers = {};
49-
}
50-
51-
on(event, handler) {
52-
if (!this._handlers[event]) this._handlers[event] = [];
53-
this._handlers[event].push(handler);
54-
}
55-
56-
off(event, handler) {
57-
if (!this._handlers[event]) return;
58-
this._handlers[event] = this._handlers[event].filter(h => h !== handler);
59-
}
60-
61-
emit(event, data) {
62-
const handlers = this._handlers[event] || [];
63-
handlers.forEach(h => h(data));
64-
}
65-
}
66-
67-
/**
68-
* Mock call object — a proper EventEmitter with spy tracking.
69-
*
70-
* Exposed on window.mockCallObject so tests can:
71-
* - Fire Daily events: window.mockCallObject.emit('joined-meeting', {})
72-
* - Inspect calls: window.mockCallObject._updateParticipantsCalls
73-
* - Control state: window.mockCallObject._meetingState = 'left-meeting'
74-
* - Simulate muted mic: window.mockCallObject._audioEnabled = false
75-
* - Simulate ended track: window.mockCallObject._audioReadyState = 'ended'
76-
*/
77-
class MockCallObject extends MockEventEmitter {
78-
constructor() {
79-
super();
80-
this._meetingState = 'joined-meeting';
81-
this._updateParticipantsCalls = [];
82-
this._setInputDevicesCalls = []; // eslint-disable-line no-underscore-dangle
83-
this._participants = {};
84-
this._audioEnabled = true; // false = mic muted; tests can set via _audioEnabled
85-
this._videoEnabled = true; // false = camera muted; tests can set via _videoEnabled
86-
this._audioReadyState = 'live'; // 'ended' = track ended; tests can set via _audioReadyState
87-
this._videoReadyState = 'live'; // 'ended' = track ended; tests can set via _videoReadyState
88-
this._localUserData = null; // userData passed in join() options
89-
this._localSessionId = null; // session ID of local participant (set via setLocalSessionId)
90-
this._joinCalled = false; // tracks whether join() was called (for rejoin tests)
91-
}
92-
93-
meetingState() { return this._meetingState; }
94-
isDestroyed() { return false; }
95-
96-
// join() accepts options including userData for immediate position mapping (issue #1187)
97-
// The userData is stored and returned in participants() for the local participant.
98-
join(options = {}) {
99-
this._joinCalled = true;
100-
if (options.userData) {
101-
this._localUserData = options.userData;
102-
}
103-
return Promise.resolve();
104-
}
105-
106-
leave() { return Promise.resolve(); }
107-
setUserName() {}
108-
109-
// Re-acquires a device track — simulates getUserMedia re-acquisition succeeding.
110-
// Resets readyState to 'live' for whichever device type is being re-acquired.
111-
setInputDevicesAsync({ audioDeviceId, videoDeviceId } = {}) {
112-
if (audioDeviceId !== undefined) this._audioReadyState = 'live';
113-
if (videoDeviceId !== undefined) this._videoReadyState = 'live';
114-
this._setInputDevicesCalls.push({ audioDeviceId, videoDeviceId, timestamp: Date.now() }); // eslint-disable-line no-underscore-dangle
115-
return Promise.resolve();
116-
}
117-
118-
setSubscribeToTracksAutomatically() {}
119-
120-
updateParticipants(updates) {
121-
this._updateParticipantsCalls.push({ updates, timestamp: Date.now() });
122-
}
123-
124-
// Returns participants with userData included.
125-
// Tests can set _participants directly, but userData is also available
126-
// from join() options for the local participant.
127-
participants() {
128-
// Merge any local userData into the local participant if session ID matches
129-
if (this._localSessionId && this._localUserData && this._participants[this._localSessionId]) {
130-
const localP = this._participants[this._localSessionId];
131-
return {
132-
...this._participants,
133-
[this._localSessionId]: { ...localP, userData: this._localUserData },
134-
};
135-
}
136-
return this._participants;
137-
}
138-
139-
// Helper for tests to set local session ID so userData gets merged correctly
140-
setLocalSessionId(id) {
141-
this._localSessionId = id;
142-
}
143-
getNetworkStats() { return Promise.resolve({}); }
144-
getInputDevices() { return { mic: { deviceId: 'default-mic', label: 'Default Microphone' }, camera: { deviceId: 'default-cam', label: 'Default Camera' } }; }
145-
getOutputDevices() { return { speaker: { deviceId: 'default-speaker', label: 'Default Speaker' } }; }
146-
147-
// Local media state — avRecovery reads these to detect muted mic/camera or ended tracks
148-
async localAudio() { return { enabled: this._audioEnabled, muted: false, readyState: this._audioReadyState }; }
149-
async localVideo() { return { enabled: this._videoEnabled, muted: false, readyState: this._videoReadyState }; }
150-
151-
// Soft-fix actions — avRecovery calls these to unmute mic/camera
152-
async setLocalAudio(enabled) { this._audioEnabled = enabled; }
153-
async setLocalVideo(enabled) { this._videoEnabled = enabled; }
154-
}
155-
156-
export function MockDailyProvider({
157-
localSessionId: initialLocalSessionId = null,
158-
participantIds = [],
159-
videoTracks = {},
160-
audioTracks = {},
161-
participants = {},
162-
callObject = null,
163-
devices = null,
164-
children,
165-
}) {
166-
// Allow tests to update localSessionId mid-test via window.mockDailySetLocalSessionId.
167-
// This enables HISTORY-004: testing that a new history entry is logged when the
168-
// Daily session ID changes (e.g., participant rejoins after network drop).
169-
const [localSessionId, setLocalSessionId] = useState(initialLocalSessionId);
170-
171-
// Stable MockCallObject instance — created once, exposed on window.
172-
// Tests can fire events and inspect calls via window.mockCallObject.
173-
const mockCallObjectRef = useRef(null);
174-
if (!mockCallObjectRef.current) {
175-
mockCallObjectRef.current = new MockCallObject();
176-
}
177-
178-
useEffect(() => {
179-
window.mockDailySetLocalSessionId = setLocalSessionId;
180-
window.mockCallObject = mockCallObjectRef.current;
181-
return () => {
182-
delete window.mockDailySetLocalSessionId;
183-
delete window.mockCallObject;
184-
};
185-
}, []);
186-
187-
// Sync localSessionId to mock call object so participants() can merge userData correctly
188-
useEffect(() => {
189-
if (mockCallObjectRef.current) {
190-
mockCallObjectRef.current._localSessionId = localSessionId;
191-
}
192-
}, [localSessionId]);
193-
194-
// Device functions read window.mockDailyDeviceOverrides at call-time, allowing
195-
// tests to set up overrides via page.evaluate() before (or even after) mount.
196-
// Pattern: set window.mockDailyDeviceOverrides = { setSpeaker: () => Promise.reject(...) }
197-
// before mounting to simulate device errors like NotAllowedError.
198-
// eslint-disable-next-line react-hooks/exhaustive-deps
199-
const defaultDevices = useMemo(() => ({
200-
cameras: [],
201-
microphones: [],
202-
speakers: [],
203-
currentCam: null,
204-
currentMic: null,
205-
currentSpeaker: null,
206-
// Functions read window.mockDailyDeviceOverrides at call-time, so no render deps needed.
207-
setSpeaker: (id) => (window.mockDailyDeviceOverrides?.setSpeaker || (() => Promise.resolve()))(id),
208-
setCamera: (id) => (window.mockDailyDeviceOverrides?.setCamera || (() => Promise.resolve()))(id),
209-
setMicrophone: (id) => (window.mockDailyDeviceOverrides?.setMicrophone || (() => Promise.resolve()))(id),
210-
}), []);
211-
212-
// Merge provided device data with default functions so tests can pass
213-
// data-only devices (without functions) via serializable hooksConfig.
214-
// The function properties from defaultDevices are preserved even when devices prop
215-
// is provided, since JSON serialization strips functions from hooksConfig.
216-
const mergedDevices = useMemo(() => (devices
217-
? { ...defaultDevices, ...devices, setSpeaker: defaultDevices.setSpeaker, setCamera: defaultDevices.setCamera, setMicrophone: defaultDevices.setMicrophone }
218-
: defaultDevices), [devices, defaultDevices]);
219-
220-
const contextValue = useMemo(() => ({
221-
localSessionId,
222-
participantIds,
223-
videoTracks,
224-
audioTracks,
225-
participants,
226-
callObject: callObject || mockCallObjectRef.current,
227-
devices: mergedDevices,
228-
}), [
229-
localSessionId, participantIds, videoTracks, audioTracks,
230-
participants, callObject, mergedDevices,
231-
]);
232-
233-
return (
234-
<MockDailyContext.Provider value={contextValue}>
235-
{children}
236-
</MockDailyContext.Provider>
237-
);
238-
}
1+
// Re-exported from new location — see mocks/daily/MockDailyProvider.jsx
2+
export { MockDailyProvider, MockDailyContext } from './daily/MockDailyProvider.jsx';

0 commit comments

Comments
 (0)