form-model-v2.test.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. import { beforeEach, describe, expect, it, vi } from 'vitest';
  2. import { FlowNodeEntity } from '@flowgram.ai/document';
  3. import { DataEvent, FormMeta } from '../src/types';
  4. import { defineFormPluginCreator } from '../src/form-plugin';
  5. import { FormModelV2 } from '../src/form-model-v2';
  6. describe('FormModelV2', () => {
  7. const node = {
  8. getService: vi.fn().mockReturnValue({}),
  9. getData: vi.fn().mockReturnValue({ fireChange: vi.fn() }),
  10. } as unknown as FlowNodeEntity;
  11. let formModelV2 = new FormModelV2(node);
  12. beforeEach(() => {
  13. formModelV2.dispose();
  14. formModelV2 = new FormModelV2(node);
  15. });
  16. describe('v1 apis', () => {
  17. it('getFormItemValueByPath', () => {
  18. const formMeta = {
  19. render: vi.fn(),
  20. };
  21. formModelV2.init(formMeta, {
  22. a: 1,
  23. b: 2,
  24. });
  25. expect(formModelV2.getFormItemValueByPath('/a')).toBe(1);
  26. expect(formModelV2.getFormItemValueByPath('/b')).toBe(2);
  27. expect(formModelV2.getFormItemValueByPath('/')).toEqual({ a: 1, b: 2 });
  28. });
  29. it('getFormItemByPath when path is /', () => {
  30. const formMeta = {
  31. render: vi.fn(),
  32. };
  33. formModelV2.init(formMeta, {
  34. a: 1,
  35. b: 2,
  36. });
  37. const formItem = formModelV2.getFormItemByPath('/');
  38. expect(formItem.value).toEqual({
  39. a: 1,
  40. b: 2,
  41. });
  42. formItem.value = { a: 3, b: 4 };
  43. expect(formItem.value).toEqual({
  44. a: 3,
  45. b: 4,
  46. });
  47. });
  48. });
  49. describe('effects', () => {
  50. it('should trigger init effects when initialValues exists', () => {
  51. const mockEffect = vi.fn();
  52. const formMeta = {
  53. render: vi.fn(),
  54. effect: {
  55. a: [
  56. {
  57. event: DataEvent.onValueInit,
  58. effect: mockEffect,
  59. },
  60. ],
  61. },
  62. };
  63. formModelV2.init(formMeta, { a: 1 });
  64. expect(mockEffect).toHaveBeenCalledOnce();
  65. });
  66. it('should trigger init effects when formatOnInit return value', () => {
  67. const mockEffect = vi.fn();
  68. const formMeta = {
  69. render: vi.fn(),
  70. formatOnInit: () => ({ a: { b: 1 } }),
  71. effect: {
  72. 'a.b': [
  73. {
  74. event: DataEvent.onValueInit,
  75. effect: mockEffect,
  76. },
  77. ],
  78. },
  79. };
  80. formModelV2.init(formMeta);
  81. expect(mockEffect).toHaveBeenCalledOnce();
  82. });
  83. it('should trigger value change effects', () => {
  84. const mockEffect = vi.fn();
  85. const formMeta = {
  86. render: vi.fn(),
  87. effect: {
  88. a: [
  89. {
  90. event: DataEvent.onValueChange,
  91. effect: mockEffect,
  92. },
  93. ],
  94. },
  95. };
  96. formModelV2.init(formMeta, { a: 1 });
  97. formModelV2.setValueIn('a', 2);
  98. expect(mockEffect).toHaveBeenCalledOnce();
  99. });
  100. it('should trigger onValueInitOrChange effects when form defaultValue init', () => {
  101. const mockEffect = vi.fn();
  102. const formMeta = {
  103. render: vi.fn(),
  104. effect: {
  105. a: [
  106. {
  107. event: DataEvent.onValueInitOrChange,
  108. effect: mockEffect,
  109. },
  110. ],
  111. },
  112. };
  113. formModelV2.init(formMeta, { a: 1 });
  114. expect(mockEffect).toHaveBeenCalledOnce();
  115. });
  116. it('should trigger onValueInitOrChange effects when field defaultValue init', () => {
  117. const mockEffect = vi.fn();
  118. const formMeta = {
  119. render: vi.fn(),
  120. effect: {
  121. a: [
  122. {
  123. event: DataEvent.onValueInitOrChange,
  124. effect: mockEffect,
  125. },
  126. ],
  127. },
  128. };
  129. formModelV2.init(formMeta);
  130. formModelV2.nativeFormModel?.setInitValueIn('a', 2);
  131. expect(mockEffect).toHaveBeenCalledOnce();
  132. });
  133. it('should trigger child onValueInit effects when field defaultValue init', () => {
  134. const mockEffect = vi.fn();
  135. const formMeta = {
  136. render: vi.fn(),
  137. effect: {
  138. 'a.b.c': [
  139. {
  140. event: DataEvent.onValueInit,
  141. effect: mockEffect,
  142. },
  143. ],
  144. },
  145. };
  146. formModelV2.init(formMeta);
  147. formModelV2.nativeFormModel?.setInitValueIn('a', { b: { c: 1 } });
  148. expect(mockEffect).toHaveBeenCalledOnce();
  149. });
  150. it('should not trigger child onValueInit effects when field defaultValue init but child path has no value', () => {
  151. const mockEffect = vi.fn();
  152. const formMeta = {
  153. render: vi.fn(),
  154. effect: {
  155. 'a.b.c': [
  156. {
  157. event: DataEvent.onValueInit,
  158. effect: mockEffect,
  159. },
  160. ],
  161. },
  162. };
  163. formModelV2.init(formMeta);
  164. formModelV2.nativeFormModel?.setInitValueIn('a', 2);
  165. expect(mockEffect).not.toHaveBeenCalled();
  166. });
  167. it('should trigger onValueInitOrChange effects when value change', () => {
  168. const mockEffect = vi.fn();
  169. const formMeta = {
  170. render: vi.fn(),
  171. effect: {
  172. a: [
  173. {
  174. event: DataEvent.onValueInitOrChange,
  175. effect: mockEffect,
  176. },
  177. ],
  178. },
  179. };
  180. formModelV2.init(formMeta);
  181. formModelV2.setValueIn('a', 2);
  182. expect(mockEffect).toHaveBeenCalledOnce();
  183. });
  184. it('should trigger single item init effect when array append', () => {
  185. const mockEffect = vi.fn();
  186. const formMeta = {
  187. render: vi.fn(),
  188. effect: {
  189. ['arr.*']: [
  190. {
  191. event: DataEvent.onValueInit,
  192. effect: mockEffect,
  193. },
  194. ],
  195. },
  196. };
  197. formModelV2.init(formMeta);
  198. const arrModel = formModelV2.nativeFormModel?.createFieldArray('arr');
  199. arrModel?.append(1);
  200. arrModel?.append(2);
  201. expect(mockEffect).toHaveBeenCalledTimes(2);
  202. });
  203. it('should trigger value change effects return when value change', () => {
  204. const mockEffectReturn = vi.fn();
  205. const mockEffect = vi.fn(() => mockEffectReturn);
  206. const formMeta = {
  207. render: vi.fn(),
  208. effect: {
  209. a: [
  210. {
  211. event: DataEvent.onValueChange,
  212. effect: mockEffect,
  213. },
  214. ],
  215. },
  216. };
  217. formModelV2.init(formMeta, { a: 1 });
  218. formModelV2.setValueIn('a', 2);
  219. formModelV2.setValueIn('a', 3);
  220. expect(mockEffect).toHaveBeenCalledTimes(2);
  221. expect(mockEffectReturn).toHaveBeenCalledOnce();
  222. });
  223. it('should trigger onValueInitOrChange effects return when value init', () => {
  224. const mockEffectReturn = vi.fn();
  225. const mockEffect = vi.fn(() => mockEffectReturn);
  226. const formMeta = {
  227. render: vi.fn(),
  228. effect: {
  229. a: [
  230. {
  231. event: DataEvent.onValueInitOrChange,
  232. effect: mockEffect,
  233. },
  234. ],
  235. },
  236. };
  237. formModelV2.init(formMeta, { a: 1 });
  238. formModelV2.setValueIn('a', 2);
  239. expect(mockEffectReturn).toHaveBeenCalledOnce();
  240. });
  241. it('should trigger onValueInitOrChange effects return when value init and change', () => {
  242. const mockEffectReturn = vi.fn();
  243. const mockEffect = vi.fn(() => mockEffectReturn);
  244. const formMeta = {
  245. render: vi.fn(),
  246. effect: {
  247. a: [
  248. {
  249. event: DataEvent.onValueInitOrChange,
  250. effect: mockEffect,
  251. },
  252. ],
  253. },
  254. };
  255. formModelV2.init(formMeta, { a: 1 });
  256. formModelV2.setValueIn('a', 2);
  257. formModelV2.setValueIn('a', 3);
  258. // 第一次setValue,触发 init 时记录的return, 第二次setValue 触发 第一次setValue时记录的return, 共2次
  259. expect(mockEffectReturn).toHaveBeenCalledTimes(2);
  260. });
  261. it('should update effect return function each time init or change the value', () => {
  262. const mockEffectReturn = vi.fn().mockReturnValueOnce(1).mockReturnValueOnce(2);
  263. const mockEffect = vi.fn(() => mockEffectReturn);
  264. const formMeta = {
  265. render: vi.fn(),
  266. effect: {
  267. ['arr.*.var']: [
  268. {
  269. event: DataEvent.onValueInitOrChange,
  270. effect: mockEffect,
  271. },
  272. ],
  273. },
  274. };
  275. formModelV2.init(formMeta, { arr: [] });
  276. const form = formModelV2.nativeFormModel!;
  277. const arrayField = form.createFieldArray('arr');
  278. arrayField!.append({ var: 'x' });
  279. form.setValueIn('arr.0.var', 'y');
  280. formModelV2.dispose();
  281. expect(mockEffectReturn).toHaveNthReturnedWith(1, 1);
  282. expect(mockEffectReturn).toHaveNthReturnedWith(2, 2);
  283. });
  284. it('should trigger all effects return when formModel dispose', () => {
  285. const mockEffectReturn1 = vi.fn();
  286. const mockEffect1 = vi.fn(() => mockEffectReturn1);
  287. const mockEffectReturn2 = vi.fn();
  288. const mockEffect2 = vi.fn(() => mockEffectReturn2);
  289. const formMeta = {
  290. render: vi.fn(),
  291. effect: {
  292. a: [
  293. {
  294. event: DataEvent.onValueInitOrChange,
  295. effect: mockEffect1,
  296. },
  297. ],
  298. b: [
  299. {
  300. event: DataEvent.onValueInit,
  301. effect: mockEffect2,
  302. },
  303. ],
  304. },
  305. };
  306. formModelV2.init(formMeta, { a: 1, b: 2 });
  307. formModelV2.dispose();
  308. expect(mockEffectReturn1).toHaveBeenCalledTimes(1);
  309. expect(mockEffectReturn2).toHaveBeenCalledTimes(1);
  310. });
  311. });
  312. describe('plugins', () => {
  313. beforeEach(() => {
  314. formModelV2.dispose();
  315. formModelV2 = new FormModelV2(node);
  316. });
  317. it('should call onInit when formModel init', () => {
  318. const mockInit = vi.fn();
  319. const plugin = defineFormPluginCreator('test', {
  320. onInit: mockInit,
  321. })({ opt1: 1 });
  322. const formMeta = {
  323. render: vi.fn(),
  324. plugins: [plugin],
  325. } as unknown as FormMeta;
  326. formModelV2.init(formMeta);
  327. expect(mockInit).toHaveBeenCalledOnce();
  328. expect(mockInit).toHaveBeenCalledWith(
  329. { formModel: formModelV2, ...formModelV2.nodeContext },
  330. { opt1: 1 }
  331. );
  332. });
  333. it('should call onDispose when formModel dispose', () => {
  334. const mockDispose = vi.fn();
  335. const plugin = defineFormPluginCreator('test', {
  336. onDispose: mockDispose,
  337. })({ opt1: 1 });
  338. const formMeta = {
  339. render: vi.fn(),
  340. plugins: [plugin],
  341. } as unknown as FormMeta;
  342. formModelV2.init(formMeta);
  343. formModelV2.dispose();
  344. expect(mockDispose).toHaveBeenCalledOnce();
  345. expect(mockDispose).toHaveBeenCalledWith(
  346. { formModel: formModelV2, ...formModelV2.nodeContext },
  347. { opt1: 1 }
  348. );
  349. });
  350. it('should call effects when corresponding events trigger', () => {
  351. const mockEffectPlugin = vi.fn();
  352. const mockEffectOrigin = vi.fn();
  353. const plugin = defineFormPluginCreator('test', {
  354. effect: {
  355. a: [
  356. {
  357. event: DataEvent.onValueInitOrChange,
  358. effect: mockEffectPlugin,
  359. },
  360. ],
  361. },
  362. })({ opt1: 1 });
  363. const formMeta = {
  364. render: vi.fn(),
  365. effect: {
  366. a: [
  367. {
  368. event: DataEvent.onValueInitOrChange,
  369. effect: mockEffectOrigin,
  370. },
  371. ],
  372. },
  373. plugins: [plugin],
  374. } as unknown as FormMeta;
  375. formModelV2.init(formMeta, { a: 0 });
  376. expect(mockEffectPlugin).toHaveBeenCalledOnce();
  377. expect(mockEffectOrigin).toHaveBeenCalledOnce();
  378. });
  379. it('should call effects when corresponding events trigger: array case', () => {
  380. const mockEffectPluginArrStar = vi.fn();
  381. const mockEffectOriginArrStar = vi.fn();
  382. const mockEffectPluginOther = vi.fn();
  383. const plugin = defineFormPluginCreator('test', {
  384. effect: {
  385. 'arr.*': [
  386. {
  387. event: DataEvent.onValueChange,
  388. effect: mockEffectPluginArrStar,
  389. },
  390. ],
  391. other: [
  392. {
  393. event: DataEvent.onValueChange,
  394. effect: mockEffectPluginOther,
  395. },
  396. ],
  397. },
  398. })({ opt1: 1 });
  399. const formMeta = {
  400. render: vi.fn(),
  401. effect: {
  402. 'arr.*': [
  403. {
  404. event: DataEvent.onValueChange,
  405. effect: mockEffectOriginArrStar,
  406. },
  407. ],
  408. },
  409. plugins: [plugin],
  410. } as unknown as FormMeta;
  411. formModelV2.init(formMeta, { arr: [0], other: 1 });
  412. formModelV2.setValueIn('arr.0', 2);
  413. formModelV2.setValueIn('other', 2);
  414. expect(mockEffectOriginArrStar).toHaveBeenCalledOnce();
  415. expect(mockEffectPluginArrStar).toHaveBeenCalledOnce();
  416. expect(mockEffectPluginOther).toHaveBeenCalledOnce();
  417. });
  418. });
  419. describe('onFormValueChangeIn', () => {
  420. beforeEach(() => {
  421. formModelV2.dispose();
  422. formModelV2 = new FormModelV2(node);
  423. });
  424. it('should trigger callback when value change', () => {
  425. const mockCallback = vi.fn();
  426. const formMeta = {
  427. render: vi.fn(),
  428. } as unknown as FormMeta;
  429. formModelV2.init(formMeta, { a: 1 });
  430. formModelV2.onFormValueChangeIn('a', mockCallback);
  431. formModelV2.setValueIn('a', 2);
  432. expect(mockCallback).toHaveBeenCalledOnce();
  433. });
  434. it('should throw error when formModel is not initialized', () => {
  435. expect(() => formModelV2.onFormValueChangeIn('a', vi.fn())).toThrowError();
  436. });
  437. });
  438. });