field-array-model.test.ts 32 KB

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