Test Element on page when request is pending in puppeteer - jestjs

I use next code to check Request and Response when submit button is clicked:
const postRequestInfo = await Promise.all([
page.waitForRequest(
request =>
request.url() === 'https://httpbin.org/status/200' && request.method() === 'POST'
),
page.waitForResponse(
response =>
response.url() === 'https://httpbin.org/status/200' && response.status() === 200
),
page.click('[data-hook="async-button__post-submit-btn"]')
]);
Is there possibility to test property of element when request is pending?
So, I want to execute this piece of code after request was sent but response didn't come yet:
expect(
await page.$eval('[data-hook="async-button__post-submit-btn"]', el =>
el.disabled)
).toEqual(true)

I found this! I should use page.on
page.on('request', async interceptedRequest => {
if (
interceptedRequest.url() === 'https://httpbin.org/status/200' &&
interceptedRequest.method() === 'POST'
) {
expect(
await page.$eval('[data-hook="async-button__post-submit-btn"]', el => el.disabled)
).toEqual(true);
}
interceptedRequest.continue();
});

Related

How display payment succeeded without return_url param in Stripe

I have one problem in integration Stripe into my React application. I use code from official Stripe documentation. It works expected. My question is how to check is payment succeeded without using return_url ? Am I required to use return url ? I found in Stripe documentation redirect: "if_required" option, but that doesnt make anything. I just get error problem in my console if I put this object in confirmPayment method. I would like have scenario is payment successfull that client navigate to some Confirmation page and to get message payment successfully.
App.jsx
import { loadStripe } from "#stripe/stripe-js";
import { Elements } from "#stripe/react-stripe-js";
import CheckoutForm from "./CheckoutForm";
import "./App.css";
// Make sure to call loadStripe outside of a component’s render to avoid
// recreating the Stripe object on every render.
// This is your test publishable API key.
const stripePromise = loadStripe("pk_test_51LmE9VAoYs2flpvClDqeh0f1vhaDUkBM0bRGaJgThjtaMd3PiPUGQOHjn9f7XW1HGgSQBvTq3xoLy9PovlWLPUnR0031srjgyb");
export default function App() {
const [clientSecret, setClientSecret] = useState("");
useEffect(() => {
// Create PaymentIntent as soon as the page loads
fetch("/create-payment-intent", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ items: [{ id: "xl-tshirt" }] }),
})
.then((res) => res.json())
.then((data) => setClientSecret(data.clientSecret));
}, []);
const appearance = {
theme: 'stripe',
};
const options = {
clientSecret,
appearance,
};
return (
<div className="App">
{clientSecret && (
<Elements options={options} stripe={stripePromise}>
<CheckoutForm />
</Elements>
)}
</div>
);
}
CheckoutForm.jsx
import {
PaymentElement,
useStripe,
useElements
} from "#stripe/react-stripe-js";
export default function CheckoutForm() {
const stripe = useStripe();
const elements = useElements();
const [message, setMessage] = useState(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
if (!stripe) {
return;
}
const clientSecret = new URLSearchParams(window.location.search).get(
"payment_intent_client_secret"
);
if (!clientSecret) {
return;
}
stripe.retrievePaymentIntent(clientSecret).then(({ paymentIntent }) => {
switch (paymentIntent.status) {
case "succeeded":
setMessage("Payment succeeded!");
break;
case "processing":
setMessage("Your payment is processing.");
break;
case "requires_payment_method":
setMessage("Your payment was not successful, please try again.");
break;
default:
setMessage("Something went wrong.");
break;
}
});
}, [stripe]);
const handleSubmit = async (e) => {
e.preventDefault();
if (!stripe || !elements) {
// Stripe.js has not yet loaded.
// Make sure to disable form submission until Stripe.js has loaded.
return;
}
setIsLoading(true);
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
// Make sure to change this to your payment completion page
return_url: "http://localhost:3000",
},
});
// This point will only be reached if there is an immediate error when
// confirming the payment. Otherwise, your customer will be redirected to
// your `return_url`. For some payment methods like iDEAL, your customer will
// be redirected to an intermediate site first to authorize the payment, then
// redirected to the `return_url`.
if (error.type === "card_error" || error.type === "validation_error") {
setMessage(error.message);
} else {
setMessage("An unexpected error occurred.");
}
setIsLoading(false);
};
return (
<form id="payment-form" onSubmit={handleSubmit}>
<PaymentElement id="payment-element" />
<button disabled={isLoading || !stripe || !elements} id="submit">
<span id="button-text">
{isLoading ? <div className="spinner" id="spinner"></div> : "Pay now"}
</span>
</button>
{/* Show any error or success messages */}
{message && <div id="payment-message">{message}</div>}
</form>
);
}
When using redirect: 'if_required', then the return_url attribute becomes not required.
If no redirection is required then you need to wait for the confirmation from the method stripe.confirmPayment and check if there is an error in the response.
To do so, you can adapt your CheckoutForm.jsx file and adapt your function handleSubmit like below:
setIsLoading(true);
const response = await stripe.confirmPayment({
elements,
confirmParams: {
},
redirect: 'if_required'
});
if (response.error) {
showMessage(response.error.message);
} else {
showMessage(`Payment Succeeded: ${response.paymentIntent.id}`);
}
setIsLoading(false);
Also, if you want to get notified from your backend when a successful payment has occurred, you can set a webhook[1] and listen to this Stripe event payment_intent.succeeded[2]
[1] https://stripe.com/docs/webhooks
[2] https://stripe.com/docs/api/events/types#event_types-payment_intent.succeeded

