Scribbler310's picture
feat: enhance dashboard
c2b7eb3 verified
import type {
EnhancedStore,
Middleware,
Reducer,
Store,
UnknownAction,
} from '@reduxjs/toolkit'
import { configureStore } from '@reduxjs/toolkit'
import { setupListeners } from '@reduxjs/toolkit/query'
import { useCallback, useEffect, useRef } from 'react'
import { Provider } from 'react-redux'
import { act, cleanup } from '@testing-library/react'
export const ANY = 0 as any
export const DEFAULT_DELAY_MS = 150
export const getSerializedHeaders = (headers: Headers = new Headers()) => {
const result: Record<string, string> = {}
headers.forEach((val, key) => {
result[key] = val
})
return result
}
export async function waitMs(time = DEFAULT_DELAY_MS) {
const now = Date.now()
while (Date.now() < now + time) {
await new Promise((res) => process.nextTick(res))
}
}
export function waitForFakeTimer(time = DEFAULT_DELAY_MS) {
return new Promise((resolve) => setTimeout(resolve, time))
}
export function withProvider(store: Store<any>) {
return function Wrapper({ children }: any) {
return <Provider store={store}>{children}</Provider>
}
}
export const hookWaitFor = async (cb: () => void, time = 2000) => {
const startedAt = Date.now()
while (true) {
try {
cb()
return true
} catch (e) {
if (Date.now() > startedAt + time) {
throw e
}
await act(async () => {
await waitMs(2)
})
}
}
}
export const fakeTimerWaitFor = async (cb: () => void, time = 2000) => {
const startedAt = Date.now()
while (true) {
try {
cb()
return true
} catch (e) {
if (Date.now() > startedAt + time) {
throw e
}
await act(async () => {
await vi.advanceTimersByTimeAsync(2)
})
}
}
}
export const useRenderCounter = () => {
const countRef = useRef(0)
useEffect(() => {
countRef.current += 1
})
useEffect(() => {
return () => {
countRef.current = 0
}
}, [])
return useCallback(() => countRef.current, [])
}
expect.extend({
toMatchSequence(
_actions: UnknownAction[],
...matchers: Array<(arg: any) => boolean>
) {
const actions = _actions.concat()
actions.shift() // remove INIT
for (let i = 0; i < matchers.length; i++) {
if (!matchers[i](actions[i])) {
return {
message: () =>
`Action ${actions[i].type} does not match sequence at position ${i}.
All actions:
${actions.map((a) => a.type).join('\n')}`,
pass: false,
}
}
}
return {
message: () => `All actions match the sequence.`,
pass: true,
}
},
})
export const actionsReducer = {
actions: (state: UnknownAction[] = [], action: UnknownAction) => {
// As of 2.0-beta.4, we are going to ignore all `subscriptionsUpdated` actions in tests
if (action.type.includes('subscriptionsUpdated')) {
return state
}
return [...state, action]
},
}
export function setupApiStore<
A extends {
reducerPath: 'api'
reducer: Reducer<any, any>
middleware: Middleware
util: { resetApiState(): any }
},
R extends Record<string, Reducer<any, any>> = Record<never, never>,
>(
api: A,
extraReducers?: R,
options: {
withoutListeners?: boolean
withoutTestLifecycles?: boolean
middleware?: {
prepend?: Middleware[]
concat?: Middleware[]
}
} = {},
) {
const { middleware } = options
const getStore = () =>
configureStore({
reducer: { api: api.reducer, ...extraReducers },
middleware: (gdm) => {
const tempMiddleware = gdm({
serializableCheck: false,
immutableCheck: false,
}).concat(api.middleware)
return tempMiddleware
.concat(middleware?.concat ?? [])
.prepend(middleware?.prepend ?? []) as typeof tempMiddleware
},
enhancers: (gde) =>
gde({
autoBatch: false,
}),
})
type State = {
api: ReturnType<A['reducer']>
} & {
[K in keyof R]: ReturnType<R[K]>
}
type StoreType = EnhancedStore<
{
api: ReturnType<A['reducer']>
} & {
[K in keyof R]: ReturnType<R[K]>
},
UnknownAction,
ReturnType<typeof getStore> extends EnhancedStore<any, any, infer M>
? M
: never
>
const initialStore = getStore() as StoreType
const refObj = {
api,
store: initialStore,
wrapper: withProvider(initialStore),
}
let cleanupListeners: () => void
if (!options.withoutTestLifecycles) {
beforeEach(() => {
const store = getStore() as StoreType
refObj.store = store
refObj.wrapper = withProvider(store)
if (!options.withoutListeners) {
cleanupListeners = setupListeners(store.dispatch)
}
})
afterEach(() => {
cleanup()
if (!options.withoutListeners) {
cleanupListeners()
}
refObj.store.dispatch(api.util.resetApiState())
})
}
return refObj
}