diff --git a/packages/vant/src/form/test/__snapshots__/props.legacy.js.snap b/packages/vant/src/form/test/__snapshots__/props.legacy.js.snap
deleted file mode 100644
index b708bbb89..000000000
--- a/packages/vant/src/form/test/__snapshots__/props.legacy.js.snap
+++ /dev/null
@@ -1,100 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`colon prop 1`] = `
-
-`;
-
-exports[`error-message-align prop 1`] = `
-
-`;
-
-exports[`input-align prop 1`] = `
-
-`;
-
-exports[`label-align prop 1`] = `
-
-`;
-
-exports[`label-width prop 1`] = `
-
-`;
-
-exports[`validate-first prop 1`] = `
-
-`;
diff --git a/packages/vant/src/form/test/__snapshots__/props.spec.tsx.snap b/packages/vant/src/form/test/__snapshots__/props.spec.tsx.snap
new file mode 100644
index 000000000..ebc6033b5
--- /dev/null
+++ b/packages/vant/src/form/test/__snapshots__/props.spec.tsx.snap
@@ -0,0 +1,171 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render colon when using colon prop 1`] = `
+
+`;
+
+exports[`should render error-message-align prop correctly 1`] = `
+
+`;
+
+exports[`should render input-align prop correctly 1`] = `
+
+`;
+
+exports[`should render label-align prop correctly 1`] = `
+
+`;
+
+exports[`should render label-width prop correctly 1`] = `
+
+`;
+
+exports[`should validate first field when using validate-first prop 1`] = `
+
+`;
diff --git a/packages/vant/src/form/test/props.legacy.js b/packages/vant/src/form/test/props.legacy.js
deleted file mode 100644
index 00d764e79..000000000
--- a/packages/vant/src/form/test/props.legacy.js
+++ /dev/null
@@ -1,433 +0,0 @@
-import { later, mockScrollIntoView } from '../../../test';
-import { mountForm, submitForm, getSimpleRules } from './shared';
-
-test('rules prop - execute order', async () => {
- const onFailed = jest.fn();
- const wrapper = mountForm({
- template: `
-
-
-
-
- `,
- data() {
- return {
- rules: [
- { required: true, message: 'A' },
- { validator: (val) => val.length > 6, message: 'B' },
- { validator: (val) => val !== 'foo', message: 'C' },
- ],
- };
- },
- methods: {
- onFailed,
- },
- });
-
- await submitForm(wrapper);
-
- expect(onFailed).toHaveBeenCalledWith({
- errors: [{ message: 'B', name: 'A' }],
- values: { A: '123' },
- });
-});
-
-test('rules prop - pattern', async () => {
- const onFailed = jest.fn();
- const wrapper = mountForm({
- template: `
-
-
-
-
- `,
- data() {
- return {
- rules: [{ pattern: /\d{6}/, message: 'foo' }],
- };
- },
- methods: {
- onFailed,
- },
- });
-
- await submitForm(wrapper);
-
- expect(onFailed).toHaveBeenCalledWith({
- errors: [{ message: 'foo', name: 'A' }],
- values: { A: '123' },
- });
-});
-
-test('rules prop - message function', async () => {
- const onFailed = jest.fn();
- const wrapper = mountForm({
- template: `
-
-
-
-
- `,
- data() {
- return {
- rules: [{ pattern: /\d{6}/, message: (val) => val }],
- };
- },
- methods: {
- onFailed,
- },
- });
-
- await submitForm(wrapper);
-
- expect(onFailed).toHaveBeenCalledWith({
- errors: [{ message: '123', name: 'A' }],
- values: { A: '123' },
- });
-});
-
-test('rules prop - formatter', async () => {
- const onFailed = jest.fn();
- const wrapper = mountForm({
- template: `
-
-
-
-
- `,
- data() {
- return {
- rules: [
- {
- message: 'foo',
- required: true,
- formatter: (val, rule) => {
- expect(rule.message).toEqual('foo');
- return val.trim();
- },
- },
- ],
- };
- },
- methods: {
- onFailed,
- },
- });
-
- await submitForm(wrapper);
-
- expect(onFailed).toHaveBeenCalledWith({
- errors: [{ message: 'foo', name: 'A' }],
- values: { A: ' ' },
- });
-});
-
-test('rules prop - async validator', async () => {
- const onFailed = jest.fn();
- const wrapper = mountForm({
- template: `
-
-
-
-
- `,
- data() {
- return {
- rules: [
- {
- validator: (value, rule) => {
- expect(value).toEqual('123');
- expect(typeof rule).toEqual('object');
- return new Promise((resolve) => resolve(true));
- },
- message: 'should pass',
- },
- {
- validator: () => new Promise((resolve) => resolve(false)),
- message: 'should fail',
- },
- ],
- };
- },
- methods: {
- onFailed,
- },
- });
-
- await submitForm(wrapper);
-
- expect(onFailed).toHaveBeenCalledWith({
- errors: [{ message: 'should fail', name: 'A' }],
- values: { A: '123' },
- });
-});
-
-test('validate-first prop', async () => {
- const onSubmit = jest.fn();
- const onFailed = jest.fn();
-
- const wrapper = mountForm({
- template: `
-
-
-
-
-
- `,
- data() {
- return {
- ...getSimpleRules(),
- value: '',
- };
- },
- methods: {
- onSubmit,
- onFailed,
- },
- });
-
- await submitForm(wrapper);
-
- expect(wrapper.html()).toMatchSnapshot();
- expect(onFailed).toHaveBeenCalledWith({
- errors: [{ message: 'A failed', name: 'A' }],
- values: { A: '', B: '' },
- });
-
- wrapper.setData({ value: 'foo' });
- await submitForm(wrapper);
-
- expect(onSubmit).toHaveBeenCalledWith({ A: 'foo', B: 'foo' });
-});
-
-test('colon prop', () => {
- const wrapper = mountForm({
- template: `
-
-
-
- Custom Label
-
-
- `,
- });
- expect(wrapper.html()).toMatchSnapshot();
-});
-
-test('label-align prop', () => {
- const wrapper = mountForm({
- template: `
-
-
-
-
- `,
- });
- expect(wrapper.html()).toMatchSnapshot();
-});
-
-test('label-width prop', () => {
- const wrapper = mountForm({
- template: `
-
-
-
-
- `,
- });
- expect(wrapper.html()).toMatchSnapshot();
-});
-
-test('input-align prop', () => {
- const wrapper = mountForm({
- template: `
-
-
-
-
-
-
-
-
- `,
- });
- expect(wrapper.html()).toMatchSnapshot();
-});
-
-test('error-message-align prop', () => {
- const wrapper = mountForm({
- template: `
-
-
-
- `,
- });
- expect(wrapper.html()).toMatchSnapshot();
-});
-
-test('validate-trigger - onBlur', async () => {
- const wrapper = mountForm({
- template: `
-
-
-
- `,
- data: getSimpleRules,
- });
-
- const input = wrapper.find('input');
-
- input.trigger('input');
- await later();
- expect(wrapper.contains('.van-field__error-message')).toBeFalsy();
-
- input.trigger('blur');
- await later();
- expect(wrapper.contains('.van-field__error-message')).toBeTruthy();
-});
-
-test('validate-trigger - onChange', async () => {
- const wrapper = mountForm({
- template: `
-
-
-
- `,
- data() {
- return {
- ...getSimpleRules(),
- value: '',
- };
- },
- });
-
- const input = wrapper.find('input');
-
- input.trigger('blur');
- await later();
- expect(wrapper.contains('.van-field__error-message')).toBeFalsy();
-
- wrapper.setData({ value: '1' });
- await later();
- expect(wrapper.contains('.van-field__error-message')).toBeFalsy();
-
- wrapper.setData({ value: '' });
- await later();
- expect(wrapper.contains('.van-field__error-message')).toBeTruthy();
-});
-
-test('validate-trigger - custom trigger in rules', async () => {
- const wrapper = mountForm({
- template: `
-
-
-
-
- `,
- data() {
- return {
- valueA: '',
- valueB: '',
- rulesA: [
- {
- message: 'A',
- required: true,
- trigger: 'onChange',
- },
- ],
- rulesB: [
- {
- message: 'B',
- required: true,
- trigger: 'onBlur',
- },
- ],
- };
- },
- });
-
- const inputs = wrapper.findAll('input');
-
- inputs[0].trigger('blur');
- wrapper.setData({ valueB: '1' });
- await later();
- wrapper.setData({ valueB: '' });
- await later();
- expect(wrapper.contains('.van-field__error-message')).toBeFalsy();
-
- inputs[1].trigger('blur');
- wrapper.setData({ valueA: '1' });
- await later();
- wrapper.setData({ valueA: '' });
- await later();
- expect(
- wrapper.element.querySelectorAll('.van-field__error-message').length
- ).toEqual(2);
-});
-
-test('scroll-to-error prop', async () => {
- const fn = mockScrollIntoView();
- const wrapper = mountForm({
- template: `
-
-
-
-
- `,
- data: getSimpleRules,
- });
-
- await submitForm(wrapper);
-
- expect(fn).toHaveBeenCalledTimes(1);
-});
-
-test('show-error-message prop', async () => {
- const wrapper = mountForm({
- template: `
-
-
-
-
- `,
- data() {
- return {
- ...getSimpleRules(),
- showErrorMessage: false,
- };
- },
- });
-
- await submitForm(wrapper);
- expect(wrapper.contains('.van-field__error-message')).toBeFalsy();
-
- wrapper.setData({ showErrorMessage: true });
-
- await submitForm(wrapper);
- expect(wrapper.contains('.van-field__error-message')).toBeTruthy();
-});
-
-test('show-error prop', async () => {
- const wrapper = mountForm({
- template: `
-
-
-
-
- `,
- data() {
- return {
- ...getSimpleRules(),
- showError: false,
- };
- },
- });
-
- await submitForm(wrapper);
- expect(wrapper.contains('.van-field--error')).toBeFalsy();
-
- wrapper.setData({ showError: true });
-
- await submitForm(wrapper);
- expect(wrapper.contains('.van-field--error')).toBeTruthy();
-});
diff --git a/packages/vant/src/form/test/props.spec.tsx b/packages/vant/src/form/test/props.spec.tsx
new file mode 100644
index 000000000..5f841fce5
--- /dev/null
+++ b/packages/vant/src/form/test/props.spec.tsx
@@ -0,0 +1,405 @@
+import { mount, later, mockScrollIntoView } from '../../../test';
+import { submitForm, getSimpleRules } from './shared';
+import { Form } from '..';
+import { Field, FieldRule } from '../../field';
+
+test('should ensure execute order of rules prop', async () => {
+ const onFailed = jest.fn();
+ const rules: FieldRule[] = [
+ { required: true, message: 'A' },
+ { validator: (val) => val.length > 6, message: 'B' },
+ { validator: (val) => val !== 'foo', message: 'C' },
+ ];
+ const wrapper = mount({
+ render() {
+ return (
+
+ );
+ },
+ });
+
+ await submitForm(wrapper);
+
+ expect(onFailed).toHaveBeenCalledWith({
+ errors: [{ message: 'B', name: 'A' }],
+ values: { A: '123' },
+ });
+});
+
+test('should support pattern in rules prop', async () => {
+ const onFailed = jest.fn();
+ const rules: FieldRule[] = [{ pattern: /\d{6}/, message: 'foo' }];
+ const wrapper = mount({
+ render() {
+ return (
+
+ );
+ },
+ });
+
+ await submitForm(wrapper);
+
+ expect(onFailed).toHaveBeenCalledWith({
+ errors: [{ message: 'foo', name: 'A' }],
+ values: { A: '123' },
+ });
+});
+
+test('should support message function in rules prop', async () => {
+ const onFailed = jest.fn();
+ const rules: FieldRule[] = [{ pattern: /\d{6}/, message: (val) => val }];
+ const wrapper = mount({
+ render() {
+ return (
+
+ );
+ },
+ });
+
+ await submitForm(wrapper);
+
+ expect(onFailed).toHaveBeenCalledWith({
+ errors: [{ message: '123', name: 'A' }],
+ values: { A: '123' },
+ });
+});
+
+test('should support formatter in rules prop', async () => {
+ const onFailed = jest.fn();
+ const rules: FieldRule[] = [
+ {
+ message: 'foo',
+ required: true,
+ formatter: (val, rule) => {
+ expect(rule.message).toEqual('foo');
+ return val.trim();
+ },
+ },
+ ];
+ const wrapper = mount({
+ render() {
+ return (
+
+ );
+ },
+ });
+
+ await submitForm(wrapper);
+
+ expect(onFailed).toHaveBeenCalledWith({
+ errors: [{ message: 'foo', name: 'A' }],
+ values: { A: ' ' },
+ });
+});
+
+test('should support async validator in rules prop', async () => {
+ const onFailed = jest.fn();
+ const rules: FieldRule[] = [
+ {
+ validator: (value, rule) => {
+ expect(value).toEqual('123');
+ expect(typeof rule).toEqual('object');
+ return new Promise((resolve) => resolve(true));
+ },
+ message: 'should pass',
+ },
+ {
+ validator: () => new Promise((resolve) => resolve(false)),
+ message: 'should fail',
+ },
+ ];
+ const wrapper = mount({
+ render() {
+ return (
+
+ );
+ },
+ });
+
+ await submitForm(wrapper);
+
+ expect(onFailed).toHaveBeenCalledWith({
+ errors: [{ message: 'should fail', name: 'A' }],
+ values: { A: '123' },
+ });
+});
+
+test('should validate first field when using validate-first prop', async () => {
+ const onSubmit = jest.fn();
+ const onFailed = jest.fn();
+
+ const wrapper = mount({
+ data() {
+ return {
+ ...getSimpleRules(),
+ value: '',
+ };
+ },
+ render() {
+ return (
+
+ );
+ },
+ });
+
+ await submitForm(wrapper);
+
+ expect(wrapper.html()).toMatchSnapshot();
+ expect(onFailed).toHaveBeenCalledWith({
+ errors: [{ message: 'A failed', name: 'A' }],
+ values: { A: '', B: '' },
+ });
+
+ await wrapper.setData({ value: 'foo' });
+ await submitForm(wrapper);
+
+ expect(onSubmit).toHaveBeenCalledWith({ A: 'foo', B: 'foo' });
+});
+
+test('should render colon when using colon prop', () => {
+ const wrapper = mount({
+ render() {
+ return (
+
+ );
+ },
+ });
+ expect(wrapper.html()).toMatchSnapshot();
+});
+
+test('should render label-align prop correctly', () => {
+ const wrapper = mount({
+ render() {
+ return (
+
+ );
+ },
+ });
+ expect(wrapper.html()).toMatchSnapshot();
+});
+
+test('should render label-width prop correctly', () => {
+ const wrapper = mount({
+ render() {
+ return (
+
+ );
+ },
+ });
+ expect(wrapper.html()).toMatchSnapshot();
+});
+
+test('should render input-align prop correctly', () => {
+ const wrapper = mount({
+ render() {
+ return (
+
+ );
+ },
+ });
+ expect(wrapper.html()).toMatchSnapshot();
+});
+
+test('should render error-message-align prop correctly', () => {
+ const wrapper = mount({
+ render() {
+ return (
+
+ );
+ },
+ });
+ expect(wrapper.html()).toMatchSnapshot();
+});
+
+test('should trigger validate after blurring when validate-trigger prop is onBlur', async () => {
+ const wrapper = mount({
+ data: getSimpleRules,
+ render() {
+ return (
+
+ );
+ },
+ });
+
+ const input = wrapper.find('input');
+
+ await input.trigger('input');
+ expect(wrapper.find('.van-field__error-message').exists()).toBeFalsy();
+
+ await input.trigger('blur');
+ expect(wrapper.find('.van-field__error-message').exists()).toBeTruthy();
+});
+
+test('should trigger validate after inputting when validate-trigger prop is onChange', async () => {
+ const wrapper = mount({
+ data() {
+ return {
+ ...getSimpleRules(),
+ value: '',
+ };
+ },
+ render() {
+ return (
+
+ );
+ },
+ });
+
+ const input = wrapper.find('input');
+
+ await input.trigger('blur');
+ expect(wrapper.find('.van-field__error-message').exists()).toBeFalsy();
+
+ await wrapper.setData({ value: '1' });
+ expect(wrapper.find('.van-field__error-message').exists()).toBeFalsy();
+
+ await wrapper.setData({ value: '' });
+ expect(wrapper.find('.van-field__error-message').exists).toBeTruthy();
+});
+
+test('should allow to custom trigger in rules', async () => {
+ const rulesA = [
+ {
+ message: 'A',
+ required: true,
+ trigger: 'onChange' as const,
+ },
+ ];
+ const rulesB = [
+ {
+ message: 'B',
+ required: true,
+ trigger: 'onBlur' as const,
+ },
+ ];
+ const wrapper = mount({
+ data() {
+ return {
+ valueA: '',
+ valueB: '',
+ };
+ },
+ render() {
+ return (
+
+ );
+ },
+ });
+
+ const inputs = wrapper.findAll('input');
+
+ await inputs[0].trigger('blur');
+ await wrapper.setData({ valueB: '1' });
+ await wrapper.setData({ valueB: '' });
+ expect(wrapper.find('.van-field__error-message').exists()).toBeFalsy();
+
+ await inputs[1].trigger('blur');
+ await wrapper.setData({ valueA: '1' });
+ await wrapper.setData({ valueA: '' });
+ await later();
+ expect(
+ wrapper.element.querySelectorAll('.van-field__error-message').length
+ ).toEqual(2);
+});
+
+test('should trigger scroll when using scroll-to-error prop', async () => {
+ const fn = mockScrollIntoView();
+ const wrapper = mount({
+ data: getSimpleRules,
+ render() {
+ return (
+
+ );
+ },
+ });
+
+ await submitForm(wrapper);
+
+ expect(fn).toHaveBeenCalledTimes(1);
+});
+
+test('should allow to control the display of error message by show-error-message prop', async () => {
+ const wrapper = mount({
+ data() {
+ return {
+ ...getSimpleRules(),
+ showErrorMessage: false,
+ };
+ },
+ render() {
+ return (
+
+ );
+ },
+ });
+
+ await submitForm(wrapper);
+ expect(wrapper.find('.van-field__error-message').exists()).toBeFalsy();
+
+ await wrapper.setData({ showErrorMessage: true });
+ await submitForm(wrapper);
+ expect(wrapper.find('.van-field__error-message').exists()).toBeTruthy();
+});
+
+test('should allow to control the display of error status by show-error prop', async () => {
+ const wrapper = mount({
+ data() {
+ return {
+ ...getSimpleRules(),
+ showError: false,
+ };
+ },
+ render() {
+ return (
+
+ );
+ },
+ });
+
+ await submitForm(wrapper);
+ expect(wrapper.find('.van-field--error').exists()).toBeFalsy();
+
+ await wrapper.setData({ showError: true });
+ await submitForm(wrapper);
+ expect(wrapper.find('.van-field--error').exists()).toBeTruthy();
+});