import { vi } from 'vitest' import type { ThunkAction, UnknownAction } from '@reduxjs/toolkit' import { isAllOf, isAnyOf, isAsyncThunkAction, isFulfilled, isPending, isRejected, isRejectedWithValue, createAction, createAsyncThunk, createReducer, } from '@reduxjs/toolkit' const thunk: ThunkAction = () => {} describe('isAnyOf', () => { it('returns true only if any matchers match (match function)', () => { const actionA = createAction('a') const actionB = createAction('b') const trueAction = { type: 'a', payload: 'payload', } expect(isAnyOf(actionA, actionB)(trueAction)).toEqual(true) const falseAction = { type: 'c', payload: 'payload', } expect(isAnyOf(actionA, actionB)(falseAction)).toEqual(false) }) it('returns true only if any type guards match', () => { const actionA = createAction('a') const actionB = createAction('b') const isActionA = actionA.match const isActionB = actionB.match const trueAction = { type: 'a', payload: 'payload', } expect(isAnyOf(isActionA, isActionB)(trueAction)).toEqual(true) const falseAction = { type: 'c', payload: 'payload', } expect(isAnyOf(isActionA, isActionB)(falseAction)).toEqual(false) }) it('returns true only if any matchers match (thunk action creators)', () => { const thunkA = createAsyncThunk('a', () => { return 'noop' }) const thunkB = createAsyncThunk('b', () => { return 0 }) const action = thunkA.fulfilled('fakeRequestId', 'test') expect(isAnyOf(thunkA.fulfilled, thunkB.fulfilled)(action)).toEqual(true) expect( isAnyOf(thunkA.pending, thunkA.rejected, thunkB.fulfilled)(action), ).toEqual(false) }) it('works with reducers', () => { const actionA = createAction('a') const actionB = createAction('b') const trueAction = { type: 'a', payload: 'payload', } const initialState = { value: false } const reducer = createReducer(initialState, (builder) => { builder.addMatcher(isAnyOf(actionA, actionB), (state) => { return { ...state, value: true } }) }) expect(reducer(initialState, trueAction)).toEqual({ value: true }) const falseAction = { type: 'c', payload: 'payload', } expect(reducer(initialState, falseAction)).toEqual(initialState) }) }) describe('isAllOf', () => { it('returns true only if all matchers match', () => { const actionA = createAction('a') interface SpecialAction { payload: 'SPECIAL' } const isActionSpecial = (action: any): action is SpecialAction => { return action.payload === 'SPECIAL' } const trueAction = { type: 'a', payload: 'SPECIAL', } expect(isAllOf(actionA, isActionSpecial)(trueAction)).toEqual(true) const falseAction = { type: 'a', payload: 'ORDINARY', } expect(isAllOf(actionA, isActionSpecial)(falseAction)).toEqual(false) const thunkA = createAsyncThunk('a', () => 'result') const specialThunkAction = thunkA.fulfilled('SPECIAL', 'fakeRequestId') expect(isAllOf(thunkA.fulfilled, isActionSpecial)(specialThunkAction)).toBe( true, ) const ordinaryThunkAction = thunkA.fulfilled('ORDINARY', 'fakeRequestId') expect( isAllOf(thunkA.fulfilled, isActionSpecial)(ordinaryThunkAction), ).toBe(false) }) }) describe('isPending', () => { test('should return false for a regular action', () => { const action = createAction('action/type')('testPayload') expect(isPending()(action)).toBe(false) expect(isPending(action)).toBe(false) expect(isPending(thunk)).toBe(false) }) test('should return true only for pending async thunk actions', () => { const thunk = createAsyncThunk('a', () => 'result') const pendingAction = thunk.pending('fakeRequestId') expect(isPending()(pendingAction)).toBe(true) expect(isPending(pendingAction)).toBe(true) const rejectedAction = thunk.rejected( new Error('rejected'), 'fakeRequestId', ) expect(isPending()(rejectedAction)).toBe(false) const fulfilledAction = thunk.fulfilled('result', 'fakeRequestId') expect(isPending()(fulfilledAction)).toBe(false) }) test('should return true only for thunks provided as arguments', () => { const thunkA = createAsyncThunk('a', () => 'result') const thunkB = createAsyncThunk('b', () => 'result') const thunkC = createAsyncThunk('c', () => 'result') const matchAC = isPending(thunkA, thunkC) const matchB = isPending(thunkB) function testPendingAction( thunk: typeof thunkA | typeof thunkB | typeof thunkC, expected: boolean, ) { const pendingAction = thunk.pending('fakeRequestId') expect(matchAC(pendingAction)).toBe(expected) expect(matchB(pendingAction)).toBe(!expected) const rejectedAction = thunk.rejected( new Error('rejected'), 'fakeRequestId', ) expect(matchAC(rejectedAction)).toBe(false) const fulfilledAction = thunk.fulfilled('result', 'fakeRequestId') expect(matchAC(fulfilledAction)).toBe(false) } testPendingAction(thunkA, true) testPendingAction(thunkC, true) testPendingAction(thunkB, false) }) }) describe('isRejected', () => { test('should return false for a regular action', () => { const action = createAction('action/type')('testPayload') expect(isRejected()(action)).toBe(false) expect(isRejected(action)).toBe(false) expect(isRejected(thunk)).toBe(false) }) test('should return true only for rejected async thunk actions', () => { const thunk = createAsyncThunk('a', () => 'result') const pendingAction = thunk.pending('fakeRequestId') expect(isRejected()(pendingAction)).toBe(false) const rejectedAction = thunk.rejected( new Error('rejected'), 'fakeRequestId', ) expect(isRejected()(rejectedAction)).toBe(true) expect(isRejected(rejectedAction)).toBe(true) const fulfilledAction = thunk.fulfilled('result', 'fakeRequestId') expect(isRejected()(fulfilledAction)).toBe(false) }) test('should return true only for thunks provided as arguments', () => { const thunkA = createAsyncThunk('a', () => 'result') const thunkB = createAsyncThunk('b', () => 'result') const thunkC = createAsyncThunk('c', () => 'result') const matchAC = isRejected(thunkA, thunkC) const matchB = isRejected(thunkB) function testRejectedAction( thunk: typeof thunkA | typeof thunkB | typeof thunkC, expected: boolean, ) { const pendingAction = thunk.pending('fakeRequestId') expect(matchAC(pendingAction)).toBe(false) const rejectedAction = thunk.rejected( new Error('rejected'), 'fakeRequestId', ) expect(matchAC(rejectedAction)).toBe(expected) expect(matchB(rejectedAction)).toBe(!expected) const fulfilledAction = thunk.fulfilled('result', 'fakeRequestId') expect(matchAC(fulfilledAction)).toBe(false) } testRejectedAction(thunkA, true) testRejectedAction(thunkC, true) testRejectedAction(thunkB, false) }) }) describe('isRejectedWithValue', () => { test('should return false for a regular action', () => { const action = createAction('action/type')('testPayload') expect(isRejectedWithValue()(action)).toBe(false) expect(isRejectedWithValue(action)).toBe(false) expect(isRejectedWithValue(thunk)).toBe(false) }) test('should return true only for rejected-with-value async thunk actions', async () => { const thunk = createAsyncThunk('a', (_, { rejectWithValue }) => { return rejectWithValue('rejectWithValue!') }) const pendingAction = thunk.pending('fakeRequestId') expect(isRejectedWithValue()(pendingAction)).toBe(false) const rejectedAction = thunk.rejected( new Error('rejected'), 'fakeRequestId', ) expect(isRejectedWithValue()(rejectedAction)).toBe(false) const getState = vi.fn(() => ({})) const dispatch = vi.fn((x: any) => x) const extra = {} // note: doesn't throw because we don't unwrap it const rejectedWithValueAction = await thunk()(dispatch, getState, extra) expect(isRejectedWithValue()(rejectedWithValueAction)).toBe(true) const fulfilledAction = thunk.fulfilled('result', 'fakeRequestId') expect(isRejectedWithValue()(fulfilledAction)).toBe(false) }) test('should return true only for thunks provided as arguments', async () => { const payloadCreator = (_: any, { rejectWithValue }: any) => { return rejectWithValue('rejectWithValue!') } const thunkA = createAsyncThunk('a', payloadCreator) const thunkB = createAsyncThunk('b', payloadCreator) const thunkC = createAsyncThunk('c', payloadCreator) const matchAC = isRejectedWithValue(thunkA, thunkC) const matchB = isRejectedWithValue(thunkB) async function testRejectedAction( thunk: typeof thunkA | typeof thunkB | typeof thunkC, expected: boolean, ) { const pendingAction = thunk.pending('fakeRequestId') expect(matchAC(pendingAction)).toBe(false) const rejectedAction = thunk.rejected( new Error('rejected'), 'fakeRequestId', ) // rejected-with-value is a narrower requirement than rejected expect(matchAC(rejectedAction)).toBe(false) const getState = vi.fn(() => ({})) const dispatch = vi.fn((x: any) => x) const extra = {} // note: doesn't throw because we don't unwrap it const rejectedWithValueAction = await thunk()(dispatch, getState, extra) expect(matchAC(rejectedWithValueAction)).toBe(expected) expect(matchB(rejectedWithValueAction)).toBe(!expected) const fulfilledAction = thunk.fulfilled('result', 'fakeRequestId') expect(matchAC(fulfilledAction)).toBe(false) } await testRejectedAction(thunkA, true) await testRejectedAction(thunkC, true) await testRejectedAction(thunkB, false) }) }) describe('isFulfilled', () => { test('should return false for a regular action', () => { const action = createAction('action/type')('testPayload') expect(isFulfilled()(action)).toBe(false) expect(isFulfilled(action)).toBe(false) expect(isFulfilled(thunk)).toBe(false) }) test('should return true only for fulfilled async thunk actions', () => { const thunk = createAsyncThunk('a', () => 'result') const pendingAction = thunk.pending('fakeRequestId') expect(isFulfilled()(pendingAction)).toBe(false) const rejectedAction = thunk.rejected( new Error('rejected'), 'fakeRequestId', ) expect(isFulfilled()(rejectedAction)).toBe(false) const fulfilledAction = thunk.fulfilled('result', 'fakeRequestId') expect(isFulfilled()(fulfilledAction)).toBe(true) expect(isFulfilled(fulfilledAction)).toBe(true) }) test('should return true only for thunks provided as arguments', () => { const thunkA = createAsyncThunk('a', () => 'result') const thunkB = createAsyncThunk('b', () => 'result') const thunkC = createAsyncThunk('c', () => 'result') const matchAC = isFulfilled(thunkA, thunkC) const matchB = isFulfilled(thunkB) function testFulfilledAction( thunk: typeof thunkA | typeof thunkB | typeof thunkC, expected: boolean, ) { const pendingAction = thunk.pending('fakeRequestId') expect(matchAC(pendingAction)).toBe(false) const rejectedAction = thunk.rejected( new Error('rejected'), 'fakeRequestId', ) expect(matchAC(rejectedAction)).toBe(false) const fulfilledAction = thunk.fulfilled('result', 'fakeRequestId') expect(matchAC(fulfilledAction)).toBe(expected) expect(matchB(fulfilledAction)).toBe(!expected) } testFulfilledAction(thunkA, true) testFulfilledAction(thunkC, true) testFulfilledAction(thunkB, false) }) }) describe('isAsyncThunkAction', () => { test('should return false for a regular action', () => { const action = createAction('action/type')('testPayload') expect(isAsyncThunkAction()(action)).toBe(false) expect(isAsyncThunkAction(action)).toBe(false) expect(isAsyncThunkAction(thunk)).toBe(false) }) test('should return true for any async thunk action if no arguments were provided', () => { const thunk = createAsyncThunk('a', () => 'result') const matcher = isAsyncThunkAction() const pendingAction = thunk.pending('fakeRequestId') expect(matcher(pendingAction)).toBe(true) const rejectedAction = thunk.rejected( new Error('rejected'), 'fakeRequestId', ) expect(matcher(rejectedAction)).toBe(true) const fulfilledAction = thunk.fulfilled('result', 'fakeRequestId') expect(matcher(fulfilledAction)).toBe(true) }) test('should return true only for thunks provided as arguments', () => { const thunkA = createAsyncThunk('a', () => 'result') const thunkB = createAsyncThunk('b', () => 'result') const thunkC = createAsyncThunk('c', () => 'result') const matchAC = isAsyncThunkAction(thunkA, thunkC) const matchB = isAsyncThunkAction(thunkB) function testAllActions( thunk: typeof thunkA | typeof thunkB | typeof thunkC, expected: boolean, ) { const pendingAction = thunk.pending('fakeRequestId') expect(matchAC(pendingAction)).toBe(expected) expect(matchB(pendingAction)).toBe(!expected) const rejectedAction = thunk.rejected( new Error('rejected'), 'fakeRequestId', ) expect(matchAC(rejectedAction)).toBe(expected) expect(matchB(rejectedAction)).toBe(!expected) const fulfilledAction = thunk.fulfilled('result', 'fakeRequestId') expect(matchAC(fulfilledAction)).toBe(expected) expect(matchB(fulfilledAction)).toBe(!expected) } testAllActions(thunkA, true) testAllActions(thunkC, true) testAllActions(thunkB, false) }) })