form-effects.test.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  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 { FlowNodeEntity } from '@flowgram.ai/document';
  7. import { DataEvent } from '../src/types';
  8. import { FormModelV2 } from '../src/form-model-v2';
  9. describe('FormModelV2 effects', () => {
  10. const node = {
  11. getService: vi.fn().mockReturnValue({}),
  12. getData: vi.fn().mockReturnValue({ fireChange: vi.fn() }),
  13. } as unknown as FlowNodeEntity;
  14. let formModelV2 = new FormModelV2(node);
  15. beforeEach(() => {
  16. formModelV2.dispose();
  17. formModelV2 = new FormModelV2(node);
  18. });
  19. it('should trigger init effects when initialValues exists', () => {
  20. const mockEffect = vi.fn();
  21. const formMeta = {
  22. render: vi.fn(),
  23. effect: {
  24. a: [
  25. {
  26. event: DataEvent.onValueInit,
  27. effect: mockEffect,
  28. },
  29. ],
  30. },
  31. };
  32. formModelV2.init(formMeta, { a: 1 });
  33. expect(mockEffect).toHaveBeenCalledOnce();
  34. });
  35. it('should trigger init effects when formatOnInit return value', () => {
  36. const mockEffect = vi.fn();
  37. const formMeta = {
  38. render: vi.fn(),
  39. formatOnInit: () => ({ a: { b: 1 } }),
  40. effect: {
  41. 'a.b': [
  42. {
  43. event: DataEvent.onValueInit,
  44. effect: mockEffect,
  45. },
  46. ],
  47. },
  48. };
  49. formModelV2.init(formMeta);
  50. expect(mockEffect).toHaveBeenCalledOnce();
  51. });
  52. it('should trigger value change effects', () => {
  53. const mockEffect = vi.fn();
  54. const formMeta = {
  55. render: vi.fn(),
  56. effect: {
  57. a: [
  58. {
  59. event: DataEvent.onValueChange,
  60. effect: mockEffect,
  61. },
  62. ],
  63. },
  64. };
  65. formModelV2.init(formMeta, { a: 1 });
  66. formModelV2.setValueIn('a', 2);
  67. expect(mockEffect).toHaveBeenCalledOnce();
  68. });
  69. it('should trigger onValueInitOrChange effects when form defaultValue init', () => {
  70. const mockEffect = vi.fn();
  71. const formMeta = {
  72. render: vi.fn(),
  73. effect: {
  74. a: [
  75. {
  76. event: DataEvent.onValueInitOrChange,
  77. effect: mockEffect,
  78. },
  79. ],
  80. },
  81. };
  82. formModelV2.init(formMeta, { a: 1 });
  83. expect(mockEffect).toHaveBeenCalledOnce();
  84. });
  85. it('should trigger onValueInitOrChange effects when field defaultValue init', () => {
  86. const mockEffect = vi.fn();
  87. const formMeta = {
  88. render: vi.fn(),
  89. effect: {
  90. a: [
  91. {
  92. event: DataEvent.onValueInitOrChange,
  93. effect: mockEffect,
  94. },
  95. ],
  96. },
  97. };
  98. formModelV2.init(formMeta);
  99. formModelV2.nativeFormModel?.setInitValueIn('a', 2);
  100. expect(mockEffect).toHaveBeenCalledOnce();
  101. });
  102. it('should trigger child onValueInit effects when field defaultValue init', () => {
  103. const mockEffect = vi.fn();
  104. const formMeta = {
  105. render: vi.fn(),
  106. effect: {
  107. 'a.b.c': [
  108. {
  109. event: DataEvent.onValueInit,
  110. effect: mockEffect,
  111. },
  112. ],
  113. },
  114. };
  115. formModelV2.init(formMeta);
  116. formModelV2.nativeFormModel?.setInitValueIn('a', { b: { c: 1 } });
  117. expect(mockEffect).toHaveBeenCalledOnce();
  118. });
  119. it('should not trigger child onValueInit effects when field defaultValue init but child path has no value', () => {
  120. const mockEffect = vi.fn();
  121. const formMeta = {
  122. render: vi.fn(),
  123. effect: {
  124. 'a.b.c': [
  125. {
  126. event: DataEvent.onValueInit,
  127. effect: mockEffect,
  128. },
  129. ],
  130. },
  131. };
  132. formModelV2.init(formMeta);
  133. formModelV2.nativeFormModel?.setInitValueIn('a', 2);
  134. expect(mockEffect).not.toHaveBeenCalled();
  135. });
  136. it('should trigger onValueInitOrChange effects when value change', () => {
  137. const mockEffect = vi.fn();
  138. const formMeta = {
  139. render: vi.fn(),
  140. effect: {
  141. a: [
  142. {
  143. event: DataEvent.onValueInitOrChange,
  144. effect: mockEffect,
  145. },
  146. ],
  147. },
  148. };
  149. formModelV2.init(formMeta);
  150. formModelV2.setValueIn('a', 2);
  151. expect(mockEffect).toHaveBeenCalledOnce();
  152. formModelV2.setValueIn('a', {});
  153. expect(mockEffect).toHaveBeenCalledTimes(2);
  154. formModelV2.setValueIn('a.b', 2);
  155. expect(mockEffect).toHaveBeenCalledTimes(3);
  156. });
  157. it('should trigger single item init effect when array append', () => {
  158. const mockArrItemEffect = vi.fn();
  159. const formMeta = {
  160. render: vi.fn(),
  161. effect: {
  162. ['arr.*']: [
  163. {
  164. event: DataEvent.onValueInit,
  165. effect: mockArrItemEffect,
  166. },
  167. ],
  168. },
  169. };
  170. formModelV2.init(formMeta);
  171. const arrModel = formModelV2.nativeFormModel?.createFieldArray('arr');
  172. arrModel?.append(1);
  173. arrModel?.append(2);
  174. expect(mockArrItemEffect).toHaveBeenCalledTimes(2);
  175. });
  176. it('should trigger value change effects return when value change', () => {
  177. const mockEffectReturn = vi.fn();
  178. const mockEffect = vi.fn(() => mockEffectReturn);
  179. const formMeta = {
  180. render: vi.fn(),
  181. effect: {
  182. a: [
  183. {
  184. event: DataEvent.onValueChange,
  185. effect: mockEffect,
  186. },
  187. ],
  188. },
  189. };
  190. formModelV2.init(formMeta, { a: 1 });
  191. formModelV2.setValueIn('a', 2);
  192. formModelV2.setValueIn('a', 3);
  193. expect(mockEffect).toHaveBeenCalledTimes(2);
  194. expect(mockEffectReturn).toHaveBeenCalledOnce();
  195. });
  196. it('should trigger onValueInitOrChange effects return when value init', () => {
  197. const mockEffectReturn = vi.fn();
  198. const mockEffect = vi.fn(() => mockEffectReturn);
  199. const formMeta = {
  200. render: vi.fn(),
  201. effect: {
  202. a: [
  203. {
  204. event: DataEvent.onValueInitOrChange,
  205. effect: mockEffect,
  206. },
  207. ],
  208. },
  209. };
  210. formModelV2.init(formMeta, { a: 1 });
  211. formModelV2.setValueIn('a', 2);
  212. expect(mockEffectReturn).toHaveBeenCalledOnce();
  213. });
  214. it('should trigger onValueInitOrChange effects return when value init and change', () => {
  215. const mockEffectReturn = vi.fn();
  216. const mockEffect = vi.fn(() => mockEffectReturn);
  217. const formMeta = {
  218. render: vi.fn(),
  219. effect: {
  220. a: [
  221. {
  222. event: DataEvent.onValueInitOrChange,
  223. effect: mockEffect,
  224. },
  225. ],
  226. },
  227. };
  228. formModelV2.init(formMeta, { a: 1 });
  229. formModelV2.setValueIn('a', 2);
  230. formModelV2.setValueIn('a', 3);
  231. // 第一次setValue,触发 init 时记录的return, 第二次setValue 触发 第一次setValue时记录的return, 共2次
  232. expect(mockEffectReturn).toHaveBeenCalledTimes(2);
  233. });
  234. it('should update effect return function each time init or change the value', () => {
  235. const mockEffectReturn = vi.fn().mockReturnValueOnce(1).mockReturnValueOnce(2);
  236. const mockEffect = vi.fn(() => mockEffectReturn);
  237. const formMeta = {
  238. render: vi.fn(),
  239. effect: {
  240. ['arr.*.var']: [
  241. {
  242. event: DataEvent.onValueInitOrChange,
  243. effect: mockEffect,
  244. },
  245. ],
  246. },
  247. };
  248. formModelV2.init(formMeta, { arr: [] });
  249. const form = formModelV2.nativeFormModel!;
  250. const arrayField = form.createFieldArray('arr');
  251. arrayField!.append({ var: 'x' });
  252. form.setValueIn('arr.0.var', 'y');
  253. formModelV2.dispose();
  254. expect(mockEffectReturn).toHaveNthReturnedWith(1, 1);
  255. expect(mockEffectReturn).toHaveNthReturnedWith(2, 2);
  256. });
  257. it('should trigger effects when setValueIn called in parent name', () => {
  258. const mockInitEffectReturn = vi.fn();
  259. const mockInitEffect = vi.fn(() => mockInitEffectReturn);
  260. const mockInitOrChangeEffectReturn = vi.fn();
  261. const mockInitOrChangeEffect = vi.fn(() => mockInitOrChangeEffectReturn);
  262. const mockChangeEffectReturn = vi.fn();
  263. const mockChangeEffect = vi.fn(() => mockChangeEffectReturn);
  264. const formMeta = {
  265. render: vi.fn(),
  266. effect: {
  267. 'inputsValues.*': [
  268. {
  269. event: DataEvent.onValueInit,
  270. effect: mockInitEffect,
  271. },
  272. {
  273. event: DataEvent.onValueInitOrChange,
  274. effect: mockInitOrChangeEffect,
  275. },
  276. {
  277. event: DataEvent.onValueChange,
  278. effect: mockChangeEffect,
  279. },
  280. ],
  281. },
  282. };
  283. formModelV2.init(formMeta, { inputsValues: { a: 1 } });
  284. expect(mockInitEffect).toHaveBeenCalledTimes(1);
  285. expect(mockInitOrChangeEffect).toHaveBeenCalledTimes(1);
  286. expect(mockChangeEffect).toHaveBeenCalledTimes(0);
  287. formModelV2.setValueIn('inputsValues', { a: 2 });
  288. expect(mockInitEffect).toHaveBeenCalledTimes(1);
  289. expect(mockInitOrChangeEffect).toHaveBeenCalledTimes(2);
  290. expect(mockChangeEffect).toHaveBeenCalledTimes(1);
  291. formModelV2.setValueIn('inputsValues', { b: 3 });
  292. expect(mockInitEffect).toHaveBeenCalledTimes(2);
  293. expect(mockInitOrChangeEffect).toHaveBeenCalledTimes(4);
  294. expect(mockChangeEffect).toHaveBeenCalledTimes(2);
  295. formModelV2.setValueIn('inputsValues', { b: 4 });
  296. expect(mockInitEffect).toHaveBeenCalledTimes(2);
  297. expect(mockInitOrChangeEffect).toHaveBeenCalledTimes(5);
  298. expect(mockChangeEffect).toHaveBeenCalledTimes(3);
  299. formModelV2.setValueIn('inputsValues', { a: 1, b: 4 });
  300. expect(mockInitEffect).toHaveBeenCalledTimes(3);
  301. expect(mockInitOrChangeEffect).toHaveBeenCalledTimes(6);
  302. expect(mockChangeEffect).toHaveBeenCalledTimes(3);
  303. formModelV2.setValueIn('inputsValues', {});
  304. expect(mockInitEffect).toHaveBeenCalledTimes(3);
  305. expect(mockInitOrChangeEffect).toHaveBeenCalledTimes(8);
  306. expect(mockChangeEffect).toHaveBeenCalledTimes(5);
  307. });
  308. it('should trigger all effects return when formModel dispose', () => {
  309. const mockEffectReturn1 = vi.fn();
  310. const mockEffect1 = vi.fn(() => mockEffectReturn1);
  311. const mockEffectReturn2 = vi.fn();
  312. const mockEffect2 = vi.fn(() => mockEffectReturn2);
  313. const formMeta = {
  314. render: vi.fn(),
  315. effect: {
  316. a: [
  317. {
  318. event: DataEvent.onValueInitOrChange,
  319. effect: mockEffect1,
  320. },
  321. ],
  322. b: [
  323. {
  324. event: DataEvent.onValueInit,
  325. effect: mockEffect2,
  326. },
  327. ],
  328. },
  329. };
  330. formModelV2.init(formMeta, { a: 1, b: 2 });
  331. formModelV2.dispose();
  332. expect(mockEffectReturn1).toHaveBeenCalledTimes(1);
  333. expect(mockEffectReturn2).toHaveBeenCalledTimes(1);
  334. });
  335. });