mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
122 lines
4.6 KiB
TypeScript
122 lines
4.6 KiB
TypeScript
/**
|
|
* Test utilities — provides a renderWithProviders helper that wraps
|
|
* components in a fresh Redux store + MemoryRouter for isolated testing.
|
|
*/
|
|
import { combineReducers, configureStore } from '@reduxjs/toolkit';
|
|
import { render, type RenderOptions } from '@testing-library/react';
|
|
import type { PropsWithChildren, ReactElement } from 'react';
|
|
import { createPortal } from 'react-dom';
|
|
import { Provider } from 'react-redux';
|
|
import { MemoryRouter } from 'react-router-dom';
|
|
|
|
import { SidebarSlotOutlet, SidebarSlotProvider } from '../components/layout/shell/SidebarSlot';
|
|
import { getCoreStateSnapshot } from '../lib/coreState/store';
|
|
import { CoreStateContext } from '../providers/coreStateContext';
|
|
import accountsReducer from '../store/accountsSlice';
|
|
import backendMeetReducer from '../store/backendMeetSlice';
|
|
import channelConnectionsReducer from '../store/channelConnectionsSlice';
|
|
import companionReducer from '../store/companionSlice';
|
|
import connectivityReducer from '../store/connectivitySlice';
|
|
import coreModeReducer from '../store/coreModeSlice';
|
|
import layoutReducer from '../store/layoutSlice';
|
|
import localeReducer from '../store/localeSlice';
|
|
import mascotReducer from '../store/mascotSlice';
|
|
import notificationReducer from '../store/notificationSlice';
|
|
import personaReducer from '../store/personaSlice';
|
|
import { pttReducer } from '../store/pttSlice';
|
|
import socketReducer from '../store/socketSlice';
|
|
import themeReducer from '../store/themeSlice';
|
|
import threadReducer from '../store/threadSlice';
|
|
|
|
/**
|
|
* Creates a fresh Redux store for testing.
|
|
* Uses raw (non-persisted) reducers to avoid persist complexity in tests.
|
|
*
|
|
* `mascot` is wired in for the mascot voice picker (issue #1762): the
|
|
* VoicePanel reads + dispatches against this slice, and useSelector
|
|
* would throw on a missing reducer without a stub here. `persona` is wired
|
|
* in for the same reason (issue #2345): PersonaPanel reads + dispatches
|
|
* against it. `backendMeet` is wired in for MeetingBotsCard which reads
|
|
* meeting status from this slice.
|
|
*/
|
|
const testRootReducer = combineReducers({
|
|
accounts: accountsReducer,
|
|
backendMeet: backendMeetReducer,
|
|
channelConnections: channelConnectionsReducer,
|
|
companion: companionReducer,
|
|
connectivity: connectivityReducer,
|
|
coreMode: coreModeReducer,
|
|
layout: layoutReducer,
|
|
locale: localeReducer,
|
|
mascot: mascotReducer,
|
|
notifications: notificationReducer,
|
|
persona: personaReducer,
|
|
ptt: pttReducer,
|
|
socket: socketReducer,
|
|
theme: themeReducer,
|
|
thread: threadReducer,
|
|
});
|
|
|
|
export function createTestStore(preloadedState?: Record<string, unknown>) {
|
|
return configureStore({ reducer: testRootReducer, preloadedState: preloadedState as never });
|
|
}
|
|
|
|
type TestStore = ReturnType<typeof createTestStore>;
|
|
|
|
interface ExtendedRenderOptions extends Omit<RenderOptions, 'queries'> {
|
|
preloadedState?: Record<string, unknown>;
|
|
store?: TestStore;
|
|
initialEntries?: string[];
|
|
}
|
|
|
|
/**
|
|
* Render a component wrapped in Redux Provider + MemoryRouter.
|
|
*/
|
|
export function renderWithProviders(
|
|
ui: ReactElement,
|
|
{
|
|
preloadedState,
|
|
store = createTestStore(preloadedState),
|
|
initialEntries = ['/'],
|
|
...renderOptions
|
|
}: ExtendedRenderOptions = {}
|
|
) {
|
|
const coreStateStub = {
|
|
...getCoreStateSnapshot(),
|
|
refresh: async () => {},
|
|
refreshTeams: async () => {},
|
|
refreshTeamMembers: async () => {},
|
|
refreshTeamInvites: async () => {},
|
|
setAnalyticsEnabled: async () => {},
|
|
setMeetAutoOrchestratorHandoff: async () => {},
|
|
setOnboardingCompletedFlag: async () => {},
|
|
setEncryptionKey: async () => {},
|
|
patchSnapshot: () => {},
|
|
setOnboardingTasks: async () => {},
|
|
storeSessionToken: async () => {},
|
|
clearSession: async () => {},
|
|
};
|
|
|
|
function Wrapper({ children }: PropsWithChildren) {
|
|
return (
|
|
<Provider store={store}>
|
|
<CoreStateContext.Provider value={coreStateStub}>
|
|
<MemoryRouter initialEntries={initialEntries}>
|
|
{/* Provide the root sidebar slot so pages that project their nav via
|
|
SidebarContent (Settings/Brain/Connections/Chat) render it. The
|
|
outlet is portaled to document.body so it doesn't become the
|
|
render container's firstChild (which would break tests asserting
|
|
a null/empty render); `screen` queries still find projected content. */}
|
|
<SidebarSlotProvider>
|
|
{createPortal(<SidebarSlotOutlet />, document.body)}
|
|
{children}
|
|
</SidebarSlotProvider>
|
|
</MemoryRouter>
|
|
</CoreStateContext.Provider>
|
|
</Provider>
|
|
);
|
|
}
|
|
|
|
return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) };
|
|
}
|