I want to test a React component where certificate file is uploaded and the result of file can be shown in a text area.
I am still learning writing unit test and here facing difficulty to mock FileReader, readAsText and onloadend.
I have read few examples but they do not work as expected. Any guidance would be helpful
Thanks
Cert Component
export default function Cert() {
const fileInputRef = useRef();
function handleEvent(obj, event) {
var event = new Event(event, { target: obj, bubbles: true });
return obj ? obj.dispatchEvent(event) : false;
}
let handleChangeFile = (file) => {
let fileData = new FileReader();
fileData.readAsText(file);
fileData.onloadend = () => {
let el = document.getElementById('cert');
el.value = event.target.result;
handleEvent(el, 'input');
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
};
fileData.onerror = function () {
console.log(fileData.error);
};
};
return (
<CheckboxWithOptions name="int" label='app'>
<Field
component={ExpandingTextareaField}
name="certificate"
label='Upload Certificate *'
isRequired
validate={required}
id="cert"
/>
<div>
<Button
label='Upload file'
type="button"
appearance="brand"
size="large"
onClick={() => {
fileInputRef.current.click();
}}
data-typeId="test"
/>
<input
style={{
opacity: 0,
position: 'fixed',
top: 0,
left: 0,
width: 0,
}}
type="file"
accept=".cert"
ref={fileInputRef}
onChange={(e) => handleChangeFile(e.target.files[0])}
data-testid="testinput"
/>
</div>
}
Cert.test.js
it('upload file to show in textarea ', async () => {
const file = new File(['dummy'], 'test.cert', { type: 'cert' })
render(
<div>
<label htmlFor="file-uploader">Upload file:</label>
<input id="file-uploader" type="file" onChange={(e) => handleChangeFile('e')} />
</div>,
)
const { getByLabelText } = render(<TestComponent />);
await waitForElementToBeRemoved(() => screen.getByText(/loading/i));
expect(getByLabelText("CheckBox").value).toBe("false");
fireEvent.click(screen.getByLabelText("CheckBox"))
const input = screen.getByLabelText(/Upload file/i)
fireEvent.click(input)
userEvent.upload(input, file)
await waitFor(() => expect(handleChangeFile).toBeCalledTimes(1)); // working fine till here
jest.spyOn(global, "FileReader")
.mockImplementation(function () {
readAsText = jest.fn();
});
fireEvent.change(input, {
target: {
files: [file]
}
});
expect(FileReader).toHaveBeenCalled(); // not working
// test the FileReader and textarea
})
Had the same issue with checking some side effect that should be invoked on FileReader.onload, so I just ended up setting a short pause after triggering the event (I'm using enzyme):
const pauseFor = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds));
...
wrapper.find('.upload-box').simulate('drop', someMockedDropEvent);
// set pause was the only way to make reader.onload to fire
await pauseFor(100);
expect(something).toEqual(something)
Related
I uploading file with Upload Manually in Ant Design but it not able to show preview image as base64. I want to show preview image base64 as picture card when I picked image from browser. How I can fix it?
My code upload:
const FormPost = () => {
const [fileList, setFileList] = useState([]);
const [uploading, setUploading] = useState(false);
const onFinish = (values) => {
console.log('Success:', values);
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
const normFile = (e) => {
console.log('Upload event:', e);
if (Array.isArray(e)) {
return e;
}
return e?.fileList;
};
const uploadProps = {
onRemove: (file) => {
const index = fileList.indexOf(file);
const newFileList = fileList.slice();
newFileList.splice(index, 1);
setFileList(newFileList);
},
beforeUpload: (file) => {
setFileList([...fileList, file]);
return false;
},
listType: 'picture-card',
fileList,
};
return (
<Form layout="vertical" name="new-post" onFinish={onFinish} onFinishFailed={onFinishFailed} autoComplete="off">
<Form.Item label="Upload" valuePropName="fileList" getValueFromEvent={normFile}>
<Upload {...uploadProps}>
<div>New photo</div>
</Upload>
</Form.Item>
</Form>
);
};
I had the same question in my mind some days ago. The key is to use ["thumbUrl"] attribute of the file user selected. This is how I solved it:
If it is how you have Upload component defined:
<Upload
action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
listType="picture-card"
fileList={params.fileList}
onChange={params.onChange}
onPreview={params.onPreview}
>
{params.fileList.length === 0 && '+ Upload'}
</Upload>
It should be the Image component you are trying to show the user:
<Image
width={'80%'}
height={'60%'}
src={params.fileList[0]["thumbUrl"]}
/>
I think the title is self-explanatory. What am I doing wrong below? What I want to achieve is getting the text out of a photo, right after the user selects a photo.
The error I get is:
createWorker.js:173 Uncaught Error: RuntimeError: null function or function signature mismatch
What am I doing wrong?
const { createWorker } = require("tesseract.js");
const [file,setFile] = useState();
const worker = createWorker({
logger: (m) => console.log(m),
});
const doOCR = async (image) => {
await worker.load();
await worker.loadLanguage("eng");
await worker.initialize("eng");
const {
data: { text },
} = await worker.recognize(image);
// } = await worker.recognize('https://tesseract.projectnaptha.com/img/eng_bw.png');
console.log(text);
setOcr(text);
};
const [ocr, setOcr] = useState("Recognizing...");
useEffect(() => {
file ? doOCR(file) : console.log('no file selected yet!');
}, [file]);
const getFile = (e) => {
console.log("Upload event:", e);
if (e) {
if (Array.isArray(e)) setFile(e[0]);
setFile(e)
}
}
....
<p>{ocr}</p> /* this only displays "Recognizing..." */
<Form.Item
name="uploadedPhoto"
label="Upload your photo scan"
getValueFromEvent={getFile}
// rules={[{ required: true }]}>
<Input type="file"
// onChange={onImageUpload}
/>
</Form.Item>
Solved it by doing it like this instead of the above (I applied the function to the onChange of the Input itself, not the Form.Item element)
const handleFileSelected = (e) => {
const files = Array.from(e.target.files);
setFile(files[0]);
};
<Input type="file" onChange={handleFileSelected} />
I have an upload button like this. I want to show preview of uploaded Image on a div. Hence, I want the previewImage. This is how i'm trying to achieve it but onPreview is not getting fired at all.
It's a functional component. Sandbox=> https://codesandbox.io/s/silly-breeze-2gvewe
function AddAttachment(props) {
const getBase64 = (file)=>{
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
}
const onChange = ({ fileList: newFileList }) => {
setFileList(()=>(newFileList));
console.log(fileList);
};
const handlePreviewImage = async (file)=>{
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj);
}
setBase64({
previewImage: file.url || file.preview,
previewVisible: true,
previewTitle: file.name || file.url.substring(file.url.lastIndexOf('/') + 1),
});
}
return (
<UploadButton
fileList={fileList}
onPreview ={handlePreviewImage}
onChange={onChange}
aspect={2} listType="picture" />
)
}
From the docs:
onPreview
A callback function, will be executed when file link or preview icon is clicked.
When you try clicking on the preview image or link, you should see handlePreviewImage get called.
I'm using "Input" to get files in a react app. I'm able to get a blob using readAsDataURL() but it's stripping out the file name and is replacing with "Data". When I log the blob I'm getting "data:image/jpeg;base64,/9j/4QAYRXhpZgAAS..." - "data" being the name that displays.
const handleGetFiles = (e) => {
const reader = new FileReader();
reader.readAsDataURL(e);
reader.onload = () => {
fileCollection((fileCollection) => [...fileCollection, {id: index, data: reader.result}]);
}
}
<Input
accept="image/*,video/*"
id="contained-button-file"
multiple type="file"
onChange={e => handleGetFiles(e)}
/>
Is there a way to use URL.createObjectURL(file) instead? I can't get that to work either.
The filename that you're looking for doesn't come from the FileReader API, but rather from the input element's files property.
You can expose the file in 2 ways from React:
Through a ref:
const Input = () => {
const inputEl = React.useRef();
const handleGetFiles = () => {
alert(inputEl.current.files[0].name)
};
return (
<input
...
onChange={handleGetFiles}
ref={inputEl}
/>
);
}
Through the event target
const Input = () => {
const handleGetFiles = (e) => {
alert(e.target.files[0].name)
};
return (
<input
...
onChange={handleGetFiles}
/>
);
}
Currently I have code working where it will upload multiple files to s3.
My problem is, the image files are corrupted and will not open.
I want to upload them as images, or Form data, not encoded in base64 format.
How would I go about solving this?
I apologize for the sloppy code.
My Code
import { API, Storage } from "aws-amplify";
import { S3Album } from 'aws-amplify-react';
function Upload(props) {
async function readContent(fileToUpload) {
return new Promise((accept, reject) => {
const reader = new FileReader();
reader.readAsDataURL(fileToUpload);
reader.onload = () => accept({
name: fileToUpload.name,
type: fileToUpload.type,
content: reader.result
});
reader.onerror = () => reject();
});
}
async function handleSubmit(event) {
event.preventDefault();
const filesAsArray = [...fileToUpload.current.files];
const fileInfo = Promise.all(filesAsArray.map(readContent))
.then(files => Promise.all(files.map(uploadFile)))
.then(console.log);
return false;
}
async function uploadFile(fileToUpload) {
setShowLoading(true);
const filename = `${job.jobId}/${fileToUpload.name}`;
const stored = await Storage.put(filename, fileToUpload.content, {
contentType: fileToUpload.type
});
return new Promise(accept => {
setTimeout(() => accept(fileToUpload), 1000);
});
setShowLoading(false);
}
return(
<h4 style={centerText}> Please upload all images for {(job.streetAddress)} </h4>
<br></br>
<br></br>
<S3Album path={job.jobId}/>
<form onSubmit={handleSubmit}>
<input multiple type="file" ref={fileToUpload}></input>
<Button expand="block" color="primary" strong="true" size="default" type="submit"> Upload </Button>
</form>
);
}
export default withRouter(Upload);
const stored = await Storage.put(filename, fileToUpload, {contentType:fileToUpload.type});
Storage.put() can take in a file object directly, so no need for FileReader().
See image example: https://aws-amplify.github.io/docs/js/storage