Testing a component with two fetch calls? - next.js

I'm using next js 13 along with Jest and react-testing-library for testing. I have a page with a form that i'm trying to test. I'm using react-hook-form.
export default function FormPage() {
const [isSuccess, setIsSuccess] = useState()
const submitForm: SubmitHandler<T> = async (data) => {
// call an api route here to send data to db
if (response.status == 200) setIsSuccess(true)
else setIsSuccess(false)
}
return (
<>
<form onSubmit={handleSubmit(submitForm)}>
// inputs here
</form>
{isSuccess ? <SuccessAlert /> : <Error />}
</>
)
}
I want to check if the success alert appears after the form is successfully submitted. I could mock the global.fetch but my problem is that there are two fetch happening in this page. A location input is also making fetch calls to give location suggestions to the user as they type. So when I mock global.fetch for the location input, it appears that it's also replacing the fetch call inside the submitForm function, hence, submitting the form doesn't really do anything.
Here's how I mocked the global.fetch for the location input:
describe("Form" , () => {
beforeEach(() => {
render(<FormPage/>)
global.fetch = jest.fn(() =>
Promise.resolve({
status: () => 200,
json: () =>
Promise.resolve({
data: { suggestions: SAMPLE_LOCATION_SUGGESTIONS },
}),
})
) as jest.Mock;
})
it("should show success alert after successfully submitting form", async () => {
await act(() => // fill up the form)
fireEvent.click(Submitbutton)
expect(scree.getByTestId("success-alert")).toBeInThisDocument()
})
})
Running screen.debug() just before the assertion shows that there are no errors appearing upon submission so the form is already valid. But it appears that calling the api for form submission does not happen at all. I'm guessing it's because of my initial mocking of fetch in beforeEach(). But i'm not sure how should should I mock 2 fetch inside a component.

Related

Unable to catch the 'auth' event in Hub.listen while calling Auth.federatedSignIn

I am using SolidJS and building a SPA (no server rendering). For authentication, I use the #aws-amplify/core and #aws-amplify/auth packages. At the application root I call the Hub.listen function:
Hub.listen('auth', ({ payload }) => console.log(payload));
In the SignUp component I call Auth.federatedSignIn:
const SignUp = () => {
return (
<button onClick={() => {
Auth.federatedSignIn({ provider: CognitoHostedUIIdentityProvider.Google });
}}>
Sign up
</button>
);
}
I have configured the Amplify as such:
Amplify.configure({
Auth: {
region: import.meta.env.VITE_AWS_REGION,
userPoolId: import.meta.env.VITE_AWS_POOL_ID,
userPoolWebClientId: import.meta.env.VITE_AWS_POOL_CLIENT_ID,
oauth: {
domain: import.meta.env.VITE_AUTH_URL,
responseType: 'code',
redirectSignIn: location.origin + '/account/external',
redirectSignOut: location.origin + '/my',
},
},
});
When I click on the button I am redirected to the import.meta.env.VITE_AUTH_URL (simply outside of my app), choose an account, and then return back to the /account/external page. At that time I expect a consoled payload object in Web tools, but there is nothing. I get it when I call Auth.signOut(), so I assume that I configured Amplify correctly and Hub is subscribed to the auth channel.
My thoughts were that Hub cannot catch any events because after returning the application basically renders again in a new context and Hub simply isn't able to catch anything (events aren't sent from AWS?). I tried to declare the urlOpener function under the oauth property in the config and Google's sign page opened in a new tab, but even then I couldn't get any events in the preserved old page (from which I called Auth.federatedSignIn).
Questions:
How should I organize the code to get the signIn and signUp events?
Can I pass some data into the Auth.federatedSignIn to get it back in the Hub.listen, so I will be able to join the CognitoUser with the data that existed at the time of starting Sign in/Sign up (I want to add a new login type to existed user)?
Here is an example regarding the first question. Just check that your listener is set before you call the Auth.federatedSignIn() method.
export default class SignInService {
constructor(private landingFacade: LandingFacade) {
this.setupAuthListeners(); // Should be called at the top level.
}
private setupAuthListeners() {
Hub.listen('auth', ({ payload: { event, data } }) => {
switch (event) {
case 'signIn':
this.landingFacade.signInSuccess();
break;
case 'signIn_failure':
console.log('Sign in failure', data);
break;
case 'configured':
console.log('the Auth module is configured', data);
}
});
}
public async signIn(): Promise<void> {
await Auth.federatedSignIn();
}
}
For the second one: I'll use a local state and set/query the object you need.

