@strictly/react-form 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/.eslintrc.cjs +26 -0
  2. package/.out/.storybook/main.d.ts +3 -0
  3. package/.out/.storybook/main.js +32 -0
  4. package/.out/.storybook/preview.d.ts +4 -0
  5. package/.out/.storybook/preview.js +20 -0
  6. package/.out/.vitest/install_deterministic_random.d.ts +2 -0
  7. package/.out/.vitest/install_deterministic_random.js +15 -0
  8. package/.out/.vitest/install_storybook_preview.d.ts +1 -0
  9. package/.out/.vitest/install_storybook_preview.js +7 -0
  10. package/.out/.vitest/match_media.d.ts +1 -0
  11. package/.out/.vitest/match_media.js +5 -0
  12. package/.out/.vitest/resize_observer.d.ts +1 -0
  13. package/.out/.vitest/resize_observer.js +4 -0
  14. package/.out/core/mobx/field_adapter.d.ts +9 -0
  15. package/.out/core/mobx/field_adapter.js +1 -0
  16. package/.out/core/mobx/field_adapter_builder.d.ts +22 -0
  17. package/.out/core/mobx/field_adapter_builder.js +56 -0
  18. package/.out/core/mobx/flattened_adapters_of_fields.d.ts +9 -0
  19. package/.out/core/mobx/flattened_adapters_of_fields.js +1 -0
  20. package/.out/core/mobx/flattened_list_type_defs_of.d.ts +8 -0
  21. package/.out/core/mobx/flattened_list_type_defs_of.js +1 -0
  22. package/.out/core/mobx/form_presenter.d.ts +61 -0
  23. package/.out/core/mobx/form_presenter.js +425 -0
  24. package/.out/core/mobx/specs/flattened_adapters_of_fields.tests.d.ts +1 -0
  25. package/.out/core/mobx/specs/flattened_adapters_of_fields.tests.js +13 -0
  26. package/.out/core/mobx/specs/flattened_list_type_defs_of.tests.d.ts +1 -0
  27. package/.out/core/mobx/specs/flattened_list_type_defs_of.tests.js +16 -0
  28. package/.out/core/mobx/specs/form_presenter.tests.d.ts +1 -0
  29. package/.out/core/mobx/specs/form_presenter.tests.js +697 -0
  30. package/.out/core/mobx/types.d.ts +19 -0
  31. package/.out/core/mobx/types.js +1 -0
  32. package/.out/core/props.d.ts +12 -0
  33. package/.out/core/props.js +1 -0
  34. package/.out/field_converters/chain_field_converter.d.ts +3 -0
  35. package/.out/field_converters/chain_field_converter.js +46 -0
  36. package/.out/field_converters/identity_converter.d.ts +3 -0
  37. package/.out/field_converters/identity_converter.js +14 -0
  38. package/.out/field_converters/integer_to_string_converter.d.ts +7 -0
  39. package/.out/field_converters/integer_to_string_converter.js +26 -0
  40. package/.out/field_converters/list_converter.d.ts +2 -0
  41. package/.out/field_converters/list_converter.js +8 -0
  42. package/.out/field_converters/maybe_identity_converter.d.ts +8 -0
  43. package/.out/field_converters/maybe_identity_converter.js +15 -0
  44. package/.out/field_converters/nullable_to_boolean_converter.d.ts +11 -0
  45. package/.out/field_converters/nullable_to_boolean_converter.js +31 -0
  46. package/.out/field_converters/select_value_type_converter.d.ts +23 -0
  47. package/.out/field_converters/select_value_type_converter.js +60 -0
  48. package/.out/field_converters/trimming_string_converter.d.ts +6 -0
  49. package/.out/field_converters/trimming_string_converter.js +14 -0
  50. package/.out/field_converters/validating_converter.d.ts +3 -0
  51. package/.out/field_converters/validating_converter.js +21 -0
  52. package/.out/field_validators/minimum_string_length_field_validator.d.ts +2 -0
  53. package/.out/field_validators/minimum_string_length_field_validator.js +8 -0
  54. package/.out/field_value_factories/prototyping_field_value_factory.d.ts +2 -0
  55. package/.out/field_value_factories/prototyping_field_value_factory.js +5 -0
  56. package/.out/index.d.ts +16 -0
  57. package/.out/index.js +16 -0
  58. package/.out/mantine/create_checkbox.d.ts +9 -0
  59. package/.out/mantine/create_checkbox.js +37 -0
  60. package/.out/mantine/create_list.d.ts +15 -0
  61. package/.out/mantine/create_list.js +16 -0
  62. package/.out/mantine/create_pill.d.ts +7 -0
  63. package/.out/mantine/create_pill.js +15 -0
  64. package/.out/mantine/create_radio.d.ts +8 -0
  65. package/.out/mantine/create_radio.js +10 -0
  66. package/.out/mantine/create_radio_group.d.ts +9 -0
  67. package/.out/mantine/create_radio_group.js +34 -0
  68. package/.out/mantine/create_text_input.d.ts +19 -0
  69. package/.out/mantine/create_text_input.js +38 -0
  70. package/.out/mantine/create_value_input.d.ts +17 -0
  71. package/.out/mantine/create_value_input.js +38 -0
  72. package/.out/mantine/hooks.d.ts +56 -0
  73. package/.out/mantine/hooks.js +135 -0
  74. package/.out/mantine/specs/checkbox_constants.d.ts +1 -0
  75. package/.out/mantine/specs/checkbox_constants.js +1 -0
  76. package/.out/mantine/specs/checkbox_hooks.stories.d.ts +13 -0
  77. package/.out/mantine/specs/checkbox_hooks.stories.js +63 -0
  78. package/.out/mantine/specs/checkbox_hooks.tests.d.ts +1 -0
  79. package/.out/mantine/specs/checkbox_hooks.tests.js +74 -0
  80. package/.out/mantine/specs/list_hooks.stories.d.ts +11 -0
  81. package/.out/mantine/specs/list_hooks.stories.js +48 -0
  82. package/.out/mantine/specs/list_hooks.tests.d.ts +1 -0
  83. package/.out/mantine/specs/list_hooks.tests.js +12 -0
  84. package/.out/mantine/specs/radio_group_constants.d.ts +4 -0
  85. package/.out/mantine/specs/radio_group_constants.js +11 -0
  86. package/.out/mantine/specs/radio_group_hooks.stories.d.ts +14 -0
  87. package/.out/mantine/specs/radio_group_hooks.stories.js +68 -0
  88. package/.out/mantine/specs/radio_group_hooks.tests.d.ts +1 -0
  89. package/.out/mantine/specs/radio_group_hooks.tests.js +62 -0
  90. package/.out/mantine/specs/select_hooks.stories.d.ts +12 -0
  91. package/.out/mantine/specs/select_hooks.stories.js +57 -0
  92. package/.out/mantine/specs/select_hooks.tests.d.ts +1 -0
  93. package/.out/mantine/specs/select_hooks.tests.js +12 -0
  94. package/.out/mantine/specs/select_hooks_constant.d.ts +1 -0
  95. package/.out/mantine/specs/select_hooks_constant.js +1 -0
  96. package/.out/mantine/specs/text_input_constants.d.ts +1 -0
  97. package/.out/mantine/specs/text_input_constants.js +1 -0
  98. package/.out/mantine/specs/text_input_hooks.stories.d.ts +21 -0
  99. package/.out/mantine/specs/text_input_hooks.stories.js +88 -0
  100. package/.out/mantine/specs/text_input_hooks.tests.d.ts +1 -0
  101. package/.out/mantine/specs/text_input_hooks.tests.js +79 -0
  102. package/.out/mantine/specs/value_input_constants.d.ts +2 -0
  103. package/.out/mantine/specs/value_input_constants.js +2 -0
  104. package/.out/mantine/specs/value_input_hooks.stories.d.ts +23 -0
  105. package/.out/mantine/specs/value_input_hooks.stories.js +124 -0
  106. package/.out/mantine/specs/value_input_hooks.tests.d.ts +1 -0
  107. package/.out/mantine/specs/value_input_hooks.tests.js +12 -0
  108. package/.out/mantine/types.d.ts +11 -0
  109. package/.out/mantine/types.js +1 -0
  110. package/.out/tsconfig.json +27 -0
  111. package/.out/tsconfig.tsbuildinfo +1 -0
  112. package/.out/tsup.config.d.ts +3 -0
  113. package/.out/tsup.config.js +12 -0
  114. package/.out/types/all_fields_of_fields.d.ts +5 -0
  115. package/.out/types/all_fields_of_fields.js +1 -0
  116. package/.out/types/boolean_fields_of_fields.d.ts +5 -0
  117. package/.out/types/boolean_fields_of_fields.js +1 -0
  118. package/.out/types/error_type_of_field.d.ts +2 -0
  119. package/.out/types/error_type_of_field.js +1 -0
  120. package/.out/types/field.d.ts +7 -0
  121. package/.out/types/field.js +1 -0
  122. package/.out/types/field_converters.d.ts +29 -0
  123. package/.out/types/field_converters.js +5 -0
  124. package/.out/types/field_validator.d.ts +3 -0
  125. package/.out/types/field_validator.js +1 -0
  126. package/.out/types/flattened_form_fields_of.d.ts +9 -0
  127. package/.out/types/flattened_form_fields_of.js +1 -0
  128. package/.out/types/list_fields_of_fields.d.ts +5 -0
  129. package/.out/types/list_fields_of_fields.js +1 -0
  130. package/.out/types/specs/boolean_fields_of_fields.tests.d.ts +1 -0
  131. package/.out/types/specs/boolean_fields_of_fields.tests.js +11 -0
  132. package/.out/types/specs/error_type_of_field.tests.d.ts +1 -0
  133. package/.out/types/specs/error_type_of_field.tests.js +7 -0
  134. package/.out/types/specs/flattened_form_fields_of.tests.d.ts +1 -0
  135. package/.out/types/specs/flattened_form_fields_of.tests.js +13 -0
  136. package/.out/types/specs/string_fields_of_fields.tests.d.ts +1 -0
  137. package/.out/types/specs/string_fields_of_fields.tests.js +19 -0
  138. package/.out/types/specs/value_type_of_field.tests.d.ts +1 -0
  139. package/.out/types/specs/value_type_of_field.tests.js +7 -0
  140. package/.out/types/string_fields_of_fields.d.ts +5 -0
  141. package/.out/types/string_fields_of_fields.js +1 -0
  142. package/.out/types/value_type_of_field.d.ts +2 -0
  143. package/.out/types/value_type_of_field.js +1 -0
  144. package/.out/util/partial.d.ts +11 -0
  145. package/.out/util/partial.js +74 -0
  146. package/.out/vitest.workspace.d.ts +2 -0
  147. package/.out/vitest.workspace.js +22 -0
  148. package/.storybook/main.ts +40 -0
  149. package/.storybook/preview.tsx +28 -0
  150. package/.storybook/vite.config.mts +38 -0
  151. package/.turbo/turbo-build.log +18 -0
  152. package/.turbo/turbo-check-types.log +3 -0
  153. package/.turbo/turbo-release$colon$exports.log +3 -0
  154. package/.vitest/install_deterministic_random.ts +17 -0
  155. package/.vitest/install_storybook_preview.ts +9 -0
  156. package/.vitest/match_media.ts +7 -0
  157. package/.vitest/resize_observer.ts +5 -0
  158. package/README.md +2 -0
  159. package/core/mobx/field_adapter.ts +32 -0
  160. package/core/mobx/field_adapter_builder.ts +313 -0
  161. package/core/mobx/flattened_adapters_of_fields.ts +35 -0
  162. package/core/mobx/flattened_list_type_defs_of.ts +17 -0
  163. package/core/mobx/form_presenter.ts +705 -0
  164. package/core/mobx/specs/flattened_adapters_of_fields.tests.ts +72 -0
  165. package/core/mobx/specs/flattened_list_type_defs_of.tests.ts +35 -0
  166. package/core/mobx/specs/form_presenter.tests.ts +989 -0
  167. package/core/mobx/types.ts +54 -0
  168. package/core/props.ts +21 -0
  169. package/dist/index.cjs +11479 -0
  170. package/dist/index.d.cts +345 -0
  171. package/dist/index.d.ts +345 -0
  172. package/dist/index.js +11486 -0
  173. package/field_converters/chain_field_converter.ts +74 -0
  174. package/field_converters/identity_converter.ts +39 -0
  175. package/field_converters/integer_to_string_converter.ts +32 -0
  176. package/field_converters/list_converter.ts +15 -0
  177. package/field_converters/maybe_identity_converter.ts +23 -0
  178. package/field_converters/nullable_to_boolean_converter.ts +56 -0
  179. package/field_converters/select_value_type_converter.ts +141 -0
  180. package/field_converters/trimming_string_converter.ts +23 -0
  181. package/field_converters/validating_converter.ts +35 -0
  182. package/field_validators/minimum_string_length_field_validator.ts +13 -0
  183. package/field_value_factories/prototyping_field_value_factory.ts +11 -0
  184. package/index.ts +16 -0
  185. package/mantine/create_checkbox.tsx +79 -0
  186. package/mantine/create_list.tsx +58 -0
  187. package/mantine/create_pill.tsx +43 -0
  188. package/mantine/create_radio.tsx +36 -0
  189. package/mantine/create_radio_group.tsx +71 -0
  190. package/mantine/create_text_input.tsx +80 -0
  191. package/mantine/create_value_input.tsx +81 -0
  192. package/mantine/hooks.tsx +394 -0
  193. package/mantine/specs/__snapshots__/check_box_hooks.tests.tsx.snap +227 -0
  194. package/mantine/specs/__snapshots__/checkbox_hooks.tests.tsx.snap +227 -0
  195. package/mantine/specs/__snapshots__/list_hooks.tests.tsx.snap +68 -0
  196. package/mantine/specs/__snapshots__/radio_group_hooks.tests.tsx.snap +695 -0
  197. package/mantine/specs/__snapshots__/select_hooks.tests.tsx.snap +225 -0
  198. package/mantine/specs/__snapshots__/text_input_hooks.tests.tsx.snap +202 -0
  199. package/mantine/specs/__snapshots__/value_input_hooks.tests.tsx.snap +613 -0
  200. package/mantine/specs/checkbox_constants.ts +1 -0
  201. package/mantine/specs/checkbox_hooks.stories.tsx +79 -0
  202. package/mantine/specs/checkbox_hooks.tests.tsx +100 -0
  203. package/mantine/specs/list_hooks.stories.tsx +83 -0
  204. package/mantine/specs/list_hooks.tests.tsx +15 -0
  205. package/mantine/specs/radio_group_constants.ts +12 -0
  206. package/mantine/specs/radio_group_hooks.stories.tsx +103 -0
  207. package/mantine/specs/radio_group_hooks.tests.tsx +92 -0
  208. package/mantine/specs/select_hooks.stories.tsx +77 -0
  209. package/mantine/specs/select_hooks.tests.tsx +14 -0
  210. package/mantine/specs/select_hooks_constant.ts +1 -0
  211. package/mantine/specs/text_input_constants.ts +1 -0
  212. package/mantine/specs/text_input_hooks.stories.tsx +124 -0
  213. package/mantine/specs/text_input_hooks.tests.tsx +106 -0
  214. package/mantine/specs/value_input_constants.ts +2 -0
  215. package/mantine/specs/value_input_hooks.stories.tsx +182 -0
  216. package/mantine/specs/value_input_hooks.tests.tsx +14 -0
  217. package/mantine/types.ts +13 -0
  218. package/package.exports.json +18 -0
  219. package/package.json +74 -0
  220. package/tsconfig.build.json +13 -0
  221. package/tsconfig.json +27 -0
  222. package/tsup.config.ts +16 -0
  223. package/types/all_fields_of_fields.ts +9 -0
  224. package/types/boolean_fields_of_fields.ts +8 -0
  225. package/types/error_type_of_field.ts +3 -0
  226. package/types/field.ts +9 -0
  227. package/types/field_converters.ts +64 -0
  228. package/types/field_validator.ts +7 -0
  229. package/types/flattened_form_fields_of.ts +16 -0
  230. package/types/list_fields_of_fields.ts +7 -0
  231. package/types/specs/boolean_fields_of_fields.tests.ts +23 -0
  232. package/types/specs/error_type_of_field.tests.ts +10 -0
  233. package/types/specs/flattened_form_fields_of.tests.ts +43 -0
  234. package/types/specs/string_fields_of_fields.tests.ts +40 -0
  235. package/types/specs/value_type_of_field.tests.ts +10 -0
  236. package/types/string_fields_of_fields.ts +6 -0
  237. package/types/value_type_of_field.ts +3 -0
  238. package/util/partial.tsx +200 -0
  239. package/vitest.workspace.ts +26 -0
