field.test.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. import { Form, Select } from '../../index';
  2. import { noop } from 'lodash';
  3. import { func } from 'prop-types';
  4. import { BASE_CLASS_PREFIX } from '../../../semi-foundation/base/constants';
  5. import { sleep as baseSleep } from '../../_test_/utils/index';
  6. const sleep = (ms = 200) => baseSleep(ms);
  7. function getForm(props) {
  8. return mount(<Form {...props}></Form>);
  9. }
  10. function getInput(props) {
  11. return <Form.Input {...props} />;
  12. }
  13. const Option = Select.Option;
  14. const FormSelect = (
  15. <Form.Select label="business" field="business" style={{ width: 200 }}>
  16. <Option value="dy">Douyin</Option>
  17. <Option value="hotsoon">Hotsoon</Option>
  18. <Option value="topbuzz">TopBuzz</Option>
  19. </Form.Select>
  20. );
  21. const FormInput = <Form.Input field="name" />;
  22. const FieldCls = `.${BASE_CLASS_PREFIX}-form-field`;
  23. const fields = (
  24. <>
  25. {FormInput}
  26. {FormSelect}
  27. </>
  28. );
  29. describe('Form-field', () => {
  30. it('className & style & fieldClassName', () => {
  31. const fieldProps = {
  32. className: 'test-a',
  33. style: {
  34. color: 'red',
  35. },
  36. fieldClassName: 'field-test-b',
  37. };
  38. const props = {
  39. children: getInput(fieldProps),
  40. };
  41. const form = getForm(props);
  42. expect(form.exists(`.${BASE_CLASS_PREFIX}-input-wrapper.test-a`)).toEqual(true);
  43. expect(form.find(`.${BASE_CLASS_PREFIX}-input-wrapper.test-a`)).toHaveStyle('color', 'red');
  44. expect(form.find(`.${BASE_CLASS_PREFIX}-form-field.field-test-b`).length).toEqual(1);
  45. });
  46. it('label', () => {
  47. const props = {
  48. children: getInput({
  49. label: 'Company',
  50. field: 'name',
  51. }),
  52. };
  53. const form = getForm(props);
  54. expect(form.find(`.${BASE_CLASS_PREFIX}-form-field-label`).text()).toEqual('Company');
  55. });
  56. it('labelPosition', () => {
  57. // field's labelPosition has a higher weight than form
  58. const props = {
  59. labelPosition: 'top',
  60. children: (
  61. <>
  62. {getInput({ labelPosition: 'left', fieldClassName: 'left-input' })}
  63. {getInput({ fieldClassName: 'top-input' })}
  64. </>
  65. ),
  66. };
  67. const form = getForm(props);
  68. expect(
  69. form
  70. .find('.left-input')
  71. .instance()
  72. .getAttribute('x-label-pos')
  73. ).toEqual('left');
  74. expect(
  75. form
  76. .find('.top-input')
  77. .instance()
  78. .getAttribute('x-label-pos')
  79. ).toEqual('top');
  80. });
  81. it('labelAlign', () => {
  82. // field's labelAlign has a higher weight than form
  83. const props = {
  84. labelAlign: 'right',
  85. children: (
  86. <>
  87. {getInput({ labelAlign: 'left', fieldClassName: 'left-input' })}
  88. {getInput({ fieldClassName: 'right-input' })}
  89. </>
  90. ),
  91. };
  92. const form = getForm(props);
  93. expect(form.exists(`.left-input .${BASE_CLASS_PREFIX}-form-field-label-left`)).toEqual(true);
  94. expect(form.exists(`.right-input .${BASE_CLASS_PREFIX}-form-field-label-right`)).toEqual(true);
  95. });
  96. it('noLabel', () => {
  97. const fieldProps = {
  98. noLabel: true,
  99. field: 'name',
  100. };
  101. const props = {
  102. children: getInput(fieldProps),
  103. };
  104. const form = getForm(props);
  105. expect(form.exists(`.${BASE_CLASS_PREFIX}-form-field-label`)).toEqual(false);
  106. });
  107. it('name', () => {
  108. const props = {
  109. children: getInput({ field: 'name', name: 'company' }),
  110. };
  111. const form = getForm(props);
  112. expect(form.exists(`.${BASE_CLASS_PREFIX}-form-field.${BASE_CLASS_PREFIX}-form-field-company`)).toEqual(true);
  113. });
  114. it('initValue', () => {
  115. // field's initValue has a higher weight than form initValues
  116. const props = {
  117. children: getInput({ initValue: 'b', field: 'name' }),
  118. initValues: {
  119. name: 'a',
  120. },
  121. };
  122. const form = getForm(props);
  123. expect(form.find(`.${BASE_CLASS_PREFIX}-input`).instance().value).toEqual('b');
  124. });
  125. it('onChange', () => {
  126. const onChange = () => {};
  127. const spyOnChange = sinon.spy(onChange);
  128. const fieldProps = {
  129. onChange: spyOnChange,
  130. field: 'name',
  131. };
  132. const props = {
  133. children: getInput(fieldProps),
  134. };
  135. const form = getForm(props);
  136. const event = { target: { value: 'semi' } };
  137. form.find(`.${BASE_CLASS_PREFIX}-input`).simulate('change', event);
  138. expect(spyOnChange.calledOnce).toEqual(true);
  139. expect(spyOnChange.calledWithMatch('semi')).toEqual(true);
  140. });
  141. it('validate-sync', () => {
  142. const validate = value => {
  143. return value !== 'semi' ? 'invalid' : '';
  144. };
  145. const spyValidate = sinon.spy(validate);
  146. const fieldProps = {
  147. field: 'name',
  148. trigger: 'change',
  149. validate: spyValidate,
  150. // rules are invalidated when validate is also declared
  151. rules: [
  152. { type: 'string', message: 'rules error1' },
  153. { validator: (rule, value) => value === 'muji', message: 'rules error2' },
  154. ],
  155. };
  156. const props = {
  157. children: getInput(fieldProps),
  158. };
  159. const form = getForm(props);
  160. // fail
  161. const failedEvent = { target: { value: 'milk' } };
  162. form.find(`.${BASE_CLASS_PREFIX}-input`).simulate('change', failedEvent);
  163. expect(spyValidate.calledWithMatch('milk')).toEqual(true);
  164. expect(form.find(`.${BASE_CLASS_PREFIX}-form-field-error-message`).text()).toEqual('invalid');
  165. // success
  166. const successEvent = { target: { value: 'semi' } };
  167. form.find(`.${BASE_CLASS_PREFIX}-input`).simulate('change', successEvent);
  168. expect(form.exists(`.${BASE_CLASS_PREFIX}-form-field-error-message`)).toEqual(false);
  169. });
  170. it('validate-async', done => {
  171. const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
  172. const validate = val => {
  173. return sleep(50).then(() => {
  174. if (val !== 'semi') {
  175. return 'invalid';
  176. }
  177. return '';
  178. });
  179. };
  180. const spyValidate = sinon.spy(validate);
  181. const fieldProps = {
  182. trigger: 'change',
  183. validate: spyValidate,
  184. field: 'name',
  185. };
  186. let formApi = null;
  187. const getFormApi = api => {
  188. formApi = api;
  189. };
  190. const props = {
  191. getFormApi,
  192. children: getInput(fieldProps),
  193. };
  194. const form = getForm(props);
  195. // fail
  196. const failedEvent = { target: { value: 'milk' } };
  197. form.find(`.${BASE_CLASS_PREFIX}-input`).simulate('change', failedEvent);
  198. setTimeout(() => {
  199. form.update();
  200. expect(spyValidate.firstCall.calledWithMatch('milk')).toEqual(true);
  201. expect(formApi.getError('name')).toEqual('invalid');
  202. expect(form.find(`.${BASE_CLASS_PREFIX}-form-field-error-message`).text()).toEqual('invalid');
  203. const successEvent = { target: { value: 'semi' } };
  204. form.find(`.${BASE_CLASS_PREFIX}-input`).simulate('change', successEvent);
  205. }, 200);
  206. setTimeout(() => {
  207. form.update();
  208. // success
  209. expect(spyValidate.secondCall.calledWithMatch('semi')).toEqual(true);
  210. expect(formApi.getError('name')).toEqual(undefined);
  211. expect(form.exists(`.${BASE_CLASS_PREFIX}-form-field-error-message`)).toEqual(false);
  212. done();
  213. }, 800);
  214. });
  215. it('rules', done => {
  216. // rules work
  217. let fieldProps = {
  218. field: 'name',
  219. trigger: 'change',
  220. rules: [
  221. { type: 'string', message: 'type error' },
  222. { validator: (rule, value) => value === 'muji', message: 'not muji' },
  223. ],
  224. };
  225. let formApi = null;
  226. const getFormApi = api => {
  227. formApi = api;
  228. };
  229. const props = {
  230. getFormApi,
  231. children: getInput(fieldProps),
  232. };
  233. const form = getForm(props);
  234. const event2 = { target: { value: 2 } };
  235. form.find(`.${BASE_CLASS_PREFIX}-input`).simulate('change', event2);
  236. setTimeout(() => {
  237. form.update();
  238. expect(form.find(`.${BASE_CLASS_PREFIX}-form-field-error-message`).text()).toEqual('type error, not muji');
  239. }, 50);
  240. setTimeout(() => {
  241. const event3 = { target: { value: 'semi' } };
  242. form.find(`.${BASE_CLASS_PREFIX}-input`).simulate('change', event3);
  243. form.update();
  244. }, 100);
  245. setTimeout(() => {
  246. // console.log(formApi);
  247. expect(form.find(`.${BASE_CLASS_PREFIX}-form-field-error-message`).text()).toEqual('not muji');
  248. done();
  249. }, 200);
  250. });
  251. it('transform', () => {
  252. const transform = stringVal => {
  253. return Number(stringVal);
  254. };
  255. const validate = value => {
  256. return value !== 5 ? 'invalid' : '';
  257. };
  258. const spyValidate = sinon.spy(validate);
  259. const fieldProps = {
  260. transform,
  261. validate: spyValidate,
  262. field: 'count',
  263. };
  264. let formApi = null;
  265. const getFormApi = api => {
  266. formApi = api;
  267. };
  268. const props = {
  269. getFormApi,
  270. children: getInput(fieldProps),
  271. };
  272. const form = getForm(props);
  273. const event = { target: { value: '5' } };
  274. form.find(`.${BASE_CLASS_PREFIX}-input`).simulate('change', event);
  275. expect(spyValidate.calledWithMatch(5)).toEqual(true);
  276. // only transform value before validate, can't change value in formState
  277. expect(formApi.getValue('count')).toEqual('5');
  278. });
  279. it('convert', () => {
  280. const convert = stringVal => {
  281. return Number(stringVal);
  282. };
  283. const spyConvert = sinon.spy(convert);
  284. const fieldProps = {
  285. convert: spyConvert,
  286. field: 'count',
  287. };
  288. let formApi = null;
  289. const getFormApi = api => {
  290. formApi = api;
  291. };
  292. const props = {
  293. getFormApi,
  294. children: getInput(fieldProps),
  295. };
  296. const form = getForm(props);
  297. const event = { target: { value: '5' } };
  298. form.find(`.${BASE_CLASS_PREFIX}-input`).simulate('change', event);
  299. expect(spyConvert.calledWithMatch('5')).toEqual(true);
  300. expect(formApi.getValue('count')).toEqual(5);
  301. });
  302. it('trigger - mounted / change / blur ', done => {
  303. const validate = val => (val !== 'semi' ? 'invalid' : '');
  304. const propsChange = {
  305. trigger: 'change',
  306. field: 'a',
  307. fieldClassName: 'a',
  308. validate,
  309. };
  310. const propsBlur = {
  311. trigger: 'blur',
  312. field: 'b',
  313. fieldClassName: 'b',
  314. initValue: 'milk',
  315. validate,
  316. };
  317. //TODO mounted
  318. const propsMounted = {
  319. trigger: 'mounted',
  320. field: 'c',
  321. fieldClassName: 'c',
  322. validate,
  323. };
  324. const props = {
  325. children: (
  326. <>
  327. {getInput(propsChange)}
  328. {getInput(propsBlur)}
  329. </>
  330. ),
  331. };
  332. const form = getForm(props);
  333. const event = { target: { value: 'trigger' } };
  334. form.find(`.a .${BASE_CLASS_PREFIX}-input`).simulate('change', event);
  335. form.find(`.b .${BASE_CLASS_PREFIX}-input`).simulate('blur', event);
  336. setTimeout(() => {
  337. form.update();
  338. expect(form.find(`.a .${BASE_CLASS_PREFIX}-form-field-error-message`).text()).toEqual('invalid');
  339. expect(form.find(`.b .${BASE_CLASS_PREFIX}-form-field-error-message`).text()).toEqual('invalid');
  340. done();
  341. }, 100);
  342. });
  343. it('trigger - change & blur', done => {
  344. const validate = val => {
  345. if (val === 'changeVal') {
  346. return 'changeError';
  347. } else if (val === 'blurVal') {
  348. return 'blurError';
  349. } else {
  350. return '';
  351. }
  352. };
  353. const fieldProps = {
  354. trigger: ['change', 'blur'],
  355. field: 'a',
  356. fieldClassName: 'a',
  357. validate,
  358. };
  359. const props = {
  360. children: <>{getInput(fieldProps)}</>,
  361. };
  362. const form = getForm(props);
  363. let event = { target: { value: 'changeVal' } };
  364. form.find(`.a .${BASE_CLASS_PREFIX}-input`).simulate('change', event);
  365. setTimeout(() => {
  366. form.update();
  367. expect(form.find(`.a .${BASE_CLASS_PREFIX}-form-field-error-message`).text()).toEqual('changeError');
  368. }, 50);
  369. setTimeout(() => {
  370. event = { target: { value: 'blurVal' } };
  371. form.find(`.a .${BASE_CLASS_PREFIX}-input`).simulate('change', event);
  372. form.find(`.a .${BASE_CLASS_PREFIX}-input`).simulate('blur', {});
  373. }, 80);
  374. setTimeout(() => {
  375. form.update();
  376. expect(form.find(`.a .${BASE_CLASS_PREFIX}-form-field-error-message`).text()).toEqual('blurError');
  377. done();
  378. }, 100);
  379. });
  380. it('field', () => {
  381. // 1、username
  382. // 2、user[0]
  383. // 3、siblings.1
  384. // 4、siblings['2']
  385. // 5、parents[0].name
  386. // 6、parents[1]['name']
  387. const fields = ['username', 'user[0]', 'siblings.1', 'siblings[2]', 'parents[0].name', "parents[1]['name']"];
  388. const props = {
  389. children: <>{fields.map((field, index) => getInput({ field: field, fieldClassName: `field-${index}` }))}</>,
  390. initValues: {
  391. username: 'a',
  392. user: ['b'],
  393. siblings: [0, 'c', 'd'],
  394. parents: [{ name: 'e' }, { name: 'f' }],
  395. },
  396. };
  397. const form = getForm(props);
  398. // If you do not pass a specific label prop, the label content is consistent with the field
  399. const fieldsDOM = form.find(`.${BASE_CLASS_PREFIX}-form-field .${BASE_CLASS_PREFIX}-input`);
  400. expect(fieldsDOM.at(0).instance().value).toEqual('a');
  401. expect(fieldsDOM.at(1).instance().value).toEqual('b');
  402. expect(fieldsDOM.at(2).instance().value).toEqual('c');
  403. expect(fieldsDOM.at(3).instance().value).toEqual('d');
  404. expect(fieldsDOM.at(4).instance().value).toEqual('e');
  405. expect(fieldsDOM.at(5).instance().value).toEqual('f');
  406. });
  407. it('validate race condition', async () => {
  408. let formApi = null;
  409. let asyncValidatorCallback = null;
  410. const fieldProps = {
  411. field: 'text',
  412. rules: [
  413. { type: 'string', max: 10 },
  414. {
  415. asyncValidator(rule, value, callback) {
  416. if (!asyncValidatorCallback) {
  417. asyncValidatorCallback = callback;
  418. } else {
  419. callback();
  420. }
  421. }
  422. }
  423. ]
  424. }
  425. const props = {
  426. getFormApi(api) {
  427. formApi = api;
  428. },
  429. children: getInput(fieldProps),
  430. };
  431. const form = getForm(props);
  432. const event1 = { target: { value: 'semi' } };
  433. form.find(`.${BASE_CLASS_PREFIX}-input`).simulate('change', event1);
  434. await sleep(200);
  435. form.update();
  436. expect(formApi.getError('text')).toBeUndefined();
  437. const event2 = { target: { value: 'Prefer knowledge to wealth, for the one is transitory, the other perpetual.' } };
  438. form.find(`.${BASE_CLASS_PREFIX}-input`).simulate('change', event2);
  439. await sleep(200);
  440. asyncValidatorCallback();
  441. await sleep(200);
  442. form.update();
  443. expect(formApi.getError('text')).not.toBeUndefined();
  444. });
  445. // TODO
  446. // it('allowEmptyString', () => {});
  447. // it('extraText')
  448. // it('extraTextPosition')
  449. // it('helpText')
  450. // it('stopValidateWithError')
  451. // it('noErrorMessage)
  452. // it('pure')
  453. // it('fieldStyle')
  454. });