@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.
- package/.eslintrc.cjs +26 -0
- package/.out/.storybook/main.d.ts +3 -0
- package/.out/.storybook/main.js +32 -0
- package/.out/.storybook/preview.d.ts +4 -0
- package/.out/.storybook/preview.js +20 -0
- package/.out/.vitest/install_deterministic_random.d.ts +2 -0
- package/.out/.vitest/install_deterministic_random.js +15 -0
- package/.out/.vitest/install_storybook_preview.d.ts +1 -0
- package/.out/.vitest/install_storybook_preview.js +7 -0
- package/.out/.vitest/match_media.d.ts +1 -0
- package/.out/.vitest/match_media.js +5 -0
- package/.out/.vitest/resize_observer.d.ts +1 -0
- package/.out/.vitest/resize_observer.js +4 -0
- package/.out/core/mobx/field_adapter.d.ts +9 -0
- package/.out/core/mobx/field_adapter.js +1 -0
- package/.out/core/mobx/field_adapter_builder.d.ts +22 -0
- package/.out/core/mobx/field_adapter_builder.js +56 -0
- package/.out/core/mobx/flattened_adapters_of_fields.d.ts +9 -0
- package/.out/core/mobx/flattened_adapters_of_fields.js +1 -0
- package/.out/core/mobx/flattened_list_type_defs_of.d.ts +8 -0
- package/.out/core/mobx/flattened_list_type_defs_of.js +1 -0
- package/.out/core/mobx/form_presenter.d.ts +61 -0
- package/.out/core/mobx/form_presenter.js +425 -0
- package/.out/core/mobx/specs/flattened_adapters_of_fields.tests.d.ts +1 -0
- package/.out/core/mobx/specs/flattened_adapters_of_fields.tests.js +13 -0
- package/.out/core/mobx/specs/flattened_list_type_defs_of.tests.d.ts +1 -0
- package/.out/core/mobx/specs/flattened_list_type_defs_of.tests.js +16 -0
- package/.out/core/mobx/specs/form_presenter.tests.d.ts +1 -0
- package/.out/core/mobx/specs/form_presenter.tests.js +697 -0
- package/.out/core/mobx/types.d.ts +19 -0
- package/.out/core/mobx/types.js +1 -0
- package/.out/core/props.d.ts +12 -0
- package/.out/core/props.js +1 -0
- package/.out/field_converters/chain_field_converter.d.ts +3 -0
- package/.out/field_converters/chain_field_converter.js +46 -0
- package/.out/field_converters/identity_converter.d.ts +3 -0
- package/.out/field_converters/identity_converter.js +14 -0
- package/.out/field_converters/integer_to_string_converter.d.ts +7 -0
- package/.out/field_converters/integer_to_string_converter.js +26 -0
- package/.out/field_converters/list_converter.d.ts +2 -0
- package/.out/field_converters/list_converter.js +8 -0
- package/.out/field_converters/maybe_identity_converter.d.ts +8 -0
- package/.out/field_converters/maybe_identity_converter.js +15 -0
- package/.out/field_converters/nullable_to_boolean_converter.d.ts +11 -0
- package/.out/field_converters/nullable_to_boolean_converter.js +31 -0
- package/.out/field_converters/select_value_type_converter.d.ts +23 -0
- package/.out/field_converters/select_value_type_converter.js +60 -0
- package/.out/field_converters/trimming_string_converter.d.ts +6 -0
- package/.out/field_converters/trimming_string_converter.js +14 -0
- package/.out/field_converters/validating_converter.d.ts +3 -0
- package/.out/field_converters/validating_converter.js +21 -0
- package/.out/field_validators/minimum_string_length_field_validator.d.ts +2 -0
- package/.out/field_validators/minimum_string_length_field_validator.js +8 -0
- package/.out/field_value_factories/prototyping_field_value_factory.d.ts +2 -0
- package/.out/field_value_factories/prototyping_field_value_factory.js +5 -0
- package/.out/index.d.ts +16 -0
- package/.out/index.js +16 -0
- package/.out/mantine/create_checkbox.d.ts +9 -0
- package/.out/mantine/create_checkbox.js +37 -0
- package/.out/mantine/create_list.d.ts +15 -0
- package/.out/mantine/create_list.js +16 -0
- package/.out/mantine/create_pill.d.ts +7 -0
- package/.out/mantine/create_pill.js +15 -0
- package/.out/mantine/create_radio.d.ts +8 -0
- package/.out/mantine/create_radio.js +10 -0
- package/.out/mantine/create_radio_group.d.ts +9 -0
- package/.out/mantine/create_radio_group.js +34 -0
- package/.out/mantine/create_text_input.d.ts +19 -0
- package/.out/mantine/create_text_input.js +38 -0
- package/.out/mantine/create_value_input.d.ts +17 -0
- package/.out/mantine/create_value_input.js +38 -0
- package/.out/mantine/hooks.d.ts +56 -0
- package/.out/mantine/hooks.js +135 -0
- package/.out/mantine/specs/checkbox_constants.d.ts +1 -0
- package/.out/mantine/specs/checkbox_constants.js +1 -0
- package/.out/mantine/specs/checkbox_hooks.stories.d.ts +13 -0
- package/.out/mantine/specs/checkbox_hooks.stories.js +63 -0
- package/.out/mantine/specs/checkbox_hooks.tests.d.ts +1 -0
- package/.out/mantine/specs/checkbox_hooks.tests.js +74 -0
- package/.out/mantine/specs/list_hooks.stories.d.ts +11 -0
- package/.out/mantine/specs/list_hooks.stories.js +48 -0
- package/.out/mantine/specs/list_hooks.tests.d.ts +1 -0
- package/.out/mantine/specs/list_hooks.tests.js +12 -0
- package/.out/mantine/specs/radio_group_constants.d.ts +4 -0
- package/.out/mantine/specs/radio_group_constants.js +11 -0
- package/.out/mantine/specs/radio_group_hooks.stories.d.ts +14 -0
- package/.out/mantine/specs/radio_group_hooks.stories.js +68 -0
- package/.out/mantine/specs/radio_group_hooks.tests.d.ts +1 -0
- package/.out/mantine/specs/radio_group_hooks.tests.js +62 -0
- package/.out/mantine/specs/select_hooks.stories.d.ts +12 -0
- package/.out/mantine/specs/select_hooks.stories.js +57 -0
- package/.out/mantine/specs/select_hooks.tests.d.ts +1 -0
- package/.out/mantine/specs/select_hooks.tests.js +12 -0
- package/.out/mantine/specs/select_hooks_constant.d.ts +1 -0
- package/.out/mantine/specs/select_hooks_constant.js +1 -0
- package/.out/mantine/specs/text_input_constants.d.ts +1 -0
- package/.out/mantine/specs/text_input_constants.js +1 -0
- package/.out/mantine/specs/text_input_hooks.stories.d.ts +21 -0
- package/.out/mantine/specs/text_input_hooks.stories.js +88 -0
- package/.out/mantine/specs/text_input_hooks.tests.d.ts +1 -0
- package/.out/mantine/specs/text_input_hooks.tests.js +79 -0
- package/.out/mantine/specs/value_input_constants.d.ts +2 -0
- package/.out/mantine/specs/value_input_constants.js +2 -0
- package/.out/mantine/specs/value_input_hooks.stories.d.ts +23 -0
- package/.out/mantine/specs/value_input_hooks.stories.js +124 -0
- package/.out/mantine/specs/value_input_hooks.tests.d.ts +1 -0
- package/.out/mantine/specs/value_input_hooks.tests.js +12 -0
- package/.out/mantine/types.d.ts +11 -0
- package/.out/mantine/types.js +1 -0
- package/.out/tsconfig.json +27 -0
- package/.out/tsconfig.tsbuildinfo +1 -0
- package/.out/tsup.config.d.ts +3 -0
- package/.out/tsup.config.js +12 -0
- package/.out/types/all_fields_of_fields.d.ts +5 -0
- package/.out/types/all_fields_of_fields.js +1 -0
- package/.out/types/boolean_fields_of_fields.d.ts +5 -0
- package/.out/types/boolean_fields_of_fields.js +1 -0
- package/.out/types/error_type_of_field.d.ts +2 -0
- package/.out/types/error_type_of_field.js +1 -0
- package/.out/types/field.d.ts +7 -0
- package/.out/types/field.js +1 -0
- package/.out/types/field_converters.d.ts +29 -0
- package/.out/types/field_converters.js +5 -0
- package/.out/types/field_validator.d.ts +3 -0
- package/.out/types/field_validator.js +1 -0
- package/.out/types/flattened_form_fields_of.d.ts +9 -0
- package/.out/types/flattened_form_fields_of.js +1 -0
- package/.out/types/list_fields_of_fields.d.ts +5 -0
- package/.out/types/list_fields_of_fields.js +1 -0
- package/.out/types/specs/boolean_fields_of_fields.tests.d.ts +1 -0
- package/.out/types/specs/boolean_fields_of_fields.tests.js +11 -0
- package/.out/types/specs/error_type_of_field.tests.d.ts +1 -0
- package/.out/types/specs/error_type_of_field.tests.js +7 -0
- package/.out/types/specs/flattened_form_fields_of.tests.d.ts +1 -0
- package/.out/types/specs/flattened_form_fields_of.tests.js +13 -0
- package/.out/types/specs/string_fields_of_fields.tests.d.ts +1 -0
- package/.out/types/specs/string_fields_of_fields.tests.js +19 -0
- package/.out/types/specs/value_type_of_field.tests.d.ts +1 -0
- package/.out/types/specs/value_type_of_field.tests.js +7 -0
- package/.out/types/string_fields_of_fields.d.ts +5 -0
- package/.out/types/string_fields_of_fields.js +1 -0
- package/.out/types/value_type_of_field.d.ts +2 -0
- package/.out/types/value_type_of_field.js +1 -0
- package/.out/util/partial.d.ts +11 -0
- package/.out/util/partial.js +74 -0
- package/.out/vitest.workspace.d.ts +2 -0
- package/.out/vitest.workspace.js +22 -0
- package/.storybook/main.ts +40 -0
- package/.storybook/preview.tsx +28 -0
- package/.storybook/vite.config.mts +38 -0
- package/.turbo/turbo-build.log +18 -0
- package/.turbo/turbo-check-types.log +3 -0
- package/.turbo/turbo-release$colon$exports.log +3 -0
- package/.vitest/install_deterministic_random.ts +17 -0
- package/.vitest/install_storybook_preview.ts +9 -0
- package/.vitest/match_media.ts +7 -0
- package/.vitest/resize_observer.ts +5 -0
- package/README.md +2 -0
- package/core/mobx/field_adapter.ts +32 -0
- package/core/mobx/field_adapter_builder.ts +313 -0
- package/core/mobx/flattened_adapters_of_fields.ts +35 -0
- package/core/mobx/flattened_list_type_defs_of.ts +17 -0
- package/core/mobx/form_presenter.ts +705 -0
- package/core/mobx/specs/flattened_adapters_of_fields.tests.ts +72 -0
- package/core/mobx/specs/flattened_list_type_defs_of.tests.ts +35 -0
- package/core/mobx/specs/form_presenter.tests.ts +989 -0
- package/core/mobx/types.ts +54 -0
- package/core/props.ts +21 -0
- package/dist/index.cjs +11479 -0
- package/dist/index.d.cts +345 -0
- package/dist/index.d.ts +345 -0
- package/dist/index.js +11486 -0
- package/field_converters/chain_field_converter.ts +74 -0
- package/field_converters/identity_converter.ts +39 -0
- package/field_converters/integer_to_string_converter.ts +32 -0
- package/field_converters/list_converter.ts +15 -0
- package/field_converters/maybe_identity_converter.ts +23 -0
- package/field_converters/nullable_to_boolean_converter.ts +56 -0
- package/field_converters/select_value_type_converter.ts +141 -0
- package/field_converters/trimming_string_converter.ts +23 -0
- package/field_converters/validating_converter.ts +35 -0
- package/field_validators/minimum_string_length_field_validator.ts +13 -0
- package/field_value_factories/prototyping_field_value_factory.ts +11 -0
- package/index.ts +16 -0
- package/mantine/create_checkbox.tsx +79 -0
- package/mantine/create_list.tsx +58 -0
- package/mantine/create_pill.tsx +43 -0
- package/mantine/create_radio.tsx +36 -0
- package/mantine/create_radio_group.tsx +71 -0
- package/mantine/create_text_input.tsx +80 -0
- package/mantine/create_value_input.tsx +81 -0
- package/mantine/hooks.tsx +394 -0
- package/mantine/specs/__snapshots__/check_box_hooks.tests.tsx.snap +227 -0
- package/mantine/specs/__snapshots__/checkbox_hooks.tests.tsx.snap +227 -0
- package/mantine/specs/__snapshots__/list_hooks.tests.tsx.snap +68 -0
- package/mantine/specs/__snapshots__/radio_group_hooks.tests.tsx.snap +695 -0
- package/mantine/specs/__snapshots__/select_hooks.tests.tsx.snap +225 -0
- package/mantine/specs/__snapshots__/text_input_hooks.tests.tsx.snap +202 -0
- package/mantine/specs/__snapshots__/value_input_hooks.tests.tsx.snap +613 -0
- package/mantine/specs/checkbox_constants.ts +1 -0
- package/mantine/specs/checkbox_hooks.stories.tsx +79 -0
- package/mantine/specs/checkbox_hooks.tests.tsx +100 -0
- package/mantine/specs/list_hooks.stories.tsx +83 -0
- package/mantine/specs/list_hooks.tests.tsx +15 -0
- package/mantine/specs/radio_group_constants.ts +12 -0
- package/mantine/specs/radio_group_hooks.stories.tsx +103 -0
- package/mantine/specs/radio_group_hooks.tests.tsx +92 -0
- package/mantine/specs/select_hooks.stories.tsx +77 -0
- package/mantine/specs/select_hooks.tests.tsx +14 -0
- package/mantine/specs/select_hooks_constant.ts +1 -0
- package/mantine/specs/text_input_constants.ts +1 -0
- package/mantine/specs/text_input_hooks.stories.tsx +124 -0
- package/mantine/specs/text_input_hooks.tests.tsx +106 -0
- package/mantine/specs/value_input_constants.ts +2 -0
- package/mantine/specs/value_input_hooks.stories.tsx +182 -0
- package/mantine/specs/value_input_hooks.tests.tsx +14 -0
- package/mantine/types.ts +13 -0
- package/package.exports.json +18 -0
- package/package.json +74 -0
- package/tsconfig.build.json +13 -0
- package/tsconfig.json +27 -0
- package/tsup.config.ts +16 -0
- package/types/all_fields_of_fields.ts +9 -0
- package/types/boolean_fields_of_fields.ts +8 -0
- package/types/error_type_of_field.ts +3 -0
- package/types/field.ts +9 -0
- package/types/field_converters.ts +64 -0
- package/types/field_validator.ts +7 -0
- package/types/flattened_form_fields_of.ts +16 -0
- package/types/list_fields_of_fields.ts +7 -0
- package/types/specs/boolean_fields_of_fields.tests.ts +23 -0
- package/types/specs/error_type_of_field.tests.ts +10 -0
- package/types/specs/flattened_form_fields_of.tests.ts +43 -0
- package/types/specs/string_fields_of_fields.tests.ts +40 -0
- package/types/specs/value_type_of_field.tests.ts +10 -0
- package/types/string_fields_of_fields.ts +6 -0
- package/types/value_type_of_field.ts +3 -0
- package/util/partial.tsx +200 -0
- package/vitest.workspace.ts +26 -0
|
@@ -0,0 +1,989 @@
|
|
|
1
|
+
import { expectDefinedAndReturn } from '@strictly/base'
|
|
2
|
+
import {
|
|
3
|
+
booleanType,
|
|
4
|
+
type FlattenedValueTypesOf,
|
|
5
|
+
list,
|
|
6
|
+
nullType,
|
|
7
|
+
numberType,
|
|
8
|
+
object,
|
|
9
|
+
record,
|
|
10
|
+
stringType,
|
|
11
|
+
union,
|
|
12
|
+
type ValueToTypePathsOf,
|
|
13
|
+
type ValueTypeOf,
|
|
14
|
+
} from '@strictly/define'
|
|
15
|
+
import { type FieldAdapter } from 'core/mobx/field_adapter'
|
|
16
|
+
import {
|
|
17
|
+
adapterFromTwoWayConverter,
|
|
18
|
+
identityAdapter,
|
|
19
|
+
} from 'core/mobx/field_adapter_builder'
|
|
20
|
+
import {
|
|
21
|
+
type FlattenedTypePathsToAdaptersOf,
|
|
22
|
+
FormModel,
|
|
23
|
+
FormPresenter,
|
|
24
|
+
type ValuePathsToAdaptersOf,
|
|
25
|
+
} from 'core/mobx/form_presenter'
|
|
26
|
+
import { IntegerToStringConverter } from 'field_converters/integer_to_string_converter'
|
|
27
|
+
import { NullableToBooleanConverter } from 'field_converters/nullable_to_boolean_converter'
|
|
28
|
+
import { prototypingFieldValueFactory } from 'field_value_factories/prototyping_field_value_factory'
|
|
29
|
+
import { type Simplify } from 'type-fest'
|
|
30
|
+
import { type Field } from 'types/field'
|
|
31
|
+
import {
|
|
32
|
+
FieldConversionResult,
|
|
33
|
+
} from 'types/field_converters'
|
|
34
|
+
import { type Mocked } from 'vitest'
|
|
35
|
+
import {
|
|
36
|
+
mock,
|
|
37
|
+
mockClear,
|
|
38
|
+
} from 'vitest-mock-extended'
|
|
39
|
+
|
|
40
|
+
const IS_NAN_ERROR = 1
|
|
41
|
+
|
|
42
|
+
function createMockedAdapter<
|
|
43
|
+
E,
|
|
44
|
+
To,
|
|
45
|
+
From,
|
|
46
|
+
ValuePath extends string,
|
|
47
|
+
>({
|
|
48
|
+
convert,
|
|
49
|
+
revert,
|
|
50
|
+
create,
|
|
51
|
+
}: FieldAdapter<From, To, E, ValuePath>): Mocked<
|
|
52
|
+
Required<FieldAdapter<From, To, E, ValuePath>>
|
|
53
|
+
> {
|
|
54
|
+
const mockedAdapter = mock<Required<FieldAdapter<From, To, E, ValuePath>>>()
|
|
55
|
+
if (revert) {
|
|
56
|
+
mockedAdapter.revert?.mockImplementation(revert)
|
|
57
|
+
}
|
|
58
|
+
mockedAdapter.convert.mockImplementation(convert)
|
|
59
|
+
mockedAdapter.create.mockImplementation(create)
|
|
60
|
+
|
|
61
|
+
return mockedAdapter
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
describe('all', function () {
|
|
65
|
+
const integerToStringAdapter = createMockedAdapter(
|
|
66
|
+
adapterFromTwoWayConverter(
|
|
67
|
+
new IntegerToStringConverter(IS_NAN_ERROR),
|
|
68
|
+
prototypingFieldValueFactory(0),
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
const booleanToBooleanAdapter = createMockedAdapter(
|
|
72
|
+
identityAdapter(false),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
beforeEach(function () {
|
|
76
|
+
mockClear(integerToStringAdapter)
|
|
77
|
+
mockClear(booleanToBooleanAdapter)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe('FlattenedTypePathsToConvertersOf', function () {
|
|
81
|
+
type ConvenientFieldAdapter<
|
|
82
|
+
From,
|
|
83
|
+
Context,
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
85
|
+
To = any,
|
|
86
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
87
|
+
E = any,
|
|
88
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
89
|
+
ValuePath extends string = any,
|
|
90
|
+
> = FieldAdapter<
|
|
91
|
+
From,
|
|
92
|
+
To,
|
|
93
|
+
E,
|
|
94
|
+
ValuePath,
|
|
95
|
+
Context
|
|
96
|
+
>
|
|
97
|
+
|
|
98
|
+
describe('record', function () {
|
|
99
|
+
const typeDef = record<typeof numberType, 'a' | 'b'>(numberType)
|
|
100
|
+
type T = Simplify<
|
|
101
|
+
FlattenedTypePathsToAdaptersOf<
|
|
102
|
+
FlattenedValueTypesOf<typeof typeDef>,
|
|
103
|
+
ValueTypeOf<typeof typeDef>
|
|
104
|
+
>
|
|
105
|
+
>
|
|
106
|
+
let t: Partial<{
|
|
107
|
+
readonly $: ConvenientFieldAdapter<Readonly<Record<'a' | 'b', number>>, ValueTypeOf<typeof typeDef>>,
|
|
108
|
+
readonly ['$.a']: ConvenientFieldAdapter<number, ValueTypeOf<typeof typeDef>>,
|
|
109
|
+
readonly ['$.b']: ConvenientFieldAdapter<number, ValueTypeOf<typeof typeDef>>,
|
|
110
|
+
}>
|
|
111
|
+
|
|
112
|
+
it('equals expected type', function () {
|
|
113
|
+
expectTypeOf(t).toEqualTypeOf<T>()
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
describe('object', function () {
|
|
118
|
+
const typeDef = object()
|
|
119
|
+
.set('x', stringType)
|
|
120
|
+
.set('y', booleanType)
|
|
121
|
+
type T = FlattenedTypePathsToAdaptersOf<
|
|
122
|
+
FlattenedValueTypesOf<typeof typeDef>,
|
|
123
|
+
ValueTypeOf<typeof typeDef>
|
|
124
|
+
>
|
|
125
|
+
let t: Partial<{
|
|
126
|
+
readonly $: ConvenientFieldAdapter<{ readonly x: string, readonly y: boolean }, ValueTypeOf<typeof typeDef>>,
|
|
127
|
+
readonly ['$.x']: ConvenientFieldAdapter<string, ValueTypeOf<typeof typeDef>>,
|
|
128
|
+
readonly ['$.y']: ConvenientFieldAdapter<boolean, ValueTypeOf<typeof typeDef>>,
|
|
129
|
+
}>
|
|
130
|
+
it('equals expected type', function () {
|
|
131
|
+
expectTypeOf(t).toEqualTypeOf<T>()
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('matches representative adapters', function () {
|
|
135
|
+
type A = {
|
|
136
|
+
'$.x': FieldAdapter<string, string>,
|
|
137
|
+
'$.y': FieldAdapter<boolean, string>,
|
|
138
|
+
}
|
|
139
|
+
expectTypeOf<A>().toMatchTypeOf<T>()
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('does not allow mismatched adapters', function () {
|
|
143
|
+
type A = {
|
|
144
|
+
'$.x': FieldAdapter<boolean, string, Record<string, Field>>,
|
|
145
|
+
'$.y': FieldAdapter<string, string, Record<string, Field>>,
|
|
146
|
+
}
|
|
147
|
+
expectTypeOf<A>().not.toMatchTypeOf<T>()
|
|
148
|
+
})
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
describe('ValuePathsToAdaptersOf', function () {
|
|
153
|
+
describe('superset', function () {
|
|
154
|
+
type A = {
|
|
155
|
+
'$.x': FieldAdapter<number, string, string, '$.a'>,
|
|
156
|
+
'$.y': FieldAdapter<boolean, boolean, string, '$.b'>,
|
|
157
|
+
}
|
|
158
|
+
const valuePathsToTypePaths = {
|
|
159
|
+
$: '$',
|
|
160
|
+
'$.a': '$.x',
|
|
161
|
+
'$.b': '$.y',
|
|
162
|
+
'$.c': '$.z',
|
|
163
|
+
} as const
|
|
164
|
+
type T = ValuePathsToAdaptersOf<
|
|
165
|
+
A,
|
|
166
|
+
typeof valuePathsToTypePaths
|
|
167
|
+
>
|
|
168
|
+
let t: {
|
|
169
|
+
readonly '$.a': A['$.x'],
|
|
170
|
+
readonly '$.b': A['$.y'],
|
|
171
|
+
}
|
|
172
|
+
it('equals expected type', function () {
|
|
173
|
+
expectTypeOf(t).toEqualTypeOf<T>()
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
describe('FormModel', function () {
|
|
179
|
+
describe('literal', function () {
|
|
180
|
+
const typeDef = numberType
|
|
181
|
+
const adapters = {
|
|
182
|
+
$: integerToStringAdapter,
|
|
183
|
+
} as const
|
|
184
|
+
let originalValue: ValueTypeOf<typeof typeDef>
|
|
185
|
+
let model: FormModel<
|
|
186
|
+
typeof typeDef,
|
|
187
|
+
ValueToTypePathsOf<typeof typeDef>,
|
|
188
|
+
typeof adapters
|
|
189
|
+
>
|
|
190
|
+
beforeEach(function () {
|
|
191
|
+
originalValue = 5
|
|
192
|
+
model = new FormModel<
|
|
193
|
+
typeof typeDef,
|
|
194
|
+
ValueToTypePathsOf<typeof typeDef>,
|
|
195
|
+
typeof adapters
|
|
196
|
+
>(
|
|
197
|
+
typeDef,
|
|
198
|
+
originalValue,
|
|
199
|
+
adapters,
|
|
200
|
+
)
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
describe('accessors', function () {
|
|
204
|
+
it('gets the expected value', function () {
|
|
205
|
+
const accessor = expectDefinedAndReturn(model.accessors.$)
|
|
206
|
+
expect(accessor.value).toEqual(originalValue)
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('sets the underlying value', function () {
|
|
210
|
+
const newValue = 1
|
|
211
|
+
const accessor = expectDefinedAndReturn(model.accessors.$)
|
|
212
|
+
accessor.set(newValue)
|
|
213
|
+
expect(model.value).toEqual(newValue)
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
describe('fields', function () {
|
|
218
|
+
it('equals expected value', function () {
|
|
219
|
+
expect(model.fields).toEqual(
|
|
220
|
+
expect.objectContaining({
|
|
221
|
+
$: expect.objectContaining({
|
|
222
|
+
value: '5',
|
|
223
|
+
}),
|
|
224
|
+
}),
|
|
225
|
+
)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('has the expected keys', function () {
|
|
229
|
+
expect(Object.keys(model.fields)).toEqual(['$'])
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
describe('list', function () {
|
|
235
|
+
const typeDef = list(numberType)
|
|
236
|
+
const adapters = {
|
|
237
|
+
'$.*': integerToStringAdapter,
|
|
238
|
+
} as const
|
|
239
|
+
let value: ValueTypeOf<typeof typeDef>
|
|
240
|
+
let model: FormModel<
|
|
241
|
+
typeof typeDef,
|
|
242
|
+
ValueToTypePathsOf<typeof typeDef>,
|
|
243
|
+
typeof adapters
|
|
244
|
+
>
|
|
245
|
+
beforeEach(function () {
|
|
246
|
+
value = [
|
|
247
|
+
1,
|
|
248
|
+
4,
|
|
249
|
+
17,
|
|
250
|
+
]
|
|
251
|
+
model = new FormModel<
|
|
252
|
+
typeof typeDef,
|
|
253
|
+
ValueToTypePathsOf<typeof typeDef>,
|
|
254
|
+
typeof adapters
|
|
255
|
+
>(
|
|
256
|
+
typeDef,
|
|
257
|
+
value,
|
|
258
|
+
adapters,
|
|
259
|
+
)
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
describe('accessors', function () {
|
|
263
|
+
it.each([
|
|
264
|
+
[
|
|
265
|
+
'$.0',
|
|
266
|
+
1,
|
|
267
|
+
],
|
|
268
|
+
[
|
|
269
|
+
'$.1',
|
|
270
|
+
4,
|
|
271
|
+
],
|
|
272
|
+
[
|
|
273
|
+
'$.2',
|
|
274
|
+
17,
|
|
275
|
+
],
|
|
276
|
+
] as const)('gets the expected values for %s', function (valuePath, value) {
|
|
277
|
+
const accessor = expectDefinedAndReturn(model.accessors[valuePath])
|
|
278
|
+
expect(accessor.value).toEqual(value)
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
it('sets a value', function () {
|
|
282
|
+
const accessor = expectDefinedAndReturn(model.accessors['$.0'])
|
|
283
|
+
accessor.set(100)
|
|
284
|
+
expect(model.value).toEqual([
|
|
285
|
+
100,
|
|
286
|
+
4,
|
|
287
|
+
17,
|
|
288
|
+
])
|
|
289
|
+
})
|
|
290
|
+
})
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
describe('record', function () {
|
|
294
|
+
const typeDef = record<typeof numberType, 'a' | 'b'>(numberType)
|
|
295
|
+
const converters = {
|
|
296
|
+
'$.*': integerToStringAdapter,
|
|
297
|
+
// '$.*': booleanToBooleanConverter,
|
|
298
|
+
} as const
|
|
299
|
+
let value: ValueTypeOf<typeof typeDef>
|
|
300
|
+
let model: FormModel<
|
|
301
|
+
typeof typeDef,
|
|
302
|
+
ValueToTypePathsOf<typeof typeDef>,
|
|
303
|
+
typeof converters
|
|
304
|
+
>
|
|
305
|
+
beforeEach(function () {
|
|
306
|
+
value = {
|
|
307
|
+
a: 1,
|
|
308
|
+
b: 2,
|
|
309
|
+
}
|
|
310
|
+
model = new FormModel<
|
|
311
|
+
typeof typeDef,
|
|
312
|
+
ValueToTypePathsOf<typeof typeDef>,
|
|
313
|
+
typeof converters
|
|
314
|
+
>(
|
|
315
|
+
typeDef,
|
|
316
|
+
value,
|
|
317
|
+
converters,
|
|
318
|
+
)
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
describe('accessors', function () {
|
|
322
|
+
it.each([
|
|
323
|
+
[
|
|
324
|
+
'$.a',
|
|
325
|
+
1,
|
|
326
|
+
],
|
|
327
|
+
[
|
|
328
|
+
'$.b',
|
|
329
|
+
2,
|
|
330
|
+
],
|
|
331
|
+
] as const)('gets the expected value for %s', function (valuePath, value) {
|
|
332
|
+
const accessor = expectDefinedAndReturn(model.accessors[valuePath])
|
|
333
|
+
expect(accessor.value).toEqual(value)
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
it('sets a value', function () {
|
|
337
|
+
const accessor = expectDefinedAndReturn(model.accessors['$.b'])
|
|
338
|
+
const newValue = 100
|
|
339
|
+
accessor.set(newValue)
|
|
340
|
+
|
|
341
|
+
expect(model.value.b).toEqual(newValue)
|
|
342
|
+
})
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
describe('fields', function () {
|
|
346
|
+
it('equals expected value', function () {
|
|
347
|
+
expect(model.fields).toEqual(
|
|
348
|
+
expect.objectContaining({
|
|
349
|
+
'$.a': expect.objectContaining({
|
|
350
|
+
value: '1',
|
|
351
|
+
}),
|
|
352
|
+
'$.b': expect.objectContaining({
|
|
353
|
+
value: '2',
|
|
354
|
+
}),
|
|
355
|
+
}),
|
|
356
|
+
)
|
|
357
|
+
})
|
|
358
|
+
})
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
describe('object', function () {
|
|
362
|
+
const typeDef = object()
|
|
363
|
+
.set('a', numberType)
|
|
364
|
+
.set('b', booleanType)
|
|
365
|
+
const converters = {
|
|
366
|
+
'$.a': integerToStringAdapter,
|
|
367
|
+
'$.b': booleanToBooleanAdapter,
|
|
368
|
+
} as const
|
|
369
|
+
let value: ValueTypeOf<typeof typeDef>
|
|
370
|
+
let model: FormModel<
|
|
371
|
+
typeof typeDef,
|
|
372
|
+
ValueToTypePathsOf<typeof typeDef>,
|
|
373
|
+
typeof converters
|
|
374
|
+
>
|
|
375
|
+
beforeEach(function () {
|
|
376
|
+
value = {
|
|
377
|
+
a: 1,
|
|
378
|
+
b: true,
|
|
379
|
+
}
|
|
380
|
+
model = new FormModel<
|
|
381
|
+
typeof typeDef,
|
|
382
|
+
ValueToTypePathsOf<typeof typeDef>,
|
|
383
|
+
typeof converters
|
|
384
|
+
>(
|
|
385
|
+
typeDef,
|
|
386
|
+
value,
|
|
387
|
+
converters,
|
|
388
|
+
)
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
describe('accessors', function () {
|
|
392
|
+
it.each([
|
|
393
|
+
[
|
|
394
|
+
'$.a',
|
|
395
|
+
1,
|
|
396
|
+
],
|
|
397
|
+
[
|
|
398
|
+
'$.b',
|
|
399
|
+
true,
|
|
400
|
+
],
|
|
401
|
+
] as const)('gets the expected value for %s', function (valuePath, value) {
|
|
402
|
+
const accessor = expectDefinedAndReturn(model.accessors[valuePath])
|
|
403
|
+
expect(accessor.value).toEqual(value)
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
it('sets a value', function () {
|
|
407
|
+
const accessor = expectDefinedAndReturn(model.accessors['$.b'])
|
|
408
|
+
accessor.set(false)
|
|
409
|
+
expect(model.value.b).toEqual(false)
|
|
410
|
+
})
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
describe('fields', function () {
|
|
414
|
+
it('equals expected value', function () {
|
|
415
|
+
expect(model.fields).toEqual(
|
|
416
|
+
expect.objectContaining({
|
|
417
|
+
'$.a': expect.objectContaining({
|
|
418
|
+
value: '1',
|
|
419
|
+
}),
|
|
420
|
+
'$.b': expect.objectContaining({
|
|
421
|
+
value: true,
|
|
422
|
+
}),
|
|
423
|
+
}),
|
|
424
|
+
)
|
|
425
|
+
})
|
|
426
|
+
})
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
// TODO union
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
describe('FormPresenter', function () {
|
|
433
|
+
describe('literal', function () {
|
|
434
|
+
const typeDef = numberType
|
|
435
|
+
const adapters = {
|
|
436
|
+
$: integerToStringAdapter,
|
|
437
|
+
} as const
|
|
438
|
+
const presenter = new FormPresenter<
|
|
439
|
+
typeof typeDef,
|
|
440
|
+
ValueToTypePathsOf<typeof typeDef>,
|
|
441
|
+
typeof adapters
|
|
442
|
+
>(
|
|
443
|
+
typeDef,
|
|
444
|
+
adapters,
|
|
445
|
+
)
|
|
446
|
+
const originalValue: ValueTypeOf<typeof typeDef> = 2
|
|
447
|
+
let model: FormModel<
|
|
448
|
+
typeof typeDef,
|
|
449
|
+
ValueToTypePathsOf<typeof typeDef>,
|
|
450
|
+
typeof adapters
|
|
451
|
+
>
|
|
452
|
+
beforeEach(function () {
|
|
453
|
+
model = presenter.createModel(originalValue)
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
describe('setFieldValueAndValidate', function () {
|
|
457
|
+
describe('success', function () {
|
|
458
|
+
beforeEach(function () {
|
|
459
|
+
presenter.setFieldValueAndValidate<'$'>(model, '$', '1')
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
it('does set the underlying value', function () {
|
|
463
|
+
expect(model.value).toEqual(1)
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
it('sets the fields', function () {
|
|
467
|
+
expect(model.fields).toEqual(expect.objectContaining({
|
|
468
|
+
$: expect.objectContaining({
|
|
469
|
+
value: '1',
|
|
470
|
+
// eslint-disable-next-line no-undefined
|
|
471
|
+
error: undefined,
|
|
472
|
+
}),
|
|
473
|
+
}))
|
|
474
|
+
})
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
describe('failure', function () {
|
|
478
|
+
describe('conversion fails', function () {
|
|
479
|
+
beforeEach(function () {
|
|
480
|
+
presenter.setFieldValueAndValidate<'$'>(model, '$', 'x')
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
it('does not set the underlying value', function () {
|
|
484
|
+
expect(model.value).toEqual(originalValue)
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
it('sets the error state', function () {
|
|
488
|
+
expect(model.fields).toEqual(expect.objectContaining({
|
|
489
|
+
$: expect.objectContaining({
|
|
490
|
+
value: 'x',
|
|
491
|
+
error: IS_NAN_ERROR,
|
|
492
|
+
}),
|
|
493
|
+
}))
|
|
494
|
+
})
|
|
495
|
+
})
|
|
496
|
+
|
|
497
|
+
describe('conversion succeeds, but validation fails', function () {
|
|
498
|
+
const newValue = -1
|
|
499
|
+
const errorCode = 65
|
|
500
|
+
beforeEach(function () {
|
|
501
|
+
integerToStringAdapter.revert?.mockReturnValueOnce({
|
|
502
|
+
type: FieldConversionResult.Failure,
|
|
503
|
+
error: errorCode,
|
|
504
|
+
value: [newValue],
|
|
505
|
+
})
|
|
506
|
+
presenter.setFieldValueAndValidate<'$'>(model, '$', '-1')
|
|
507
|
+
})
|
|
508
|
+
|
|
509
|
+
it('does set the underlying value', function () {
|
|
510
|
+
expect(model.value).toEqual(newValue)
|
|
511
|
+
})
|
|
512
|
+
|
|
513
|
+
it('does update the field', function () {
|
|
514
|
+
expect(model.fields).toEqual({
|
|
515
|
+
$: expect.objectContaining({
|
|
516
|
+
value: '-1',
|
|
517
|
+
error: errorCode,
|
|
518
|
+
disabled: false,
|
|
519
|
+
}),
|
|
520
|
+
})
|
|
521
|
+
})
|
|
522
|
+
})
|
|
523
|
+
})
|
|
524
|
+
})
|
|
525
|
+
|
|
526
|
+
describe.each([
|
|
527
|
+
[
|
|
528
|
+
'1',
|
|
529
|
+
1,
|
|
530
|
+
],
|
|
531
|
+
[
|
|
532
|
+
'x',
|
|
533
|
+
originalValue,
|
|
534
|
+
],
|
|
535
|
+
] as const)('setFieldValue to %s', function (newValue, expectedValue) {
|
|
536
|
+
beforeEach(function () {
|
|
537
|
+
presenter.setFieldValue<'$'>(model, '$', newValue)
|
|
538
|
+
})
|
|
539
|
+
|
|
540
|
+
it('does set the underlying value', function () {
|
|
541
|
+
expect(model.value).toEqual(expectedValue)
|
|
542
|
+
})
|
|
543
|
+
|
|
544
|
+
it('sets the field value', function () {
|
|
545
|
+
expect(model.fields).toEqual(expect.objectContaining({
|
|
546
|
+
$: expect.objectContaining({
|
|
547
|
+
value: newValue,
|
|
548
|
+
// eslint-disable-next-line no-undefined
|
|
549
|
+
error: undefined,
|
|
550
|
+
}),
|
|
551
|
+
}))
|
|
552
|
+
})
|
|
553
|
+
})
|
|
554
|
+
})
|
|
555
|
+
|
|
556
|
+
describe('list', function () {
|
|
557
|
+
const typeDef = list(numberType)
|
|
558
|
+
const converters = {
|
|
559
|
+
'$.*': integerToStringAdapter,
|
|
560
|
+
} as const
|
|
561
|
+
const presenter = new FormPresenter<
|
|
562
|
+
typeof typeDef,
|
|
563
|
+
ValueToTypePathsOf<typeof typeDef>,
|
|
564
|
+
typeof converters
|
|
565
|
+
>(
|
|
566
|
+
typeDef,
|
|
567
|
+
converters,
|
|
568
|
+
)
|
|
569
|
+
let originalValue: ValueTypeOf<typeof typeDef>
|
|
570
|
+
let model: FormModel<
|
|
571
|
+
typeof typeDef,
|
|
572
|
+
ValueToTypePathsOf<typeof typeDef>,
|
|
573
|
+
typeof converters
|
|
574
|
+
>
|
|
575
|
+
beforeEach(function () {
|
|
576
|
+
originalValue = [
|
|
577
|
+
1,
|
|
578
|
+
3,
|
|
579
|
+
7,
|
|
580
|
+
]
|
|
581
|
+
model = presenter.createModel(originalValue)
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
describe('setFieldValueAndValidate', function () {
|
|
585
|
+
describe('success', function () {
|
|
586
|
+
beforeEach(function () {
|
|
587
|
+
presenter.setFieldValueAndValidate<'$.0'>(model, '$.0', '100')
|
|
588
|
+
})
|
|
589
|
+
|
|
590
|
+
it('sets the underlying value', function () {
|
|
591
|
+
expect(model.value).toEqual([
|
|
592
|
+
100,
|
|
593
|
+
3,
|
|
594
|
+
7,
|
|
595
|
+
])
|
|
596
|
+
})
|
|
597
|
+
|
|
598
|
+
it('sets the fields', function () {
|
|
599
|
+
expect(model.fields).toEqual(expect.objectContaining({
|
|
600
|
+
'$.0': expect.objectContaining({
|
|
601
|
+
value: '100',
|
|
602
|
+
// eslint-disable-next-line no-undefined
|
|
603
|
+
error: undefined,
|
|
604
|
+
}),
|
|
605
|
+
}))
|
|
606
|
+
})
|
|
607
|
+
})
|
|
608
|
+
|
|
609
|
+
describe('failure', function () {
|
|
610
|
+
beforeEach(function () {
|
|
611
|
+
presenter.setFieldValueAndValidate<'$.0'>(model, '$.0', 'x')
|
|
612
|
+
})
|
|
613
|
+
|
|
614
|
+
it('does not set the underlying value', function () {
|
|
615
|
+
expect(model.value).toEqual(originalValue)
|
|
616
|
+
})
|
|
617
|
+
|
|
618
|
+
it('sets the error state', function () {
|
|
619
|
+
expect(model.fields).toEqual(expect.objectContaining({
|
|
620
|
+
'$.0': expect.objectContaining({
|
|
621
|
+
value: 'x',
|
|
622
|
+
error: IS_NAN_ERROR,
|
|
623
|
+
}),
|
|
624
|
+
}))
|
|
625
|
+
})
|
|
626
|
+
})
|
|
627
|
+
})
|
|
628
|
+
|
|
629
|
+
describe.each([
|
|
630
|
+
'1',
|
|
631
|
+
'x',
|
|
632
|
+
])('setFieldValue to %s', function (newValue) {
|
|
633
|
+
beforeEach(function () {
|
|
634
|
+
presenter.setFieldValue(model, '$.0', newValue)
|
|
635
|
+
})
|
|
636
|
+
|
|
637
|
+
it('does not set the underlying value', function () {
|
|
638
|
+
expect(model.value).toEqual(originalValue)
|
|
639
|
+
})
|
|
640
|
+
|
|
641
|
+
it('sets the field value', function () {
|
|
642
|
+
expect(model.fields).toEqual(expect.objectContaining({
|
|
643
|
+
'$.0': expect.objectContaining({
|
|
644
|
+
value: newValue,
|
|
645
|
+
// eslint-disable-next-line no-undefined
|
|
646
|
+
error: undefined,
|
|
647
|
+
}),
|
|
648
|
+
}))
|
|
649
|
+
})
|
|
650
|
+
})
|
|
651
|
+
|
|
652
|
+
describe('validate', function () {
|
|
653
|
+
beforeEach(function () {
|
|
654
|
+
presenter.setFieldValue(model, '$.0', 'x')
|
|
655
|
+
presenter.setFieldValue(model, '$.1', '2')
|
|
656
|
+
presenter.setFieldValue(model, '$.2', 'z')
|
|
657
|
+
presenter.validateAll(model)
|
|
658
|
+
})
|
|
659
|
+
|
|
660
|
+
it('contains errors for all invalid fields', function () {
|
|
661
|
+
expect(model.fields).toEqual(expect.objectContaining({
|
|
662
|
+
'$.0': expect.objectContaining({
|
|
663
|
+
value: 'x',
|
|
664
|
+
error: IS_NAN_ERROR,
|
|
665
|
+
}),
|
|
666
|
+
'$.1': expect.objectContaining({
|
|
667
|
+
value: '2',
|
|
668
|
+
// eslint-disable-next-line no-undefined
|
|
669
|
+
error: undefined,
|
|
670
|
+
}),
|
|
671
|
+
'$.2': expect.objectContaining({
|
|
672
|
+
value: 'z',
|
|
673
|
+
error: IS_NAN_ERROR,
|
|
674
|
+
}),
|
|
675
|
+
}))
|
|
676
|
+
})
|
|
677
|
+
|
|
678
|
+
it('sets the value only for valid fields', function () {
|
|
679
|
+
expect(model.value).toEqual([
|
|
680
|
+
1,
|
|
681
|
+
2,
|
|
682
|
+
7,
|
|
683
|
+
])
|
|
684
|
+
})
|
|
685
|
+
})
|
|
686
|
+
|
|
687
|
+
// no longer passes context, but will pass context eventually again
|
|
688
|
+
describe('passes context', function () {
|
|
689
|
+
let contextCopy: number[]
|
|
690
|
+
beforeEach(function () {
|
|
691
|
+
integerToStringAdapter.revert.mockImplementationOnce(function (_value, _path, context) {
|
|
692
|
+
contextCopy = [...context]
|
|
693
|
+
return {
|
|
694
|
+
type: FieldConversionResult.Success,
|
|
695
|
+
value: 1,
|
|
696
|
+
}
|
|
697
|
+
})
|
|
698
|
+
})
|
|
699
|
+
|
|
700
|
+
it('supplies the full, previous context when converting', function () {
|
|
701
|
+
presenter.setFieldValueAndValidate(model, '$.2', '4')
|
|
702
|
+
|
|
703
|
+
expect(integerToStringAdapter.revert).toHaveBeenCalledOnce()
|
|
704
|
+
expect(integerToStringAdapter.revert).toHaveBeenCalledWith(
|
|
705
|
+
'4',
|
|
706
|
+
'$.2',
|
|
707
|
+
// uses the same pointer
|
|
708
|
+
model.value,
|
|
709
|
+
)
|
|
710
|
+
})
|
|
711
|
+
|
|
712
|
+
it('supplies the context as it is at the time call', function () {
|
|
713
|
+
expect(contextCopy).toEqual([
|
|
714
|
+
1,
|
|
715
|
+
3,
|
|
716
|
+
7,
|
|
717
|
+
])
|
|
718
|
+
})
|
|
719
|
+
})
|
|
720
|
+
|
|
721
|
+
describe('addListItem', function () {
|
|
722
|
+
describe('adds default to start of the list', function () {
|
|
723
|
+
beforeEach(function () {
|
|
724
|
+
model.errors['$.0'] = 0
|
|
725
|
+
model.errors['$.1'] = 1
|
|
726
|
+
model.errors['$.2'] = 2
|
|
727
|
+
presenter.addListItem(model, '$', null, 0)
|
|
728
|
+
})
|
|
729
|
+
|
|
730
|
+
it('adds the list item to the underlying value', function () {
|
|
731
|
+
expect(model.value).toEqual([
|
|
732
|
+
0,
|
|
733
|
+
1,
|
|
734
|
+
3,
|
|
735
|
+
7,
|
|
736
|
+
])
|
|
737
|
+
})
|
|
738
|
+
|
|
739
|
+
it.each([
|
|
740
|
+
[
|
|
741
|
+
'$.0',
|
|
742
|
+
'0',
|
|
743
|
+
],
|
|
744
|
+
[
|
|
745
|
+
'$.1',
|
|
746
|
+
'1',
|
|
747
|
+
],
|
|
748
|
+
[
|
|
749
|
+
'$.2',
|
|
750
|
+
'3',
|
|
751
|
+
],
|
|
752
|
+
[
|
|
753
|
+
'$.3',
|
|
754
|
+
'7',
|
|
755
|
+
],
|
|
756
|
+
] as const)('it reports the value of field %s as %s', function (path, fieldValue) {
|
|
757
|
+
expect(model.fields[path]?.value).toBe(fieldValue)
|
|
758
|
+
})
|
|
759
|
+
|
|
760
|
+
it.each([
|
|
761
|
+
[
|
|
762
|
+
'$.0',
|
|
763
|
+
// eslint-disable-next-line no-undefined
|
|
764
|
+
undefined,
|
|
765
|
+
],
|
|
766
|
+
[
|
|
767
|
+
'$.1',
|
|
768
|
+
0,
|
|
769
|
+
],
|
|
770
|
+
[
|
|
771
|
+
'$.2',
|
|
772
|
+
1,
|
|
773
|
+
],
|
|
774
|
+
[
|
|
775
|
+
'$.3',
|
|
776
|
+
2,
|
|
777
|
+
],
|
|
778
|
+
] as const)('it reports the error of field %s', function (path, error) {
|
|
779
|
+
expect(model.fields[path]?.error).toBe(error)
|
|
780
|
+
})
|
|
781
|
+
})
|
|
782
|
+
|
|
783
|
+
describe('add defined value', function () {
|
|
784
|
+
beforeEach(function () {
|
|
785
|
+
presenter.addListItem(model, '$', [5])
|
|
786
|
+
})
|
|
787
|
+
|
|
788
|
+
it('adds the expected value at the end', function () {
|
|
789
|
+
expect(model.fields).toEqual(
|
|
790
|
+
expect.objectContaining({
|
|
791
|
+
'$.0': expect.objectContaining({
|
|
792
|
+
value: '1',
|
|
793
|
+
}),
|
|
794
|
+
'$.1': expect.objectContaining({
|
|
795
|
+
value: '3',
|
|
796
|
+
}),
|
|
797
|
+
'$.2': expect.objectContaining({
|
|
798
|
+
value: '7',
|
|
799
|
+
}),
|
|
800
|
+
'$.3': expect.objectContaining({
|
|
801
|
+
value: '5',
|
|
802
|
+
}),
|
|
803
|
+
}),
|
|
804
|
+
)
|
|
805
|
+
})
|
|
806
|
+
|
|
807
|
+
it('updates the underlying value', function () {
|
|
808
|
+
expect(model.value).toEqual([
|
|
809
|
+
1,
|
|
810
|
+
3,
|
|
811
|
+
7,
|
|
812
|
+
5,
|
|
813
|
+
])
|
|
814
|
+
})
|
|
815
|
+
})
|
|
816
|
+
})
|
|
817
|
+
|
|
818
|
+
describe('removeListItem', function () {
|
|
819
|
+
beforeEach(function () {
|
|
820
|
+
model.errors['$.0'] = 0
|
|
821
|
+
model.errors['$.1'] = 1
|
|
822
|
+
model.errors['$.2'] = 2
|
|
823
|
+
})
|
|
824
|
+
|
|
825
|
+
describe('remove first item', function () {
|
|
826
|
+
beforeEach(function () {
|
|
827
|
+
presenter.removeListItem(model, '$.0')
|
|
828
|
+
})
|
|
829
|
+
|
|
830
|
+
it('updates the underlying value', function () {
|
|
831
|
+
expect(model.value).toEqual([
|
|
832
|
+
3,
|
|
833
|
+
7,
|
|
834
|
+
])
|
|
835
|
+
})
|
|
836
|
+
|
|
837
|
+
it('updates the field values and errors', function () {
|
|
838
|
+
expect(model.fields).toEqual({
|
|
839
|
+
'$.0': expect.objectContaining({
|
|
840
|
+
value: '3',
|
|
841
|
+
error: 1,
|
|
842
|
+
}),
|
|
843
|
+
'$.1': expect.objectContaining({
|
|
844
|
+
value: '7',
|
|
845
|
+
error: 2,
|
|
846
|
+
}),
|
|
847
|
+
})
|
|
848
|
+
})
|
|
849
|
+
})
|
|
850
|
+
|
|
851
|
+
describe('remove second item', function () {
|
|
852
|
+
beforeEach(function () {
|
|
853
|
+
presenter.removeListItem(model, '$.1')
|
|
854
|
+
})
|
|
855
|
+
|
|
856
|
+
it('updates the underlying value', function () {
|
|
857
|
+
expect(model.value).toEqual([
|
|
858
|
+
1,
|
|
859
|
+
7,
|
|
860
|
+
])
|
|
861
|
+
})
|
|
862
|
+
|
|
863
|
+
it('updates the field values and errors', function () {
|
|
864
|
+
expect(model.fields).toEqual({
|
|
865
|
+
'$.0': expect.objectContaining({
|
|
866
|
+
value: '1',
|
|
867
|
+
error: 0,
|
|
868
|
+
}),
|
|
869
|
+
'$.1': expect.objectContaining({
|
|
870
|
+
value: '7',
|
|
871
|
+
error: 2,
|
|
872
|
+
}),
|
|
873
|
+
})
|
|
874
|
+
})
|
|
875
|
+
})
|
|
876
|
+
})
|
|
877
|
+
})
|
|
878
|
+
|
|
879
|
+
// TODO record / object
|
|
880
|
+
|
|
881
|
+
describe('union', function () {
|
|
882
|
+
describe('non-discriminated', function () {
|
|
883
|
+
const listOfNumbersTypeDef = list(numberType)
|
|
884
|
+
const typeDef = union()
|
|
885
|
+
.add('null', nullType)
|
|
886
|
+
.add('0', listOfNumbersTypeDef)
|
|
887
|
+
const adapters = {
|
|
888
|
+
$: adapterFromTwoWayConverter(new NullableToBooleanConverter(typeDef, [1])),
|
|
889
|
+
'$.*': integerToStringAdapter,
|
|
890
|
+
} as const
|
|
891
|
+
type JsonPaths = ValueToTypePathsOf<typeof typeDef>
|
|
892
|
+
const presenter = new FormPresenter<
|
|
893
|
+
typeof typeDef,
|
|
894
|
+
JsonPaths,
|
|
895
|
+
typeof adapters
|
|
896
|
+
>(
|
|
897
|
+
typeDef,
|
|
898
|
+
adapters,
|
|
899
|
+
)
|
|
900
|
+
let originalValue: ValueTypeOf<typeof typeDef>
|
|
901
|
+
let model: FormModel<
|
|
902
|
+
typeof typeDef,
|
|
903
|
+
JsonPaths,
|
|
904
|
+
typeof adapters
|
|
905
|
+
>
|
|
906
|
+
beforeEach(function () {
|
|
907
|
+
originalValue = null
|
|
908
|
+
model = presenter.createModel(originalValue)
|
|
909
|
+
})
|
|
910
|
+
|
|
911
|
+
it('has the expected fields', function () {
|
|
912
|
+
expect(model.fields).toEqual({
|
|
913
|
+
$: {
|
|
914
|
+
disabled: false,
|
|
915
|
+
// eslint-disable-next-line no-undefined
|
|
916
|
+
error: undefined,
|
|
917
|
+
value: false,
|
|
918
|
+
required: false,
|
|
919
|
+
},
|
|
920
|
+
})
|
|
921
|
+
})
|
|
922
|
+
|
|
923
|
+
describe('setFieldValueAndValidate', function () {
|
|
924
|
+
describe('success', function () {
|
|
925
|
+
beforeEach(function () {
|
|
926
|
+
presenter.setFieldValueAndValidate<'$'>(model, '$', true)
|
|
927
|
+
})
|
|
928
|
+
|
|
929
|
+
it('sets the underlying value', function () {
|
|
930
|
+
expect(model.value).toEqual([1])
|
|
931
|
+
})
|
|
932
|
+
})
|
|
933
|
+
})
|
|
934
|
+
})
|
|
935
|
+
})
|
|
936
|
+
|
|
937
|
+
describe('fake', function () {
|
|
938
|
+
const typeDef = numberType
|
|
939
|
+
const converters = {
|
|
940
|
+
$: integerToStringAdapter,
|
|
941
|
+
'$.fake': booleanToBooleanAdapter,
|
|
942
|
+
} as const
|
|
943
|
+
type JsonPaths = {
|
|
944
|
+
$: '$',
|
|
945
|
+
'$.fake': '$.fake',
|
|
946
|
+
}
|
|
947
|
+
const presenter = new FormPresenter<
|
|
948
|
+
typeof typeDef,
|
|
949
|
+
JsonPaths,
|
|
950
|
+
typeof converters
|
|
951
|
+
>(
|
|
952
|
+
typeDef,
|
|
953
|
+
converters,
|
|
954
|
+
)
|
|
955
|
+
let originalValue: ValueTypeOf<typeof typeDef>
|
|
956
|
+
let model: FormModel<
|
|
957
|
+
typeof typeDef,
|
|
958
|
+
JsonPaths,
|
|
959
|
+
typeof converters
|
|
960
|
+
>
|
|
961
|
+
beforeEach(function () {
|
|
962
|
+
originalValue = 1
|
|
963
|
+
model = presenter.createModel(originalValue)
|
|
964
|
+
})
|
|
965
|
+
|
|
966
|
+
it('returns the default value for the fake field', function () {
|
|
967
|
+
expect(model.fields['$.fake']).toEqual(expect.objectContaining({
|
|
968
|
+
value: false,
|
|
969
|
+
}))
|
|
970
|
+
})
|
|
971
|
+
|
|
972
|
+
describe('setting fake field', function () {
|
|
973
|
+
beforeEach(function () {
|
|
974
|
+
presenter.setFieldValue(model, '$.fake', true)
|
|
975
|
+
})
|
|
976
|
+
|
|
977
|
+
it('stores the new value', function () {
|
|
978
|
+
expect(model.fields['$.fake']).toEqual(expect.objectContaining({
|
|
979
|
+
value: true,
|
|
980
|
+
}))
|
|
981
|
+
})
|
|
982
|
+
|
|
983
|
+
it('does not change the original value', function () {
|
|
984
|
+
expect(model.value).toBe(originalValue)
|
|
985
|
+
})
|
|
986
|
+
})
|
|
987
|
+
})
|
|
988
|
+
})
|
|
989
|
+
})
|