-
Notifications
You must be signed in to change notification settings - Fork 231
Convert to TypeScript #515
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Glad to have you. Feel free to ask any questions and push up any changes for a review before it's ready. I look forward to seeing what you all come up with. |
@mpeyper Luckily Batman is here, in case an emergency happens 😝 |
9def482 to
b7c28dc
Compare
Codecov Report
@@ Coverage Diff @@
## master #515 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 4 4
Lines 127 123 -4
Branches 24 23 -1
=========================================
- Hits 127 123 -4
Continue to review full report at Codecov.
|
src/cleanup.ts
Outdated
| @@ -1,4 +1,4 @@ | |||
| let cleanupCallbacks = [] | |||
| let cleanupCallbacks: (() => Promise<void>)[] = [] | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the return type of callbacks should be Promise<void>|void in case the callback is not async
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@merodiro I had done it this way, as the callbacks are always called with await - Does it still make sense to be Promise<void> | void even if the callbacks are always called with await?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
await can still work if the code is synchronous you can see an example in the documentation for the await operator.
so it makes sense to use await in this case because it may or may not be a promise
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@merodiro Thanks! Fixing that now...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Progress is looking awesome!!
Only been a day or so! Crushing it!
|
@kentcdodds @mpeyper I was hoping to get input on this. Currently, if I remove the |
@merodiro noticed if we utilize the OR operator with the types we wanted to pass in the possible desired behavior we wanted would work with the current tests. |
src/asyncUtils.ts
Outdated
| // TODO: Discuss with Kent and Maintainers about behavior of returning nothing currently there are tests handling this behavior that may be an anti-pattern. | ||
| // ? Should waitFor() always expect something returned | ||
| const waitFor = async <T>( | ||
| callback: () => T | Promise<T>, | ||
| { interval, timeout, suppressErrors = true }: WaitOptions = {} | ||
| ) => { | ||
| const checkResult = () => { | ||
| try { | ||
| const callbackResult = callback() | ||
| return callbackResult || callbackResult === undefined | ||
| } catch (e) { | ||
| } catch (error: unknown) { | ||
| if (!suppressErrors) { | ||
| throw e | ||
| throw error as Error | ||
| } | ||
| return undefined | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kentcdodds @mpeyper This is the current Types for waitFor() the type is currently expecting "something" to be returned. We could make it more explicit to never expect void null etc...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are two use cases for waitFor in this library (I'm not sure how it behaves in RTL)
waitForthis thing to not throw:In this case, the only value that would break the waiting is if the callback returnswaitFor(() => { expect(result.current).toBe(expectedValue) })
undefined(no return).waitForthis thing to returntruthyIn this case, the wait will only break when thewaitFor(() => result.current === expectedValue)
true(or anythruthyvalue) is returned.
Does that help?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely helps! Thanks.
JacobMGEvans
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Minimum types started for Pure file
- File needs better, improved typing
- Refactoring needed for ESLint Disables to be removed IF POSSIBLE
| // eslint-disable-next-line @typescript-eslint/no-floating-promises | ||
| act(() => { | ||
| removeCleanup(unmountHook) | ||
| unmount() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Pure file needs to be heavily reviewed and ensure that the types are ideal for user interfacing.
|
It's gonna pass... 😅 lol EDIT: Victory!! lol |
src/pure.tsx
Outdated
| } | ||
|
|
||
| const updateResult = (value: unknown, error?: unknown) => { | ||
| const updateResult = (value?: R, error?: Error) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
|
previous commit: All tests are strongly typed with minimal types |
|
@mpeyper This is likely ready for review. |
|
Well done heroes! The villain is soon defeated 🎉 Just gotta wait for Batman 😁 🙌 |
|
Thanks so much for the effort! I'll give this a review tonight, which is about 13 hours away in my timezone. |
src/pure.tsx
Outdated
| type Props<T = any, R = any> = { | ||
| callback: (props: T) => R | ||
| hookProps: unknown | ||
| onError: CallableFunction | ||
| children: CallableFunction | ||
| } | ||
| function TestHook({ callback, hookProps, onError, children }: Props) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if this needs to be generic but we are not calling passing anything to it so it will always be any
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@merodiro It should be inferred from the callback wich is passed on line 69:

Agree that it probably shouldn't be any, though...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<TestHook> isn't generic and isn't passing anything to Props on line 15 so it will always be any
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@merodiro Oh 🤦♂️ I completely missed that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think TestHook should be made generic.
src/pure.tsx
Outdated
| } | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| function renderHook<T = any, R = any>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can remove the default value (any) because it will be inferred
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed that these should ideally be inferred in s many situations as possible.
I'm a bit worried about the scenario where initialProps is not provided in the renderHooks call, but new props are provided in the rerender call, e.g.
test('should infer from initialProps', () => {
const { rerender } = renderHook(({ arg }) => useSomeHook(arg), {
initialProps: { arg: false }
})
rerender({ arg: true })
})
test('should infer from defaulted args', () => {
const { rerender } = renderHook(({ arg = false } = {}) => useSomeHook(arg))
rerender({ arg: true })
})(note: the inferring from the defaulted args is the ideal goal, but might not actually have enough information to infer successfully, even with the best typings in the world)
src/pure.tsx
Outdated
| import { cleanup, addCleanup, removeCleanup } from './cleanup' | ||
|
|
||
| // TODO: Add better type, currently file is utilizing minimum types to work | ||
| // TODO: Attempt to refactor code to remove ESLint disables if possible |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably don't need these TODO lines anymore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do not, please remove them.
EDIT: I was able to remove it. 😄
mpeyper
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't reviewed asyncUtils yet, but ran out of time tonight. Other than the comments I've left a few other notes:
- running
npm run buildproduces types into nested directories in thelibdirectory. This is because of the seperatedtestroot. I'm happy if you want to move them intosrc/__tests__to make the issue go away. - you haven't defined a
typesfield inpackage.json. This is required if the generated types are no at/index.d.tsof the repo's root (kcd-scriptsdoes not output them there). I thinklib/index.d.tswill work if you do1of this list - There's a lot of eslint rules being disabled. Generally I'm against doing this on a per-line basis. either the rule is bogus for this project (like the dangling promise one and
act) and should be turned off globally, or the issue should actually be addressed.
I'll continue with this tomorrow.
test/suspenseHook.ts
Outdated
| describe('suspense hook tests', () => { | ||
| const cache = {} | ||
| const fetchName = (isSuccessful) => { | ||
| const cache: { value?: Promise<string> | string | Error } = {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Running npm run validate produces:
[lint] /Users/mxp001/programming/frontend/libraries/react-hooks-testing-library/test/suspenseHook.ts
[lint] 17:24 warning Unsafe assignment of an any value @typescript-eslint/no-unsafe-assignment
Making this const cache: { value?: Promise<string | Error> | string | Error } = {} and .catch((e: Error) => (cache.value = e)) on line 17 removes the warning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Has been fixed 🎉
|
|
||
| function addCleanup(callback) { | ||
| function addCleanup(callback: () => Promise<void> | void) { | ||
| cleanupCallbacks.unshift(callback) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't related to the PR, but I wonder if this would be clearer as cleanupCallbacks = [callback, ...cleanupCallbacks] and also align better with the other immutable updates of cleanupCallbacks in this file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like a good one, updated 🎉
src/pure.tsx
Outdated
| } | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| function renderHook<T = any, R = any>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed that these should ideally be inferred in s many situations as possible.
I'm a bit worried about the scenario where initialProps is not provided in the renderHooks call, but new props are provided in the rerender call, e.g.
test('should infer from initialProps', () => {
const { rerender } = renderHook(({ arg }) => useSomeHook(arg), {
initialProps: { arg: false }
})
rerender({ arg: true })
})
test('should infer from defaulted args', () => {
const { rerender } = renderHook(({ arg = false } = {}) => useSomeHook(arg))
rerender({ arg: true })
})(note: the inferring from the defaulted args is the ideal goal, but might not actually have enough information to infer successfully, even with the best typings in the world)
src/pure.tsx
Outdated
| type Props<T = any, R = any> = { | ||
| callback: (props: T) => R | ||
| hookProps: unknown | ||
| onError: CallableFunction | ||
| children: CallableFunction | ||
| } | ||
| function TestHook({ callback, hookProps, onError, children }: Props) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think TestHook should be made generic.
src/pure.tsx
Outdated
| hookProps: unknown | ||
| onError: CallableFunction | ||
| children: CallableFunction | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think Props would be better as
type TestHookProps<T, R> = {
callback: (props: T) => R
hookProps: R
onError: (error: Error) => void
children: (value: R) => void
}
src/pure.tsx
Outdated
| import { cleanup, addCleanup, removeCleanup } from './cleanup' | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| type Props<T = any, R = any> = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, I prefer generic types to better describe what they are genericizing, e.g. TProps and TResult make following their usage (and intellisense) much easier to me.
|
@mpeyper Thanks Batman 🥰, will continue fighting the villains 😄, I promise you 😤! 💪 I will take a look at this now and try to do what I can, I will also take a second look at |
mpeyper
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
asyncUtils is looking ok. Still more disabled lint rules than I generally like. I may have to be a bit lenient on them though given the codebase was never really designed with TS in mind. We can always work on them piecemeal after these changes are merged.
One more thing: I noticed we have a reference to the DefinitelyTyped types in our contributing guide. That probably needs to reworded in some way or just removed completely as well as it won't be relevant once this is merged.
src/asyncUtils.ts
Outdated
| timeout = true | ||
| } | ||
|
|
||
| function createTimeoutError(utilName: string, { timeout }: Pick<WaitOptions, 'timeout'>) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could this just be a constructor on TimeoutError?
src/asyncUtils.ts
Outdated
| timeoutId = setTimeout( | ||
| let timeoutId: number | ||
| if (options.timeout && options.timeout > 0) { | ||
| timeoutId = window.setTimeout( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is window required here but not on line 20?
src/asyncUtils.ts
Outdated
| } | ||
|
|
||
| const waitForValueToChange = async (selector, options = {}) => { | ||
| const waitForValueToChange = async (selector: () => unknown, options = {}) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think options should be typed to WaitOptions here to have better support for consumers.
src/asyncUtils.ts
Outdated
| } | ||
|
|
||
| class TimeoutError extends Error { | ||
| timeout = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this field is being used anymore now that error instanceof TimeoutError works
src/pure.tsx
Outdated
| type TestHookProps<TProps, TResult> = { | ||
| callback: (props: TProps) => TResult | ||
| hookProps: TProps | undefined | ||
| onError: (error: Error) => void | ||
| children: (value: TResult) => void |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome follow-up!
|
@mpeyper sorry I have closed it with a wrong force push. I asked @juhanakristian to open it again |


What:
Converting library to TypeScript, issue #498
Removes deprecated
waitutil (as per discussion in KCD Discord)https://discord.com/channels/715220730605731931/785649901782433852/786338242101379142
Creating this PR on behalf of @tigerabrodi we and other people from KCD Discord are going to work on it as a group
Checklist: