@ditojs/admin 2.86.0 → 2.88.0
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/package.json +6 -6
- package/types/index.d.ts +2535 -431
- package/types/tests/admin.test-d.ts +27 -0
- package/types/tests/component-buttons.test-d.ts +44 -0
- package/types/tests/component-list.test-d.ts +159 -0
- package/types/tests/component-misc.test-d.ts +137 -0
- package/types/tests/component-object.test-d.ts +69 -0
- package/types/tests/component-section.test-d.ts +174 -0
- package/types/tests/component-select.test-d.ts +107 -0
- package/types/tests/components.test-d.ts +81 -0
- package/types/tests/context.test-d.ts +31 -0
- package/types/tests/fixtures.ts +24 -0
- package/types/tests/form.test-d.ts +109 -0
- package/types/tests/instance.test-d.ts +20 -0
- package/types/tests/schema-features.test-d.ts +402 -0
- package/types/tests/variance.test-d.ts +125 -0
- package/types/tests/view.test-d.ts +146 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { assertType, describe, it } from 'vitest'
|
|
2
|
+
import type { default as DitoAdmin, View } from '../index.d.ts'
|
|
3
|
+
import type { Entry } from './fixtures.ts'
|
|
4
|
+
|
|
5
|
+
describe('DitoAdmin', () => {
|
|
6
|
+
it('constructor accepts element and views option', () => {
|
|
7
|
+
assertType<ConstructorParameters<typeof DitoAdmin>>([
|
|
8
|
+
document.body,
|
|
9
|
+
{
|
|
10
|
+
views: {
|
|
11
|
+
entries: {
|
|
12
|
+
type: 'view',
|
|
13
|
+
component: {
|
|
14
|
+
type: 'list',
|
|
15
|
+
form: {
|
|
16
|
+
type: 'form',
|
|
17
|
+
components: {
|
|
18
|
+
title: { type: 'text' }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
} satisfies View<Entry>
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
])
|
|
26
|
+
})
|
|
27
|
+
})
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { expectTypeOf, assertType, describe, it } from 'vitest'
|
|
2
|
+
import type { Buttons } from '../index.d.ts'
|
|
3
|
+
import type { Entry } from './fixtures.ts'
|
|
4
|
+
|
|
5
|
+
describe('Buttons', () => {
|
|
6
|
+
it('accepts button without type', () => {
|
|
7
|
+
assertType<Buttons<Entry>>({
|
|
8
|
+
save: { text: 'Save' }
|
|
9
|
+
})
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('accepts button with type', () => {
|
|
13
|
+
assertType<Buttons<Entry>>({
|
|
14
|
+
save: { type: 'button', text: 'Save' }
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('click callback receives typed item', () => {
|
|
19
|
+
assertType<Buttons<Entry>>({
|
|
20
|
+
save: {
|
|
21
|
+
text: 'Save',
|
|
22
|
+
events: {
|
|
23
|
+
click({ item }) {
|
|
24
|
+
expectTypeOf(item).not.toBeAny()
|
|
25
|
+
expectTypeOf(item).toHaveProperty('id')
|
|
26
|
+
expectTypeOf(item).toHaveProperty('title')
|
|
27
|
+
expectTypeOf(item.id).toBeNumber()
|
|
28
|
+
expectTypeOf(item.title).toBeString()
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('rejects invalid button type', () => {
|
|
36
|
+
assertType<Buttons<Entry>>({
|
|
37
|
+
save: {
|
|
38
|
+
// @ts-expect-error 'text' is not a valid button type
|
|
39
|
+
type: 'text',
|
|
40
|
+
text: 'Save'
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
})
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { expectTypeOf, assertType, describe, it } from 'vitest'
|
|
2
|
+
import type {
|
|
3
|
+
Components,
|
|
4
|
+
Form,
|
|
5
|
+
ListSchema,
|
|
6
|
+
ColumnSchema,
|
|
7
|
+
DitoContext
|
|
8
|
+
} from '../index.d.ts'
|
|
9
|
+
import type { Entry, Parent } from './fixtures.ts'
|
|
10
|
+
|
|
11
|
+
describe('ListSchema', () => {
|
|
12
|
+
it('accepts form components typed against $Item', () => {
|
|
13
|
+
assertType<ListSchema<Entry>>({
|
|
14
|
+
type: 'list',
|
|
15
|
+
form: {
|
|
16
|
+
type: 'form',
|
|
17
|
+
components: {
|
|
18
|
+
title: { type: 'text' }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('accepts columns as a record of ColumnSchema', () => {
|
|
25
|
+
assertType<ListSchema<Entry>>({
|
|
26
|
+
type: 'list',
|
|
27
|
+
columns: {
|
|
28
|
+
title: {
|
|
29
|
+
label: 'Title',
|
|
30
|
+
sortable: true
|
|
31
|
+
} satisfies ColumnSchema<Entry>
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('column render callback receives typed value', () => {
|
|
37
|
+
assertType<ListSchema<Entry>>({
|
|
38
|
+
type: 'list',
|
|
39
|
+
columns: {
|
|
40
|
+
title: {
|
|
41
|
+
render({ value }) {
|
|
42
|
+
expectTypeOf(value).not.toBeAny()
|
|
43
|
+
expectTypeOf(value).toBeString()
|
|
44
|
+
return value
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('accepts columns as an array of item keys', () => {
|
|
52
|
+
assertType<ListSchema<Entry>>({
|
|
53
|
+
type: 'list',
|
|
54
|
+
columns: ['title', 'id']
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('rejects unknown keys in columns array', () => {
|
|
59
|
+
assertType<ListSchema<Entry>>({
|
|
60
|
+
type: 'list',
|
|
61
|
+
// @ts-expect-error 'missing' is not a key of Entry
|
|
62
|
+
columns: ['title', 'missing']
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('accepts creatable as boolean or callback', () => {
|
|
67
|
+
assertType<ListSchema<Entry>>({
|
|
68
|
+
type: 'list',
|
|
69
|
+
creatable: true
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
assertType<ListSchema<Entry>>({
|
|
73
|
+
type: 'list',
|
|
74
|
+
creatable(ctx) {
|
|
75
|
+
expectTypeOf(ctx).not.toBeAny()
|
|
76
|
+
expectTypeOf(ctx).toMatchTypeOf<DitoContext<Entry>>()
|
|
77
|
+
expectTypeOf(ctx.item.title).toBeString()
|
|
78
|
+
return true
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('types list correctly inside Components', () => {
|
|
84
|
+
assertType<Components<Parent>>({
|
|
85
|
+
entries: {
|
|
86
|
+
type: 'list',
|
|
87
|
+
form: {
|
|
88
|
+
type: 'form',
|
|
89
|
+
components: {
|
|
90
|
+
title: { type: 'text' }
|
|
91
|
+
}
|
|
92
|
+
} satisfies Form<Entry>
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('rejects wrong form type via typed variable', () => {
|
|
98
|
+
const parentForm: Form<Parent> = {
|
|
99
|
+
type: 'form',
|
|
100
|
+
components: {
|
|
101
|
+
title: { type: 'text' },
|
|
102
|
+
entries: { type: 'list' }
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
assertType<ListSchema<Entry>>({
|
|
107
|
+
type: 'list',
|
|
108
|
+
// @ts-expect-error Form<Parent> is not assignable to ResolvableForm<Entry>
|
|
109
|
+
form: parentForm
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('accepts draggable as OrItemAccessor', () => {
|
|
114
|
+
assertType<ListSchema<Entry>>({
|
|
115
|
+
type: 'list',
|
|
116
|
+
draggable(ctx) {
|
|
117
|
+
expectTypeOf(ctx).not.toBeAny()
|
|
118
|
+
expectTypeOf(ctx).toMatchTypeOf<DitoContext<Entry>>()
|
|
119
|
+
return true
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('accepts scopes as string array or object', () => {
|
|
125
|
+
assertType<ListSchema<Entry>>({
|
|
126
|
+
type: 'list',
|
|
127
|
+
scopes: ['active', 'archived']
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
assertType<ListSchema<Entry>>({
|
|
131
|
+
type: 'list',
|
|
132
|
+
scopes: {
|
|
133
|
+
active: { label: 'Active', defaultScope: true },
|
|
134
|
+
archived: 'Archived'
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('accepts itemLabel as string, callback, or false', () => {
|
|
140
|
+
assertType<ListSchema<Entry>>({
|
|
141
|
+
type: 'list',
|
|
142
|
+
itemLabel: 'title'
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
assertType<ListSchema<Entry>>({
|
|
146
|
+
type: 'list',
|
|
147
|
+
itemLabel: false
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
assertType<ListSchema<Entry>>({
|
|
151
|
+
type: 'list',
|
|
152
|
+
itemLabel({ item }) {
|
|
153
|
+
expectTypeOf(item).not.toBeAny()
|
|
154
|
+
expectTypeOf(item.title).toBeString()
|
|
155
|
+
return item.title
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
})
|
|
159
|
+
})
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { expectTypeOf, assertType, describe, it } from 'vitest'
|
|
2
|
+
import type { MenuSchema, View, NumberSchema, Form } from '../index.d.ts'
|
|
3
|
+
import type { Entry } from './fixtures.ts'
|
|
4
|
+
|
|
5
|
+
describe('MenuSchema', () => {
|
|
6
|
+
it('accepts menu with items containing views', () => {
|
|
7
|
+
assertType<MenuSchema<Entry>>({
|
|
8
|
+
type: 'menu',
|
|
9
|
+
label: 'Main Menu',
|
|
10
|
+
name: 'mainMenu',
|
|
11
|
+
items: {
|
|
12
|
+
entries: {
|
|
13
|
+
type: 'view',
|
|
14
|
+
component: {
|
|
15
|
+
type: 'list',
|
|
16
|
+
form: {
|
|
17
|
+
type: 'form',
|
|
18
|
+
components: {
|
|
19
|
+
title: { type: 'text' }
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('View union accepts both ViewSchema and MenuSchema', () => {
|
|
29
|
+
const viewSchema: View<Entry> = {
|
|
30
|
+
type: 'view',
|
|
31
|
+
components: {
|
|
32
|
+
title: { type: 'text' }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const menuSchema: View<Entry> = {
|
|
37
|
+
type: 'menu',
|
|
38
|
+
label: 'Group',
|
|
39
|
+
items: {
|
|
40
|
+
sub: {
|
|
41
|
+
type: 'view',
|
|
42
|
+
components: {
|
|
43
|
+
title: { type: 'text' }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
assertType<View<Entry>>(viewSchema)
|
|
50
|
+
assertType<View<Entry>>(menuSchema)
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
describe('NumberSchema', () => {
|
|
55
|
+
it('accepts min, max, step, decimals', () => {
|
|
56
|
+
assertType<NumberSchema<Entry>>({
|
|
57
|
+
type: 'number',
|
|
58
|
+
min: 0,
|
|
59
|
+
max: 100,
|
|
60
|
+
step: 0.5,
|
|
61
|
+
decimals: 2
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('accepts range as tuple', () => {
|
|
66
|
+
assertType<NumberSchema<Entry>>({
|
|
67
|
+
type: 'integer',
|
|
68
|
+
range: [0, 100]
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('accepts rules including integer', () => {
|
|
73
|
+
assertType<NumberSchema<Entry>>({
|
|
74
|
+
type: 'number',
|
|
75
|
+
rules: {
|
|
76
|
+
min: 0,
|
|
77
|
+
max: 999,
|
|
78
|
+
integer: true
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
describe('SchemaAffixMixin (prefix/suffix)', () => {
|
|
85
|
+
it('accepts prefix as string', () => {
|
|
86
|
+
assertType<NumberSchema<Entry>>({
|
|
87
|
+
type: 'number',
|
|
88
|
+
prefix: '$'
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('accepts prefix as array of SchemaAffix objects', () => {
|
|
93
|
+
assertType<NumberSchema<Entry>>({
|
|
94
|
+
type: 'number',
|
|
95
|
+
prefix: [
|
|
96
|
+
{ type: 'icon', text: 'currency' },
|
|
97
|
+
{ html: '<b>$</b>' }
|
|
98
|
+
]
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('accepts suffix with conditional if callback', () => {
|
|
103
|
+
assertType<NumberSchema<Entry>>({
|
|
104
|
+
type: 'number',
|
|
105
|
+
suffix: {
|
|
106
|
+
text: '%',
|
|
107
|
+
if({ item }) {
|
|
108
|
+
expectTypeOf(item).not.toBeAny()
|
|
109
|
+
return item.id > 0
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
describe('ClipboardConfig', () => {
|
|
117
|
+
it('accepts copy/paste callbacks on a form', () => {
|
|
118
|
+
assertType<Form<Entry>>({
|
|
119
|
+
type: 'form',
|
|
120
|
+
clipboard: {
|
|
121
|
+
copy(context) {
|
|
122
|
+
expectTypeOf(context).not.toBeAny()
|
|
123
|
+
expectTypeOf(context.item).not.toBeAny()
|
|
124
|
+
return context.item
|
|
125
|
+
},
|
|
126
|
+
paste(context) {
|
|
127
|
+
expectTypeOf(context).not.toBeAny()
|
|
128
|
+
expectTypeOf(context.item).not.toBeAny()
|
|
129
|
+
return context.item
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
components: {
|
|
133
|
+
title: { type: 'text' }
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
})
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { expectTypeOf, assertType, describe, it } from 'vitest'
|
|
2
|
+
import type {
|
|
3
|
+
ObjectSchema,
|
|
4
|
+
TreeListSchema,
|
|
5
|
+
Form,
|
|
6
|
+
Components
|
|
7
|
+
} from '../index.d.ts'
|
|
8
|
+
import type { Address, Entry, ParentWithAddress } from './fixtures.ts'
|
|
9
|
+
|
|
10
|
+
describe('ObjectSchema', () => {
|
|
11
|
+
it('accepts typed form', () => {
|
|
12
|
+
assertType<ObjectSchema<Address>>({
|
|
13
|
+
type: 'object',
|
|
14
|
+
form: {
|
|
15
|
+
type: 'form',
|
|
16
|
+
components: {
|
|
17
|
+
street: { type: 'text' },
|
|
18
|
+
city: { type: 'text' }
|
|
19
|
+
}
|
|
20
|
+
} satisfies Form<Address>
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('accepts typed columns', () => {
|
|
25
|
+
assertType<ObjectSchema<Address>>({
|
|
26
|
+
type: 'object',
|
|
27
|
+
columns: {
|
|
28
|
+
street: { label: 'Street' },
|
|
29
|
+
city: { label: 'City' }
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('rejects unknown key in columns array', () => {
|
|
35
|
+
assertType<ObjectSchema<Address>>({
|
|
36
|
+
type: 'object',
|
|
37
|
+
// @ts-expect-error 'zipCode' is not a key of Address
|
|
38
|
+
columns: ['street', 'zipCode']
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('works inside Components for a data key', () => {
|
|
43
|
+
assertType<Components<ParentWithAddress>>({
|
|
44
|
+
address: {
|
|
45
|
+
type: 'object',
|
|
46
|
+
form: {
|
|
47
|
+
type: 'form',
|
|
48
|
+
components: {
|
|
49
|
+
title: { type: 'text' }
|
|
50
|
+
}
|
|
51
|
+
} satisfies Form<ParentWithAddress>
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
describe('TreeListSchema', () => {
|
|
58
|
+
it('accepts typed form', () => {
|
|
59
|
+
assertType<TreeListSchema<Entry>>({
|
|
60
|
+
type: 'tree-list',
|
|
61
|
+
form: {
|
|
62
|
+
type: 'form',
|
|
63
|
+
components: {
|
|
64
|
+
title: { type: 'text' }
|
|
65
|
+
}
|
|
66
|
+
} satisfies Form<Entry>
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
})
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { expectTypeOf, assertType, describe, it } from 'vitest'
|
|
2
|
+
import type { Components, Form } from '../index.d.ts'
|
|
3
|
+
import type { Address, Entry, Parent, ParentWithAddress } from './fixtures.ts'
|
|
4
|
+
|
|
5
|
+
type ParentWithSection = Parent & {
|
|
6
|
+
details: never
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
describe('Section components', () => {
|
|
10
|
+
it('accepts section with nested components', () => {
|
|
11
|
+
assertType<Components<ParentWithSection>>({
|
|
12
|
+
details: {
|
|
13
|
+
type: 'section',
|
|
14
|
+
components: {
|
|
15
|
+
title: { type: 'text' }
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('provides typed item in section callbacks', () => {
|
|
22
|
+
assertType<Components<ParentWithSection>>({
|
|
23
|
+
details: {
|
|
24
|
+
type: 'section',
|
|
25
|
+
label({ item }) {
|
|
26
|
+
expectTypeOf(item).not.toBeAny()
|
|
27
|
+
expectTypeOf(item.title).toBeString()
|
|
28
|
+
expectTypeOf(item.entries).toEqualTypeOf<Entry[]>()
|
|
29
|
+
expectTypeOf(item).not.toHaveProperty('details')
|
|
30
|
+
return item.title
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('rejects invalid component keys in section', () => {
|
|
37
|
+
assertType<Components<ParentWithSection>>({
|
|
38
|
+
details: {
|
|
39
|
+
type: 'section',
|
|
40
|
+
components: {
|
|
41
|
+
title: { type: 'text' },
|
|
42
|
+
// @ts-expect-error 'nonExistent' is not a key of ParentWithSection
|
|
43
|
+
nonExistent: { type: 'text' }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('accepts section with form prop', () => {
|
|
50
|
+
const sectionForm: Form<ParentWithSection> = {
|
|
51
|
+
type: 'form',
|
|
52
|
+
components: {
|
|
53
|
+
title: { type: 'text' }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
assertType<Components<ParentWithSection>>({
|
|
58
|
+
details: {
|
|
59
|
+
type: 'section',
|
|
60
|
+
form: sectionForm
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
describe('Section tabs', () => {
|
|
67
|
+
it('accepts tabs on a non-nested section', () => {
|
|
68
|
+
assertType<Components<ParentWithSection>>({
|
|
69
|
+
details: {
|
|
70
|
+
type: 'section',
|
|
71
|
+
tabs: {
|
|
72
|
+
general: {
|
|
73
|
+
type: 'tab',
|
|
74
|
+
components: {
|
|
75
|
+
title: { type: 'text' }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('accepts tabs on a nested section typed against nested data', () => {
|
|
84
|
+
assertType<Components<ParentWithAddress>>({
|
|
85
|
+
address: {
|
|
86
|
+
type: 'section',
|
|
87
|
+
nested: true,
|
|
88
|
+
tabs: {
|
|
89
|
+
main: {
|
|
90
|
+
type: 'tab',
|
|
91
|
+
components: {
|
|
92
|
+
street: { type: 'text' },
|
|
93
|
+
city: { type: 'text' }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
describe('Nested sections', () => {
|
|
103
|
+
it('types nested section components against the value type', () => {
|
|
104
|
+
assertType<Components<ParentWithAddress>>({
|
|
105
|
+
address: {
|
|
106
|
+
type: 'section',
|
|
107
|
+
nested: true,
|
|
108
|
+
components: {
|
|
109
|
+
street: { type: 'text' },
|
|
110
|
+
city: { type: 'text' }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('rejects invalid keys in nested section components', () => {
|
|
117
|
+
assertType<Components<ParentWithAddress>>({
|
|
118
|
+
address: {
|
|
119
|
+
type: 'section',
|
|
120
|
+
nested: true,
|
|
121
|
+
components: {
|
|
122
|
+
street: { type: 'text' },
|
|
123
|
+
// @ts-expect-error 'zipCode' is not a key of Address
|
|
124
|
+
zipCode: { type: 'text' }
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('types item as Address in nested section callbacks', () => {
|
|
131
|
+
assertType<Components<ParentWithAddress>>({
|
|
132
|
+
address: {
|
|
133
|
+
type: 'section',
|
|
134
|
+
nested: true,
|
|
135
|
+
components: {
|
|
136
|
+
street: {
|
|
137
|
+
type: 'text',
|
|
138
|
+
onChange({ item }) {
|
|
139
|
+
expectTypeOf(item).not.toBeAny()
|
|
140
|
+
expectTypeOf(item).toEqualTypeOf<Address>()
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('types nested section form against the value type', () => {
|
|
149
|
+
assertType<Components<ParentWithAddress>>({
|
|
150
|
+
address: {
|
|
151
|
+
type: 'section',
|
|
152
|
+
nested: true,
|
|
153
|
+
form: {
|
|
154
|
+
type: 'form',
|
|
155
|
+
components: {
|
|
156
|
+
street: { type: 'text' },
|
|
157
|
+
city: { type: 'text' }
|
|
158
|
+
}
|
|
159
|
+
} satisfies Form<Address>
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('non-nested section on data key accepts parent item keys', () => {
|
|
165
|
+
assertType<Components<ParentWithAddress>>({
|
|
166
|
+
address: {
|
|
167
|
+
type: 'section',
|
|
168
|
+
components: {
|
|
169
|
+
title: { type: 'text' }
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
})
|