Trigger a action upon success full response of another action ngrx

I created a ngrx effect that's responsible for sending a POST request to a back end. now i want to change the implementation little bit. if the post request is successful I want to trigger another action that responsible update my ngrx store. but I couldn't figure out how to do that.
I tried to use switch map and merge map but didn't work.
creatTour$ = createEffect(() =>
this.actions$.pipe(
ofType(TourAction.createTour),
mergeMap((action) => {
const payload = action.tour;
const endpoint = environment.urls.tour.replace("{tourProviderId}", "1");
const request = new ServiceRequest(endpoint, 'POST', payload, options);
return this.requestHandlerService.invoke(request).pipe(
map((sucessResponce) => {
if (sucessResponce.code === 200) {
TourListAction.updateTourList({ tour: [tour] }) // i want to trigger this action and
// below action
}
return TourAction.createTourSucess({ responce: sucessResponce.message })
}),
catchError(err => {
const errorMessage = JSON.stringify(err);
console.log(errorMessage);
return of(TourAction.createTourFail({ errorMessage }))
})
)
})
)
);
i tried this way too
return [TourAction.createTourSucess({ responce: sucessResponce.message }
TourListAction.updateTourList({ tour: [tour] })]
it throws this error
Property 'type' is missing in type '(({ responce: string; }
& TypedAction<"[Tour] Create Tour Success">) | ({ tour: Tours[]; } &
TypedAction<"[Tour List] Tour List Update">))[]' but required in type
'Action'.
is this the better way to do this.i saw there is new thing called entity should I use that for this implementation?
Why not update your state on the createTourSucess action?
You can also return multiple actions:
.pipe(
switchMap(() =>
return [
TourListAction.updateTourList({ tour: [tour] }) ,
TourAction.createTourSucess({ responce: sucessResponce.message })
]
)
)
https://medium.com/#amcdnl/dispatching-multiple-actions-from-ngrx-effects-c1447ceb6b22
You can return multiple actions from your effect, they will all be dispatched.
See https://medium.com/#tanya/understanding-ngrx-effects-and-the-action-stream-1a74996a0c1c
For your code:
return successResponse.code === 200 ? [
createTourUpdateAction(tour),
TourAction.createTourSuccess
] : [TourAction.createTourSuccess]

Access to the latest state from redux-observable epic after api response

I have read this Accessing the state from within a redux-observable epic, but it doesn't helps me finding the answer.
I am using redux-observable in my react-redux app, I have an epic will trigger an API invoke, code as below:
const streamEpicGet = (action$: Observable<Action>, state$) => action$
.pipe(
ofType(streamActions.STREAM_ITEM_GET),
withLatestFrom(state$),
mergeMap(([action, state]) => {
return observableRequest(
{
failureObservable: error => {
const actions = []
return actions
},
settings: {
body: queryParams, // I can access to the state data here to organize the query params I need for calling the API
url: '/path/to/api',
},
successObservable: result => {
const { pushQueue } = state
// here I want to access to the latest state data after API response
}
})
}),
)
In the above code, I use withLatestFrom(state$) so I can access to the latest data when executing the mergeMap operator code, that is to say I can access to the state data here to organize the query params for API.
However, during the time after API request sends out and before it responses, there are other actions happening, which are changing the state's pushQueue, so after the API response, I want to read the latest state pushQueue in my successObservable callback.
My problem is I always get the same state data as when preparing the query param, i.e.: I cannot get the latest state data after API response in successObservable callback.
Let me know if you need more info, thanks.
const streamEpicGet = (action$: Observable<Action>, state$) => action$
.pipe(
ofType(streamActions.STREAM_ITEM_GET),
withLatestFrom(state$),
mergeMap(([action, state]) => {
return observableRequest(
{
failureObservable: error => {
const actions = []
return actions
},
settings: {
body: queryParams, // I can access to the state data here to organize the query params I need for calling the API
url: '/path/to/api',
},
successObservable: result => {
const { pushQueue } = state$.value // Use state$.value to always access to the latest state
// here I want to access to the latest state data after API response
}
})
}),
)
So the answer is to use state$.value.
I get this by reading Accessing state in redux-observable Migration document

