-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: global forms provider * fix: install react-test-renderer * fix: use yarn 4 * fix: use act from @testing-library/react-hooks * refactor: undo yarn changes as no package updates were done * refactor: rename FormInfoObjectOrString * docs: submit * refactor: render hook options fn * object.values * refactor: use object functions * refactor: make GlobalFormContext private * fix: export * Update src/GlobalFormsProvider/GlobalFormsContext.ts Co-authored-by: Benedikt Franke <[email protected]> * Update src/GlobalFormsProvider/GlobalFormsContext.ts Co-authored-by: Benedikt Franke <[email protected]> * Update src/GlobalFormsProvider/GlobalFormsProvider.tsx Co-authored-by: Benedikt Franke <[email protected]> * refactor: import maybe --------- Co-authored-by: Benedikt Franke <[email protected]>
- Loading branch information
Showing
3 changed files
with
405 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import React, { useCallback, useContext, useMemo } from 'react'; | ||
|
||
export type FormInfo = { name: string; order: number }; | ||
type FormInfoObjectOrString = FormInfo | string; | ||
|
||
export type CallbackWithOrder = { | ||
callback: () => Promise<void>; | ||
order: number; | ||
}; | ||
|
||
export type GlobalFormsContextType = { | ||
isSubmitting: boolean; | ||
reset: () => void; | ||
setResetCallback: (form: FormInfo, callback: () => void) => void; | ||
setSubmitCallback: (form: FormInfo, callback: () => Promise<void>) => void; | ||
setSubmitting: (form: FormInfo, submitting: boolean) => void; | ||
setSubmitSuccessful: (form: FormInfo, submitSuccessful: boolean) => void; | ||
/** Returns true when all forms were submitted successfully. */ | ||
submit: (additionalCallback?: CallbackWithOrder) => Promise<boolean>; | ||
}; | ||
|
||
export const GlobalFormsContext = React.createContext<GlobalFormsContextType>({ | ||
isSubmitting: false, | ||
reset: () => {}, | ||
setResetCallback: () => {}, | ||
setSubmitCallback: () => {}, | ||
setSubmitting: () => {}, | ||
setSubmitSuccessful: () => {}, | ||
submit: () => Promise.resolve(false), | ||
}); | ||
|
||
export function useGlobalForms() { | ||
return useContext(GlobalFormsContext); | ||
} | ||
|
||
export function normalizeFormInfo(form: FormInfoObjectOrString): FormInfo { | ||
if (typeof form === 'string') { | ||
return { name: form, order: 0 }; | ||
} | ||
|
||
return form; | ||
} | ||
|
||
export function useGlobalForm(form: FormInfoObjectOrString) { | ||
const formInfo = useMemo(() => normalizeFormInfo(form), [form]); | ||
|
||
const { | ||
setResetCallback, | ||
setSubmitCallback, | ||
setSubmitting, | ||
setSubmitSuccessful, | ||
} = useContext(GlobalFormsContext); | ||
|
||
const setResetCallbackWrapped = useCallback( | ||
(callback: () => void) => { | ||
setResetCallback(formInfo, callback); | ||
}, | ||
[formInfo, setResetCallback], | ||
); | ||
|
||
const setSubmitCallbackWrapped = useCallback( | ||
(callback: () => Promise<void>) => { | ||
setSubmitCallback(formInfo, callback); | ||
}, | ||
[formInfo, setSubmitCallback], | ||
); | ||
|
||
const setSubmittingWrapped = useCallback( | ||
(submitting: boolean) => { | ||
setSubmitting(formInfo, submitting); | ||
}, | ||
[formInfo, setSubmitting], | ||
); | ||
|
||
const setSubmitSuccessfulWrapped = useCallback( | ||
(submitSuccessful: boolean) => { | ||
setSubmitSuccessful(formInfo, submitSuccessful); | ||
}, | ||
[formInfo, setSubmitSuccessful], | ||
); | ||
|
||
return { | ||
setResetCallback: setResetCallbackWrapped, | ||
setSubmitCallback: setSubmitCallbackWrapped, | ||
setSubmitSuccessful: setSubmitSuccessfulWrapped, | ||
setSubmitting: setSubmittingWrapped, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
import { | ||
act, | ||
renderHook, | ||
RenderHookOptions, | ||
} from '@testing-library/react-hooks'; | ||
import React, { PropsWithChildren } from 'react'; | ||
|
||
import { useGlobalForms, useGlobalForm } from './GlobalFormsContext'; | ||
import { GlobalFormsProvider } from './GlobalFormsProvider'; | ||
|
||
function createRenderHookOptions(): RenderHookOptions< | ||
PropsWithChildren<unknown> | ||
> { | ||
return { | ||
wrapper: function Wrapper({ children }: PropsWithChildren<unknown>) { | ||
return <GlobalFormsProvider>{children}</GlobalFormsProvider>; | ||
}, | ||
}; | ||
} | ||
|
||
describe('GlobalFormsProvider', () => { | ||
it('finds one submitting form', () => { | ||
const { result } = renderHook(() => { | ||
const { isSubmitting } = useGlobalForms(); | ||
const { setSubmitting: setSubmitting1 } = useGlobalForm('form1'); | ||
const { setSubmitting: setSubmitting2 } = useGlobalForm('form2'); | ||
|
||
return { isSubmitting, setSubmitting1, setSubmitting2 }; | ||
}, createRenderHookOptions()); | ||
expect(result.error).toBeFalsy(); | ||
expect(result.current.isSubmitting).toBeFalsy(); | ||
|
||
act(() => result.current.setSubmitting1(true)); | ||
act(() => result.current.setSubmitting2(false)); | ||
expect(result.current.isSubmitting).toBeTruthy(); | ||
}); | ||
|
||
it('overrides submitting value', () => { | ||
const { result } = renderHook(() => { | ||
const { isSubmitting } = useGlobalForms(); | ||
const { setSubmitting } = useGlobalForm('form'); | ||
|
||
return { isSubmitting, setSubmitting }; | ||
}, createRenderHookOptions()); | ||
expect(result.current.isSubmitting).toBeFalsy(); | ||
|
||
act(() => result.current.setSubmitting(true)); | ||
expect(result.current.isSubmitting).toBeTruthy(); | ||
|
||
act(() => result.current.setSubmitting(false)); | ||
expect(result.current.isSubmitting).toBeFalsy(); | ||
}); | ||
|
||
it('calls all reset callbacks', () => { | ||
const { result } = renderHook(() => { | ||
const { reset } = useGlobalForms(); | ||
const { setResetCallback: setResetCallback1 } = useGlobalForm('form1'); | ||
const { setResetCallback: setResetCallback2 } = useGlobalForm('form2'); | ||
|
||
return { reset, setResetCallback1, setResetCallback2 }; | ||
}, createRenderHookOptions()); | ||
result.current.reset(); | ||
|
||
const reset1 = jest.fn(); | ||
act(() => result.current.setResetCallback1(reset1)); | ||
|
||
const reset2 = jest.fn(); | ||
act(() => result.current.setResetCallback2(reset2)); | ||
|
||
result.current.reset(); | ||
expect(reset1).toHaveBeenCalledTimes(1); | ||
expect(reset2).toHaveBeenCalledTimes(1); | ||
|
||
act(() => result.current.setResetCallback1(jest.fn())); | ||
result.current.reset(); | ||
expect(reset1).toHaveBeenCalledTimes(1); | ||
expect(reset2).toHaveBeenCalledTimes(2); | ||
}); | ||
|
||
it('overrides reset callback', () => { | ||
const { result } = renderHook(() => { | ||
const { reset } = useGlobalForms(); | ||
const { setResetCallback } = useGlobalForm('form'); | ||
|
||
return { reset, setResetCallback }; | ||
}, createRenderHookOptions()); | ||
const resetOld = jest.fn(); | ||
act(() => result.current.setResetCallback(resetOld)); | ||
result.current.reset(); | ||
expect(resetOld).toHaveBeenCalledTimes(1); | ||
|
||
const resetNew = jest.fn(); | ||
act(() => result.current.setResetCallback(resetNew)); | ||
result.current.reset(); | ||
expect(resetOld).toHaveBeenCalledTimes(1); | ||
expect(resetNew).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('calls all submit callbacks', async () => { | ||
const { result } = renderHook(() => { | ||
const { submit } = useGlobalForms(); | ||
const { setSubmitCallback: setSubmitCallback1 } = useGlobalForm('form1'); | ||
const { setSubmitCallback: setSubmitCallback2 } = useGlobalForm('form2'); | ||
|
||
return { submit, setSubmitCallback1, setSubmitCallback2 }; | ||
}, createRenderHookOptions()); | ||
await expect(result.current.submit()).resolves.toBeTruthy(); | ||
|
||
const submit1 = jest.fn(() => Promise.resolve()); | ||
act(() => result.current.setSubmitCallback1(submit1)); | ||
|
||
const submit2 = jest.fn(() => Promise.resolve()); | ||
act(() => result.current.setSubmitCallback2(submit2)); | ||
|
||
await expect(result.current.submit()).resolves.toBeFalsy(); | ||
expect(submit1).toHaveBeenCalledTimes(1); | ||
expect(submit2).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('overrides submit callback', async () => { | ||
const { result } = renderHook(() => { | ||
const { submit } = useGlobalForms(); | ||
const { setSubmitCallback } = useGlobalForm('form'); | ||
|
||
return { submit, setSubmitCallback }; | ||
}, createRenderHookOptions()); | ||
|
||
const submitOld = jest.fn(() => Promise.resolve()); | ||
act(() => result.current.setSubmitCallback(submitOld)); | ||
|
||
await expect(result.current.submit()).resolves.toBeFalsy(); | ||
expect(submitOld).toHaveBeenCalledTimes(1); | ||
|
||
const submitNew = jest.fn(() => Promise.resolve()); | ||
act(() => result.current.setSubmitCallback(submitNew)); | ||
|
||
await expect(result.current.submit()).resolves.toBeFalsy(); | ||
expect(submitOld).toHaveBeenCalledTimes(1); | ||
expect(submitNew).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('maintains submit order', async () => { | ||
const { result } = renderHook(() => { | ||
const { submit } = useGlobalForms(); | ||
const { setSubmitCallback: setSubmitCallbackError } = useGlobalForm({ | ||
name: 'form-error', | ||
order: 2, | ||
}); | ||
const { setSubmitCallback: setSubmitCallbackFirst } = useGlobalForm({ | ||
name: 'form-first', | ||
order: 1, | ||
}); | ||
const { setSubmitCallback: setSubmitCallbackLast } = useGlobalForm({ | ||
name: 'form-last', | ||
order: 3, | ||
}); | ||
|
||
return { | ||
submit, | ||
setSubmitCallbackError, | ||
setSubmitCallbackFirst, | ||
setSubmitCallbackLast, | ||
}; | ||
}, createRenderHookOptions()); | ||
const submitError = jest.fn(() => Promise.reject()); | ||
act(() => result.current.setSubmitCallbackError(submitError)); | ||
|
||
const submitFirst = jest.fn(() => Promise.resolve()); | ||
act(() => result.current.setSubmitCallbackFirst(submitFirst)); | ||
|
||
const submitLast = jest.fn(() => Promise.resolve()); | ||
act(() => result.current.setSubmitCallbackLast(submitLast)); | ||
|
||
await expect(result.current.submit()).rejects.toBeFalsy(); | ||
expect(submitFirst).toHaveBeenCalledTimes(1); | ||
expect(submitError).toHaveBeenCalledTimes(1); | ||
expect(submitLast).toHaveBeenCalledTimes(0); | ||
}); | ||
}); |
Oops, something went wrong.