@rokkit/forms 1.0.0-next.122

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/dist/src/forms-old/input/types.d.ts +7 -0
  2. package/dist/src/forms-old/lib/form.d.ts +95 -0
  3. package/dist/src/forms-old/lib/index.d.ts +1 -0
  4. package/dist/src/index.d.ts +5 -0
  5. package/dist/src/input/index.d.ts +18 -0
  6. package/dist/src/lib/builder.svelte.d.ts +140 -0
  7. package/dist/src/lib/deprecated/nested.d.ts +48 -0
  8. package/dist/src/lib/deprecated/nested.spec.d.ts +1 -0
  9. package/dist/src/lib/deprecated/validator.d.ts +30 -0
  10. package/dist/src/lib/deprecated/validator.spec.d.ts +1 -0
  11. package/dist/src/lib/fields.d.ts +16 -0
  12. package/dist/src/lib/fields.spec.d.ts +1 -0
  13. package/dist/src/lib/index.d.ts +7 -0
  14. package/dist/src/lib/layout.d.ts +7 -0
  15. package/dist/src/lib/schema.d.ts +7 -0
  16. package/dist/src/lib/validation.d.ts +41 -0
  17. package/dist/src/types.d.ts +5 -0
  18. package/package.json +38 -0
  19. package/src/DataEditor.svelte +30 -0
  20. package/src/FieldLayout.svelte +48 -0
  21. package/src/FormRenderer.svelte +118 -0
  22. package/src/Input.svelte +75 -0
  23. package/src/InputField.svelte +55 -0
  24. package/src/ListEditor.svelte +44 -0
  25. package/src/NestedEditor.svelte +85 -0
  26. package/src/forms-old/CheckBox.svelte +56 -0
  27. package/src/forms-old/DataEditor.svelte +30 -0
  28. package/src/forms-old/FieldLayout.svelte +48 -0
  29. package/src/forms-old/Form.svelte +17 -0
  30. package/src/forms-old/Icon.svelte +76 -0
  31. package/src/forms-old/Item.svelte +25 -0
  32. package/src/forms-old/ListEditor.svelte +44 -0
  33. package/src/forms-old/Tabs.svelte +57 -0
  34. package/src/forms-old/Wrapper.svelte +12 -0
  35. package/src/forms-old/input/Input.svelte +17 -0
  36. package/src/forms-old/input/InputField.svelte +70 -0
  37. package/src/forms-old/input/InputSelect.svelte +23 -0
  38. package/src/forms-old/input/InputSwitch.svelte +19 -0
  39. package/src/forms-old/input/types.js +29 -0
  40. package/src/forms-old/lib/form.js +72 -0
  41. package/src/forms-old/lib/index.js +12 -0
  42. package/src/forms-old/mocks/CustomField.svelte +7 -0
  43. package/src/forms-old/mocks/CustomWrapper.svelte +8 -0
  44. package/src/forms-old/mocks/Register.svelte +25 -0
  45. package/src/index.js +7 -0
  46. package/src/inp/Input.svelte +17 -0
  47. package/src/inp/InputField.svelte +69 -0
  48. package/src/inp/InputSelect.svelte +23 -0
  49. package/src/inp/InputSwitch.svelte +19 -0
  50. package/src/input/InputCheckbox.svelte +74 -0
  51. package/src/input/InputColor.svelte +42 -0
  52. package/src/input/InputDate.svelte +54 -0
  53. package/src/input/InputDateTime.svelte +54 -0
  54. package/src/input/InputEmail.svelte +63 -0
  55. package/src/input/InputFile.svelte +45 -0
  56. package/src/input/InputMonth.svelte +54 -0
  57. package/src/input/InputNumber.svelte +57 -0
  58. package/src/input/InputPassword.svelte +60 -0
  59. package/src/input/InputRadio.svelte +60 -0
  60. package/src/input/InputRange.svelte +51 -0
  61. package/src/input/InputSelect.svelte +71 -0
  62. package/src/input/InputSwitch.svelte +29 -0
  63. package/src/input/InputTel.svelte +60 -0
  64. package/src/input/InputText.svelte +60 -0
  65. package/src/input/InputTextArea.svelte +59 -0
  66. package/src/input/InputTime.svelte +54 -0
  67. package/src/input/InputUrl.svelte +60 -0
  68. package/src/input/InputWeek.svelte +54 -0
  69. package/src/input/index.js +23 -0
  70. package/src/lib/Input.svelte +291 -0
  71. package/src/lib/builder.svelte.js +359 -0
  72. package/src/lib/deprecated/Form.svelte +17 -0
  73. package/src/lib/deprecated/FormRenderer.svelte +121 -0
  74. package/src/lib/deprecated/nested.js +192 -0
  75. package/src/lib/deprecated/nested.spec.js +512 -0
  76. package/src/lib/deprecated/validator.js +137 -0
  77. package/src/lib/deprecated/validator.spec.js +348 -0
  78. package/src/lib/fields.js +119 -0
  79. package/src/lib/fields.spec.js +250 -0
  80. package/src/lib/index.js +7 -0
  81. package/src/lib/layout.js +63 -0
  82. package/src/lib/schema.js +32 -0
  83. package/src/lib/validation.js +273 -0
  84. package/src/types.js +29 -0
@@ -0,0 +1,348 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { verifiable, getPatternValidator, getRangeValidator, getTypeValidator } from './validator'
3
+ import { get } from 'svelte/store'
4
+
5
+ describe('validator', () => {
6
+ describe('getPatternValidator', () => {
7
+ it('should return a pattern validator function for a string pattern', () => {
8
+ const pattern = '^example$'
9
+ const validator = getPatternValidator(pattern)
10
+
11
+ expect(validator).toBeDefined()
12
+ expect(typeof validator).toBe('function')
13
+
14
+ expect(validator('example')).toBeTruthy()
15
+ expect(validator('wrong')).toBeFalsy()
16
+ })
17
+
18
+ it('should return a pattern validator function for a RegExp pattern', () => {
19
+ const pattern = /^example$/
20
+ const validator = getPatternValidator(pattern)
21
+
22
+ expect(validator).toBeDefined()
23
+ expect(typeof validator).toBe('function')
24
+
25
+ expect(validator('example')).toBeTruthy()
26
+ expect(validator('wrong')).toBeFalsy()
27
+ })
28
+
29
+ it('should throw an error for an invalid pattern', () => {
30
+ const pattern = 123
31
+
32
+ expect(() => {
33
+ getPatternValidator(pattern)
34
+ }).toThrowError('Invalid pattern')
35
+ })
36
+ })
37
+
38
+ describe('getRangeValidator', () => {
39
+ it('should return a range validator function', () => {
40
+ const min = 5
41
+ const max = 10
42
+ const validator = getRangeValidator(min, max)
43
+
44
+ expect(validator).toBeDefined()
45
+ expect(typeof validator).toBe('function')
46
+
47
+ expect(validator(3)).toBeFalsy()
48
+ expect(validator(7)).toBeTruthy()
49
+ expect(validator(12)).toBeFalsy()
50
+ })
51
+
52
+ it('should return a range validator for upper bound', () => {
53
+ const validator = getRangeValidator(null, 10)
54
+
55
+ expect(validator).toBeDefined()
56
+ expect(typeof validator).toBe('function')
57
+
58
+ expect(validator(7)).toBeTruthy()
59
+ expect(validator(12)).toBeFalsy()
60
+ })
61
+ it('should return a range validator for lower bound', () => {
62
+ const validator = getRangeValidator(5, null)
63
+
64
+ expect(validator).toBeDefined()
65
+ expect(typeof validator).toBe('function')
66
+
67
+ expect(validator(3)).toBeFalsy()
68
+ expect(validator(12)).toBeTruthy()
69
+ })
70
+ })
71
+
72
+ describe('getTypeValidator', () => {
73
+ it('should return a validator for "number" type', () => {
74
+ const validator = getTypeValidator('number')
75
+ expect(validator).toBeInstanceOf(Function)
76
+ expect(validator(42)).toBeTruthy()
77
+ expect(validator('hello')).toBeFalsy()
78
+ expect(validator(null)).toBeFalsy()
79
+ expect(validator()).toBeFalsy()
80
+ })
81
+
82
+ it('should return a validator for "email" type', () => {
83
+ const validator = getTypeValidator('email')
84
+ expect(validator).toBeInstanceOf(Function)
85
+ expect(validator('test@example.com')).toBeTruthy()
86
+ expect(validator('a@b.co.in')).toBeTruthy()
87
+ expect(validator('invalid-email')).toBeFalsy()
88
+ })
89
+
90
+ it('should return a validator for "url" type', () => {
91
+ const validator = getTypeValidator('url')
92
+ expect(validator).toBeInstanceOf(Function)
93
+ expect(validator('https://example.com')).toBeTruthy()
94
+ expect(validator('http://example.com')).toBeTruthy()
95
+ expect(validator('http://example.com?a=b')).toBeTruthy()
96
+ expect(validator('http://example.com?a=1&c=1')).toBeTruthy()
97
+ expect(validator('http://example.com?#123')).toBeTruthy()
98
+ expect(validator('invalid-url')).toBeFalsy()
99
+ expect(validator('http://x.y abc')).toBeFalsy()
100
+ })
101
+
102
+ it('should return a validator for "array" type', () => {
103
+ const validator = getTypeValidator('array')
104
+ expect(validator).toBeInstanceOf(Function)
105
+ expect(validator([])).toBeTruthy()
106
+ expect(validator(null)).toBeFalsy()
107
+ expect(validator()).toBeFalsy()
108
+ expect(validator({})).toBeFalsy()
109
+ expect(validator('hello')).toBeFalsy()
110
+ })
111
+ it('should return a validator for "object" type', () => {
112
+ const validator = getTypeValidator('object')
113
+ expect(validator).toBeInstanceOf(Function)
114
+ expect(validator({})).toBeTruthy()
115
+ expect(validator({ alpha: 'value' })).toBeTruthy()
116
+ expect(validator(null)).toBeFalsy()
117
+ expect(validator()).toBeFalsy()
118
+ expect(validator([])).toBeFalsy()
119
+ expect(validator('hello')).toBeFalsy()
120
+ expect(validator(2)).toBeFalsy()
121
+ })
122
+ it('should return a validator for "color" type', () => {
123
+ const validator = getTypeValidator('color')
124
+ expect(validator).toBeInstanceOf(Function)
125
+ expect(validator('#000fff')).toBeTruthy()
126
+ expect(validator('#abcdef')).toBeTruthy()
127
+ expect(validator('#xxxxxx')).toBeFalsy()
128
+ expect(validator('123456')).toBeFalsy()
129
+ })
130
+ it('should return a validator for "string" type', () => {
131
+ const validator = getTypeValidator('string')
132
+ expect(validator).toBeInstanceOf(Function)
133
+ expect(validator('hello')).toBeTruthy()
134
+ expect(validator('')).toBeTruthy()
135
+ expect(validator(null)).toBeFalsy()
136
+ expect(validator()).toBeFalsy()
137
+ expect(validator([])).toBeFalsy()
138
+ expect(validator({})).toBeFalsy()
139
+ expect(validator(2)).toBeFalsy()
140
+ })
141
+ it('should return a validator for "date" type', () => {
142
+ const validator = getTypeValidator('date')
143
+ expect(validator).toBeInstanceOf(Function)
144
+ expect(validator('2020-01-01')).toBeTruthy()
145
+ expect(validator('2020-01-01T00:00:00.000Z')).toBeTruthy()
146
+ expect(validator('2020-01-01T00:00:00.000')).toBeTruthy()
147
+ expect(validator('2020-01-01T00:00:00')).toBeTruthy()
148
+ expect(validator('2020-01-01T00:00')).toBeTruthy()
149
+ expect(validator('2020-01-01T00')).toBeFalsy()
150
+ expect(validator('2020-01-01T')).toBeFalsy()
151
+ expect(validator('xyz')).toBeFalsy()
152
+ expect(validator('2020-01-01T00:00:00.000+00:00')).toBeTruthy()
153
+ })
154
+ it('should return a validator for "boolean" type', () => {
155
+ const validator = getTypeValidator('boolean')
156
+ expect(validator).toBeInstanceOf(Function)
157
+ expect(validator(true)).toBeTruthy()
158
+ expect(validator(false)).toBeTruthy()
159
+ expect(validator(null)).toBeFalsy()
160
+ expect(validator()).toBeFalsy()
161
+ expect(validator([])).toBeFalsy()
162
+ expect(validator({})).toBeFalsy()
163
+ expect(validator(2)).toBeFalsy()
164
+ })
165
+ })
166
+
167
+ describe('verifiable', () => {
168
+ const rules = [
169
+ { text: 'At least 1 upper case letter', pattern: /[A-Z]+/ },
170
+ { text: 'At least 1 lower case letter', pattern: /[a-z]+/ },
171
+ { text: 'At least 1 number', pattern: /[0-9]+/ },
172
+ { text: 'At least 1 symbol [!@#$]', pattern: /[!@#$]+/ },
173
+ { text: 'At least 8 characters long', pattern: /.{8,}/ }
174
+ ]
175
+ const initialValidations = [
176
+ { text: 'At least 1 upper case letter', valid: false, status: 'fail' },
177
+ { text: 'At least 1 lower case letter', valid: true, status: 'pass' },
178
+ { text: 'At least 1 number', valid: false, status: 'fail' },
179
+ { text: 'At least 1 symbol [!@#$]', valid: false, status: 'fail' },
180
+ { text: 'At least 8 characters long', valid: false, status: 'fail' }
181
+ ]
182
+
183
+ it('should create a validation store with initial value and rules', () => {
184
+ const value = 'example'
185
+ const store = verifiable(value, rules)
186
+
187
+ expect(store).toBeDefined()
188
+ expect(store.subscribe).toBeDefined()
189
+ expect(store.update).toBeDefined()
190
+
191
+ const currentValue = get(store)
192
+
193
+ expect(currentValue.value).toBe(value)
194
+ expect(currentValue.status).toBe('fail')
195
+ expect(currentValue.isValid).toBeFalsy()
196
+ expect(currentValue.validations).toHaveLength(5)
197
+ expect(currentValue.validations).toEqual(initialValidations)
198
+ })
199
+
200
+ it('should update the validation store with a new value', () => {
201
+ const value = 'example'
202
+ const store = verifiable(value, rules)
203
+
204
+ let currentValue = get(store)
205
+ expect(currentValue.value).toBe(value)
206
+ expect(currentValue.status).toBe('fail')
207
+ expect(currentValue.isValid).toBeFalsy()
208
+ expect(currentValue.validations).toHaveLength(5)
209
+ expect(currentValue.validations).toEqual(initialValidations)
210
+
211
+ store.update('newExample')
212
+ currentValue = get(store)
213
+
214
+ expect(currentValue.value).toBe('newExample')
215
+ expect(currentValue.status).toBe('fail')
216
+ expect(currentValue.validations).toHaveLength(5)
217
+ expect(currentValue.isValid).toBeFalsy()
218
+ expect(currentValue.validations).toEqual([
219
+ {
220
+ text: 'At least 1 upper case letter',
221
+ valid: true,
222
+ status: 'pass'
223
+ },
224
+ {
225
+ text: 'At least 1 lower case letter',
226
+ valid: true,
227
+ status: 'pass'
228
+ },
229
+ { text: 'At least 1 number', valid: false, status: 'fail' },
230
+ { text: 'At least 1 symbol [!@#$]', valid: false, status: 'fail' },
231
+ { text: 'At least 8 characters long', valid: true, status: 'pass' }
232
+ ])
233
+
234
+ store.update('Example1!')
235
+ currentValue = get(store)
236
+
237
+ expect(currentValue.value).toBe('Example1!')
238
+ expect(currentValue.status).toBe('pass')
239
+ expect(currentValue.validations).toHaveLength(5)
240
+ expect(currentValue.isValid).toBeTruthy()
241
+ expect(currentValue.validations).toEqual([
242
+ { text: 'At least 1 upper case letter', valid: true, status: 'pass' },
243
+ { text: 'At least 1 lower case letter', valid: true, status: 'pass' },
244
+ { text: 'At least 1 number', valid: true, status: 'pass' },
245
+ { text: 'At least 1 symbol [!@#$]', valid: true, status: 'pass' },
246
+ { text: 'At least 8 characters long', valid: true, status: 'pass' }
247
+ ])
248
+ })
249
+
250
+ it('should handle optional rules', () => {
251
+ const optionalRules = rules
252
+ optionalRules[1].optional = true
253
+
254
+ const store = verifiable('EXAMPLE1!', optionalRules)
255
+ const currentValue = get(store)
256
+
257
+ expect(currentValue.value).toBe('EXAMPLE1!')
258
+ expect(currentValue.status).toBe('warn')
259
+ expect(currentValue.validations).toHaveLength(5)
260
+ expect(currentValue.isValid).toBeFalsy()
261
+ expect(currentValue.validations).toEqual([
262
+ { text: 'At least 1 upper case letter', valid: true, status: 'pass' },
263
+ {
264
+ text: 'At least 1 lower case letter',
265
+ valid: false,
266
+ status: 'warn'
267
+ },
268
+ { text: 'At least 1 number', valid: true, status: 'pass' },
269
+ { text: 'At least 1 symbol [!@#$]', valid: true, status: 'pass' },
270
+ { text: 'At least 8 characters long', valid: true, status: 'pass' }
271
+ ])
272
+ })
273
+
274
+ it('should handle lower/upper bound', () => {
275
+ const store = verifiable(0, [
276
+ { text: 'should be numeric', type: 'number' },
277
+ { text: 'should be >= 5', min: 5 },
278
+ { text: 'should be <= 10', max: 10 }
279
+ ])
280
+
281
+ let currentValue = get(store)
282
+
283
+ expect(currentValue.status).toBe('fail')
284
+ expect(currentValue.validations).toEqual([
285
+ { text: 'should be numeric', valid: true, status: 'pass' },
286
+ { text: 'should be >= 5', valid: false, status: 'fail' },
287
+ { text: 'should be <= 10', valid: true, status: 'pass' }
288
+ ])
289
+
290
+ store.update(7)
291
+ currentValue = get(store)
292
+
293
+ expect(currentValue.status).toBe('pass')
294
+ expect(currentValue.validations).toEqual([
295
+ { text: 'should be numeric', valid: true, status: 'pass' },
296
+ { text: 'should be >= 5', valid: true, status: 'pass' },
297
+ { text: 'should be <= 10', valid: true, status: 'pass' }
298
+ ])
299
+ store.update(11)
300
+ currentValue = get(store)
301
+ expect(currentValue.status).toBe('fail')
302
+ expect(currentValue.validations).toEqual([
303
+ { text: 'should be numeric', valid: true, status: 'pass' },
304
+ { text: 'should be >= 5', valid: true, status: 'pass' },
305
+ { text: 'should be <= 10', valid: false, status: 'fail' }
306
+ ])
307
+ })
308
+
309
+ it('should handle custom validation', () => {
310
+ const store = verifiable('example', [
311
+ {
312
+ text: 'should be "example"',
313
+ validator: (value) => value === 'example'
314
+ }
315
+ ])
316
+ let currentValue = get(store)
317
+ expect(currentValue.status).toBe('pass')
318
+ expect(currentValue.validations).toEqual([
319
+ { text: 'should be "example"', valid: true, status: 'pass' }
320
+ ])
321
+
322
+ store.update('not example')
323
+ currentValue = get(store)
324
+ expect(currentValue.status).toBe('fail')
325
+ expect(currentValue.validations).toEqual([
326
+ { text: 'should be "example"', valid: false, status: 'fail' }
327
+ ])
328
+ })
329
+ it('should work with empty ruleset', () => {
330
+ const store = verifiable('example')
331
+ let currentValue = get(store)
332
+ expect(currentValue.value).toBe('example')
333
+ expect(currentValue.status).toBe('pass')
334
+ expect(currentValue.validations).toEqual([])
335
+
336
+ store.update('not example')
337
+ currentValue = get(store)
338
+ expect(currentValue.value).toBe('not example')
339
+ expect(currentValue.status).toBe('pass')
340
+ expect(currentValue.validations).toEqual([])
341
+ })
342
+ it('should throw error for invalid rule', () => {
343
+ expect(() => {
344
+ verifiable('example', [{ text: 'Invalid rule' }])
345
+ }).toThrow(/Invalid rule/)
346
+ })
347
+ })
348
+ })
@@ -0,0 +1,119 @@
1
+ import { omit, pick } from 'ramda'
2
+
3
+ /**
4
+ * Combines array elements with schema
5
+ *
6
+ * @param {import('../types').LayoutElement} element
7
+ * @param {import('../types').LayoutSchema} attribute
8
+ */
9
+ function combineArrayElementsWithSchema(element, attribute) {
10
+ // eslint-disable-next-line no-use-before-define
11
+ const schema = getSchemaWithLayout(attribute.props.items, element.schema)
12
+ return {
13
+ ...attribute,
14
+ ...pick(['component'], element),
15
+ props: {
16
+ ...omit(['items'], attribute.props),
17
+ schema
18
+ }
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Combines basic elements with schema
24
+ * @param {import('../types').LayoutElement} element
25
+ * @param {import('../types').LayoutSchema} attribute
26
+ * @returns
27
+ */
28
+ function combineBasicElementsWithSchema(element, attribute) {
29
+ return {
30
+ ...attribute,
31
+ ...pick(['component'], element),
32
+ props: {
33
+ ...omit(['scope', 'props', 'component', 'key'], element),
34
+ ...attribute.props,
35
+ ...element.props
36
+ }
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Find an attribute in a schema by path
42
+ * @param {string} scope
43
+ * @param {import('../types').DataSchema} schema
44
+ * @returns {import('../types').LayoutSchema}
45
+ * @throws {Error} Invalid path
46
+ */
47
+ export function findAttributeByPath(scope, schema) {
48
+ if (!scope) return { props: { ...schema } }
49
+
50
+ const pathArray = scope.split('/').slice(1)
51
+ let schemaPointer = schema
52
+ let currentKey = ''
53
+
54
+ pathArray.forEach((key) => {
55
+ schemaPointer = schemaPointer.properties[key]
56
+ currentKey = key
57
+ })
58
+
59
+ if (!schemaPointer) throw new Error(`Invalid scope: ${scope}`)
60
+
61
+ return {
62
+ key: currentKey,
63
+ props: { ...schemaPointer }
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Combines an element from layout with schema
69
+ *
70
+ * @param {import('../types').LayoutElement} element
71
+ * @param {import('../types').DataSchema} schema
72
+ * @returns
73
+ */
74
+ function combineElementWithSchema(element, schema) {
75
+ const { scope } = element
76
+ let attribute = findAttributeByPath(scope, schema)
77
+
78
+ if (Array.isArray(element.elements)) {
79
+ // eslint-disable-next-line no-use-before-define
80
+ attribute = combineNestedElementsWithSchema(element, attribute, schema)
81
+ } else if (element.schema || attribute.props?.type === 'array') {
82
+ attribute = combineArrayElementsWithSchema(element, attribute)
83
+ } else {
84
+ attribute = combineBasicElementsWithSchema(element, attribute)
85
+ }
86
+
87
+ return attribute
88
+ }
89
+ /**
90
+ * Combines nested elements with schema
91
+ *
92
+ * @param {import('../types').LayoutElement} element
93
+ * @param {import('../types').LayoutSchema} attribute
94
+ * @param {import('../types').DataSchema} schema
95
+ * @returns
96
+ */
97
+ function combineNestedElementsWithSchema(element, attribute, schema) {
98
+ const temp = element.elements.map((el) => combineElementWithSchema(el, schema))
99
+ return {
100
+ ...omit(['component', 'props'], attribute),
101
+ ...omit(['scope', 'elements'], element),
102
+ elements: temp
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Get combined schema and layout
108
+ * @param {*} data
109
+ * @param {import('../types').DataSchema} schema
110
+ * @param {import('../types').LayoutSchema} layout
111
+ * @returns {import('../types').LayoutSchema}
112
+ */
113
+ export function getSchemaWithLayout(schema, layout) {
114
+ const combined = omit(['elements'], layout)
115
+ combined.elements =
116
+ layout.elements?.map((element) => combineElementWithSchema(element, schema)) ?? []
117
+
118
+ return combined
119
+ }