React front end not updating after delete operation in MERN application

I have a MERN application where I'm trying to delete a note (consisting of a title and content) when the delete button is clicked. When I click the button, the backend mongoDB database is updated - the item is, in fact, deleted - but then the console spits out an error.
Here's the delete operation, traced through the relevant files:
// App.jsx
const deleteNote = id => {
NoteDataService.deleteNote(id)
.then(response => {
console.log(`note ${id} deleted`);
setNotes(prevState => {
return prevState.notes.filter(note => note._id !== id);
});
})
.catch(e => console.log(e));
};
// note.js
import http from '../http-common.js';
class NoteDataService {
createNote = data => http.post('/', data);
getAll = () => http.get('/');
deleteNote = id => http.delete(`?id=${id}`);
}
export default new NoteDataService();
// http-common.js
import http from '../http-common.js';
class NoteDataService {
createNote = data => http.post('/', data);
getAll = () => http.get('/');
deleteNote = id => http.delete(`?id=${id}`);
}
export default new NoteDataService();
And here's the error:
App.jsx:41 Uncaught TypeError: Cannot read property 'filter' of undefined
at App.jsx:41
Of the CRUD operations I'm attempting to implement, it seems only the "Read" (get) functionality is working properly to initially populate my list of notes from the backend (using useEffect() on App component load).
Before the component mount your state notes will be null. Do check notes in your state is not null and it's an array will be fine
prevState.notes && prevState.notes.filter(note => note._id !== id)
This works...
const deleteNote = id => {
NoteDataService.deleteNote(id)
.then(response => {
console.log(`note ${id} deleted`);
console.log(notes);
setNotes(notes.filter(note => note._id !== id));
})
.catch(e => console.log(e));
};

React-Hooks PUT request a specific field from an object on click

