form-model-v2.test.ts 14 KB

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