How to add custom validator for form.item in ANTD.
I have a form item with image upload.
But because it's not textfield it doesn't work as should have.
Is there are any ways to add custom validator and condition true/false to form item ?
<Form.Item
name='photo'
label={'photo'}
rules={[
{
required: true,
message: 'upload Photo',
},
]}
>
<ImgCrop rotate>
<Upload
listType="picture-card"
fileList={fileList}
beforeUpload={beforeUpload}
onChange={onChange}
onPreview={onPreview}
maxCount={1}
>
UPLOAD
</Upload>
</Form.Item>
You can use beforeUpload prop of Upload component as custom validator. Suppose, if you want to restrict files less than 200 mb. try the below.
<Upload
name="file"
accept=".mov,.mp4,.mpeg-ts,.avi,.png,.jpg,.jpeg,.bmp"
showUploadList={false}
beforeUpload={(file) => {
if (file.type.includes("video")) {
const isLt2M = file.size / 1024 / 1024 < 200;
if (!isLt2M) {
message.error(
intl.formatMessage({
defaultMessage: "Video must smaller than 200MB!",
})
);
setDefaultFileList(null);
return false;
}
return true;
}
return true;
}}
customRequest={handleUploadMedia}
css="margin-top:20px;display:block"
></Upload>
Related
First off, let me admit to being a React newbie...
I want to set the value displayed and selected in this component after the first render (eg from a button)
Here's my test code (with imports etc removed)
$(function () {
const data = [
{
DataID: 1,
DataType: 'Data1'
},
{
DataID: 2,
DataType: 'Data2'
},
{
DataID: 3,
DataType: 'Data3'
},
{
DataID: 4,
DataType: 'Data4'
}
]
ReactDOM.render(
<SelectionsExample
options={data}
preset={[data[1]]}
/>,
document.getElementById('divExampleSelector')
)
});
function SelectionsExample(props) {
const [options, setOptions] = useState(props.options);
const [preset, setPreset] = useState(props.preset);
function handleSelect(s) {
console.log((s ? s.DataType : 'Nothing') + ' selected');
}
function handlePreset() {
let s = options[2];
console.log('Preset', s);
setPreset([s]);
}
return (
<>
<Typeahead
id="selections-example"
options={options}
defaultSelected={preset ?? []}
onChange={(s) => handleSelect(s[0])}
labelKey="DataType"
clearButton
placeholder="Choose a value..."
/>
<Button
onClick={handlePreset}
variant="outline-secondary">Preset </Button>
</>
)
}
On first render, all works fine with as expected the second item in my options list shown.
But when I click the 'Preset' button, handlePreset runs but nothing changes in the control. I would have expected the selection to change to value of options[2].
If I change the Typeahead prop 'defaultSelected' to 'selected', then the only item I can select is the one I pass in in the 'preset' prop.
What am I doing wrong?
Using defaultSelected makes the typeahead uncontrolled, and will only display a preset selection when the component mounts. Since you want to be able to change the preset later, you should use selected to make the typeahead controlled:
function SelectionsExample(props) {
const [selected, setSelected] = useState(props.preset);
function handleSelect(s) {
console.log((s[0] ? s[0].DataType : 'Nothing') + ' selected');
setSelected(s);
}
function handlePreset() {
let s = props.options[2];
console.log('Preset', s);
setSelected([s]);
}
return (
<>
<Typeahead
clearButton
id="selections-example"
labelKey="DataType"
onChange={handleSelect}
options={props.options}
placeholder="Choose a value..."
selected={selected}
/>
<button onClick={handlePreset}>
Preset
</button>
</>
);
}
Working sandbox: https://codesandbox.io/s/heuristic-haze-3vugt
I am trying to test a component rendered with Controller from react-hook-form with react-testing-library
<Controller
render={({ onChange, onBlur, value }) => (
<IonInput
onIonChange={onChange}
onIonBlur={onBlur}
value={value}
type="text"
data-testid="firstname-field"
/>
)}
name="firstName"
control={control}
defaultValue={firstName}
/>
Default values are as expected when I render the component with a some mock data. However, when I go about changing values, it seems the events are not firing. From this blog post it looks like ionic exports a set of test utils to handle ionic's custom events. After setting that up in my setupTests.ts I'm attempting to use both the ionFireEvent and the fireEvent from RTU, neither of which reflect changes in the component when I use debug(). I've set it up so I can use both fireEvent and ionFireEvent to test:
import { render, screen, wait, fireEvent } from "#testing-library/react";
import { ionFireEvent } from "#ionic/react-test-utils";
// using RTL fireEvent - no change
it("fires change event on firstname", () => {
const { baseElement } = renderGolferContext(mockGolfer);
const firstNameField = screen.getByTestId("firstname-field") as HTMLInputElement;
fireEvent.change(firstNameField, { target: { detail: { value: "Jill" } } });
expect(firstNameField.value).toBe("Jill");
});
// using IRTL ionFireEvent/ionChange - no change
it("fires change event on firstname", () => {
const { baseElement } = renderGolferContext(mockGolfer);
const firstNameField = screen.getByTestId("firstname-field") as HTMLInputElement;
ionFireEvent.ionChange(firstNameField, "Jill");
expect(firstNameField.value).toBe("Jill");
});
screen.debug(baseElement);
I've also tried moving the data-testid property to the controller rather than the IonInput suggested here, with the result being the same: no event is fired.
Here are the versions I'm using:
Using Ionic 5.1.1
#ionic/react-test-utils 0.0.3
jest 24.9
#testing-library/react 9.5
#testing-library/dom 6.16
Here is a repo I've created to demonstrate.
Any help would be much appreciated!
this line appears to be incorrect...
expect(firstNameField.value).toBe("Jill");
It should be looking at detail.value since that is what you set
expect((firstNameField as any).detail.value).toBe("Jill");
this is my test,
describe("RTL fireEvent on ion-input", () => {
it("change on firstname", () => {
const { baseElement, getByTestId } = render(<IonicHookForm />);
const firstNameField = screen.getByTestId(
"firstname-field"
) as HTMLInputElement;
fireEvent.change(firstNameField, {
target: { detail: { value: "Princess" } },
});
expect((firstNameField as any).detail.value).toEqual("Princess");
});
});
I'm trying to test an Info HOC on my react app :
const InfoHOC = (HocComponent) => ({ message }) => (
<>
<Tooltip title={message}>
<InfoIcon />
</Tooltip>
{HocComponent}
</>
);
export default InfoHOC;
I've simplified it. But as it's using material ui Tooltip component, I can't test if message is displayed on mouseover...
it('should display info message on <div /> mouseover', () => {
const Component = InfoHoc(<div>jest div</div>)({ message: 'jest infoHoc message' });
const { getByTitle, getByDisplayValue } = render(Component);
const icon = getByTitle('jest infoHoc message');
act(() => {
fireEvent(
icon,
new MouseEvent('mouseover', {
bubbles: true,
}),
);
});
expect(getByDisplayValue('jest infoHoc message')).toBeInTheDocument();
});
My last line is wrong... I think it's because mui tooltip display the message in a div at the end of the body, so not really in my rtl tree... BUT the first element of this tree is body !
I know that I should not test mui component, but here is not the purpose, I just want to be sure that InfoHoc has the right comportment, using mui tooltip or something else.
Here is the RTL tree after mouseover action :
<body>
<div>
<div
class="infoHoc"
>
<div>
jest div
</div>
<svg
aria-hidden="true"
class="MuiSvgIcon-root icon--right"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"
/>
</svg>
</div>
</div>
</body>
The event is good because icon has a title attr with message as value till mouseover is fired. As title attr is not here on my tree, I assume my event is well executed ;p
I am wrong testing that ? If not do you have an idea to solve my problem ?
Thank you all !
I think this is the cleanest way.
it('Renders tooltip when hovering over button', async () => {
render(<Search />);
const button = await screen.findByRole('button');
await userEvent.hover(button);
const tip = await screen.findByRole('tooltip');
expect(tip).toBeInTheDocument();
});
In case this can still help you, you need to findBy instead of getBy as the Tooltip is showing the tooltip after a delay
it('should display info message on <div /> mouseover', async () => {
const Component = InfoHoc(<div>jest div</div>)({ message: 'jest infoHoc message' });
const { getByTitle, findByDisplayValue } = render(Component);
const icon = getByTitle('jest infoHoc message');
act(() => {
fireEvent(
icon,
new MouseEvent('mouseover', {
bubbles: true,
}),
);
});
// Wait for the tooltip to show up
const tooltipText = await findByDisplayValue('jest infoHoc message')
expect(tooltipText).toBeInTheDocument();
});
Side note 1: I am not sure if you really need the act around fireEvent. testing-library should do it for you.
Side note 2: you can use user-event which has a cleaner syntax (and a .hover function)
I'm using b-form-select with server-side generated option tags:
<b-form-select :state="errors.has('type') ? false : null"
v-model="type"
v-validate="'required'"
name="type"
plain>
<option value="note" >Note</option>
<option value="reminder" >Reminder</option>
</b-form-select>
When no data is set for this field I want to auto-select the first option in the list.
Is this possible? I have not found how to access the component's options from within my Vue instance.
your v-model should have the value of the first option.
example
<template>
<div>
<b-form-select v-model="selected" :options="options" />
<div class="mt-3">Selected: <strong>{{ selected }}</strong></div>
</div>
</template>
<script>
export default {
data() {
return {
selected: 'a',
options: [
{ value: null, text: 'Please select an option' },
{ value: 'a', text: 'This is First option' },
{ value: 'b', text: 'Selected Option' },
{ value: { C: '3PO' }, text: 'This is an option with object value' },
{ value: 'd', text: 'This one is disabled', disabled: true }
]
}
}
}
</script>
You can trigger this.selected=${firstOptionValue} when no data is set.
what if we don't know what the first option is. The list is generated?
if you have dynamic data, something like this will work.
<template>
<div>
<b-form-select v-model="selected" :options="options" />
<div class="mt-3">Selected: <strong>{{ selected }}</strong></div>
</div>
</template>
<script>
export default {
data() {
return {
selected: [],
options: [],
};
},
mounted: function() {
this.getOptions();
},
methods: {
getOptions() {
//Your logic goes here for data fetch from API
const options = res.data;
this.options = res.data;
this.selected = options[0].fieldName; // Assigns first index of Options to model
return options;
},
},
};
</script>
If your options are stored in a property which is loaded dynamically:
computed property
async computed (using AsyncComputed plugin)
through props, which may change
Then you can #Watch the property to set the first option.
That way the behavior of selecting the first item is separated from data-loading and your code is more understandable.
Example using Typescript and #AsyncComputed
export default class PersonComponent extends Vue {
selectedPersonId: string = undefined;
// ...
// Example method that loads persons data from API
#AsyncComputed()
async persons(): Promise<Person[]> {
return await apiClient.persons.getAll();
}
// Computed property that transforms api data to option list
get personSelectOptions() {
const persons = this.persons as Person[];
return persons.map((person) => ({
text: person.name,
value: person.id
}));
}
// Select the first person in the options list whenever the options change
#Watch('personSelectOptions')
automaticallySelectFirstPerson(persons: {value: string}[]) {
this.selectedPersonId = persons[0].value;
}
}
My brain's hurting. After my page loads, I get some HTML. This is a stripped-down version:
window.addEventListener('load', () => {
if (window.location.pathname === '/profile' && Cookies.get('token')) {
axios.get('/api/profile-info').then(res => {
const member = res.data.member
const memberInfo = `
<form enctype="multipart/form-data" id="uploadProfilePictureForm">
<input type="file"/>
<button onclick="uploadPicture(event)">Upload</button>
</form>
`;
})
}
})
I then handle the onclick event:
const uploadPicture = (event) => {
event.preventDefault()
const form = document.getElementById('uploadProfilePictureForm')
console.log(form) // Just shows the HTML form
}
This handler is placed before window.addEventListener
The file name appears on the page, but after clicking "Upload", it won't show in the console (which I plan to send to my server).
How do I allow an onclick event to handle a file upload?
Solved
Inside window.addEventListener(), I used a simple input tag:
<input type="file" id="fileUpload" onchange="uploadPicture()"/>
Then, outside this event listener, I defined the uploadPicture() function:
function uploadPicture() {
var FD = new FormData()
var fileInput = document.getElementById('fileUpload')
FD.append("pictureFile", fileInput.files[0])
const data = FD.entries().next().value
console.log('data\n', data) // This is the FormData array
}