Hello everyone! I tried to post on stackoverflow with no luck and neither my search through issues/google gave me any result :/
So I'm doing a migration. I've got this custom hook that I'm testing with @testing-library/react-hooks, jasmine and karma. It's an AngularJS/React application.
const useSearchParams = () => {
const $rootScope = useAngularInjection('$rootScope');
const stateProvider = useAngularInjection('$location');
const [ url, setUrl ] = useState(stateProvider.search());
$rootScope.$on('$locationChangeSuccess', () => {
setUrl(stateProvider.search());
});
return url;
};
Where useAngularInjection is another hook that just injects AngularJS stuff (it works fine).
This is my test:
it('should update the search query from the url when it changes', () => {
$location.url('https://localhost:8000?all=true&order=title&start=0');
$rootScope.$apply();
const { result, rerender } = renderHook(() => useSearchParams());
$location.url('https://localhost:8000?unscheduled=true&order=title&start=10');
$rootScope.$apply();
rerender();
expect(result.current).toEqual({
unscheduled: 'true',
order: 'title',
start: '10',
});
});
At the moment result.current is still {all: 'true', start: '0', order: 'title'} – it's not updating.
I tried to debug it and it actually goes back into the $locationChangeSuccess branch so I don't understand why it's not updating the value of result.current
$rootScope and $location are injected previously and also work fine.
Any idea?
Would you please be able to provide a minimal working example either the repo or a code sandbox is fine.
A working example would help a lot, but a couple of questions and observations...
Does $rootScope.$apply(); apply synchronously or does it update in the future? If it's asynchronous, you would have to wait for it with something like waitForNextUpdate.
Also, if $rootScope.$apply(); is expected to cause setUrl to be called, then you should wrap it in and act call.
Finally, the rerender should not be necessary as the setUrl call will trigger the hook to rerender automatically.
Hello, thanks for the super quick replies! I will work on a sandbox example, it's just not easy with the whole AngularJS setup :(
$rootScope.$apply() is synchronous – I tried previously with waitForNextUpdate but it was just failing to call it.
Tried by calling
act(() => {
$rootScope.$apply();
});
but it's still not working. Plus, I read the act docs multiple times but I don't seem to be getting exactly what is supposed to do?
Debugging it (without act) I can see setUrl being called again. So weird :/
what is the console output if you change the locationChangeSuccess to:
$rootScope.$on('$locationChangeSuccess', () => {
const newUrl = stateProvider.search();
console.log(newUrl)l
setUrl(newUrl);
});
Assuming it is getting the new start value, can you give me the output for:
const useSearchParams = () => {
console.log('hook 1');
const $rootScope = useAngularInjection('$rootScope');
const stateProvider = useAngularInjection('$location');
const [ url, setUrl ] = useState(stateProvider.search());
console.log('hook 2');
$rootScope.$on('$locationChangeSuccess', () => {
console.log('hook 4 - this is it');
setUrl(stateProvider.search());
});
console.log('hook 3');
return url;
};
import { renderHook, act } from '@testing-library/react-hooks'
it('should update the search query from the url when it changes', () => {
console.log('test 1');
$location.url('https://localhost:8000?all=true&order=title&start=0');
console.log('test 2');
$rootScope.$apply();
console.log('test 3');
const { result, rerender } = renderHook(() => useSearchParams());
console.log('test 4');
act(() => {
$location.url('https://localhost:8000?unscheduled=true&order=title&start=10');
console.log('test 5');
$rootScope.$apply();
console.log('test 6');
}
console.log('test 7');
expect(result.current).toEqual({
unscheduled: 'true',
order: 'title',
start: '10',
});
});
Old school, but if we see:
test 1
test 2
test 3
hook 1
hook 2
hook 3
test 4
test 5
hook 4 - this is it
hook 1
hook 2
hook 3
test 6
test 7
then something very wrong is going on. That is the output I would expect for your assertion to pass (I think).
I read the
actdocs multiple times but I don't seem to be getting exactly what is supposed to do?
It flushes any batched updates so that by the time act call returns the state represents what was updated from any actions within the callback. In this case, it is likely irrelevant, but react will spit out warnings at you if state is updated outside of an act call.
Can confirm that
$rootScope.$on('$locationChangeSuccess', () => {
const newUrl = stateProvider.search();
console.log(newUrl)l
setUrl(newUrl);
});
logs the new value.
This is the output (I see you removed the call to rerender() and so did I)
'test 1'
'test 2'
'test 3'
'act(...) is not supported in production builds of React, and might not behave as expected.'
'hook 1'
'hook 2'
'hook 3'
'test 4'
'test 5'
'hook 4 - this is it'
'test 6'
'test 7'
Just noticed the act(...) issue. We're on Jasmine, Karma and jsDom but I'm starting to think Jasmine could be the issue here?
It's odd to say the least. I'm not sure how much more we can help without being able to run it ourselves.
Out if interest, which version of @testing-library/react-hooks are you using?
I'm on the latest version. Unfortunately, can't figure out a way of building an easy sandbox for this.
Thank you very much for your help anyway! Never had such responsive support from library maintainers.
Thanks! I'm going to close this now, but if you do manage to make a MWE then we can open this up and have a look! 🚀
I'm on the latest version. Unfortunately, can't figure out a way of building an easy sandbox for this.
fwiw, it doesn't need to be a sandbox, just a repo we can clone is enough.
Also, does latest mean the v4 we released just hours before you opened this issue, or are you still on v3?
Moving to Jest actually solved the issue :)
Most helpful comment
I'm on the latest version. Unfortunately, can't figure out a way of building an easy sandbox for this.
Thank you very much for your help anyway! Never had such responsive support from library maintainers.