#ngrx Action called Infinitely with Effects

Please forgive me if this is an easy answer. I have a complicated login logic that requires a few calls before a user has a complete profile. If a step fails, it shouldn't break the app -- the user just doesn't get some supplemental information.
The flow I'm looking to achieve is this:
Call Revalidate.
Revalidate calls RevalidateSuccess as well as ProfileGet (supplemental fetch to enhance the user's state).
ProfileGetSuccess.
To save tons of code, the actions exist (it's a giant file).
The app kicks off the action: this._store.dispatch(new Revalidate())
From there, we have the following effects:
#Effect()
public Revalidate: Observable<any> = this._actions.pipe(
ofType(AuthActionTypes.REVALIDATE),
map((action: Revalidate) => action),
// This promise sets 'this._profile.currentProfile' (an Observable)
flatMap(() => Observable.fromPromise(this._auth.revalidate())),
// Settings are retrieved as a promise
flatMap(() => Observable.fromPromise(this._settings.get())),
switchMap(settings =>
// Using map to get the current instance of `this._profile.currentProfile`
this._profile.currentProfile.map(profile => {
const onboarded = _.attempt(() => settings[SettingsKeys.Tutorials.Onboarded], false);
return new RevalidateSuccess({ profile: profile, onboarded: onboarded });
}))
);
//Since I couldn't get it working using concatMap, trying NOT to call two actions at once
#Effect()
public RevalidateSuccess: Observable<any> = this._actions.pipe(
ofType(AuthActionTypes.REVALIDATE_SUCCESS),
mapTo(new ProfileGet)
);
#Effect()
public ProfileGet: Observable<any> = this._actions.pipe(
ofType(AuthActionTypes.PROFILE_GET),
// We need to retrieve an auth key from storage
flatMap(() => Observable.fromPromise(this._auth.getAuthorizationToken(Environment.ApiKey))),
// Now call the service that gets the addt. user data.
flatMap(key => this._profile.getCurrentProfile(`${Environment.Endpoints.Users}`, key)),
// Send it to the success action.
map(profile => {
console.log(profile);
return new ProfileGetSuccess({});
})
);
Reducer:
export function reducer(state = initialState, action: Actions): State
{
switch (action.type) {
case AuthActionTypes.REVALIDATE_SUCCESS:
console.log('REVALIDATE_SUCCESS');
return {
...state,
isAuthenticated: true,
profile: action.payload.profile,
onboarded: action.payload.onboarded
};
case AuthActionTypes.PROFILE_GET_SUCCESS:
console.log('PROFILE_GET_SUCCESS');
return { ...state, profile: action.payload.profile };
case AuthActionTypes.INVALIDATE_SUCCESS:
return { ...state, isAuthenticated: false, profile: undefined };
default:
return state;
}
}
As the title mentions, dispatching the action runs infinitely. Can anyone point me in the right direction?
The answer lies here:
this._profile.currentProfile.map needed to be this._profile.currentProfile.take(1).map. The issue wasn't the fact that all my actions were being called, but because I was running an action on an observable, I suppose it was re-running the action every time someone was touching the observable, which happened to be infinite times.
Moreso, I was able to refactor my action store so that I can get rid of my other actions to call to get the rest of the user's data, instead subscribing to this._profile.currentProfile and calling a non-effect based action, ProfileSet, when the observable's value changed. This let me remove 6 actions (since they were async calls and needed success/fail companion actions) so it was a pretty big win.

Google:assistance the final response did not set

we have Google Assistance project, that is working fine, for every intent,
now i want to get dynamic data from web service and return, for that, I am using request module, but its giving error
the final response did not set
below is my code
app.intent('doctor_list', (conv, {doctor}) => {
Request.get("url", (error, response, body) => {
if(error) {
con.ask('data return')
}
con.ask('err')
});
})
You aren't clear in what your Request object is, but I suspect the problem is that you're not returning a Promise object from your call. Network calls are handled asynchronously in node.js, and when you make an async call, you need to return a Promise object.
The easiest way to handle this is using the request-promise-native package. Your code might look something like this:
const Request = require('request-promise-native');
app.intent('doctor_list', (conv,{doctor}) => {
return Request.get('url')
.then( body => {
conv.ask( 'data return' );
})
.catch( err => {
console.log( err );
conv.close( 'error' );
});
});

Resources