@@ -0,0 +1,697 @@
1
+ import { expectDefinedAndReturn } from '@strictly/base';
2
+ import { booleanType, list, nullType, numberType, object, record, stringType, union, } from '@strictly/define';
3
+ import { adapterFromTwoWayConverter, identityAdapter, } from 'core/mobx/field_adapter_builder';
4
+ import { FormModel, FormPresenter, } from 'core/mobx/form_presenter';
5
+ import { IntegerToStringConverter } from 'field_converters/integer_to_string_converter';
6
+ import { NullableToBooleanConverter } from 'field_converters/nullable_to_boolean_converter';
7
+ import { prototypingFieldValueFactory } from 'field_value_factories/prototyping_field_value_factory';
8
+ import { FieldConversionResult, } from 'types/field_converters';
9
+ import { mock, mockClear, } from 'vitest-mock-extended';
10
+ const IS_NAN_ERROR = 1;
11
+ function createMockedAdapter({ convert, revert, create, }) {
12
+ const mockedAdapter = mock();
13
+ if (revert) {
14
+ mockedAdapter.revert?.mockImplementation(revert);
15
+ }
16
+ mockedAdapter.convert.mockImplementation(convert);
17
+ mockedAdapter.create.mockImplementation(create);
18
+ return mockedAdapter;
19
+ }
20
+ describe('all', function () {
21
+ const integerToStringAdapter = createMockedAdapter(adapterFromTwoWayConverter(new IntegerToStringConverter(IS_NAN_ERROR), prototypingFieldValueFactory(0)));
22
+ const booleanToBooleanAdapter = createMockedAdapter(identityAdapter(false));
23
+ beforeEach(function () {
24
+ mockClear(integerToStringAdapter);
25
+ mockClear(booleanToBooleanAdapter);
26
+ });
27
+ describe('FlattenedTypePathsToConvertersOf', function () {
28
+ describe('record', function () {
29
+ const typeDef = record(numberType);
30
+ let t;
31
+ it('equals expected type', function () {
32
+ expectTypeOf(t).toEqualTypeOf();
33
+ });
34
+ });
35
+ describe('object', function () {
36
+ const typeDef = object()
37
+ .set('x', stringType)
38
+ .set('y', booleanType);
39
+ let t;
40
+ it('equals expected type', function () {
41
+ expectTypeOf(t).toEqualTypeOf();
42
+ });
43
+ it('matches representative adapters', function () {
44
+ expectTypeOf().toMatchTypeOf();
45
+ });
46
+ it('does not allow mismatched adapters', function () {
47
+ expectTypeOf().not.toMatchTypeOf();
48
+ });
49
+ });
50
+ });
51
+ describe('ValuePathsToAdaptersOf', function () {
52
+ describe('superset', function () {
53
+ const valuePathsToTypePaths = {
54
+ $: '$',
55
+ '$.a': '$.x',
56
+ '$.b': '$.y',
57
+ '$.c': '$.z',
58
+ };
59
+ let t;
60
+ it('equals expected type', function () {
61
+ expectTypeOf(t).toEqualTypeOf();
62
+ });
63
+ });
64
+ });
65
+ describe('FormModel', function () {
66
+ describe('literal', function () {
67
+ const typeDef = numberType;
68
+ const adapters = {
69
+ $: integerToStringAdapter,
70
+ };
71
+ let originalValue;
72
+ let model;
73
+ beforeEach(function () {
74
+ originalValue = 5;
75
+ model = new FormModel(typeDef, originalValue, adapters);
76
+ });
77
+ describe('accessors', function () {
78
+ it('gets the expected value', function () {
79
+ const accessor = expectDefinedAndReturn(model.accessors.$);
80
+ expect(accessor.value).toEqual(originalValue);
81
+ });
82
+ it('sets the underlying value', function () {
83
+ const newValue = 1;
84
+ const accessor = expectDefinedAndReturn(model.accessors.$);
85
+ accessor.set(newValue);
86
+ expect(model.value).toEqual(newValue);
87
+ });
88
+ });
89
+ describe('fields', function () {
90
+ it('equals expected value', function () {
91
+ expect(model.fields).toEqual(expect.objectContaining({
92
+ $: expect.objectContaining({
93
+ value: '5',
94
+ }),
95
+ }));
96
+ });
97
+ it('has the expected keys', function () {
98
+ expect(Object.keys(model.fields)).toEqual(['$']);
99
+ });
100
+ });
101
+ });
102
+ describe('list', function () {
103
+ const typeDef = list(numberType);
104
+ const adapters = {
105
+ '$.*': integerToStringAdapter,
106
+ };
107
+ let value;
108
+ let model;
109
+ beforeEach(function () {
110
+ value = [
111
+ 1,
112
+ 4,
113
+ 17,
114
+ ];
115
+ model = new FormModel(typeDef, value, adapters);
116
+ });
117
+ describe('accessors', function () {
118
+ it.each([
119
+ [
120
+ '$.0',
121
+ 1,
122
+ ],
123
+ [
124
+ '$.1',
125
+ 4,
126
+ ],
127
+ [
128
+ '$.2',
129
+ 17,
130
+ ],
131
+ ])('gets the expected values for %s', function (valuePath, value) {
132
+ const accessor = expectDefinedAndReturn(model.accessors[valuePath]);
133
+ expect(accessor.value).toEqual(value);
134
+ });
135
+ it('sets a value', function () {
136
+ const accessor = expectDefinedAndReturn(model.accessors['$.0']);
137
+ accessor.set(100);
138
+ expect(model.value).toEqual([
139
+ 100,
140
+ 4,
141
+ 17,
142
+ ]);
143
+ });
144
+ });
145
+ });
146
+ describe('record', function () {
147
+ const typeDef = record(numberType);
148
+ const converters = {
149
+ '$.*': integerToStringAdapter,
150
+ // '$.*': booleanToBooleanConverter,
151
+ };
152
+ let value;
153
+ let model;
154
+ beforeEach(function () {
155
+ value = {
156
+ a: 1,
157
+ b: 2,
158
+ };
159
+ model = new FormModel(typeDef, value, converters);
160
+ });
161
+ describe('accessors', function () {
162
+ it.each([
163
+ [
164
+ '$.a',
165
+ 1,
166
+ ],
167
+ [
168
+ '$.b',
169
+ 2,
170
+ ],
171
+ ])('gets the expected value for %s', function (valuePath, value) {
172
+ const accessor = expectDefinedAndReturn(model.accessors[valuePath]);
173
+ expect(accessor.value).toEqual(value);
174
+ });
175
+ it('sets a value', function () {
176
+ const accessor = expectDefinedAndReturn(model.accessors['$.b']);
177
+ const newValue = 100;
178
+ accessor.set(newValue);
179
+ expect(model.value.b).toEqual(newValue);
180
+ });
181
+ });
182
+ describe('fields', function () {
183
+ it('equals expected value', function () {
184
+ expect(model.fields).toEqual(expect.objectContaining({
185
+ '$.a': expect.objectContaining({
186
+ value: '1',
187
+ }),
188
+ '$.b': expect.objectContaining({
189
+ value: '2',
190
+ }),
191
+ }));
192
+ });
193
+ });
194
+ });
195
+ describe('object', function () {
196
+ const typeDef = object()
197
+ .set('a', numberType)
198
+ .set('b', booleanType);
199
+ const converters = {
200
+ '$.a': integerToStringAdapter,
201
+ '$.b': booleanToBooleanAdapter,
202
+ };
203
+ let value;
204
+ let model;
205
+ beforeEach(function () {
206
+ value = {
207
+ a: 1,
208
+ b: true,
209
+ };
210
+ model = new FormModel(typeDef, value, converters);
211
+ });
212
+ describe('accessors', function () {
213
+ it.each([
214
+ [
215
+ '$.a',
216
+ 1,
217
+ ],
218
+ [
219
+ '$.b',
220
+ true,
221
+ ],
222
+ ])('gets the expected value for %s', function (valuePath, value) {
223
+ const accessor = expectDefinedAndReturn(model.accessors[valuePath]);
224
+ expect(accessor.value).toEqual(value);
225
+ });
226
+ it('sets a value', function () {
227
+ const accessor = expectDefinedAndReturn(model.accessors['$.b']);
228
+ accessor.set(false);
229
+ expect(model.value.b).toEqual(false);
230
+ });
231
+ });
232
+ describe('fields', function () {
233
+ it('equals expected value', function () {
234
+ expect(model.fields).toEqual(expect.objectContaining({
235
+ '$.a': expect.objectContaining({
236
+ value: '1',
237
+ }),
238
+ '$.b': expect.objectContaining({
239
+ value: true,
240
+ }),
241
+ }));
242
+ });
243
+ });
244
+ });
245
+ // TODO union
246
+ });
247
+ describe('FormPresenter', function () {
248
+ describe('literal', function () {
249
+ const typeDef = numberType;
250
+ const adapters = {
251
+ $: integerToStringAdapter,
252
+ };
253
+ const presenter = new FormPresenter(typeDef, adapters);
254
+ const originalValue = 2;
255
+ let model;
256
+ beforeEach(function () {
257
+ model = presenter.createModel(originalValue);
258
+ });
259
+ describe('setFieldValueAndValidate', function () {
260
+ describe('success', function () {
261
+ beforeEach(function () {
262
+ presenter.setFieldValueAndValidate(model, '$', '1');
263
+ });
264
+ it('does set the underlying value', function () {
265
+ expect(model.value).toEqual(1);
266
+ });
267
+ it('sets the fields', function () {
268
+ expect(model.fields).toEqual(expect.objectContaining({
269
+ $: expect.objectContaining({
270
+ value: '1',
271
+ // eslint-disable-next-line no-undefined
272
+ error: undefined,
273
+ }),
274
+ }));
275
+ });
276
+ });
277
+ describe('failure', function () {
278
+ describe('conversion fails', function () {
279
+ beforeEach(function () {
280
+ presenter.setFieldValueAndValidate(model, '$', 'x');
281
+ });
282
+ it('does not set the underlying value', function () {
283
+ expect(model.value).toEqual(originalValue);
284
+ });
285
+ it('sets the error state', function () {
286
+ expect(model.fields).toEqual(expect.objectContaining({
287
+ $: expect.objectContaining({
288
+ value: 'x',
289
+ error: IS_NAN_ERROR,
290
+ }),
291
+ }));
292
+ });
293
+ });
294
+ describe('conversion succeeds, but validation fails', function () {
295
+ const newValue = -1;
296
+ const errorCode = 65;
297
+ beforeEach(function () {
298
+ integerToStringAdapter.revert?.mockReturnValueOnce({
299
+ type: FieldConversionResult.Failure,
300
+ error: errorCode,
301
+ value: [newValue],
302
+ });
303
+ presenter.setFieldValueAndValidate(model, '$', '-1');
304
+ });
305
+ it('does set the underlying value', function () {
306
+ expect(model.value).toEqual(newValue);
307
+ });
308
+ it('does update the field', function () {
309
+ expect(model.fields).toEqual({
310
+ $: expect.objectContaining({
311
+ value: '-1',
312
+ error: errorCode,
313
+ disabled: false,
314
+ }),
315
+ });
316
+ });
317
+ });
318
+ });
319
+ });
320
+ describe.each([
321
+ [
322
+ '1',
323
+ 1,
324
+ ],
325
+ [
326
+ 'x',
327
+ originalValue,
328
+ ],
329
+ ])('setFieldValue to %s', function (newValue, expectedValue) {
330
+ beforeEach(function () {
331
+ presenter.setFieldValue(model, '$', newValue);
332
+ });
333
+ it('does set the underlying value', function () {
334
+ expect(model.value).toEqual(expectedValue);
335
+ });
336
+ it('sets the field value', function () {
337
+ expect(model.fields).toEqual(expect.objectContaining({
338
+ $: expect.objectContaining({
339
+ value: newValue,
340
+ // eslint-disable-next-line no-undefined
341
+ error: undefined,
342
+ }),
343
+ }));
344
+ });
345
+ });
346
+ });
347
+ describe('list', function () {
348
+ const typeDef = list(numberType);
349
+ const converters = {
350
+ '$.*': integerToStringAdapter,
351
+ };
352
+ const presenter = new FormPresenter(typeDef, converters);
353
+ let originalValue;
354
+ let model;
355
+ beforeEach(function () {
356
+ originalValue = [
357
+ 1,
358
+ 3,
359
+ 7,
360
+ ];
361
+ model = presenter.createModel(originalValue);
362
+ });
363
+ describe('setFieldValueAndValidate', function () {
364
+ describe('success', function () {
365
+ beforeEach(function () {
366
+ presenter.setFieldValueAndValidate(model, '$.0', '100');
367
+ });
368
+ it('sets the underlying value', function () {
369
+ expect(model.value).toEqual([
370
+ 100,
371
+ 3,
372
+ 7,
373
+ ]);
374
+ });
375
+ it('sets the fields', function () {
376
+ expect(model.fields).toEqual(expect.objectContaining({
377
+ '$.0': expect.objectContaining({
378
+ value: '100',
379
+ // eslint-disable-next-line no-undefined
380
+ error: undefined,
381
+ }),
382
+ }));
383
+ });
384
+ });
385
+ describe('failure', function () {
386
+ beforeEach(function () {
387
+ presenter.setFieldValueAndValidate(model, '$.0', 'x');
388
+ });
389
+ it('does not set the underlying value', function () {
390
+ expect(model.value).toEqual(originalValue);
391
+ });
392
+ it('sets the error state', function () {
393
+ expect(model.fields).toEqual(expect.objectContaining({
394
+ '$.0': expect.objectContaining({
395
+ value: 'x',
396
+ error: IS_NAN_ERROR,
397
+ }),
398
+ }));
399
+ });
400
+ });
401
+ });
402
+ describe.each([
403
+ '1',
404
+ 'x',
405
+ ])('setFieldValue to %s', function (newValue) {
406
+ beforeEach(function () {
407
+ presenter.setFieldValue(model, '$.0', newValue);
408
+ });
409
+ it('does not set the underlying value', function () {
410
+ expect(model.value).toEqual(originalValue);
411
+ });
412
+ it('sets the field value', function () {
413
+ expect(model.fields).toEqual(expect.objectContaining({
414
+ '$.0': expect.objectContaining({
415
+ value: newValue,
416
+ // eslint-disable-next-line no-undefined
417
+ error: undefined,
418
+ }),
419
+ }));
420
+ });
421
+ });
422
+ describe('validate', function () {
423
+ beforeEach(function () {
424
+ presenter.setFieldValue(model, '$.0', 'x');
425
+ presenter.setFieldValue(model, '$.1', '2');
426
+ presenter.setFieldValue(model, '$.2', 'z');
427
+ presenter.validateAll(model);
428
+ });
429
+ it('contains errors for all invalid fields', function () {
430
+ expect(model.fields).toEqual(expect.objectContaining({
431
+ '$.0': expect.objectContaining({
432
+ value: 'x',
433
+ error: IS_NAN_ERROR,
434
+ }),
435
+ '$.1': expect.objectContaining({
436
+ value: '2',
437
+ // eslint-disable-next-line no-undefined
438
+ error: undefined,
439
+ }),
440
+ '$.2': expect.objectContaining({
441
+ value: 'z',
442
+ error: IS_NAN_ERROR,
443
+ }),
444
+ }));
445
+ });
446
+ it('sets the value only for valid fields', function () {
447
+ expect(model.value).toEqual([
448
+ 1,
449
+ 2,
450
+ 7,
451
+ ]);
452
+ });
453
+ });
454
+ // no longer passes context, but will pass context eventually again
455
+ describe('passes context', function () {
456
+ let contextCopy;
457
+ beforeEach(function () {
458
+ integerToStringAdapter.revert.mockImplementationOnce(function (_value, _path, context) {
459
+ contextCopy = [...context];
460
+ return {
461
+ type: FieldConversionResult.Success,
462
+ value: 1,
463
+ };
464
+ });
465
+ });
466
+ it('supplies the full, previous context when converting', function () {
467
+ presenter.setFieldValueAndValidate(model, '$.2', '4');
468
+ expect(integerToStringAdapter.revert).toHaveBeenCalledOnce();
469
+ expect(integerToStringAdapter.revert).toHaveBeenCalledWith('4', '$.2',
470
+ // uses the same pointer
471
+ model.value);
472
+ });
473
+ it('supplies the context as it is at the time call', function () {
474
+ expect(contextCopy).toEqual([
475
+ 1,
476
+ 3,
477
+ 7,
478
+ ]);
479
+ });
480
+ });
481
+ describe('addListItem', function () {
482
+ describe('adds default to start of the list', function () {
483
+ beforeEach(function () {
484
+ model.errors['$.0'] = 0;
485
+ model.errors['$.1'] = 1;
486
+ model.errors['$.2'] = 2;
487
+ presenter.addListItem(model, '$', null, 0);
488
+ });
489
+ it('adds the list item to the underlying value', function () {
490
+ expect(model.value).toEqual([
491
+ 0,
492
+ 1,
493
+ 3,
494
+ 7,
495
+ ]);
496
+ });
497
+ it.each([
498
+ [
499
+ '$.0',
500
+ '0',
501
+ ],
502
+ [
503
+ '$.1',
504
+ '1',
505
+ ],
506
+ [
507
+ '$.2',
508
+ '3',
509
+ ],
510
+ [
511
+ '$.3',
512
+ '7',
513
+ ],
514
+ ])('it reports the value of field %s as %s', function (path, fieldValue) {
515
+ expect(model.fields[path]?.value).toBe(fieldValue);
516
+ });
517
+ it.each([
518
+ [
519
+ '$.0',
520
+ // eslint-disable-next-line no-undefined
521
+ undefined,
522
+ ],
523
+ [
524
+ '$.1',
525
+ 0,
526
+ ],
527
+ [
528
+ '$.2',
529
+ 1,
530
+ ],
531
+ [
532
+ '$.3',
533
+ 2,
534
+ ],
535
+ ])('it reports the error of field %s', function (path, error) {
536
+ expect(model.fields[path]?.error).toBe(error);
537
+ });
538
+ });
539
+ describe('add defined value', function () {
540
+ beforeEach(function () {
541
+ presenter.addListItem(model, '$', [5]);
542
+ });
543
+ it('adds the expected value at the end', function () {
544
+ expect(model.fields).toEqual(expect.objectContaining({
545
+ '$.0': expect.objectContaining({
546
+ value: '1',
547
+ }),
548
+ '$.1': expect.objectContaining({
549
+ value: '3',
550
+ }),
551
+ '$.2': expect.objectContaining({
552
+ value: '7',
553
+ }),
554
+ '$.3': expect.objectContaining({
555
+ value: '5',
556
+ }),
557
+ }));
558
+ });
559
+ it('updates the underlying value', function () {
560
+ expect(model.value).toEqual([
561
+ 1,
562
+ 3,
563
+ 7,
564
+ 5,
565
+ ]);
566
+ });
567
+ });
568
+ });
569
+ describe('removeListItem', function () {
570
+ beforeEach(function () {
571
+ model.errors['$.0'] = 0;
572
+ model.errors['$.1'] = 1;
573
+ model.errors['$.2'] = 2;
574
+ });
575
+ describe('remove first item', function () {
576
+ beforeEach(function () {
577
+ presenter.removeListItem(model, '$.0');
578
+ });
579
+ it('updates the underlying value', function () {
580
+ expect(model.value).toEqual([
581
+ 3,
582
+ 7,
583
+ ]);
584
+ });
585
+ it('updates the field values and errors', function () {
586
+ expect(model.fields).toEqual({
587
+ '$.0': expect.objectContaining({
588
+ value: '3',
589
+ error: 1,
590
+ }),
591
+ '$.1': expect.objectContaining({
592
+ value: '7',
593
+ error: 2,
594
+ }),
595
+ });
596
+ });
597
+ });
598
+ describe('remove second item', function () {
599
+ beforeEach(function () {
600
+ presenter.removeListItem(model, '$.1');
601
+ });
602
+ it('updates the underlying value', function () {
603
+ expect(model.value).toEqual([
604
+ 1,
605
+ 7,
606
+ ]);
607
+ });
608
+ it('updates the field values and errors', function () {
609
+ expect(model.fields).toEqual({
610
+ '$.0': expect.objectContaining({
611
+ value: '1',
612
+ error: 0,
613
+ }),
614
+ '$.1': expect.objectContaining({
615
+ value: '7',
616
+ error: 2,
617
+ }),
618
+ });
619
+ });
620
+ });
621
+ });
622
+ });
623
+ // TODO record / object
624
+ describe('union', function () {
625
+ describe('non-discriminated', function () {
626
+ const listOfNumbersTypeDef = list(numberType);
627
+ const typeDef = union()
628
+ .add('null', nullType)
629
+ .add('0', listOfNumbersTypeDef);
630
+ const adapters = {
631
+ $: adapterFromTwoWayConverter(new NullableToBooleanConverter(typeDef, [1])),
632
+ '$.*': integerToStringAdapter,
633
+ };
634
+ const presenter = new FormPresenter(typeDef, adapters);
635
+ let originalValue;
636
+ let model;
637
+ beforeEach(function () {
638
+ originalValue = null;
639
+ model = presenter.createModel(originalValue);
640
+ });
641
+ it('has the expected fields', function () {
642
+ expect(model.fields).toEqual({
643
+ $: {
644
+ disabled: false,
645
+ // eslint-disable-next-line no-undefined
646
+ error: undefined,
647
+ value: false,
648
+ required: false,
649
+ },
650
+ });
651
+ });
652
+ describe('setFieldValueAndValidate', function () {
653
+ describe('success', function () {
654
+ beforeEach(function () {
655
+ presenter.setFieldValueAndValidate(model, '$', true);
656
+ });
657
+ it('sets the underlying value', function () {
658
+ expect(model.value).toEqual([1]);
659
+ });
660
+ });
661
+ });
662
+ });
663
+ });
664
+ describe('fake', function () {
665
+ const typeDef = numberType;
666
+ const converters = {
667
+ $: integerToStringAdapter,
668
+ '$.fake': booleanToBooleanAdapter,
669
+ };
670
+ const presenter = new FormPresenter(typeDef, converters);
671
+ let originalValue;
672
+ let model;
673
+ beforeEach(function () {
674
+ originalValue = 1;
675
+ model = presenter.createModel(originalValue);
676
+ });
677
+ it('returns the default value for the fake field', function () {
678
+ expect(model.fields['$.fake']).toEqual(expect.objectContaining({
679
+ value: false,
680
+ }));
681
+ });
682
+ describe('setting fake field', function () {
683
+ beforeEach(function () {
684
+ presenter.setFieldValue(model, '$.fake', true);
685
+ });
686
+ it('stores the new value', function () {
687
+ expect(model.fields['$.fake']).toEqual(expect.objectContaining({
688
+ value: true,
689
+ }));
690
+ });
691
+ it('does not change the original value', function () {
692
+ expect(model.value).toBe(originalValue);
693
+ });
694
+ });
695
+ });
696
+ });
697
+ });