form-model.test.ts 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  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 { ValidateTrigger } from '@/types';
  7. import { FormModel } from '@/core/form-model';
  8. describe('FormModel', () => {
  9. let formModel = new FormModel();
  10. describe('validate trigger', () => {
  11. beforeEach(() => {
  12. formModel.dispose();
  13. formModel = new FormModel();
  14. });
  15. it('do not validate when value change if validateTrigger is onBlur', async () => {
  16. formModel.init({ validateTrigger: ValidateTrigger.onBlur });
  17. const field = formModel.createField('x');
  18. field.originalValidate = vi.fn();
  19. vi.spyOn(field, 'originalValidate');
  20. field.value = 'some value';
  21. expect(field.originalValidate).not.toHaveBeenCalledOnce();
  22. });
  23. describe('delete field', () => {
  24. beforeEach(() => {
  25. formModel.dispose();
  26. formModel = new FormModel();
  27. });
  28. it('validate onChange', async () => {
  29. formModel.init({ initialValues: { parent: { child1: 1 } } });
  30. formModel.createField('parent');
  31. formModel.createField('parent.child1');
  32. expect(formModel.values.parent?.child1).toBe(1);
  33. formModel.deleteField('parent');
  34. expect(formModel.values.parent?.child1).toBeUndefined();
  35. expect(formModel.getField('parent')).toBeUndefined();
  36. expect(formModel.getField('parent.child1')).toBeUndefined();
  37. });
  38. });
  39. });
  40. describe('FormModel.validate', () => {
  41. beforeEach(() => {
  42. formModel.dispose();
  43. formModel = new FormModel();
  44. });
  45. it('should run validate on all matched names', async () => {
  46. formModel.init({
  47. validate: {
  48. 'a.b.*': () => 'error',
  49. },
  50. });
  51. const bField = formModel.createField('a.b');
  52. const xField = formModel.createField('a.b.x');
  53. formModel.setValueIn('a.b', { x: 1, y: 2 });
  54. const results = await formModel.validate();
  55. // 1. assert validate has been executed correctly
  56. expect(results.length).toEqual(2);
  57. expect(results[0].message).toEqual('error');
  58. expect(results[0].name).toEqual('a.b.x');
  59. expect(results[1].message).toEqual('error');
  60. expect(results[1].name).toEqual('a.b.y');
  61. // 2. assert form state has been set correctly
  62. expect(formModel.state?.errors?.['a.b.x']?.[0].message).toEqual('error');
  63. // 3. assert field state has been set correctly
  64. expect(xField.state?.errors?.['a.b.x']?.[0].message).toEqual('error');
  65. // 4. assert field state has been bubbled to its parent
  66. expect(bField.state?.errors?.['a.b.x']?.[0].message).toEqual('error');
  67. });
  68. it('should run validate if multiple patterns match', async () => {
  69. const mockValidate1 = vi.fn();
  70. const mockValidate2 = vi.fn();
  71. formModel.init({
  72. validate: {
  73. 'a.b.*': mockValidate1,
  74. 'a.b.x': mockValidate2,
  75. },
  76. });
  77. const bField = formModel.createField('a.b');
  78. const xField = formModel.createField('a.b.x');
  79. formModel.setValueIn('a.b', { x: 1, y: 2 });
  80. formModel.validate();
  81. expect(mockValidate1).toHaveBeenCalledTimes(2);
  82. expect(mockValidate2).toHaveBeenCalledTimes(1);
  83. });
  84. it('should run validate correctly if multiple patterns match but multiple layer empty value exist', async () => {
  85. const mockValidate1 = vi.fn();
  86. const mockValidate2 = vi.fn();
  87. formModel.init({
  88. validate: {
  89. 'a.*.x': mockValidate1,
  90. 'a.b.x': mockValidate2,
  91. },
  92. });
  93. const bField = formModel.createField('a.b');
  94. const xField = formModel.createField('a.b.x');
  95. formModel.setValueIn('a', {});
  96. formModel.validate();
  97. expect(mockValidate1).toHaveBeenCalledTimes(0);
  98. expect(mockValidate2).toHaveBeenCalledTimes(1);
  99. });
  100. it('should correctly set form errors state when field does not exist', async () => {
  101. formModel.init({
  102. validate: {
  103. 'a.b.*': () => 'error',
  104. },
  105. });
  106. formModel.setValueIn('a.b', { x: 1, y: 2 });
  107. await formModel.validate();
  108. expect(formModel.state?.errors?.['a.b.x']?.[0].message).toEqual('error');
  109. });
  110. it('should set form and field state correctly when run validate twice', async () => {
  111. formModel.init({
  112. validate: {
  113. 'a.b.*': ({ value }) => (typeof value === 'string' ? undefined : 'error'),
  114. },
  115. });
  116. const bField = formModel.createField('a.b');
  117. const xField = formModel.createField('a.b.x');
  118. formModel.setValueIn('a.b', { x: 1, y: 2 });
  119. let results = await formModel.validate();
  120. // both x y is string, so 2 errors
  121. expect(results.length).toEqual(2);
  122. expect(formModel.state?.errors?.['a.b.x']?.[0].message).toEqual('error');
  123. expect(formModel.state?.errors?.['a.b.y']?.[0].message).toEqual('error');
  124. expect(xField.state?.errors?.['a.b.x']?.[0].message).toEqual('error');
  125. expect(bField.state?.errors?.['a.b.x']?.[0].message).toEqual('error');
  126. formModel.setValueIn('a.b', { x: '1', y: '2' });
  127. results = await formModel.validate();
  128. expect(results.length).toEqual(0);
  129. expect(formModel.state?.errors?.['a.b.x']).toEqual([]);
  130. expect(formModel.state?.errors?.['a.b.y']).toEqual([]);
  131. });
  132. });
  133. describe('FormModel set/get values', () => {
  134. beforeEach(() => {
  135. formModel.dispose();
  136. formModel = new FormModel();
  137. vi.spyOn(formModel.onFormValuesInitEmitter, 'fire');
  138. vi.spyOn(formModel.onFormValuesChangeEmitter, 'fire');
  139. vi.spyOn(formModel.onFormValuesUpdatedEmitter, 'fire');
  140. });
  141. it('should set value for root path', () => {
  142. formModel.init({
  143. initialValues: {
  144. a: 1,
  145. },
  146. });
  147. formModel.values = { a: 2 };
  148. expect(formModel.values).toEqual({ a: 2 });
  149. });
  150. it('should set initialValues and fire init and updated events', async () => {
  151. formModel.init({
  152. initialValues: {
  153. a: 1,
  154. },
  155. });
  156. expect(formModel.values).toEqual({ a: 1 });
  157. expect(formModel.onFormValuesInitEmitter.fire).toHaveBeenCalledWith({
  158. values: {
  159. a: 1,
  160. },
  161. name: '',
  162. });
  163. expect(formModel.onFormValuesUpdatedEmitter.fire).toHaveBeenCalledWith({
  164. values: {
  165. a: 1,
  166. },
  167. name: '',
  168. });
  169. });
  170. it('should set initialValues in certain path and fire change', async () => {
  171. formModel.init({
  172. initialValues: {
  173. a: 1,
  174. },
  175. });
  176. formModel.setInitValueIn('b', 2);
  177. expect(formModel.values).toEqual({ a: 1, b: 2 });
  178. expect(formModel.onFormValuesInitEmitter.fire).toHaveBeenCalledWith({
  179. values: {
  180. a: 1,
  181. b: 2,
  182. },
  183. prevValues: {
  184. a: 1,
  185. },
  186. name: 'b',
  187. });
  188. expect(formModel.onFormValuesUpdatedEmitter.fire).toHaveBeenCalledWith({
  189. values: {
  190. a: 1,
  191. b: 2,
  192. },
  193. prevValues: {
  194. a: 1,
  195. },
  196. name: 'b',
  197. });
  198. });
  199. it('should not set initialValues in certain path if value exists', async () => {
  200. formModel.init({
  201. initialValues: {
  202. a: 1,
  203. },
  204. });
  205. formModel.setInitValueIn('a', 2);
  206. expect(formModel.values).toEqual({ a: 1 });
  207. // 仅在初始化时调用一次,setInitValueIn 没有调用
  208. expect(formModel.onFormValuesInitEmitter.fire).toHaveBeenCalledTimes(1);
  209. expect(formModel.onFormValuesUpdatedEmitter.fire).toHaveBeenCalledTimes(1);
  210. });
  211. it('should set values in certain path and fire change and updated events', async () => {
  212. formModel.init({
  213. initialValues: {
  214. a: 1,
  215. },
  216. });
  217. formModel.setValueIn('a', 2);
  218. expect(formModel.values).toEqual({ a: 2 });
  219. // 仅在初始化时调用一次,setInitValueIn 没有调用
  220. expect(formModel.onFormValuesChangeEmitter.fire).toHaveBeenCalledTimes(1);
  221. // 初始化一次,变更值一次,所以是两次
  222. expect(formModel.onFormValuesUpdatedEmitter.fire).toHaveBeenCalledTimes(2);
  223. expect(formModel.onFormValuesChangeEmitter.fire).toHaveBeenCalledWith({
  224. values: {
  225. a: 2,
  226. },
  227. prevValues: {
  228. a: 1,
  229. },
  230. name: 'a',
  231. });
  232. expect(formModel.onFormValuesUpdatedEmitter.fire).toHaveBeenCalledWith({
  233. values: {
  234. a: 2,
  235. },
  236. prevValues: {
  237. a: 1,
  238. },
  239. name: 'a',
  240. });
  241. });
  242. });
  243. });