How can I make a PUT request and target ONLY patient.status and change it from Active to Inactive? I don't want to change the entire patient, only the patient.status.
Keep in mind I want to use fetch(), I just do not know the syntax on how to only edit patient.status and pass it through my backend, etcetera. Help would be appreciated.
I am fetching data from a specific patient this way and declaring state.
const [patient, setPatient] = useState ({})
useEffect(() => {
fetch(`/api/patients/${match.params.id}`)
.then(response => response.json())
.then(json => setPatient(json))
}, [patient])
The submit function.
const onDeactivePatientSubmit = (e) => {
e.preventDefault()
}
The button.
{ patient.status === "Active" &&
<Button size="small" color="primary" variant="outlined" onClick={onDeactivePatientSubmit}
value="Inactive">DE-ACTIVATE PATIENT</Button>
}
The backend. (Please keep in mind I want to keep the req.body.editPatient as is)
router.put('/:id', async (req, res) => {
const {id} = req.params;
await Patient.findByIdAndUpdate(id, {...req.body.editPatient});
console.log(req.body.editPatient)
res.status(200).send({});
})
fetch accepts a second parameter, an options object, where you can set the method you want to use, the data to send, etc. You can use the spread operator to create a new/updated patient object. It will make a copy of the fetched patient object, and you can update the status field. You can send this new object via fetch:
const onDeactivePatientSubmit = (e) => {
e.preventDefault()
let updatedPatient = {
...patient,
status: 'Inactive',
}
fetch(`/api/patients/${patient.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
// the data to send
body: JSON.stringify({ editPatient: updatedPatient })
})
.then(res => res.json())
.then(data => {
// check if successfully updated in db
setPatient(updatedPatient)
}

Testing and mocking fetch in async useEffect and async Redux-Saga

I'm testing a functional component, that use React-Hooks and Redux-Saga. I can pass parameters in URL for the component, because they are a login page component.
My URL i pass is 'localhost/access/parameter', and when this parameter exists, i need to call a async redux saga, and if the fetch is OK, i put the result in redux-store. When the result is on redux-store, i have a useEffect that verify the result and if is OK, i put her in a input.
I can mock the result with axios, but i'm migrating to use only fetch. i mock the fetch, but when i use
mount(component), provided by enzyme, i do not how to await the redux-saga termine the request and useEffect do your job. I put a console log inside a effect, saga and log the input props to see your value prop, but the value is always empty . I tried to use setImmediate() and process.nextTick().
Links i use to write the code: 1,2, 3
I'm using formik, so they pass some props to me.
My component
const Login = ({
setFieldError, errors, response, fetchDomain, location, values, handleChange, handleBlur, setFieldValue, history,
}) => {
useEffect(() => {
async function fetchUrlDomain() {
const { pathname } = location;
const [, , domain] = pathname.split('/');
if (typeof domain !== 'undefined') {
await fetchDomain(domain);
}
}
fetchUrlDomain();
}, [fetchDomain, location]);
useEffect(() => {
if (typeof response === 'string') {
setFieldError('domain', 'Domain not found');
inputDomain.current.focus();
} else if (Object.keys(response).length > 0) {
setFieldValue('domain', response.Domain);
setFieldError('domain', '');
}
}, [response, setFieldValue, setFieldError]);
return (
<input name="domain" id="domain" value={values.domain} onChange={handleChange} onBlur={handleBlur} type="text" />
);
}
const LoginFormik = withFormik({
mapPropsToValues: () => ({ domain: '' }),
enableReinitialize: false,
validateOnBlur: false,
validateOnChange: false,
})(Login);
const mapStateToProps = () => ({ });
const mapDispatchToProps = dispatch => ({
fetchDomain: (value) => {
dispatch(action({}, constants.RESET_RESPONSE_DOMAIN));
dispatch(action(value, constants.FETCH_DOMAIN_REQUEST));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(LoginFormik);
My Saga
export function* fetchDomain(action) {
const url = yield `${mainUrl}/${action.payload}`;
try {
const response = yield fetch(url).then(res => res.json());
yield put(reduxAction(response , constants.FETCH_DOMAIN_SUCCESS));
} catch (e) {
yield put(reduxAction(e, constants.FETCH_DOMAIN_FAILURE));
}
}
My Reducer
case constants.FETCH_DOMAIN_FAILURE:
return { ...initialState, response: 'Domain not found' };
case constants.FETCH_DOMAIN_SUCCESS: {
const { payload } = action;
return {
...initialState,
id: payload.Id,
apis: payload.Apis,
response: payload,
};
}
case constants.RESET_RESPONSE_DOMAIN:
return { ...initialState };
My Test
it('input with fetch only', (done) => {
const mockSuccessResponse = {
Id: 'fafafafa',
Apis: [],
Domain: 'NAME',
};
const mockJsonPromise = Promise.resolve(mockSuccessResponse);
const mockFetchPromise = Promise.resolve({
json: () => mockJsonPromise,
});
global.fetch = jest.fn().mockImplementation(() => mockFetchPromise);
const wrapper = mount(
<Provider store={store}>
<LoginForm
history={{ push: jest.fn() }}
location={{ pathname: 'localhost/login/Domain' }}
/>
</Provider>,
);
process.nextTick(() => {
const input = wrapper.find('#domain');
console.log(input.props());
expect(input.props().value.toLowerCase()).toBe('name');
global.fetch.mockClear();
done();
});
});
I expect my input have value, but he don't. I tried to use jest-fetch-mock but just don't work, and i want to use native jest methods, no thirty party libraries.
I cannot say what's wrong with your current code. But want to propose different approach instead.
Currently you are testing both redux part and component's one. It contradicts with unit testing strategy when ideally you should mock everything except module under the test.
So I mean if you focus on testing component itself it'd be way easier(less mocks to create) and more readable. For that you need additionally export unwrapped component(Login in your case). Then you can test only its props-vs-render result:
it('calls fetchDomain() with domain part of location', () => {
const fetchDomain = jest.fn();
const location = { pathName: 'example.com/path/sub' }
shallow(<Login fetchDomain={fetchDomain} location={location} />);
expect(fetchDomain).toHaveBeenCalledTimes(1);
expect(fetchDomain).toHaveBeenCalledWith('example.com');
});
it('re-calls fetchDomain() on each change of location prop', () => {
const fetchDomain = jest.fn();
const location = { pathName: 'example.com/path/sub' }
const wrapper = shallow(<Login fetchDomain={fetchDomain} location={location} />);
fetchDomain.mockClear();
wrapper.setProps({ location: { pathName: 'another.org/path' } });
expect(fetchDomain).toHaveBeenCalledTimes(1);
expect(fetchDomain).toHaveBeenCalledWith('another.org');
});
And the same for other cases. See with this approach if you replace redux with direct call to fetch() or whatever, or if you refactor that data to come from parent instead of reading from redux store you will not need to rewrite tests removing mocks to redux. Sure, you will still need to test redux part but it also can be done in isolation.
PS and there is no profit to await fetchDomain(...) in useEffect since you don't use what it returns. await does not work like a pause and that code may rather confuse reader.

How to fetch (Express) data ONLY once the (React) form-submitted data has been successfully received and served?

I'm currently building a league of legends (a MOBA or multiplayer online battle arena game) search-based web app that essentially allows the user to search for their summoner's name and obtain general information regarding their search input. (The data is provided by the game's own third-party api)
I've been able to successfully retrieve the form data and perform the intended backend processes, however, upon the client's initial render, my results-listing component is already trying to fetch the nonexistent processed data.
How do I prevent the server request from firing until the server has actually successfully served the data?
(abridged single-component client example)
the summoner data endpoint is set to http://localhost:3001/api/summoner
server does not contain any additional routes
const App = () => {
const [summName, setSummName] = useState('');
const summonerFormData = new FormData();
// let data;
const findSummoner = () => {
summonerFormData.set('summonerName', summName);
}
// problem here
const data = axios.get('http://localhost:3001/api/summoner');
// axios.get('http://localhost:3001/api/summoner')
// .then(res => {
// data = res;
// });
return (
<div>
<form
method="POST"
action="http://localhost:3001/api/summoner"
onSubmit={findSummoner}
>
<input
value={summName}
name="summName"
onChange={e => setSummName(e.target.value)}
/>
<button type="submit">submit</button>
</form>
{data !== undefined &&
<div className="results">
data.map(match => {
<div>
<p>{match.kills}</p>
<p>{match.deaths}</p>
<p>{match.assists}</p>
</div>
})
</div>
}
</div>
)
}
Here's the Repo for some more context, but please don't hesitate to ask if you need more information or have any questions at all!
I really appreciate any help I can get!
Thanks!
Edits:
I've also tried using the useEffect hook considering the lifecycle point I'm trying to fetch would be componentDidMount, but wasn't quite sure what the solution was. Doing more research atm!
Close, but no cigar. Request gets stuck at 'pending'.
let data;
const fetchData = () => {
axios.get('http://localhost:3001/api/summoner');
};
useEffect(() => {
if (summName !== '') {
fetchData();
}
}, summName);
I tried putting the axios request within an async function and awaiting on the request to respond, and it seems to be working, however, the server is still receiving undefined when the client starts, which then is attempting to be fetched, never allowing the promise to be fulfilled.
const fetchData = async () => {
await axios
.get('http://localhost:3001/api/summoner')
.then(res => {
data = res;
})
.catch(() => {
console.log('error');
});
};
useEffect(() => {
fetchData();
}, [])
So I took the advice and recommendations from #imjared and #HS and I'm literally so close..
I just have one more problem... My data-mapping component is trying to map non-existent data before actually receiving it, giving me an error that it's unable to map match of undefined..
const [modalStatus, setModalStatus] = useState(false);
const [loading, setLoading] = useState(false);
const [data, setData] = useState({ hits: [] });
const [summName, setSummName] = useState('');
const [summQuery, setSummQuery] = useState('');
const summonerFormData = new FormData();
const prepareResults = async () => {
await setSummQuery(summName);
};
const findSummoner = async () => {
setLoading(true);
setModalStatus(false);
await summonerFormData.set('summonerName', summQuery);
};
useEffect(() => {
const fetchData = async () => {
if (summQuery) {
setData({ hits: [] });
console.log('fetching');
await axios
.get('http://localhost:3001/api/summoner')
.then(res => {
setData(res.data);
setLoading(false);
setModalStatus(true);
return data;
})
.catch(error => {
console.log(error);
});
}
};
fetchData();
}, [summQuery]);
SUCCESS! Thank you guys! Here's what ended up working for me:
const findSummoner = async () => {
setSummQuery(summName);
};
useEffect(() => {
setData({ hits: [] });
summonerFormData.set('summonerName', summQuery);
const fetchData = async () => {
setModalStatus(false);
setLoading(true);
if (summQuery !== '') {
setLoading(true);
console.log('fetching');
await axios
.get('/api/summoner')
.then(res => {
setData({
hits: res.data,
});
setError(false);
setLoading(false);
setModalStatus(true);
return data;
})
.catch(() => {
setError(true);
console.log('error');
});
}
};
if (summQuery !== '') {
fetchData();
}
}, [summQuery]);
This flow will help you design better -
1. User - input
2. Hit - search
3. Set loading in state - true,
5. Set data in state - empty
6. Call api
7. Get data
8. Then, set data in state
6. Set loading in state - false
Along the side in the render/return -
1. if loading in the state - indicate loading.
2. if loading done( false ) and data is not empty - show data.
3. if loading done and data is empty - indicate 'not-found'.
Coming to the initial render part - the axios.get() calls the api, which should only be initiated once the form is submitted in the case. Therefore, that logic should be moved inside the event-handler.

Resources