@strictly/react-form 0.0.6 → 0.0.8
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/.out/core/mobx/hooks.d.ts +5 -1
- package/.out/core/mobx/hooks.js +13 -3
- package/.out/core/mobx/specs/form_presenter.tests.js +3 -6
- package/.out/core/mobx/specs/{merge_field_adapters_with_two_way_converter.js → merge_field_adapters_with_two_way_converter.tests.js} +15 -16
- package/.out/core/mobx/specs/sub_form_field_adapters.tests.js +127 -12
- package/.out/core/mobx/sub_form_field_adapters.d.ts +6 -4
- package/.out/core/mobx/sub_form_field_adapters.js +28 -4
- package/.out/field_converters/nullable_to_boolean_converter.d.ts +2 -2
- package/.out/mantine/create_checkbox.js +1 -0
- package/.out/mantine/create_fields_view.d.ts +1 -1
- package/.out/mantine/create_fields_view.js +4 -4
- package/.out/mantine/create_form.d.ts +1 -1
- package/.out/mantine/create_list.d.ts +1 -1
- package/.out/mantine/create_pill.d.ts +1 -1
- package/.out/mantine/create_radio.d.ts +1 -1
- package/.out/mantine/create_radio_group.js +1 -0
- package/.out/mantine/create_text_input.js +7 -2
- package/.out/mantine/create_value_input.js +1 -0
- package/.out/mantine/error_renderer.d.ts +1 -1
- package/.out/mantine/error_renderer.js +1 -1
- package/.out/mantine/hooks.d.ts +9 -9
- package/.out/mantine/specs/checkbox_hooks.stories.d.ts +2 -6
- package/.out/mantine/specs/checkbox_hooks.stories.js +4 -16
- package/.out/mantine/specs/fields_view_hooks.stories.d.ts +1 -1
- package/.out/mantine/specs/fields_view_hooks.stories.js +6 -3
- package/.out/mantine/specs/form_hooks.stories.d.ts +2 -2
- package/.out/mantine/specs/form_hooks.stories.js +4 -1
- package/.out/mantine/specs/radio_group_hooks.stories.d.ts +2 -6
- package/.out/mantine/specs/radio_group_hooks.stories.js +5 -17
- package/.out/mantine/specs/select_hooks.stories.d.ts +2 -6
- package/.out/mantine/specs/select_hooks.stories.js +4 -16
- package/.out/mantine/specs/text_input_hooks.stories.d.ts +2 -5
- package/.out/mantine/specs/text_input_hooks.stories.js +5 -5
- package/.out/mantine/specs/value_input_hooks.stories.d.ts +2 -5
- package/.out/mantine/specs/value_input_hooks.stories.js +5 -5
- package/.out/mantine/types.d.ts +4 -2
- package/.out/tsconfig.tsbuildinfo +1 -1
- package/.turbo/turbo-build.log +8 -8
- package/.turbo/turbo-check-types.log +1 -1
- package/.turbo/turbo-release$colon$exports.log +1 -1
- package/core/mobx/hooks.ts +24 -6
- package/core/mobx/specs/form_presenter.tests.ts +6 -6
- package/core/mobx/specs/{merge_field_adapters_with_two_way_converter.ts → merge_field_adapters_with_two_way_converter.tests.ts} +16 -16
- package/core/mobx/specs/sub_form_field_adapters.tests.ts +193 -17
- package/core/mobx/sub_form_field_adapters.ts +74 -11
- package/dist/index.cjs +77 -32
- package/dist/index.d.cts +25 -18
- package/dist/index.d.ts +25 -18
- package/dist/index.js +68 -21
- package/field_converters/nullable_to_boolean_converter.ts +2 -3
- package/mantine/create_checkbox.tsx +2 -1
- package/mantine/create_fields_view.tsx +17 -14
- package/mantine/create_form.tsx +2 -2
- package/mantine/create_list.tsx +1 -1
- package/mantine/create_pill.tsx +1 -1
- package/mantine/create_radio.tsx +1 -1
- package/mantine/create_radio_group.tsx +6 -2
- package/mantine/create_text_input.tsx +9 -3
- package/mantine/create_value_input.tsx +2 -1
- package/mantine/error_renderer.ts +1 -1
- package/mantine/hooks.tsx +19 -14
- package/mantine/specs/__snapshots__/checkbox_hooks.tests.tsx.snap +1 -64
- package/mantine/specs/__snapshots__/fields_view_hooks.tests.tsx.snap +52 -52
- package/mantine/specs/__snapshots__/radio_group_hooks.tests.tsx.snap +1 -179
- package/mantine/specs/__snapshots__/select_hooks.tests.tsx.snap +1 -83
- package/mantine/specs/__snapshots__/text_input_hooks.tests.tsx.snap +27 -27
- package/mantine/specs/__snapshots__/value_input_hooks.tests.tsx.snap +31 -31
- package/mantine/specs/checkbox_hooks.stories.tsx +5 -21
- package/mantine/specs/fields_view_hooks.stories.tsx +16 -4
- package/mantine/specs/form_hooks.stories.tsx +10 -3
- package/mantine/specs/radio_group_hooks.stories.tsx +6 -20
- package/mantine/specs/select_hooks.stories.tsx +5 -21
- package/mantine/specs/text_input_hooks.stories.tsx +5 -8
- package/mantine/specs/value_input_hooks.stories.tsx +5 -8
- package/mantine/types.ts +7 -3
- package/package.json +2 -1
- /package/.out/core/mobx/specs/{merge_field_adapters_with_two_way_converter.d.ts → merge_field_adapters_with_two_way_converter.tests.d.ts} +0 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -7,12 +7,12 @@ $ tsup
|
|
|
7
7
|
[34mCLI[39m Target: esnext
|
|
8
8
|
[34mCJS[39m Build start
|
|
9
9
|
[34mESM[39m Build start
|
|
10
|
-
[32mCJS[39m [1mdist/index.cjs [22m[
|
|
11
|
-
[32mCJS[39m ⚡️ Build success in
|
|
12
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
13
|
-
[32mESM[39m ⚡️ Build success in
|
|
10
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m50.73 KB[39m
|
|
11
|
+
[32mCJS[39m ⚡️ Build success in 112ms
|
|
12
|
+
[32mESM[39m [1mdist/index.js [22m[32m46.84 KB[39m
|
|
13
|
+
[32mESM[39m ⚡️ Build success in 113ms
|
|
14
14
|
[34mDTS[39m Build start
|
|
15
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
-
[32mDTS[39m [1mdist/index.d.cts [22m[
|
|
17
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
18
|
-
Done in
|
|
15
|
+
[32mDTS[39m ⚡️ Build success in 9662ms
|
|
16
|
+
[32mDTS[39m [1mdist/index.d.cts [22m[32m36.94 KB[39m
|
|
17
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m36.94 KB[39m
|
|
18
|
+
Done in 10.73s.
|
package/core/mobx/hooks.ts
CHANGED
|
@@ -25,12 +25,16 @@ type ModelOfPresenter<P extends FormPresenter<any, any, any, any>> = ReturnType<
|
|
|
25
25
|
export function useDefaultMobxFormHooks<P extends FormPresenter<any, any, any, any>>(
|
|
26
26
|
presenter: P,
|
|
27
27
|
value: ValueOfPresenter<P>,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
{
|
|
29
|
+
onValidFieldSubmit,
|
|
30
|
+
onValidFormSubmit,
|
|
31
|
+
}: {
|
|
32
|
+
onValidFieldSubmit?: <Path extends ValuePathsOfPresenter<P>>(model: ModelOfPresenter<P>, valuePath: Path) => void,
|
|
33
|
+
onValidFormSubmit?: (model: ModelOfPresenter<P>, value: ValueOfPresenter<P>) => void,
|
|
34
|
+
},
|
|
32
35
|
): {
|
|
33
36
|
model: ModelOfPresenter<P>,
|
|
37
|
+
onFormSubmit?: (value: ValueOfPresenter<P>) => void,
|
|
34
38
|
} & Omit<FieldsViewProps<ModelOfPresenter<P>['fields']>, 'fields'> {
|
|
35
39
|
const model = useMemo(function () {
|
|
36
40
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
@@ -57,14 +61,14 @@ export function useDefaultMobxFormHooks<P extends FormPresenter<any, any, any, a
|
|
|
57
61
|
const onFieldSubmit = useCallback(
|
|
58
62
|
function<Path extends ValuePathsOfPresenter<P>> (valuePath: Path) {
|
|
59
63
|
if (presenter.validateField(model, valuePath)) {
|
|
60
|
-
|
|
64
|
+
onValidFieldSubmit?.(model, valuePath)
|
|
61
65
|
}
|
|
62
66
|
return false
|
|
63
67
|
},
|
|
64
68
|
[
|
|
65
69
|
presenter,
|
|
66
70
|
model,
|
|
67
|
-
|
|
71
|
+
onValidFieldSubmit,
|
|
68
72
|
],
|
|
69
73
|
)
|
|
70
74
|
|
|
@@ -85,10 +89,24 @@ export function useDefaultMobxFormHooks<P extends FormPresenter<any, any, any, a
|
|
|
85
89
|
],
|
|
86
90
|
)
|
|
87
91
|
|
|
92
|
+
const onFormSubmit = useCallback(
|
|
93
|
+
function () {
|
|
94
|
+
if (presenter.validateAll(model)) {
|
|
95
|
+
onValidFormSubmit?.(model, model.value)
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
[
|
|
99
|
+
presenter,
|
|
100
|
+
model,
|
|
101
|
+
onValidFormSubmit,
|
|
102
|
+
],
|
|
103
|
+
)
|
|
104
|
+
|
|
88
105
|
return {
|
|
89
106
|
model,
|
|
90
107
|
onFieldValueChange,
|
|
91
108
|
onFieldSubmit,
|
|
92
109
|
onFieldBlur,
|
|
110
|
+
onFormSubmit,
|
|
93
111
|
}
|
|
94
112
|
}
|
|
@@ -85,14 +85,14 @@ describe('all', function () {
|
|
|
85
85
|
ValueOfType<typeof typeDef>
|
|
86
86
|
>
|
|
87
87
|
>
|
|
88
|
-
|
|
88
|
+
type C = Partial<{
|
|
89
89
|
readonly $: ConvenientFieldAdapter<Readonly<Record<'a' | 'b', number>>, ValueOfType<typeof typeDef>>,
|
|
90
90
|
readonly ['$.a']: ConvenientFieldAdapter<number, ValueOfType<typeof typeDef>>,
|
|
91
91
|
readonly ['$.b']: ConvenientFieldAdapter<number, ValueOfType<typeof typeDef>>,
|
|
92
92
|
}>
|
|
93
93
|
|
|
94
94
|
it('equals expected type', function () {
|
|
95
|
-
expectTypeOf(
|
|
95
|
+
expectTypeOf<C>().toEqualTypeOf<T>()
|
|
96
96
|
})
|
|
97
97
|
})
|
|
98
98
|
|
|
@@ -104,13 +104,13 @@ describe('all', function () {
|
|
|
104
104
|
FlattenedValuesOfType<typeof typeDef>,
|
|
105
105
|
ValueOfType<typeof typeDef>
|
|
106
106
|
>
|
|
107
|
-
|
|
107
|
+
type C = Partial<{
|
|
108
108
|
readonly $: ConvenientFieldAdapter<{ readonly x: string, readonly y: boolean }, ValueOfType<typeof typeDef>>,
|
|
109
109
|
readonly ['$.x']: ConvenientFieldAdapter<string, ValueOfType<typeof typeDef>>,
|
|
110
110
|
readonly ['$.y']: ConvenientFieldAdapter<boolean, ValueOfType<typeof typeDef>>,
|
|
111
111
|
}>
|
|
112
112
|
it('equals expected type', function () {
|
|
113
|
-
expectTypeOf(
|
|
113
|
+
expectTypeOf<C>().toEqualTypeOf<T>()
|
|
114
114
|
})
|
|
115
115
|
|
|
116
116
|
it('matches representative adapters', function () {
|
|
@@ -147,12 +147,12 @@ describe('all', function () {
|
|
|
147
147
|
A,
|
|
148
148
|
typeof valuePathsToTypePaths
|
|
149
149
|
>
|
|
150
|
-
|
|
150
|
+
type C = {
|
|
151
151
|
readonly '$.a': A['$.x'],
|
|
152
152
|
readonly '$.b': A['$.y'],
|
|
153
153
|
}
|
|
154
154
|
it('equals expected type', function () {
|
|
155
|
-
expectTypeOf(
|
|
155
|
+
expectTypeOf<C>().toEqualTypeOf<T>()
|
|
156
156
|
})
|
|
157
157
|
})
|
|
158
158
|
})
|
|
@@ -33,14 +33,14 @@ describe('MergedOfFieldAdapterWithTwoWayConverter', function () {
|
|
|
33
33
|
}
|
|
34
34
|
type M = MergedOfFieldAdaptersWithTwoWayConverter<T, typeof error4, typeof context>
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
type C = {
|
|
37
37
|
readonly x: FieldAdapter<boolean, string, typeof error1 | typeof error4, 'x', typeof context>,
|
|
38
38
|
readonly y: FieldAdapter<number, boolean, typeof error2 | typeof error4, 'y', typeof context>,
|
|
39
39
|
readonly z: FieldAdapter<string, number, typeof error3 | typeof error4, 'z', typeof context>,
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
it('merges the errors', function () {
|
|
43
|
-
expectTypeOf<M>().toEqualTypeOf(
|
|
43
|
+
expectTypeOf<M>().toEqualTypeOf<C>()
|
|
44
44
|
})
|
|
45
45
|
})
|
|
46
46
|
|
|
@@ -117,23 +117,23 @@ describe('mergeFieldAdaptersWithTwoWayConverter', function () {
|
|
|
117
117
|
beforeEach(function () {
|
|
118
118
|
result = merged.booleanAdapter.revert!(true, 'booleanAdapter', context)
|
|
119
119
|
})
|
|
120
|
-
})
|
|
121
120
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
121
|
+
it('returns the same value on revert', function () {
|
|
122
|
+
expect(result).toEqual(expect.objectContaining({
|
|
123
|
+
value: true,
|
|
124
|
+
type: UnreliableFieldConversionType.Success,
|
|
125
|
+
}))
|
|
126
|
+
})
|
|
128
127
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
128
|
+
it('calls the mocked converter', function () {
|
|
129
|
+
expect(converter.revert).toHaveBeenCalledOnce()
|
|
130
|
+
expect(converter.revert).toHaveBeenCalledWith(true, 'booleanAdapter', context)
|
|
131
|
+
})
|
|
133
132
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
133
|
+
it('calls the mocked adapter', function () {
|
|
134
|
+
expect(booleanAdapter.revert).toHaveBeenCalledOnce()
|
|
135
|
+
expect(booleanAdapter.revert).toHaveBeenCalledWith(true, 'booleanAdapter', context)
|
|
136
|
+
})
|
|
137
137
|
})
|
|
138
138
|
})
|
|
139
139
|
})
|
|
@@ -1,15 +1,26 @@
|
|
|
1
|
+
import {
|
|
2
|
+
list,
|
|
3
|
+
numberType,
|
|
4
|
+
object,
|
|
5
|
+
stringType,
|
|
6
|
+
} from '@strictly/define'
|
|
1
7
|
import { type FieldAdapter } from 'core/mobx/field_adapter'
|
|
2
8
|
import {
|
|
3
9
|
subFormFieldAdapters,
|
|
4
10
|
} from 'core/mobx/sub_form_field_adapters'
|
|
5
|
-
import {
|
|
11
|
+
import { UnreliableFieldConversionType } from 'types/field_converters'
|
|
12
|
+
import {
|
|
13
|
+
mockDeep,
|
|
14
|
+
mockReset,
|
|
15
|
+
} from 'vitest-mock-extended'
|
|
6
16
|
|
|
7
17
|
describe('subFormFieldAdapters', () => {
|
|
8
|
-
const fieldAdapter1: FieldAdapter<string, boolean> = mockDeep()
|
|
9
|
-
const fieldAdapter2: FieldAdapter<number, boolean> = mockDeep()
|
|
10
|
-
|
|
11
18
|
describe('empty value', () => {
|
|
12
|
-
const adapters = subFormFieldAdapters(
|
|
19
|
+
const adapters = subFormFieldAdapters(
|
|
20
|
+
{},
|
|
21
|
+
'$.a',
|
|
22
|
+
stringType,
|
|
23
|
+
)
|
|
13
24
|
|
|
14
25
|
it('equals expected type', () => {
|
|
15
26
|
expectTypeOf(adapters).toEqualTypeOf<{}>()
|
|
@@ -21,29 +32,102 @@ describe('subFormFieldAdapters', () => {
|
|
|
21
32
|
})
|
|
22
33
|
|
|
23
34
|
describe('single adapter', () => {
|
|
24
|
-
const
|
|
35
|
+
const mockedFieldAdapter1 = mockDeep<Required<FieldAdapter<string, boolean, number, '$', string>>>()
|
|
36
|
+
const fieldAdapter1: FieldAdapter<string, boolean, number, '$', string> = mockedFieldAdapter1
|
|
37
|
+
|
|
38
|
+
const type = object().field('a', stringType)
|
|
39
|
+
const subAdapters = {
|
|
25
40
|
$: fieldAdapter1,
|
|
26
|
-
}
|
|
41
|
+
} as const
|
|
42
|
+
const adapters = subFormFieldAdapters<
|
|
43
|
+
typeof subAdapters,
|
|
44
|
+
'$.a',
|
|
45
|
+
{ '$.a': '$.a' },
|
|
46
|
+
typeof type
|
|
47
|
+
>(
|
|
48
|
+
subAdapters,
|
|
49
|
+
'$.a',
|
|
50
|
+
type,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
mockReset(mockedFieldAdapter1)
|
|
55
|
+
})
|
|
27
56
|
|
|
28
57
|
it('equals expected type', () => {
|
|
29
|
-
|
|
30
|
-
|
|
58
|
+
// TODO toEqualTypeOf (cannot reason about revert optionality, seems to be a TS issue as they
|
|
59
|
+
// are both optional AFAICT)
|
|
60
|
+
expectTypeOf(adapters).toMatchTypeOf<{
|
|
61
|
+
'$.a': FieldAdapter<string, boolean, number, '$.a', {
|
|
62
|
+
a: string,
|
|
63
|
+
}>,
|
|
31
64
|
}>()
|
|
32
65
|
})
|
|
33
66
|
|
|
34
67
|
it('equals expected value', () => {
|
|
35
|
-
expect(adapters).toEqual({ '$.a':
|
|
68
|
+
expect(adapters).toEqual({ '$.a': expect.anything() })
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('calls convert with the correct paths and values', () => {
|
|
72
|
+
const mockedReturnedValue = {
|
|
73
|
+
value: false,
|
|
74
|
+
required: false,
|
|
75
|
+
readonly: false,
|
|
76
|
+
}
|
|
77
|
+
mockedFieldAdapter1.convert.mockReturnValue(mockedReturnedValue)
|
|
78
|
+
|
|
79
|
+
const returnedValue = adapters['$.a'].convert('x', '$.a', { a: 'y' })
|
|
80
|
+
expect(fieldAdapter1.convert).toHaveBeenCalledWith('x', '$', 'y')
|
|
81
|
+
expect(returnedValue).toEqual(mockedReturnedValue)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('calls revert with the correct paths and values', () => {
|
|
85
|
+
const mockedReturnedValue = {
|
|
86
|
+
type: UnreliableFieldConversionType.Success,
|
|
87
|
+
value: 'ok',
|
|
88
|
+
} as const
|
|
89
|
+
mockedFieldAdapter1.revert.mockReturnValue(mockedReturnedValue)
|
|
90
|
+
|
|
91
|
+
const returnedValue = adapters['$.a'].revert?.(true, '$.a', { a: 'y' })
|
|
92
|
+
expect(fieldAdapter1.revert).toHaveBeenCalledWith(true, '$', 'y')
|
|
93
|
+
expect(returnedValue).toEqual(mockedReturnedValue)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('calls create with the correct paths and values', () => {
|
|
97
|
+
const mockedReturnedValue = 'x'
|
|
98
|
+
mockedFieldAdapter1.create.mockReturnValue(mockedReturnedValue)
|
|
99
|
+
|
|
100
|
+
const returnedValue = adapters['$.a'].create('$.a', { a: 'y' })
|
|
101
|
+
expect(fieldAdapter1.create).toHaveBeenCalledWith('$', 'y')
|
|
102
|
+
expect(returnedValue).toEqual(mockedReturnedValue)
|
|
36
103
|
})
|
|
37
104
|
})
|
|
38
105
|
|
|
39
106
|
describe('multiple adapters', () => {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
107
|
+
const mockedFieldAdapter1 = mockDeep<Required<FieldAdapter<string, boolean>>>()
|
|
108
|
+
const fieldAdapter1: FieldAdapter<string, boolean, number> = mockedFieldAdapter1
|
|
109
|
+
const mockedFieldAdapter2 = mockDeep<FieldAdapter<number, boolean>>()
|
|
110
|
+
const fieldAdapter2: FieldAdapter<number, boolean> = mockedFieldAdapter2
|
|
111
|
+
|
|
112
|
+
const type = object()
|
|
113
|
+
.field('a', object().field('x', stringType).field('y', numberType))
|
|
114
|
+
const adapters = subFormFieldAdapters(
|
|
115
|
+
{
|
|
116
|
+
'$.x': fieldAdapter1,
|
|
117
|
+
'$.y': fieldAdapter2,
|
|
118
|
+
},
|
|
119
|
+
'$.a',
|
|
120
|
+
type,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
beforeEach(() => {
|
|
124
|
+
mockReset(mockedFieldAdapter1)
|
|
125
|
+
mockReset(mockedFieldAdapter2)
|
|
126
|
+
})
|
|
44
127
|
|
|
45
128
|
it('equals expected type', () => {
|
|
46
|
-
|
|
129
|
+
// TODO toEqualTypeOf (seems to be a TS error)
|
|
130
|
+
expectTypeOf(adapters).toMatchTypeOf<{
|
|
47
131
|
'$.a.x': FieldAdapter<string, boolean>,
|
|
48
132
|
'$.a.y': FieldAdapter<number, boolean>,
|
|
49
133
|
}>()
|
|
@@ -51,8 +135,100 @@ describe('subFormFieldAdapters', () => {
|
|
|
51
135
|
|
|
52
136
|
it('equals expected value', () => {
|
|
53
137
|
expect(adapters).toEqual({
|
|
54
|
-
'$.a.x':
|
|
55
|
-
'$.a.y':
|
|
138
|
+
'$.a.x': expect.anything(),
|
|
139
|
+
'$.a.y': expect.anything(),
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
describe('calls convert with correct paths and values', () => {
|
|
144
|
+
const subContext = {
|
|
145
|
+
x: 'a',
|
|
146
|
+
y: 1,
|
|
147
|
+
} as const
|
|
148
|
+
const context = {
|
|
149
|
+
a: subContext,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
it('calls $.a.x', () => {
|
|
153
|
+
const mockedReturnedValue = {
|
|
154
|
+
value: true,
|
|
155
|
+
readonly: true,
|
|
156
|
+
required: false,
|
|
157
|
+
}
|
|
158
|
+
mockedFieldAdapter1.convert.mockReturnValue(mockedReturnedValue)
|
|
159
|
+
|
|
160
|
+
const returnedValue = adapters['$.a.x'].convert('b', '$.a.x', context)
|
|
161
|
+
expect(fieldAdapter1.convert).toHaveBeenCalledWith('b', '$.x', subContext)
|
|
162
|
+
expect(returnedValue).toEqual(mockedReturnedValue)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('calls $.a.y', () => {
|
|
166
|
+
const mockedReturnedValue = {
|
|
167
|
+
value: false,
|
|
168
|
+
readonly: false,
|
|
169
|
+
required: false,
|
|
170
|
+
}
|
|
171
|
+
mockedFieldAdapter2.convert.mockReturnValue(mockedReturnedValue)
|
|
172
|
+
|
|
173
|
+
const returnedValue = adapters['$.a.y'].convert(2, '$.a.y', context)
|
|
174
|
+
expect(fieldAdapter2.convert).toHaveBeenCalledWith(2, '$.y', subContext)
|
|
175
|
+
expect(returnedValue).toEqual(mockedReturnedValue)
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
describe('list adapter', () => {
|
|
181
|
+
const mockedFieldAdapter1 = mockDeep<Required<FieldAdapter<string, boolean, number, '$', string>>>()
|
|
182
|
+
const fieldAdapter1: FieldAdapter<string, boolean, number, '$', string> = mockedFieldAdapter1
|
|
183
|
+
const type = list(stringType)
|
|
184
|
+
const subAdapters = {
|
|
185
|
+
$: fieldAdapter1,
|
|
186
|
+
}
|
|
187
|
+
const adapters = subFormFieldAdapters<
|
|
188
|
+
typeof subAdapters,
|
|
189
|
+
'$.*',
|
|
190
|
+
{
|
|
191
|
+
'$.*': `$.${number}`,
|
|
192
|
+
},
|
|
193
|
+
typeof type
|
|
194
|
+
>(
|
|
195
|
+
subAdapters,
|
|
196
|
+
'$.*',
|
|
197
|
+
type,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
beforeEach(() => {
|
|
201
|
+
mockReset(mockedFieldAdapter1)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it('equals expected type', () => {
|
|
205
|
+
// TODO toEqualTypeOf (seems to be a TS error)
|
|
206
|
+
expectTypeOf(adapters).toMatchTypeOf<{
|
|
207
|
+
'$.*': FieldAdapter<string, boolean, number, `$.${number}`, string[]>,
|
|
208
|
+
}>()
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('equals expected value', () => {
|
|
212
|
+
expect(adapters).toEqual({
|
|
213
|
+
'$.*': expect.anything(),
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
describe('calls convert with correct paths and values', () => {
|
|
218
|
+
const subContext = 'a'
|
|
219
|
+
const context = [subContext]
|
|
220
|
+
|
|
221
|
+
it('calls $.*', () => {
|
|
222
|
+
const mockedReturnedValue = {
|
|
223
|
+
value: false,
|
|
224
|
+
readonly: false,
|
|
225
|
+
required: false,
|
|
226
|
+
}
|
|
227
|
+
mockedFieldAdapter1.convert.mockReturnValue(mockedReturnedValue)
|
|
228
|
+
|
|
229
|
+
const returnedValue = adapters['$.*'].convert('b', '$.0', context)
|
|
230
|
+
expect(fieldAdapter1.convert).toHaveBeenCalledWith('b', '$', subContext)
|
|
231
|
+
expect(returnedValue).toEqual(mockedReturnedValue)
|
|
56
232
|
})
|
|
57
233
|
})
|
|
58
234
|
})
|
|
@@ -1,21 +1,84 @@
|
|
|
1
1
|
import { type StringConcatOf } from '@strictly/base'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
flattenValuesOfType,
|
|
4
|
+
type Type,
|
|
5
|
+
type ValueOfType,
|
|
6
|
+
} from '@strictly/define'
|
|
7
|
+
import {
|
|
8
|
+
type ErrorOfFieldAdapter,
|
|
9
|
+
type FieldAdapter,
|
|
10
|
+
type FromOfFieldAdapter,
|
|
11
|
+
type ToOfFieldAdapter,
|
|
12
|
+
type ValuePathOfFieldAdapter,
|
|
13
|
+
} from './field_adapter'
|
|
3
14
|
|
|
4
|
-
type
|
|
5
|
-
|
|
15
|
+
type SubFormFieldAdapter<F extends FieldAdapter, ValuePath extends string, Context> = FieldAdapter<
|
|
16
|
+
FromOfFieldAdapter<F>,
|
|
17
|
+
ToOfFieldAdapter<F>,
|
|
18
|
+
ErrorOfFieldAdapter<F>,
|
|
19
|
+
ValuePathOfFieldAdapter<F> extends StringConcatOf<'$', infer ValuePathSuffix> ? `${ValuePath}${ValuePathSuffix}`
|
|
20
|
+
// assume string (they don't care about the value path as a type) if there the path doesn't have a $ prefix
|
|
21
|
+
: string,
|
|
22
|
+
Context
|
|
23
|
+
>
|
|
24
|
+
|
|
25
|
+
type SubFormFieldAdapters<
|
|
26
|
+
SubAdapters extends Record<string, FieldAdapter>,
|
|
27
|
+
TypePath extends string,
|
|
28
|
+
ValuePath extends string,
|
|
29
|
+
Context,
|
|
30
|
+
> = {
|
|
31
|
+
[
|
|
32
|
+
K in keyof SubAdapters as K extends StringConcatOf<'$', infer TypePathSuffix> ? `${TypePath}${TypePathSuffix}`
|
|
33
|
+
: never
|
|
34
|
+
]: SubFormFieldAdapter<
|
|
35
|
+
SubAdapters[K],
|
|
36
|
+
ValuePath,
|
|
37
|
+
Context
|
|
38
|
+
>
|
|
6
39
|
}
|
|
7
40
|
|
|
8
|
-
export function subFormFieldAdapters<
|
|
41
|
+
export function subFormFieldAdapters<
|
|
42
|
+
SubAdapters extends Record<string, FieldAdapter>,
|
|
43
|
+
TypePath extends string,
|
|
44
|
+
TypePathsToValuePaths extends Record<TypePath, string>,
|
|
45
|
+
ContextType extends Type,
|
|
46
|
+
>(
|
|
9
47
|
subAdapters: SubAdapters,
|
|
10
|
-
|
|
11
|
-
|
|
48
|
+
parentTypePath: TypePath,
|
|
49
|
+
contextType: ContextType,
|
|
50
|
+
): SubFormFieldAdapters<SubAdapters, TypePath, TypePathsToValuePaths[TypePath], ValueOfType<ContextType>> {
|
|
51
|
+
// assume the number of '.' in the type path will correspond to the number of '.' in the value path
|
|
52
|
+
const dotCount = parentTypePath.split('.').length
|
|
53
|
+
function getSubValuePathAndContext(valuePath: string, context: ValueOfType<ContextType>) {
|
|
54
|
+
const parentValuePath = valuePath.split('.').slice(0, dotCount).join('.')
|
|
55
|
+
const subValuePath = valuePath.replace(parentValuePath, '$')
|
|
56
|
+
const subContext = flattenValuesOfType(contextType, context)[parentValuePath]
|
|
57
|
+
return [
|
|
58
|
+
subValuePath,
|
|
59
|
+
subContext,
|
|
60
|
+
] as const
|
|
61
|
+
}
|
|
62
|
+
|
|
12
63
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
13
64
|
return Object.entries(subAdapters).reduce<Record<string, FieldAdapter>>((acc, [
|
|
14
|
-
|
|
15
|
-
|
|
65
|
+
subTypePath,
|
|
66
|
+
subAdapter,
|
|
16
67
|
]) => {
|
|
17
|
-
const
|
|
18
|
-
|
|
68
|
+
const typePath = subTypePath.replace('$', parentTypePath)
|
|
69
|
+
// adapt field adapter with new path and context
|
|
70
|
+
const adaptedAdapter: FieldAdapter = {
|
|
71
|
+
convert: (from, valuePath, context) => {
|
|
72
|
+
return subAdapter.convert(from, ...getSubValuePathAndContext(valuePath, context))
|
|
73
|
+
},
|
|
74
|
+
create: (valuePath, context) => {
|
|
75
|
+
return subAdapter.create(...getSubValuePathAndContext(valuePath, context))
|
|
76
|
+
},
|
|
77
|
+
revert: subAdapter.revert && ((from, valuePath, context) => {
|
|
78
|
+
return subAdapter.revert!(from, ...getSubValuePathAndContext(valuePath, context))
|
|
79
|
+
}),
|
|
80
|
+
}
|
|
81
|
+
acc[typePath] = adaptedAdapter
|
|
19
82
|
return acc
|
|
20
|
-
}, {}) as SubFormFieldAdapters<SubAdapters,
|
|
83
|
+
}, {}) as SubFormFieldAdapters<SubAdapters, TypePath, TypePathsToValuePaths[TypePath], ValueOfType<ContextType>>
|
|
21
84
|
}
|