I working on app which records video using webcam and saves, well this feature is working perfectly, but i also want to write test for it whether its saving successfully or not. For this I am using RTL / Jest for it and i am quite struggling to write, i have write tests: -> user open recording , start recording and close recording (Tests Passed) -> But not unable to write test to check save recording, i have check after save recording whether blob state is set or not, if set then pass the test, but the state is not updating, its getting initialized value -> component structure , parent component Template form which is using react-hook-form, and in it child component , i send props including blob/setchange function to webcam and then webcam returns the blob file to template form.
export const Webcam = function ({propBlob,propFile,onUpdate,featureFlags}){
const recordWebcam = useRecordWebcam(OPTIONS);
const [recordingDuration, setRecordingDuration] = useState(0);
const [videoBlob, setVideoBlob] = useState(propBlob);
const [start, setStart] = useState(false);
const handleStartRecord = () => {
setStart(true);
setTimeout(() => {
setStart(false);
recordWebcam.start();
}, 3000);
};
useEffect(() => {
if (recordWebcam.status === "RECORDING") {
const interval = setInterval(() => {
setRecordingDuration((prevDuration) => prevDuration + 1);
}, 1000);
return () => clearInterval(interval);
}
}, [recordWebcam.status === "RECORDING"]);
useEffect(() => {
if (recordWebcam.status === "RECORDING") {
return () => setRecordingDuration(0);
}
}, [recordWebcam.status !== "RECORDING"]);
const setBlob = async (recordWebcam) => {
recordWebcam.download();
const blob = await recordWebcam.getRecording();
setVideoBlob(blob);
onUpdate(blob) // calling update prop function
};
const handleOpen = () => {
recordWebcam.open();
}
const minutes = Math.floor(recordingDuration / 60);
const seconds = Math.floor(recordingDuration % 60);
const formattedTime = `${minutes}:${seconds.toString().padStart(2, "0")}`;
const handleOutsideClick = (e) => {
if (recordWebcam.webcamRef.current && !recordWebcam.webcamRef.current.contains(e.target)) {
recordWebcam.close()
}
};
useEffect(() => {
if (recordWebcam.status === 'RECORDING' || recordWebcam.status === 'OPEN' || recordWebcam.status === 'INIT') {
document.addEventListener('click', handleOutsideClick);
}
return () => {
document.removeEventListener('click', handleOutsideClick);
};
}, [recordWebcam.status]);
const handleRemoveListener = () => {
document.removeEventListener('click', handleOutsideClick);
}
return (
<>
<div className="mainDiv ">
<div className="d-flex justify-content-center">
<div className="d-flex justify-content-center gap-3 align-items-center">
{videoBlob && <Checkmark size='44px' /> }
{FEATURE_FLAGS['browser-video']==true? <button
data-bs-toggle="modal"
data-bs-target="#videoRecord"
type="button"
className="btn-openClose"
onClick={handleOpen}
>
Record Video
</button>:null}
</div>
</div>
{videoBlob && (
<video src={URL.createObjectURL(videoBlob)} height={300} width={500} controls/>
)}
<div
className="modal fade"
id="videoRecord"
tabIndex="-1" // changed here
aria-labelledby="videoRecordLable"
aria-hidden="true"
ref={nodeRef}
>
</div>
</>
);
};
export const TemplateForm = function (props) {
const [isLoading, setIsLoading] = useState(false);
const [videoblob, setVideoBlob] = useState('hello');
const [disabled, setDisabled] = useState(() => {
if (props && props.disabled) {
return props.disabled;
}
return false;
});
const onSave = async function (formValues) {
// saving logic
};
const onSubmit = function (formValues) {
let url_file=''
let url_blob=''
if (latest=="blob"){
//const blob = new Blob([videoBlob], { type: 'application/octet-stream' });
url_blob=URL.createObjectURL(videoblob)
formValues['recorded-video']=url_blob
// formValues['recorded-video']=url_blob;
}
else{
//const file=new Blob([fileData],{ type: 'application/octet-stream' })
url_file=URL.createObjectURL(fileData)
formValues['recorded-video']=url_file
// formValues['recorded-video']=url_file;
}
console.log(" Values ",formValues);
setIsLoading(true);
onSave(formValues);
};
const handleVideoUpdate = (blob) => {
setVideoBlob(blob);
};
return (
<div>
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)}>
<section className="app-card">
<Webcam propBlob={videoBlob} onUpdate={handleVideoUpdate}/>
{errors["base-video"] && (
<p className="text-danger">{errors["base-video"].message}</p>
)}
</section>
<p data-testid="rec-video-test">{videoblob}</p>
{isLoading ? "Saving" : "Save Template"}
</button>
</form>
</FormProvider>
</div>
);
};
My test Script
test("Video is recording successfully", async () => {
const TestForm = () => {
const methods = useForm();
const mockElement: HTMLElement = document.createElement('div');
global.URL.createObjectURL = jest.fn();
mockElement.textContent = JSON.stringify({ host: 'localhost' }); // Provide the expected content
mockElement.accessKey = '';
mockElement.autocapitalize = '';
mockElement.dir = '';
const getBlob=jest.fn()
// Spy on getElementById and return the mock element
jest.spyOn(document, 'getElementById').mockReturnValue(mockElement);
return (
<FormProvider {...methods}>
<TemplateForm/>
</FormProvider>
)
};
render(<TestForm/>)
const recordBtn = screen.getByText('Record video')
fireEvent.click(recordBtn) // click to check popup // Passed
await waitFor(() => {}, { timeout: 5000 });
const startBtn= screen.getByText("Start Recording")
fireEvent.click(startBtn) // on popup click recording //Passed
const stopRecord=document.getElementsByClassName('stop-btn-theme');
const recElement = stopRecord[0];
expect (recElement).toBeInTheDocument // Passed
fireEvent.click(recElement) // when stop button click
const savebtn=screen.getByText('Save & Continue') //its to save video recording //Passed
await act(async () => { // This test Failing
// trigger the state update here
const videoSave = screen.getByTestId('rec-video-test'); // it always give initial values Hello
const updatedState = videoSave.textContent;
expect(updatedState).toContain('blob:');
});
In my test script when i screen debug hello
it gives this which is initialized, it not giving the updated videoBlob value, the code was big so just pasted it here the important components, i just need some path how to solve this,any suggestions would help a lot.