form-model.test.ts 8.4 KB

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