form-model-v2.test.ts 14 KB

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