@ditojs/admin 2.99.1 → 3.0.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/dist/dito-admin.css +1 -1
- package/dist/dito-admin.es.js +3532 -2763
- package/dist/dito-admin.umd.js +6 -6
- package/package.json +82 -46
- package/src/DitoComponent.js +1 -1
- package/src/DitoContext.js +51 -0
- package/src/DitoTypeComponent.js +6 -1
- package/src/components/DitoAffixes.vue +6 -3
- package/src/components/DitoContainer.vue +19 -10
- package/src/components/DitoCreateButton.vue +11 -2
- package/src/components/DitoDialog.vue +21 -2
- package/src/components/DitoForm.vue +156 -83
- package/src/components/DitoLabel.vue +3 -2
- package/src/components/DitoPane.vue +55 -11
- package/src/components/DitoPanel.vue +33 -5
- package/src/components/DitoPanels.vue +2 -1
- package/src/components/DitoRoot.vue +13 -6
- package/src/components/DitoSchema.vue +278 -145
- package/src/components/DitoSchemaInlined.vue +1 -1
- package/src/components/DitoTabs.vue +16 -18
- package/src/components/DitoTrail.vue +5 -4
- package/src/components/DitoTreeItem.vue +17 -6
- package/src/components/DitoView.vue +25 -2
- package/src/mixins/DitoMixin.js +42 -7
- package/src/mixins/ItemMixin.js +3 -6
- package/src/mixins/NumberMixin.js +47 -31
- package/src/mixins/OptionsMixin.js +122 -71
- package/src/mixins/ResourceMixin.js +5 -2
- package/src/mixins/RouteMixin.js +39 -1
- package/src/mixins/SortableMixin.js +0 -13
- package/src/mixins/SourceMixin.js +142 -95
- package/src/mixins/TextMixin.js +1 -1
- package/src/mixins/TypeMixin.js +37 -22
- package/src/mixins/ValidationMixin.js +9 -27
- package/src/mixins/ValidatorMixin.js +5 -7
- package/src/mixins/ValueMixin.js +5 -2
- package/src/types/DitoTypeButton.vue +1 -1
- package/src/types/DitoTypeCheckbox.vue +1 -1
- package/src/types/DitoTypeCheckboxes.vue +1 -1
- package/src/types/DitoTypeCode.vue +8 -1
- package/src/types/DitoTypeColor.vue +20 -34
- package/src/types/DitoTypeComponent.vue +1 -1
- package/src/types/DitoTypeComputed.vue +4 -27
- package/src/types/DitoTypeDate.vue +16 -5
- package/src/types/DitoTypeLabel.vue +1 -4
- package/src/types/DitoTypeList.vue +9 -5
- package/src/types/DitoTypeMarkup.vue +25 -13
- package/src/types/DitoTypeMultiselect.vue +26 -6
- package/src/types/DitoTypeNumber.vue +1 -1
- package/src/types/DitoTypeObject.vue +10 -7
- package/src/types/DitoTypePanel.vue +19 -6
- package/src/types/DitoTypeProgress.vue +1 -1
- package/src/types/DitoTypeRadio.vue +1 -1
- package/src/types/DitoTypeSection.vue +32 -4
- package/src/types/DitoTypeSelect.vue +1 -1
- package/src/types/DitoTypeSlider.vue +1 -1
- package/src/types/DitoTypeSwitch.vue +1 -1
- package/src/types/DitoTypeText.vue +13 -15
- package/src/types/DitoTypeTextarea.vue +1 -1
- package/src/types/DitoTypeTreeList.vue +12 -7
- package/src/types/DitoTypeUpload.vue +1 -2
- package/src/utils/DataSchemaResolver.js +141 -0
- package/src/utils/DataSchemaResolver.test.js +132 -0
- package/src/utils/FormModel.js +556 -0
- package/src/utils/FormModel.test.js +808 -0
- package/src/utils/data.js +20 -0
- package/src/utils/data.test.js +28 -0
- package/src/utils/filter.js +3 -2
- package/src/utils/options.js +3 -0
- package/src/utils/schema/data.js +617 -0
- package/src/utils/schema/data.test.js +471 -0
- package/src/utils/schema/lookup.js +266 -0
- package/src/utils/schema/lookup.test.js +61 -0
- package/src/utils/schema/setup.js +354 -0
- package/src/utils/schema/structure.js +172 -0
- package/src/utils/schema/structure.test.js +57 -0
- package/src/utils/schema/types.js +34 -0
- package/src/utils/schema/validation.js +142 -0
- package/src/utils/schema/validation.test.js +395 -0
- package/src/utils/units.js +70 -3
- package/src/utils/units.test.js +71 -0
- package/types/index.d.ts +149 -59
- package/src/mixins/DataMixin.js +0 -131
- package/src/utils/schema.js +0 -1121
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import { vi } from 'vitest'
|
|
2
|
+
import DitoContext from '../../DitoContext.js'
|
|
3
|
+
import { registerTypeComponent } from './types.js'
|
|
4
|
+
import {
|
|
5
|
+
processSchemaData,
|
|
6
|
+
getComponentPathByDataPath,
|
|
7
|
+
computeValue,
|
|
8
|
+
processData,
|
|
9
|
+
initializeData,
|
|
10
|
+
shouldRenderSchema
|
|
11
|
+
} from './data.js'
|
|
12
|
+
|
|
13
|
+
// Register minimal type options, as the actual type components can't be
|
|
14
|
+
// imported without a Vue SFC compiler:
|
|
15
|
+
registerTypeComponent('text', { defaultNested: true })
|
|
16
|
+
registerTypeComponent('section', {
|
|
17
|
+
defaultNested: false,
|
|
18
|
+
defaultValue: ({ schema }) => (schema.nested ? {} : undefined),
|
|
19
|
+
treatNullAsMissing: ({ schema }) => !!schema.nested
|
|
20
|
+
})
|
|
21
|
+
registerTypeComponent('panel', { defaultNested: false })
|
|
22
|
+
registerTypeComponent('reference', {
|
|
23
|
+
defaultNested: true,
|
|
24
|
+
processValue: ({ value }) => value && { id: value.id }
|
|
25
|
+
})
|
|
26
|
+
registerTypeComponent('list', {
|
|
27
|
+
defaultNested: true,
|
|
28
|
+
getSourceType: () => 'list'
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const chapters = {
|
|
32
|
+
type: 'list',
|
|
33
|
+
form: {
|
|
34
|
+
type: 'form',
|
|
35
|
+
components: { title: { type: 'text' } }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const schema = {
|
|
40
|
+
type: 'form',
|
|
41
|
+
components: {
|
|
42
|
+
title: { type: 'text' },
|
|
43
|
+
publishing: {
|
|
44
|
+
type: 'section',
|
|
45
|
+
components: { publisher: { type: 'text' } }
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
tabs: {
|
|
49
|
+
structure: { type: 'tab', components: { chapters } },
|
|
50
|
+
layout: { type: 'tab', components: { chapters } }
|
|
51
|
+
},
|
|
52
|
+
panels: {
|
|
53
|
+
info: { type: 'panel', components: { note: { type: 'text' } } }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const data = {
|
|
58
|
+
title: 'Book',
|
|
59
|
+
publisher: 'Publisher',
|
|
60
|
+
note: 'Note',
|
|
61
|
+
chapters: [{ title: 'One' }, { title: 'Two' }]
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
describe('processSchemaData()', () => {
|
|
65
|
+
it('passes the component paths of components, tabs and panels', () => {
|
|
66
|
+
const componentPaths = {}
|
|
67
|
+
processSchemaData(schema, data, {
|
|
68
|
+
dataPath: '',
|
|
69
|
+
componentPath: 'book',
|
|
70
|
+
shouldProcess: ({ componentPath, dataPath }) => {
|
|
71
|
+
componentPaths[componentPath] = dataPath
|
|
72
|
+
return true
|
|
73
|
+
},
|
|
74
|
+
options: { component: null }
|
|
75
|
+
})
|
|
76
|
+
expect(componentPaths).toEqual({
|
|
77
|
+
'book/title': 'title',
|
|
78
|
+
'book/publishing': '',
|
|
79
|
+
'book/publishing/publisher': 'publisher',
|
|
80
|
+
'book/structure': '',
|
|
81
|
+
'book/structure/chapters': 'chapters',
|
|
82
|
+
'book/structure/chapters/0/title': 'chapters/0/title',
|
|
83
|
+
'book/structure/chapters/1/title': 'chapters/1/title',
|
|
84
|
+
'book/layout': '',
|
|
85
|
+
'book/layout/chapters': 'chapters',
|
|
86
|
+
'book/layout/chapters/0/title': 'chapters/0/title',
|
|
87
|
+
'book/layout/chapters/1/title': 'chapters/1/title',
|
|
88
|
+
'book/info': '',
|
|
89
|
+
'book/info/note': 'note'
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
describe('processSchemaData() with panel components', () => {
|
|
95
|
+
it('walks the components of panel components with their data', () => {
|
|
96
|
+
const componentPaths = {}
|
|
97
|
+
processSchemaData(
|
|
98
|
+
{
|
|
99
|
+
type: 'form',
|
|
100
|
+
components: {
|
|
101
|
+
links: {
|
|
102
|
+
type: 'panel',
|
|
103
|
+
components: { website: { type: 'text' } }
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
{ website: 'https://example.com' },
|
|
108
|
+
{
|
|
109
|
+
dataPath: '',
|
|
110
|
+
componentPath: 'book',
|
|
111
|
+
shouldProcess: ({ componentPath, dataPath }) => {
|
|
112
|
+
componentPaths[componentPath] = dataPath
|
|
113
|
+
return true
|
|
114
|
+
},
|
|
115
|
+
options: { component: null }
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
expect(componentPaths).toEqual({
|
|
119
|
+
'book/links': '',
|
|
120
|
+
'book/links/website': 'website'
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
describe('processSchemaData() with panels that have their own data', () => {
|
|
126
|
+
it("doesn't walk their components with the data of the schema", () => {
|
|
127
|
+
const componentPaths = []
|
|
128
|
+
processSchemaData(
|
|
129
|
+
{
|
|
130
|
+
type: 'form',
|
|
131
|
+
components: {
|
|
132
|
+
search: {
|
|
133
|
+
type: 'panel',
|
|
134
|
+
data: () => ({ term: null }),
|
|
135
|
+
components: { term: { type: 'text' } }
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
panels: {
|
|
139
|
+
filters: {
|
|
140
|
+
type: 'panel',
|
|
141
|
+
data: () => ({ query: null }),
|
|
142
|
+
components: { query: { type: 'text' } }
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
{},
|
|
147
|
+
{
|
|
148
|
+
dataPath: '',
|
|
149
|
+
componentPath: '',
|
|
150
|
+
shouldProcess: ({ componentPath }) => {
|
|
151
|
+
componentPaths.push(componentPath)
|
|
152
|
+
return true
|
|
153
|
+
},
|
|
154
|
+
options: { component: null }
|
|
155
|
+
}
|
|
156
|
+
)
|
|
157
|
+
expect(componentPaths).toEqual(['search', 'filters'])
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
describe('getComponentPathByDataPath()', () => {
|
|
162
|
+
const getComponentPath = (dataPath, options) =>
|
|
163
|
+
getComponentPathByDataPath(schema, data, dataPath, options)
|
|
164
|
+
|
|
165
|
+
it('adds the names of tabs and unnested components', () => {
|
|
166
|
+
expect(getComponentPath('title')).toBe('title')
|
|
167
|
+
expect(getComponentPath('publisher')).toBe('publishing/publisher')
|
|
168
|
+
expect(getComponentPath('note')).toBe('info/note')
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('appends the item indices of list items', () => {
|
|
172
|
+
expect(getComponentPath('chapters/1')).toBe('structure/chapters/1')
|
|
173
|
+
expect(getComponentPath('chapters/1/title')).toBe(
|
|
174
|
+
'structure/chapters/1/title'
|
|
175
|
+
)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it('uses the first component in schema order', () => {
|
|
179
|
+
expect(getComponentPath('chapters')).toBe('structure/chapters')
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('appends the data path of values without component', () => {
|
|
183
|
+
expect(getComponentPath('unknown/0')).toBe('unknown/0')
|
|
184
|
+
expect(getComponentPath('chapters/0/unknown')).toBe(
|
|
185
|
+
'structure/chapters/0/unknown'
|
|
186
|
+
)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('continues `componentPath`', () => {
|
|
190
|
+
expect(getComponentPath('chapters/0', { componentPath: 'main' })).toBe(
|
|
191
|
+
'main/structure/chapters/0'
|
|
192
|
+
)
|
|
193
|
+
expect(getComponentPathByDataPath(schema, null, 'chapters/0')).toBe(
|
|
194
|
+
'chapters/0'
|
|
195
|
+
)
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
describe('computeValue()', () => {
|
|
200
|
+
const schema = {
|
|
201
|
+
type: 'text',
|
|
202
|
+
compute: ({ item }) => item.title?.toUpperCase()
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
it('returns the computed value without writing it into the data', () => {
|
|
206
|
+
const data = { title: 'Book' }
|
|
207
|
+
expect(computeValue(schema, data, 'upper', 'upper')).toBe('BOOK')
|
|
208
|
+
expect(data).toEqual({ title: 'Book' })
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('returns the value if `compute()` returns `undefined`', () => {
|
|
212
|
+
const data = { upper: 'Kept' }
|
|
213
|
+
expect(computeValue(schema, data, 'upper', 'upper')).toBe('Kept')
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('returns the default of missing values without writing it', () => {
|
|
217
|
+
const data = {}
|
|
218
|
+
const schema = { type: 'text', default: 'Untitled' }
|
|
219
|
+
expect(computeValue(schema, data, 'title', 'title')).toBe('Untitled')
|
|
220
|
+
expect(data).toEqual({})
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it('returns the defaults of nested sections that are `null`', () => {
|
|
224
|
+
const chooser = { type: 'section', nested: true, components: {} }
|
|
225
|
+
expect(
|
|
226
|
+
computeValue(chooser, { chooser: null }, 'chooser', 'chooser')
|
|
227
|
+
).toEqual(
|
|
228
|
+
{}
|
|
229
|
+
)
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
it('provides `context.options` through `getOptions()` when read', () => {
|
|
233
|
+
const options = ['Small', 'Large']
|
|
234
|
+
const getOptions = vi.fn(() => options)
|
|
235
|
+
const schema = {
|
|
236
|
+
type: 'text',
|
|
237
|
+
compute: context => context.value ?? context.options[0]
|
|
238
|
+
}
|
|
239
|
+
expect(
|
|
240
|
+
computeValue(schema, { size: 'Large' }, 'size', 'size', { getOptions })
|
|
241
|
+
).toBe('Large')
|
|
242
|
+
expect(getOptions).not.toHaveBeenCalled()
|
|
243
|
+
expect(computeValue(schema, {}, 'size', 'size', { getOptions })).toBe(
|
|
244
|
+
'Small'
|
|
245
|
+
)
|
|
246
|
+
expect(getOptions).toHaveBeenCalledTimes(1)
|
|
247
|
+
})
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
describe('processData()', () => {
|
|
251
|
+
it('processes computed values without writing into the data', () => {
|
|
252
|
+
const schema = {
|
|
253
|
+
type: 'form',
|
|
254
|
+
components: {
|
|
255
|
+
title: { type: 'text' },
|
|
256
|
+
upper: {
|
|
257
|
+
type: 'text',
|
|
258
|
+
compute: ({ item }) => item.title.toUpperCase()
|
|
259
|
+
},
|
|
260
|
+
subtitle: { type: 'text', default: 'None' }
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const data = { title: 'Book' }
|
|
264
|
+
expect(
|
|
265
|
+
processData(schema, { type: 'list' }, data, '', { target: 'server' })
|
|
266
|
+
).toEqual({ title: 'Book', upper: 'BOOK', subtitle: 'None' })
|
|
267
|
+
expect(data).toEqual({ title: 'Book' })
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
describe('with `shouldCallComputeAndProcess: false`', () => {
|
|
271
|
+
// `DitoContext` needs a component to inherit from:
|
|
272
|
+
const component = {}
|
|
273
|
+
const options = {
|
|
274
|
+
component,
|
|
275
|
+
target: 'clipboard',
|
|
276
|
+
shouldCallComputeAndProcess: false
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
it('falls back to defaults without calling `compute()`', () => {
|
|
280
|
+
const compute = vi.fn(() => 'Computed')
|
|
281
|
+
const process = vi.fn(({ value }) => value)
|
|
282
|
+
const schema = {
|
|
283
|
+
type: 'form',
|
|
284
|
+
components: {
|
|
285
|
+
title: { type: 'text', process },
|
|
286
|
+
upper: { type: 'text', compute, default: 'None' }
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
expect(
|
|
290
|
+
processData(schema, { type: 'list' }, { title: 'Book' }, '', options)
|
|
291
|
+
).toEqual({ title: 'Book', upper: 'None' })
|
|
292
|
+
expect(compute).not.toHaveBeenCalled()
|
|
293
|
+
expect(process).not.toHaveBeenCalled()
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
it("processes values through their types' `processValue()`", () => {
|
|
297
|
+
const schema = {
|
|
298
|
+
type: 'form',
|
|
299
|
+
components: { author: { type: 'reference' } }
|
|
300
|
+
}
|
|
301
|
+
const data = { author: { id: 1, name: 'Author' } }
|
|
302
|
+
expect(processData(schema, { type: 'list' }, data, '', options)).toEqual({
|
|
303
|
+
author: { id: 1 }
|
|
304
|
+
})
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
it('keeps excluded values of components with `process()`', () => {
|
|
308
|
+
const schema = {
|
|
309
|
+
type: 'form',
|
|
310
|
+
components: {
|
|
311
|
+
tagsText: {
|
|
312
|
+
type: 'text',
|
|
313
|
+
exclude: true,
|
|
314
|
+
process: ({ value, processedItem }) => {
|
|
315
|
+
processedItem.tags = value.split(',')
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
search: { type: 'text', exclude: true }
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const data = { tagsText: 'a,b', search: 'Search' }
|
|
322
|
+
expect(processData(schema, { type: 'list' }, data, '', options)).toEqual({
|
|
323
|
+
tagsText: 'a,b'
|
|
324
|
+
})
|
|
325
|
+
expect(
|
|
326
|
+
processData(schema, { type: 'list' }, data, '', {
|
|
327
|
+
component,
|
|
328
|
+
target: 'server'
|
|
329
|
+
})
|
|
330
|
+
).toEqual({ tags: ['a', 'b'] })
|
|
331
|
+
})
|
|
332
|
+
})
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
describe('shouldRenderSchema()', () => {
|
|
336
|
+
it("doesn't evaluate the components of sources for their items", () => {
|
|
337
|
+
// `DitoContext` needs a component to inherit from:
|
|
338
|
+
const context = new DitoContext({}, { data: { links: null }, dataPath: '' })
|
|
339
|
+
const condition = vi.fn(({ item }) => !!item.url)
|
|
340
|
+
const links = {
|
|
341
|
+
type: 'list',
|
|
342
|
+
components: { url: { type: 'text', if: condition } }
|
|
343
|
+
}
|
|
344
|
+
expect(shouldRenderSchema(links, context)).toBe(true)
|
|
345
|
+
expect(condition).not.toHaveBeenCalled()
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
it('hides sections whose components are all hidden', () => {
|
|
349
|
+
const context = new DitoContext({}, { data: {}, dataPath: '' })
|
|
350
|
+
const section = {
|
|
351
|
+
type: 'section',
|
|
352
|
+
components: { url: { type: 'text', if: () => false } }
|
|
353
|
+
}
|
|
354
|
+
expect(shouldRenderSchema(section, context)).toBe(false)
|
|
355
|
+
})
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
describe('computeValue() with if', () => {
|
|
359
|
+
it("doesn't compute values of components whose if is false", () => {
|
|
360
|
+
const schema = {
|
|
361
|
+
type: 'text',
|
|
362
|
+
if: ({ item }) => !!item.preview,
|
|
363
|
+
compute: ({ item }) => item.preview.name
|
|
364
|
+
}
|
|
365
|
+
expect(computeValue(schema, { name: 'Stored' }, 'name', 'name')).toBe(
|
|
366
|
+
'Stored'
|
|
367
|
+
)
|
|
368
|
+
expect(
|
|
369
|
+
computeValue(schema, { preview: { name: 'Preview' } }, 'name', 'name')
|
|
370
|
+
).toBe('Preview')
|
|
371
|
+
})
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
describe('initializeData()', () => {
|
|
375
|
+
it('passes through primitive values of lists with wrapPrimitives', () => {
|
|
376
|
+
const prices = {
|
|
377
|
+
type: 'list',
|
|
378
|
+
wrapPrimitives: 'price',
|
|
379
|
+
form: {
|
|
380
|
+
type: 'form',
|
|
381
|
+
components: { price: { type: 'text', default: 0 } }
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const data = { prices: [100, 200] }
|
|
385
|
+
initializeData({ type: 'form', components: { prices } }, data)
|
|
386
|
+
expect(data).toEqual({ prices: [100, 200] })
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
it('sets the objects of nested sections that are `null`', () => {
|
|
390
|
+
const chooser = {
|
|
391
|
+
type: 'section',
|
|
392
|
+
nested: true,
|
|
393
|
+
components: { topic: { type: 'text' } }
|
|
394
|
+
}
|
|
395
|
+
const data = { chooser: null }
|
|
396
|
+
initializeData({ type: 'form', components: { chooser } }, data, {})
|
|
397
|
+
// The section's components are initialized in its object too:
|
|
398
|
+
expect(data.chooser).toHaveProperty('topic')
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
it('can leave the defaults of computed values to `computeValue()`', () => {
|
|
402
|
+
const pricing = {
|
|
403
|
+
type: 'text',
|
|
404
|
+
default: 'auto',
|
|
405
|
+
compute: ({ value, item }) => (
|
|
406
|
+
value ?? (item.customFactor != null ? 'factor' : undefined)
|
|
407
|
+
)
|
|
408
|
+
}
|
|
409
|
+
const schema = { type: 'form', components: { pricing } }
|
|
410
|
+
const data = { customFactor: 10 }
|
|
411
|
+
initializeData(schema, data, null, {
|
|
412
|
+
shouldSetDefaultsOfComponentsWithCompute: false
|
|
413
|
+
})
|
|
414
|
+
expect(data).toEqual({ customFactor: 10 })
|
|
415
|
+
// New data starts with all defaults:
|
|
416
|
+
expect(initializeData(schema, {})).toEqual({ pricing: 'auto' })
|
|
417
|
+
expect(computeValue(pricing, data, 'pricing', 'pricing')).toBe('factor')
|
|
418
|
+
expect(computeValue(pricing, {}, 'pricing', 'pricing')).toBe('auto')
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
it('numbers the items of lists with `orderKey`, also nested ones', () => {
|
|
422
|
+
const parts = {
|
|
423
|
+
type: 'list',
|
|
424
|
+
orderKey: 'order',
|
|
425
|
+
form: { type: 'form', components: { name: { type: 'text' } } }
|
|
426
|
+
}
|
|
427
|
+
const items = {
|
|
428
|
+
type: 'list',
|
|
429
|
+
orderKey: 'order',
|
|
430
|
+
// Lists with `compute()` are numbered too.
|
|
431
|
+
compute: ({ value }) => value,
|
|
432
|
+
form: { type: 'form', components: { parts } }
|
|
433
|
+
}
|
|
434
|
+
const data = {
|
|
435
|
+
items: [
|
|
436
|
+
{ order: 5, parts: [{ name: 'A', order: 2 }, { name: 'B', order: 7 }] },
|
|
437
|
+
{ order: 9, parts: [] }
|
|
438
|
+
]
|
|
439
|
+
}
|
|
440
|
+
initializeData(
|
|
441
|
+
{ type: 'form', tabs: { main: { type: 'tab', components: { items } } } },
|
|
442
|
+
data,
|
|
443
|
+
// `DitoContext` needs a component to inherit from:
|
|
444
|
+
{}
|
|
445
|
+
)
|
|
446
|
+
expect(data.items.map(item => item.order)).toEqual([0, 1])
|
|
447
|
+
expect(data.items[0].parts.map(part => part.order)).toEqual([0, 1])
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
it('leaves lists without `orderKey` and of primitives untouched', () => {
|
|
451
|
+
const form = { type: 'form', components: { name: { type: 'text' } } }
|
|
452
|
+
const schema = {
|
|
453
|
+
type: 'form',
|
|
454
|
+
components: {
|
|
455
|
+
items: { type: 'list', form },
|
|
456
|
+
prices: {
|
|
457
|
+
type: 'list',
|
|
458
|
+
orderKey: 'order',
|
|
459
|
+
wrapPrimitives: 'price',
|
|
460
|
+
form: { type: 'form', components: { price: { type: 'text' } } }
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
const data = { items: [{ name: 'A', order: 5 }], prices: [100, 200] }
|
|
465
|
+
initializeData(schema, data, {})
|
|
466
|
+
expect(data).toEqual({
|
|
467
|
+
items: [{ name: 'A', order: 5 }],
|
|
468
|
+
prices: [100, 200]
|
|
469
|
+
})
|
|
470
|
+
})
|
|
471
|
+
})
|