-
Notifications
You must be signed in to change notification settings - Fork 324
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
Remove withRouter
(1/n)
#3007
Remove withRouter
(1/n)
#3007
Conversation
For a more modern testing API that: 1. Discourages testing implementation details. 2. Makes it easier to test user-visible behavior.
Prepares GlobalSearchContainer tests for functionalization. The existing tests tested methods on the GlobalSearchContainer class, which were an implementation detail (those methods were meant to be called by the child `GlobalSearch` component). Those methods will be turned into `useCallback` callbacks in a functionalized component not accessible from outside the component, requiring a rewrite of the tests.
Preparing to replace those HOCs with hooks.
Codecov Report
@@ Coverage Diff @@
## master #3007 +/- ##
==========================================
+ Coverage 46.35% 47.78% +1.42%
==========================================
Files 252 254 +2
Lines 5324 5316 -8
Branches 1224 1218 -6
==========================================
+ Hits 2468 2540 +72
+ Misses 2856 2776 -80
Continue to review full report at Codecov.
|
Deployment preview for |
export function queryMatch(query: QueryObject) { | ||
return window.matchMedia(json2mq(query)); | ||
} |
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.
Dead code originally used in module search before we moved to Elasticsearch
|
||
// Fetch the module data of the existing modules in the timetable and ensure all | ||
// lessons are filled | ||
const timetables = store.getState().timetables.lessons; |
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.
Not using useSelector
here, because if we bind timetables
outside this hook:
- We'll need to pass
timetables
as a dependency touseCallback
, which will cause the callback to change when the user modifies the timetable. This will cause the effect below to be executed, which is not what we want. - If we don't pass
timetables
as a dependency, the callback may capture a old copy oftimetables
, which will cause the incorrect modules to be fetched.
expect(window.scrollTo).toHaveBeenCalledTimes(1); | ||
expect(window.scrollTo).toHaveBeenCalledWith(0, 0); | ||
history.push('/foo'); | ||
act(() => history.push('/foo')); |
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.
act
is needed to flush effects
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.
Man, this is truly a heroic effort. Props to you for doing this :P
const [Kawaii] = useState(() => sample(icons) ?? icons[0]); | ||
const [defaultMood] = useState(() => sample(defaultMoods) ?? defaultMoods[0]); |
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.
Using state to only initialize once instead of using refs is rather weird lol. It looks like there's an easy way to write a hook for this that does it correctly facebook/react#14490 (comment).
Also array access like this is not really safe anyway (if sample returns undefined then the array is empty, so [0]
would also return undefined), so just use !
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.
Using state to only initialize once instead of using refs is rather weird lol.
Hmm, I'm not sure if the additional code overhead is worth it. Also looks like someone else also suggested using useState
in that issue 😆. IMO useState
is the best solution here as conceptually what we want is literally state that we don't want to change. I don't think it's exactly a ref either as refs tend to imply some sort of imperative/mutable variable.
Also array access like this is not really safe anyway (if sample returns undefined then the array is empty, so [0] would also return undefined), so just use !
Yeah I just wanted to avoid the noise of eslint-disable
, but sure that works too. What we really want is nullthrows
but probably it's probably not worth adding it for just one callsite.
/** | ||
* Fetch module list on mount. | ||
*/ | ||
function useFetchModuleListAndTimetableModules(): { |
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.
Do we need to get rid of connect
? This feels a fair bit more awkward then the old code, though having good typings is nice
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.
What feels awkward? If this hook is weird, maybe we could just inline it into the AppShell
component. I just thought it was nicer to isolate the fetching logic into its own hook so that we didn't have so many variables lying around. I don't think we need to get rid of connect
, but I don't think keeping it around helps much?
`ScrollToTop` was used in exactly 1 way: scroll to top/hash on mount. The new hook implements that and removes all configuration and props.
jest.mock('utils/react'); | ||
const mockedScrollToHash = scrollToHash as jest.MockedFunction<typeof scrollToHash>; | ||
|
||
const Tester: FC = () => { |
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.
Maybe consider using the hook testing library https://react-hooks-testing-library.com/usage/basic-hooks
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.
Hmm, I think having an explicit tester component is clearer? I didn't think using that library helps much since this component is only 2 lines long, and we'll still need to provide a Router
wrapper (similar to the snippet below from hook testing library docs). The additional indirection seems to provide little value in this case.
const wrapper = ({ children }) => <CounterStepProvider step={2}>{children}</CounterStepProvider>
const { result } = renderHook(() => useCounter(), { wrapper })
draft.moduleBank.moduleList = (storeOverrides.moduleBank?.moduleList ?? | ||
relevantStoreContents.moduleBank.moduleList) as typeof draft.moduleBank.moduleList; |
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.
Ah. I guess it is possible to fix the typing of MODULES
, but it's a bit awkward (we need to add this util type microsoft/TypeScript#24509). Cast is probably fine too, since this is just a test
let onMediaChangeCallback: () => void; | ||
const addEventListener = (_type: string, listener: (...args: unknown[]) => void) => { | ||
onMediaChangeCallback = listener; | ||
}; |
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.
Might be simpler to just mock the entire hook, but this does also add some coverage to the hook itself, so 🤷
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 should add tests for the hook itself and simplify the tests here. Was intending to do it but forgot. I'll file an issue to fix another time
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.
Created issue #3011.
Thanks for the quick reviews @ZhangYiJiang 🏃♂️ |
Context
As #2922 requires extensive changes to the whole codebase, I've decided to split it into multiple PRs.
This PR works towards React Router 6 compatibility by removing calls to
withRouter
. RR6 is necessary for preloading support, which is necessary in #2922. This is incomplete and will be continued in a future PR, but I think it may be best to get some feedback on the current work first.Implementation
Frontend tests of hoc-wrapped components will likely need to be changed significantly, because:
history
and Redux state props. We need to useMemoryRouter
/Router
and Redux'sProvider
instead.Added @testing-library/react so that it's easier to test components in context and from a user's perspective.
makeResponsive
is deprecated with a newuseMediaQuery
hook.A new global
__TEST__
variable is declared, as we sometimes need to distinguish between__DEV__
and__TEST__
.GlobalSearchContainer
tests have been rewritten with this new approach. Would appreciate feedback!