field-array-model.test.ts 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926
  1. import { beforeEach, describe, expect, it, vi } from 'vitest';
  2. import { Errors, ValidateTrigger, Warnings } from '@/types';
  3. import { FormModel } from '@/core/form-model';
  4. import { type FieldArrayModel } from '@/core/field-array-model';
  5. import { FeedbackLevel } from '../src/types';
  6. describe('FormArrayModel', () => {
  7. let formModel = new FormModel();
  8. describe('children', () => {
  9. let arrayField: FieldArrayModel;
  10. beforeEach(() => {
  11. formModel.dispose();
  12. formModel = new FormModel();
  13. // 创建数组
  14. formModel.createFieldArray('arr');
  15. const field = formModel.getField<FieldArrayModel>('arr');
  16. field!.append('a');
  17. field!.append('b');
  18. field!.append('c');
  19. arrayField = field!;
  20. });
  21. it('can get children', () => {
  22. expect(arrayField.children.length).toBe(3);
  23. });
  24. });
  25. describe('append & delete', () => {
  26. let arrayField: FieldArrayModel;
  27. let arrEffect = vi.fn();
  28. let aEffect = vi.fn();
  29. let bEffect = vi.fn();
  30. let cEffect = vi.fn();
  31. let appendEffect = vi.fn();
  32. let deleteEffect = vi.fn();
  33. let aValidate = vi.fn();
  34. let bValidate = vi.fn();
  35. let cValidate = vi.fn();
  36. beforeEach(() => {
  37. formModel.dispose();
  38. formModel = new FormModel();
  39. arrEffect = vi.fn();
  40. aEffect = vi.fn();
  41. bEffect = vi.fn();
  42. cEffect = vi.fn();
  43. appendEffect = vi.fn();
  44. deleteEffect = vi.fn();
  45. aValidate = vi.fn();
  46. bValidate = vi.fn();
  47. cValidate = vi.fn();
  48. formModel.init({
  49. validateTrigger: ValidateTrigger.onChange,
  50. validate: {
  51. ['arr.0']: aValidate,
  52. ['arr.1']: bValidate,
  53. ['arr.2']: cValidate,
  54. },
  55. });
  56. // 创建其他field, 用于测试其他元素不会被影响
  57. formModel.createField('other');
  58. // 创建数组
  59. formModel.createFieldArray('arr');
  60. const field = formModel.getField<FieldArrayModel>('arr');
  61. const a = field!.append('a');
  62. const b = field!.append('b');
  63. const c = field!.append('c');
  64. arrayField = field!;
  65. arrayField.onValueChange(arrEffect);
  66. arrayField.onAppend(appendEffect);
  67. arrayField.onDelete(deleteEffect);
  68. a.onValueChange(aEffect);
  69. b.onValueChange(bEffect);
  70. c.onValueChange(cEffect);
  71. });
  72. it('append', async () => {
  73. vi.spyOn(arrayField, 'validate');
  74. arrayField.append('d');
  75. expect(arrayField.children.length).toBe(4);
  76. expect(formModel.getField('other')).toBeDefined();
  77. expect(arrEffect).toHaveBeenCalledTimes(1);
  78. expect(appendEffect).toHaveBeenCalledTimes(1);
  79. expect(arrayField.validate).toHaveBeenCalledTimes(1);
  80. });
  81. it('should fire OnFormValueChange event for arr when append', () => {
  82. vi.spyOn(formModel.onFormValuesInitEmitter, 'fire');
  83. vi.spyOn(formModel.onFormValuesChangeEmitter, 'fire');
  84. arrayField.append('d');
  85. expect(formModel.onFormValuesChangeEmitter.fire).toHaveBeenCalledWith({
  86. values: {
  87. arr: ['a', 'b', 'c', 'd'],
  88. },
  89. prevValues: {
  90. arr: ['a', 'b', 'c'],
  91. },
  92. name: 'arr',
  93. options: {
  94. action: 'array-append',
  95. indexes: [3],
  96. },
  97. });
  98. expect(formModel.onFormValuesInitEmitter.fire).toHaveBeenCalledWith({
  99. values: {
  100. arr: ['a', 'b', 'c', 'd'],
  101. },
  102. prevValues: {
  103. arr: ['a', 'b', 'c'],
  104. },
  105. name: 'arr.3',
  106. });
  107. });
  108. it('delete first element', () => {
  109. vi.spyOn(formModel.onFormValuesChangeEmitter, 'fire');
  110. vi.spyOn(arrayField, 'validate');
  111. vi.spyOn(arrayField.onValueChangeEmitter, 'fire');
  112. arrayField.delete(0);
  113. // assert value
  114. expect(arrayField.children.length).toBe(2);
  115. expect(arrayField.children[0].value).toBe('b');
  116. expect(arrayField.children[1].value).toBe('c');
  117. expect(formModel.getField('other')).toBeDefined();
  118. // assert change events
  119. expect(arrayField.onValueChangeEmitter.fire).toHaveBeenCalledTimes(1);
  120. expect(aEffect).toHaveBeenCalledTimes(1);
  121. expect(bEffect).toHaveBeenCalledTimes(1);
  122. expect(cEffect).toHaveBeenCalledTimes(1);
  123. expect(deleteEffect).toHaveBeenCalledTimes(1);
  124. expect(formModel.onFormValuesChangeEmitter.fire).toHaveBeenCalledWith({
  125. values: {
  126. arr: ['b', 'c'],
  127. },
  128. prevValues: {
  129. arr: ['a', 'b', 'c'],
  130. },
  131. name: 'arr',
  132. options: {
  133. action: 'array-splice',
  134. indexes: [0],
  135. },
  136. });
  137. expect(formModel.onFormValuesChangeEmitter.fire).toHaveBeenCalledTimes(1);
  138. // assert validate trigger
  139. expect(arrayField.validate).toHaveBeenCalledTimes(1);
  140. expect(aValidate).not.toHaveBeenCalled();
  141. expect(bValidate).not.toHaveBeenCalled();
  142. expect(cValidate).not.toHaveBeenCalled();
  143. });
  144. it('delete middle element', () => {
  145. vi.spyOn(arrayField, 'validate');
  146. vi.spyOn(arrayField.onValueChangeEmitter, 'fire');
  147. arrayField.delete(1);
  148. // assert values
  149. expect(arrayField.children.length).toBe(2);
  150. expect(arrayField.children[0].value).toBe('a');
  151. expect(arrayField.children[1].value).toBe('c');
  152. expect(formModel.getField('other')).toBeDefined();
  153. // assert change events
  154. expect(arrayField.onValueChangeEmitter.fire).toHaveBeenCalledTimes(1);
  155. expect(aEffect).not.toHaveBeenCalled();
  156. expect(bEffect).toHaveBeenCalledTimes(1);
  157. expect(cEffect).toHaveBeenCalledTimes(1);
  158. // assert validate trigger
  159. expect(bValidate).not.toHaveBeenCalled();
  160. expect(cValidate).not.toHaveBeenCalled();
  161. expect(arrayField.validate).toHaveBeenCalledTimes(1);
  162. });
  163. it('delete last element', () => {
  164. arrayField.delete(2);
  165. expect(arrayField.children.length).toBe(2);
  166. expect(arrayField.children[0].value).toBe('a');
  167. expect(arrayField.children[1].value).toBe('b');
  168. expect(formModel.getField('other')).toBeDefined();
  169. expect(arrEffect).toHaveBeenCalled();
  170. expect(cEffect).toHaveBeenCalled();
  171. });
  172. it('delete element which has nested field', () => {
  173. vi.spyOn(arrayField, 'validate');
  174. const axField = formModel.createField('arr.0.x');
  175. const bxField = formModel.createField('arr.1.x');
  176. vi.spyOn(axField, 'validate');
  177. vi.spyOn(bxField, 'validate');
  178. formModel.setValueIn('arr.0', { x: 1 });
  179. formModel.setValueIn('arr.1', { x: 2 });
  180. expect(arrayField.value).toEqual([{ x: 1 }, { x: 2 }, 'c']);
  181. arrayField.delete(0);
  182. expect(arrayField.value).toEqual([{ x: 2 }, 'c']);
  183. // assert change events
  184. expect(aEffect).toHaveBeenCalledTimes(2); // setValueIn 触发一次, delete 触发一次
  185. expect(bEffect).toHaveBeenCalledTimes(2); // setValueIn 触发一次, delete 触发一次
  186. expect(cEffect).toHaveBeenCalledTimes(1);
  187. // assert validate trigger
  188. expect(aValidate).toHaveBeenCalledTimes(1); // setValueIn 触发一次, delete 不会触发
  189. expect(bValidate).toHaveBeenCalledTimes(1); // setValueIn 触发一次, delete 不会触发
  190. expect(cValidate).not.toHaveBeenCalled();
  191. expect(axField.validate).toHaveBeenCalledTimes(1); // setValueIn 触发一次, delete 不会触发
  192. expect(bxField.validate).toHaveBeenCalledTimes(1); // setValueIn 触发一次, delete 不会触发
  193. });
  194. it('more elements delete', () => {
  195. /**
  196. * 数组为 [a,b,c,d]
  197. * 删除 b
  198. * 希望数组值为 [a,c,d]
  199. * 希望formModel中的field也正确对应
  200. */
  201. arrayField.append('d');
  202. vi.spyOn(arrayField, 'validate');
  203. arrayField.delete(1);
  204. // assert values
  205. expect(arrayField.children.length).toBe(3);
  206. expect(arrayField.children[0].value).toBe('a');
  207. expect(arrayField.children[1].value).toBe('c');
  208. expect(arrayField.children[2].value).toBe('d');
  209. expect(formModel.getField('arr.2')?.value).toBe('d');
  210. expect(formModel.getField('other')).toBeDefined();
  211. // assert value change events
  212. expect(arrEffect).toHaveBeenCalled();
  213. expect(bEffect).toHaveBeenCalled();
  214. expect(cEffect).toHaveBeenCalled();
  215. expect(arrayField.validate).toHaveBeenCalledTimes(1);
  216. });
  217. });
  218. describe('_splice', () => {
  219. let arrayField: FieldArrayModel;
  220. let aEffect = vi.fn();
  221. let bEffect = vi.fn();
  222. let cEffect = vi.fn();
  223. let dEffect = vi.fn();
  224. let eEffect = vi.fn();
  225. let aValidate = vi.fn();
  226. let bValidate = vi.fn();
  227. let cValidate = vi.fn();
  228. let dValidate = vi.fn();
  229. let eValidate = vi.fn();
  230. beforeEach(() => {
  231. formModel.dispose();
  232. formModel = new FormModel();
  233. aEffect = vi.fn();
  234. bEffect = vi.fn();
  235. cEffect = vi.fn();
  236. dEffect = vi.fn();
  237. eEffect = vi.fn();
  238. aValidate = vi.fn();
  239. bValidate = vi.fn();
  240. cValidate = vi.fn();
  241. dValidate = vi.fn();
  242. eValidate = vi.fn();
  243. formModel.createFieldArray('arr');
  244. formModel.init({
  245. validateTrigger: ValidateTrigger.onChange,
  246. validate: {
  247. ['arr.0']: aValidate,
  248. ['arr.1']: bValidate,
  249. ['arr.2']: cValidate,
  250. ['arr.3']: dValidate,
  251. ['arr.4']: eValidate,
  252. },
  253. });
  254. const field = formModel.getField<FieldArrayModel>('arr');
  255. const aField = field!.append('a');
  256. const bField = field!.append('b');
  257. const cField = field!.append('c');
  258. const dField = field!.append('d');
  259. const eField = field!.append('e');
  260. aField.onValueChange(aEffect);
  261. bField.onValueChange(bEffect);
  262. cField.onValueChange(cEffect);
  263. dField.onValueChange(dEffect);
  264. eField.onValueChange(eEffect);
  265. arrayField = field!;
  266. vi.spyOn(arrayField, 'validate');
  267. vi.spyOn(arrayField.onValueChangeEmitter, 'fire');
  268. });
  269. it('should throw error when delete count exceeds array length', () => {
  270. expect(() => {
  271. arrayField._splice(0, 6);
  272. }).toThrowError();
  273. });
  274. it('should throw error when delete in empty array', () => {
  275. arrayField._splice(0, 5);
  276. expect(() => {
  277. arrayField._splice(0);
  278. }).toThrowError();
  279. });
  280. it('splice first 2', () => {
  281. arrayField._splice(0, 2);
  282. // assert values
  283. expect(arrayField.children.length).toBe(3);
  284. expect(arrayField.children[0].value).toBe('c');
  285. expect(arrayField.children[1].value).toBe('d');
  286. expect(arrayField.children[2].value).toBe('e');
  287. // assert value change events
  288. expect(arrayField.onValueChangeEmitter.fire).toHaveBeenCalledTimes(1);
  289. expect(aEffect).toHaveBeenCalledTimes(1);
  290. expect(bEffect).toHaveBeenCalledTimes(1);
  291. expect(cEffect).toHaveBeenCalledTimes(1);
  292. expect(dEffect).toHaveBeenCalledTimes(1);
  293. expect(eEffect).toHaveBeenCalledTimes(1);
  294. // assert validate trigger
  295. expect(arrayField.validate).toHaveBeenCalledTimes(1);
  296. expect(aValidate).not.toHaveBeenCalled();
  297. expect(bValidate).not.toHaveBeenCalled();
  298. expect(cValidate).not.toHaveBeenCalled();
  299. expect(dValidate).not.toHaveBeenCalled();
  300. expect(eValidate).not.toHaveBeenCalled();
  301. });
  302. it('splice last 2', () => {
  303. arrayField._splice(3, 2);
  304. // assert values
  305. expect(arrayField.children.length).toBe(3);
  306. expect(arrayField.children[0].value).toBe('a');
  307. expect(arrayField.children[1].value).toBe('b');
  308. expect(arrayField.children[2].value).toBe('c');
  309. // assert value change events
  310. expect(arrayField.onValueChangeEmitter.fire).toHaveBeenCalledTimes(1);
  311. expect(aEffect).not.toHaveBeenCalled();
  312. expect(bEffect).not.toHaveBeenCalled();
  313. expect(cEffect).not.toHaveBeenCalled();
  314. expect(dEffect).toHaveBeenCalledTimes(1);
  315. expect(eEffect).toHaveBeenCalledTimes(1);
  316. // assert validate trigger
  317. expect(arrayField.validate).toHaveBeenCalledTimes(1);
  318. expect(aValidate).not.toHaveBeenCalled();
  319. expect(bValidate).not.toHaveBeenCalled();
  320. expect(cValidate).not.toHaveBeenCalled();
  321. expect(dValidate).not.toHaveBeenCalled();
  322. expect(eValidate).not.toHaveBeenCalled();
  323. });
  324. it('splice middle elements', () => {
  325. arrayField._splice(1, 2);
  326. // assert values
  327. expect(arrayField.children.length).toBe(3);
  328. expect(arrayField.children[0].value).toBe('a');
  329. expect(arrayField.children[1].value).toBe('d');
  330. expect(arrayField.children[2].value).toBe('e');
  331. // assert value change events
  332. expect(arrayField.onValueChangeEmitter.fire).toHaveBeenCalledTimes(1);
  333. expect(aEffect).not.toHaveBeenCalled();
  334. expect(bEffect).toHaveBeenCalledTimes(1);
  335. expect(cEffect).toHaveBeenCalledTimes(1);
  336. expect(dEffect).toHaveBeenCalledTimes(1);
  337. expect(eEffect).toHaveBeenCalledTimes(1);
  338. // assert validate trigger
  339. expect(arrayField.validate).toHaveBeenCalledTimes(1);
  340. expect(aValidate).not.toHaveBeenCalled();
  341. expect(bValidate).not.toHaveBeenCalled();
  342. expect(cValidate).not.toHaveBeenCalled();
  343. expect(dValidate).not.toHaveBeenCalled();
  344. expect(eValidate).not.toHaveBeenCalled();
  345. });
  346. it('splice all elements', () => {
  347. arrayField._splice(0, 5);
  348. expect(arrayField.children.length).toBe(0);
  349. expect(arrayField.value).toEqual([]);
  350. // assert value change events
  351. expect(arrayField.onValueChangeEmitter.fire).toHaveBeenCalledTimes(1);
  352. expect(aEffect).toHaveBeenCalledTimes(1);
  353. expect(bEffect).toHaveBeenCalledTimes(1);
  354. expect(cEffect).toHaveBeenCalledTimes(1);
  355. expect(dEffect).toHaveBeenCalledTimes(1);
  356. expect(eEffect).toHaveBeenCalledTimes(1);
  357. // assert validate trigger
  358. expect(arrayField.validate).toHaveBeenCalledTimes(1);
  359. expect(aValidate).not.toHaveBeenCalled();
  360. expect(bValidate).not.toHaveBeenCalled();
  361. expect(cValidate).not.toHaveBeenCalled();
  362. expect(dValidate).not.toHaveBeenCalled();
  363. expect(eValidate).not.toHaveBeenCalled();
  364. });
  365. });
  366. describe('State check when _splice', () => {
  367. beforeEach(() => {
  368. formModel.dispose();
  369. formModel = new FormModel();
  370. });
  371. it('should keep state of rest fields after delete a prev field', () => {
  372. const arrayField = formModel.createFieldArray('arr');
  373. formModel.init({
  374. validateTrigger: ValidateTrigger.onChange,
  375. });
  376. arrayField!.append('a');
  377. arrayField!.append('b');
  378. arrayField!.append('c');
  379. // 设置第1项的state
  380. const aFieldModel = formModel.getField('arr.1');
  381. aFieldModel!.state.errors = {
  382. 'arr.1': [{ name: 'arr.1', message: 'error' }],
  383. } as unknown as Errors;
  384. aFieldModel!.state.warnings = {
  385. 'arr.1': [{ name: 'arr.1', message: 'warning' }],
  386. } as unknown as Warnings;
  387. // 删除第0项
  388. arrayField._splice(0);
  389. // 原第一项变为第0项且他的state 被保留了, 且errors 中的路径标识也更新了
  390. expect(formModel.getField('arr.0')!.state.errors).toEqual({
  391. 'arr.0': [{ name: 'arr.0', message: 'error' }],
  392. });
  393. expect(formModel.getField('arr.0')!.state.warnings).toEqual({
  394. 'arr.0': [{ name: 'arr.0', message: 'warning' }],
  395. });
  396. expect(formModel.getField('arr.2')).toBeUndefined();
  397. });
  398. it('should keep state of rest fields after delete a prev field, when nested field', () => {
  399. const arrayField = formModel.createFieldArray('arr');
  400. formModel.init({
  401. validateTrigger: ValidateTrigger.onChange,
  402. initialValues: {
  403. arr: [
  404. { x: 1, y: 2 },
  405. { x: 0, y: 0 },
  406. { x: 0, y: 0 },
  407. ],
  408. },
  409. });
  410. formModel.createField('arr.0');
  411. formModel.createField('arr.0.x');
  412. formModel.createField('arr.0.y');
  413. formModel.createField('arr.1');
  414. formModel.createField('arr.1.x');
  415. formModel.createField('arr.1.y');
  416. formModel.createField('arr.2');
  417. formModel.createField('arr.2.x');
  418. formModel.createField('arr.2.y');
  419. // 设置第1项的state
  420. const aFieldModel = formModel.getField('arr.1.x');
  421. aFieldModel!.state.errors = {
  422. 'arr.1.x': [{ name: 'arr.1.x', message: 'error' }],
  423. } as unknown as Errors;
  424. // 删除第0项
  425. arrayField._splice(0);
  426. // 原第一项变为第0项且他的state errors 被保留了, 且errors 中的路径标识也更新了
  427. expect(formModel.getField('arr.0')!.state.errors).toEqual({
  428. 'arr.0.x': [{ name: 'arr.0.x', message: 'error' }],
  429. });
  430. expect(formModel.getField('arr.0.x')!.state.errors).toEqual({
  431. 'arr.0.x': [{ name: 'arr.0.x', message: 'error' }],
  432. });
  433. expect(formModel.getField('arr.2')).toBeUndefined();
  434. expect(formModel.getField('arr.2.x')).toBeUndefined();
  435. expect(formModel.getField('arr.2.y')).toBeUndefined();
  436. });
  437. it('should align errors and warnings state with existing field in fieldMap ', () => {
  438. const arrayField = formModel.createFieldArray('arr');
  439. formModel.init({
  440. validateTrigger: ValidateTrigger.onChange,
  441. initialValues: {
  442. arr: [
  443. { x: 1, y: 2 },
  444. { x: 0, y: 0 },
  445. { x: 0, y: 0 },
  446. ],
  447. },
  448. });
  449. const field0 = formModel.createField('arr.0');
  450. const field0x = formModel.createField('arr.0.x');
  451. const field0y = formModel.createField('arr.0.y');
  452. const field1 = formModel.createField('arr.1');
  453. const field1x = formModel.createField('arr.1.x');
  454. const field1y = formModel.createField('arr.1.y');
  455. const field2 = formModel.createField('arr.2');
  456. const field2x = formModel.createField('arr.2.x');
  457. const field2y = formModel.createField('arr.2.y');
  458. field0x.state.errors = {
  459. 'arr.0.x': [{ name: 'arr.0.x', message: 'error' }],
  460. } as unknown as Errors;
  461. field0x.bubbleState();
  462. field1x.state.errors = {
  463. 'arr.1.x': [{ name: 'arr.1.x', message: 'error' }],
  464. } as unknown as Errors;
  465. field1x.bubbleState();
  466. field2x.state.errors = {
  467. 'arr.2.x': [{ name: 'arr.2.x', message: 'error' }],
  468. } as unknown as Errors;
  469. // 删除第0项
  470. arrayField._splice(0);
  471. expect(formModel.state.errors['arr.0.x']).toEqual([{ name: 'arr.0.x', message: 'error' }]);
  472. expect(formModel.state.errors['arr.1.x']).toEqual([{ name: 'arr.1.x', message: 'error' }]);
  473. expect(formModel.state.errors['arr.2.x']).toBeUndefined();
  474. expect(field0.state.errors['arr.0.x']).toEqual([{ name: 'arr.0.x', message: 'error' }]);
  475. expect(field1.state.errors['arr.1.x']).toEqual([{ name: 'arr.1.x', message: 'error' }]);
  476. expect(arrayField.state.errors['arr.0.x']).toEqual([{ name: 'arr.0.x', message: 'error' }]);
  477. expect(arrayField.state.errors['arr.1.x']).toEqual([{ name: 'arr.1.x', message: 'error' }]);
  478. expect(arrayField.state.errors['arr.2.x']).toBeUndefined();
  479. });
  480. it('should not keep previous error state when delete first elem in array then add back ', () => {
  481. const arrayField = formModel.createFieldArray('arr');
  482. formModel.init({
  483. validateTrigger: ValidateTrigger.onChange,
  484. initialValues: {
  485. arr: [{ x: 1, y: 2 }],
  486. },
  487. });
  488. const field0 = formModel.createField('arr.0');
  489. const field0x = formModel.createField('arr.0.x');
  490. const field0y = formModel.createField('arr.0.y');
  491. field0x.state.errors = {
  492. 'arr.0.x': [{ name: 'arr.0.x', message: 'error' }],
  493. } as unknown as Errors;
  494. field0x.bubbleState();
  495. // 删除第0项
  496. arrayField._splice(0);
  497. expect(formModel.state.errors['arr.0.x']).toBeUndefined();
  498. expect(arrayField.state.errors['arr.0.x']).toBeUndefined();
  499. expect(formModel._fieldMap.get('arr.0')).toBeUndefined();
  500. arrayField.append({ x: 1, y: 2 });
  501. formModel.createField('arr.0.x');
  502. expect(formModel._fieldMap.get('arr.0')).toBeDefined();
  503. expect(formModel.state.errors['arr.0.x']).toBeUndefined();
  504. expect(formModel._fieldMap.get('arr.0.x').state.errors).toBeUndefined();
  505. });
  506. });
  507. describe('swap', () => {
  508. beforeEach(() => {
  509. formModel.dispose();
  510. formModel = new FormModel();
  511. });
  512. it('can swap from 0 to middle index', () => {
  513. const arrayField = formModel.createFieldArray('arr');
  514. const a = arrayField!.append('a');
  515. const b = arrayField!.append('b');
  516. const c = arrayField!.append('c');
  517. formModel.init({});
  518. a.state.errors = {
  519. 'arr.0': [{ name: 'arr.0', message: 'err0', level: FeedbackLevel.Error }],
  520. };
  521. b.state.errors = {
  522. 'arr.1': [{ name: 'arr.1', message: 'err1', level: FeedbackLevel.Error }],
  523. };
  524. expect(formModel.values).toEqual({ arr: ['a', 'b', 'c'] });
  525. arrayField.swap(0, 1);
  526. expect(formModel.values).toEqual({ arr: ['b', 'a', 'c'] });
  527. expect(formModel.getField('arr.0').state.errors).toEqual({
  528. 'arr.0': [{ name: 'arr.0', message: 'err1', level: FeedbackLevel.Error }],
  529. });
  530. expect(formModel.getField('arr.1').state.errors).toEqual({
  531. 'arr.1': [{ name: 'arr.1', message: 'err0', level: FeedbackLevel.Error }],
  532. });
  533. });
  534. it('can chained swap', () => {
  535. const arrayField = formModel.createFieldArray('x.arr');
  536. const a = arrayField!.append('a');
  537. const b = arrayField!.append('b');
  538. arrayField!.append('c');
  539. formModel.init({});
  540. a.state.errors = {
  541. 'arr.0': [{ name: 'arr.0', message: 'err0', level: FeedbackLevel.Error }],
  542. };
  543. b.state.errors = {
  544. 'arr.1': [{ name: 'arr.1', message: 'err1', level: FeedbackLevel.Error }],
  545. };
  546. expect(a.name).toBe('x.arr.0');
  547. expect(b.name).toBe('x.arr.1');
  548. expect(formModel.values.x).toEqual({ arr: ['a', 'b', 'c'] });
  549. arrayField.swap(1, 0);
  550. expect(a.name).toBe('x.arr.1');
  551. expect(b.name).toBe('x.arr.0');
  552. expect(formModel.values.x).toEqual({ arr: ['b', 'a', 'c'] });
  553. arrayField.swap(1, 0);
  554. expect(a.name).toBe('x.arr.0');
  555. expect(formModel.fieldMap.get('x.arr.0').name).toBe('x.arr.0');
  556. expect(b.name).toBe('x.arr.1');
  557. expect(formModel.fieldMap.get('x.arr.1').name).toBe('x.arr.1');
  558. expect(formModel.values.x).toEqual({ arr: ['a', 'b', 'c'] });
  559. arrayField.swap(1, 0);
  560. expect(a.name).toBe('x.arr.1');
  561. expect(formModel.fieldMap.get('x.arr.1').name).toBe('x.arr.1');
  562. expect(b.name).toBe('x.arr.0');
  563. expect(formModel.fieldMap.get('x.arr.0').name).toBe('x.arr.0');
  564. expect(formModel.values.x).toEqual({ arr: ['b', 'a', 'c'] });
  565. });
  566. it('can swap from 0 to last index', () => {
  567. const arrayField = formModel.createFieldArray('arr');
  568. const a = arrayField!.append('a');
  569. const b = arrayField!.append('b');
  570. const c = arrayField!.append('c');
  571. formModel.init({});
  572. a.state.errors = {
  573. 'arr.0': [{ name: 'arr.0', message: 'err0', level: FeedbackLevel.Error }],
  574. };
  575. c.state.errors = {
  576. 'arr.2': [{ name: 'arr.2', message: 'err2', level: FeedbackLevel.Error }],
  577. };
  578. expect(formModel.values).toEqual({ arr: ['a', 'b', 'c'] });
  579. arrayField.swap(0, 2);
  580. expect(formModel.values).toEqual({ arr: ['c', 'b', 'a'] });
  581. expect(formModel.getField('arr.0').state.errors).toEqual({
  582. 'arr.0': [{ name: 'arr.0', message: 'err2', level: FeedbackLevel.Error }],
  583. });
  584. expect(formModel.getField('arr.2').state.errors).toEqual({
  585. 'arr.2': [{ name: 'arr.2', message: 'err0', level: FeedbackLevel.Error }],
  586. });
  587. });
  588. it('can swap from middle index to last index', () => {
  589. const arrayField = formModel.createFieldArray('arr');
  590. const a = arrayField!.append('a');
  591. const b = arrayField!.append('b');
  592. const c = arrayField!.append('c');
  593. formModel.init({});
  594. b.state.errors = {
  595. 'arr.1': [{ name: 'arr.1', message: 'err1', level: FeedbackLevel.Error }],
  596. };
  597. c.state.errors = {
  598. 'arr.2': [{ name: 'arr.2', message: 'err2', level: FeedbackLevel.Error }],
  599. };
  600. expect(formModel.values).toEqual({ arr: ['a', 'b', 'c'] });
  601. arrayField.swap(1, 2);
  602. expect(formModel.values).toEqual({ arr: ['a', 'c', 'b'] });
  603. expect(formModel.getField('arr.1').state.errors).toEqual({
  604. 'arr.1': [{ name: 'arr.1', message: 'err2', level: FeedbackLevel.Error }],
  605. });
  606. expect(formModel.getField('arr.2').state.errors).toEqual({
  607. 'arr.2': [{ name: 'arr.2', message: 'err1', level: FeedbackLevel.Error }],
  608. });
  609. });
  610. it('can swap from middle index to another middle index', () => {
  611. const arrayField = formModel.createFieldArray('arr');
  612. arrayField!.append('a');
  613. const b = arrayField!.append('b');
  614. const c = arrayField!.append('c');
  615. arrayField!.append('d');
  616. formModel.init({});
  617. b.state.errors = {
  618. 'arr.1': [{ name: 'arr.1', message: 'err1', level: FeedbackLevel.Error }],
  619. };
  620. c.state.errors = {
  621. 'arr.2': [{ name: 'arr.2', message: 'err2', level: FeedbackLevel.Error }],
  622. };
  623. expect(formModel.values).toEqual({ arr: ['a', 'b', 'c', 'd'] });
  624. arrayField.swap(1, 2);
  625. expect(formModel.values).toEqual({ arr: ['a', 'c', 'b', 'd'] });
  626. expect(formModel.getField('arr.1').state.errors).toEqual({
  627. 'arr.1': [{ name: 'arr.1', message: 'err2', level: FeedbackLevel.Error }],
  628. });
  629. expect(formModel.getField('arr.2').state.errors).toEqual({
  630. 'arr.2': [{ name: 'arr.2', message: 'err1', level: FeedbackLevel.Error }],
  631. });
  632. });
  633. it('can swap for nested array', () => {
  634. const arrayField = formModel.createFieldArray('arr');
  635. const a = arrayField!.append({ x: 'x0', y: 'y0' });
  636. const b = arrayField!.append({ x: 'x1', y: 'y1' });
  637. const ax = formModel.createField('arr.0.x');
  638. const ay = formModel.createField('arr.0.y');
  639. const bx = formModel.createField('arr.1.x');
  640. const by = formModel.createField('arr.1.y');
  641. formModel.init({});
  642. ax.state.errors = {
  643. 'arr.0.x': [{ name: 'arr.0.x', message: 'err0x', level: FeedbackLevel.Error }],
  644. };
  645. bx.state.errors = {
  646. 'arr.1.x': [{ name: 'arr.1.x', message: 'err1x', level: FeedbackLevel.Error }],
  647. };
  648. expect(formModel.values).toEqual({
  649. arr: [
  650. { x: 'x0', y: 'y0' },
  651. { x: 'x1', y: 'y1' },
  652. ],
  653. });
  654. arrayField.swap(0, 1);
  655. expect(formModel.values).toEqual({
  656. arr: [
  657. { x: 'x1', y: 'y1' },
  658. { x: 'x0', y: 'y0' },
  659. ],
  660. });
  661. expect(formModel.getField('arr.0.x').state.errors).toEqual({
  662. 'arr.0.x': [{ name: 'arr.0.x', message: 'err1x', level: FeedbackLevel.Error }],
  663. });
  664. expect(formModel.getField('arr.1.x').state.errors).toEqual({
  665. 'arr.1.x': [{ name: 'arr.1.x', message: 'err0x', level: FeedbackLevel.Error }],
  666. });
  667. // assert form.state.errors
  668. expect(formModel.state.errors['arr.0.x']).toEqual([
  669. { name: 'arr.0.x', message: 'err1x', level: FeedbackLevel.Error },
  670. ]);
  671. expect(formModel.state.errors['arr.1.x']).toEqual([
  672. { name: 'arr.1.x', message: 'err0x', level: FeedbackLevel.Error },
  673. ]);
  674. });
  675. it('should have correct form.state.errors after swapping invalid field with valid field', () => {
  676. const arrayField = formModel.createFieldArray('arr');
  677. const a = arrayField!.append('a');
  678. const b = arrayField!.append('b');
  679. arrayField!.append('c');
  680. formModel.init({});
  681. b.state.errors = {
  682. 'arr.1': [{ name: 'arr.1', message: 'err1', level: FeedbackLevel.Error }],
  683. };
  684. arrayField.swap(0, 1);
  685. expect(formModel.getField('arr.0').state.errors).toEqual({
  686. 'arr.0': [{ name: 'arr.0', message: 'err1', level: FeedbackLevel.Error }],
  687. });
  688. expect(formModel.getField('arr.1').state.errors).toEqual(undefined);
  689. });
  690. it('should trigger array effect and child effect', () => {
  691. const arrayField = formModel.createFieldArray('arr');
  692. const fieldA = arrayField!.append('a');
  693. arrayField!.append('b');
  694. arrayField!.append('c');
  695. const arrayEffect = vi.fn();
  696. arrayField.onValueChange(arrayEffect);
  697. const fieldAEffect = vi.fn();
  698. fieldA.onValueChange(fieldAEffect);
  699. formModel.init({});
  700. arrayField.swap(1, 2);
  701. expect(arrayEffect).toHaveBeenCalledOnce();
  702. expect(fieldAEffect).toHaveBeenCalledOnce();
  703. });
  704. });
  705. describe('move', () => {
  706. beforeEach(() => {
  707. formModel.dispose();
  708. formModel = new FormModel();
  709. });
  710. it('should throw error when from or to exceeds bound', () => {
  711. const arrayField = formModel.createFieldArray('arr');
  712. arrayField!.append('a');
  713. arrayField!.append('b');
  714. arrayField!.append('c');
  715. formModel.init({});
  716. expect(() => arrayField.move(-1, 1)).toThrowError();
  717. expect(() => arrayField.move(1, -1)).toThrowError();
  718. expect(() => arrayField.move(1, 3)).toThrowError();
  719. expect(() => arrayField.move(3, 1)).toThrowError();
  720. });
  721. it('can move from 0 to middle index', () => {
  722. const arrayField = formModel.createFieldArray('arr');
  723. arrayField!.append('a');
  724. arrayField!.append('b');
  725. arrayField!.append('c');
  726. formModel.init({});
  727. expect(formModel.values).toEqual({ arr: ['a', 'b', 'c'] });
  728. arrayField.move(0, 1);
  729. expect(formModel.values).toEqual({ arr: ['b', 'a', 'c'] });
  730. });
  731. it('can move from 0 to last index', () => {
  732. const arrayField = formModel.createFieldArray('arr');
  733. arrayField!.append('a');
  734. arrayField!.append('b');
  735. arrayField!.append('c');
  736. formModel.init({});
  737. expect(formModel.values).toEqual({ arr: ['a', 'b', 'c'] });
  738. arrayField.move(0, 2);
  739. expect(formModel.values).toEqual({ arr: ['b', 'c', 'a'] });
  740. });
  741. it('can move from middle index to last index', () => {
  742. const arrayField = formModel.createFieldArray('arr');
  743. arrayField!.append('a');
  744. arrayField!.append('b');
  745. arrayField!.append('c');
  746. formModel.init({});
  747. expect(formModel.values).toEqual({ arr: ['a', 'b', 'c'] });
  748. arrayField.move(1, 2);
  749. expect(formModel.values).toEqual({ arr: ['a', 'c', 'b'] });
  750. });
  751. it('can move from middle index to another middle index', () => {
  752. const arrayField = formModel.createFieldArray('arr');
  753. arrayField!.append('a');
  754. arrayField!.append('b');
  755. arrayField!.append('c');
  756. arrayField!.append('d');
  757. formModel.init({});
  758. expect(formModel.values).toEqual({ arr: ['a', 'b', 'c', 'd'] });
  759. arrayField.move(1, 2);
  760. expect(formModel.values).toEqual({ arr: ['a', 'c', 'b', 'd'] });
  761. });
  762. it('can move from middle index to another middle index with more elements', () => {
  763. const arrayField = formModel.createFieldArray('arr');
  764. arrayField!.append('a');
  765. arrayField!.append('b');
  766. arrayField!.append('c');
  767. arrayField!.append('d');
  768. arrayField!.append('e');
  769. arrayField!.append('f');
  770. formModel.init({});
  771. expect(formModel.values).toEqual({ arr: ['a', 'b', 'c', 'd', 'e', 'f'] });
  772. arrayField.move(1, 4);
  773. expect(formModel.values).toEqual({ arr: ['a', 'c', 'd', 'e', 'b', 'f'] });
  774. });
  775. it('can move from middle index to another middle index with more elements when to is greater than from', () => {
  776. const arrayField = formModel.createFieldArray('arr');
  777. arrayField!.append('a');
  778. arrayField!.append('b');
  779. arrayField!.append('c');
  780. arrayField!.append('d');
  781. arrayField!.append('e');
  782. arrayField!.append('f');
  783. formModel.init({});
  784. expect(formModel.values).toEqual({ arr: ['a', 'b', 'c', 'd', 'e', 'f'] });
  785. arrayField.move(4, 1);
  786. expect(formModel.values).toEqual({ arr: ['a', 'e', 'b', 'c', 'd', 'f'] });
  787. });
  788. it('should trigger array effect and child effect', () => {
  789. const arrayField = formModel.createFieldArray('arr');
  790. const fieldA = arrayField!.append('a');
  791. arrayField!.append('b');
  792. arrayField!.append('c');
  793. const arrayEffect = vi.fn();
  794. arrayField.onValueChange(arrayEffect);
  795. const fieldAEffect = vi.fn();
  796. fieldA.onValueChange(fieldAEffect);
  797. formModel.init({});
  798. arrayField.move(1, 2);
  799. expect(arrayEffect).toHaveBeenCalledOnce();
  800. expect(fieldAEffect).toHaveBeenCalledOnce();
  801. });
  802. });
  803. });