@userfrosting/sprinkle-core 6.0.0-alpha.6 → 6.0.0-beta.2
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.
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { parse as YamlParse } from 'yaml'
|
|
2
|
+
import {
|
|
3
|
+
withMessage,
|
|
4
|
+
required,
|
|
5
|
+
maxLength,
|
|
6
|
+
minLength,
|
|
7
|
+
email,
|
|
8
|
+
integer,
|
|
9
|
+
numeric,
|
|
10
|
+
url,
|
|
11
|
+
oneOf,
|
|
12
|
+
between,
|
|
13
|
+
regex,
|
|
14
|
+
not
|
|
15
|
+
} from '@regle/rules'
|
|
16
|
+
import { useTranslator } from '../stores'
|
|
17
|
+
|
|
18
|
+
export function useRuleSchemaAdapter() {
|
|
19
|
+
/**
|
|
20
|
+
* Parse the YAML schema string into a JavaScript object.
|
|
21
|
+
*
|
|
22
|
+
* @param rawSchema The YAML schema string to parse.
|
|
23
|
+
* @returns RuleSchema The Regle schema object.
|
|
24
|
+
*/
|
|
25
|
+
function adapt(rawSchema: string) {
|
|
26
|
+
// The YAML data parsed to a JavaScript object
|
|
27
|
+
const sourceSchema = parse(rawSchema)
|
|
28
|
+
|
|
29
|
+
// The Regle schema object to be returned
|
|
30
|
+
const regleSchema: any = {}
|
|
31
|
+
|
|
32
|
+
// Iterate over each field in the schema
|
|
33
|
+
for (const field in sourceSchema) {
|
|
34
|
+
// Check if the field is a direct property of the sourceSchema object
|
|
35
|
+
if (Object.prototype.hasOwnProperty.call(sourceSchema, field)) {
|
|
36
|
+
// Get the field rules from the source schema
|
|
37
|
+
const schemaFieldRules = sourceSchema[field]?.validators || {}
|
|
38
|
+
|
|
39
|
+
// The returned regle rules for the field
|
|
40
|
+
const regleRules: Record<string, any> = {}
|
|
41
|
+
|
|
42
|
+
// Iterate over each rule in the source schema field rules
|
|
43
|
+
for (const key of Object.keys(schemaFieldRules)) {
|
|
44
|
+
adaptRule(key, schemaFieldRules, regleRules)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
regleSchema[field] = regleRules
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return regleSchema
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Parse the YAML schema string into a JavaScript object.
|
|
56
|
+
*
|
|
57
|
+
* @param rawSchema The YAML schema string to parse.
|
|
58
|
+
* @returns The parsed YAML schema as a JavaScript object.
|
|
59
|
+
*/
|
|
60
|
+
function parse(rawSchema: string): Record<string, any> {
|
|
61
|
+
return YamlParse(rawSchema)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function translateMessage(fieldRulesMeta: { message?: string; [key: string]: any }): string {
|
|
65
|
+
const { translate } = useTranslator()
|
|
66
|
+
|
|
67
|
+
// If there's no message, return an empty string
|
|
68
|
+
if (!fieldRulesMeta.message) {
|
|
69
|
+
return ''
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Copy the field rules meta to avoid mutation and remove the message key
|
|
73
|
+
const fieldRulesMetaCopy = { ...fieldRulesMeta }
|
|
74
|
+
delete fieldRulesMetaCopy.message
|
|
75
|
+
|
|
76
|
+
return translate(fieldRulesMeta.message, fieldRulesMetaCopy)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function adaptRule(key: string, schemaFieldRules: any, regleRules: Record<string, any>) {
|
|
80
|
+
// Required
|
|
81
|
+
if (key === 'required' && schemaFieldRules.required) {
|
|
82
|
+
const message: string = translateMessage(schemaFieldRules.required)
|
|
83
|
+
regleRules['required'] = message === '' ? required : withMessage(required, message)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Email
|
|
87
|
+
if (key === 'email' && schemaFieldRules.email) {
|
|
88
|
+
const message: string = translateMessage(schemaFieldRules.email)
|
|
89
|
+
regleRules['email'] = withMessage(email, message)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Length
|
|
93
|
+
if (key === 'length' && schemaFieldRules.length) {
|
|
94
|
+
if (schemaFieldRules.length.min !== undefined) {
|
|
95
|
+
const message: string = translateMessage(schemaFieldRules.length)
|
|
96
|
+
regleRules['minLength'] =
|
|
97
|
+
message === ''
|
|
98
|
+
? minLength(schemaFieldRules.length.min)
|
|
99
|
+
: withMessage(minLength(schemaFieldRules.length.min), message)
|
|
100
|
+
}
|
|
101
|
+
if (schemaFieldRules.length.max !== undefined) {
|
|
102
|
+
const message: string = translateMessage(schemaFieldRules.length)
|
|
103
|
+
regleRules['maxLength'] =
|
|
104
|
+
message === ''
|
|
105
|
+
? maxLength(schemaFieldRules.length.max)
|
|
106
|
+
: withMessage(maxLength(schemaFieldRules.length.max), message)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// TODO : matches
|
|
111
|
+
if (key === 'matches' && schemaFieldRules.matches) {
|
|
112
|
+
console.warn('Validation rule "matches" not implemented yet')
|
|
113
|
+
// console.debug('Matched rule: matches', schemaFieldRules.matches)
|
|
114
|
+
// sameAs: sameAs(() => form.value.password),
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// TODO : equals
|
|
118
|
+
if (key === 'equals' && schemaFieldRules.equals) {
|
|
119
|
+
console.warn('Validation rule "equals" not implemented yet')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Integer
|
|
123
|
+
if (key === 'integer' && schemaFieldRules.integer) {
|
|
124
|
+
const message: string = translateMessage(schemaFieldRules.integer)
|
|
125
|
+
regleRules['integer'] = message === '' ? integer : withMessage(integer, message)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// member_of
|
|
129
|
+
if (key === 'member_of' && schemaFieldRules.member_of) {
|
|
130
|
+
const message: string = translateMessage(schemaFieldRules.member_of)
|
|
131
|
+
regleRules['member_of'] =
|
|
132
|
+
message === ''
|
|
133
|
+
? oneOf(schemaFieldRules.member_of.values)
|
|
134
|
+
: withMessage(oneOf(schemaFieldRules.member_of.values), message)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// no_leading_whitespace
|
|
138
|
+
if (key === 'no_leading_whitespace' && schemaFieldRules.no_leading_whitespace) {
|
|
139
|
+
const message: string = translateMessage(schemaFieldRules.no_leading_whitespace)
|
|
140
|
+
regleRules['no_leading_whitespace'] =
|
|
141
|
+
message === '' ? regex(/^\S.*$/) : withMessage(regex(/^\S.*$/), message)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// no_trailing_whitespace
|
|
145
|
+
if (key === 'no_trailing_whitespace' && schemaFieldRules.no_trailing_whitespace) {
|
|
146
|
+
const message: string = translateMessage(schemaFieldRules.no_trailing_whitespace)
|
|
147
|
+
regleRules['no_trailing_whitespace'] =
|
|
148
|
+
message === '' ? regex(/^.*\S$/) : withMessage(regex(/^.*\S$/), message)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// TODO : not_equals
|
|
152
|
+
if (key === 'not_equals' && schemaFieldRules.not_equals) {
|
|
153
|
+
console.warn('Validation rule "not_equals" not implemented yet')
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// TODO : not_matches
|
|
157
|
+
if (key === 'not_matches' && schemaFieldRules.not_matches) {
|
|
158
|
+
console.warn('Validation rule "not_matches" not implemented yet')
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// not_member_of
|
|
162
|
+
if (key === 'not_member_of' && schemaFieldRules.not_member_of) {
|
|
163
|
+
const message: string = translateMessage(schemaFieldRules.not_member_of)
|
|
164
|
+
regleRules['not_member_of'] =
|
|
165
|
+
message === ''
|
|
166
|
+
? not(oneOf(schemaFieldRules.not_member_of.values))
|
|
167
|
+
: withMessage(not(oneOf(schemaFieldRules.not_member_of.values)), message)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Numeric
|
|
171
|
+
if (key === 'numeric' && schemaFieldRules.numeric) {
|
|
172
|
+
const message: string = translateMessage(schemaFieldRules.numeric)
|
|
173
|
+
regleRules['numeric'] = message === '' ? numeric : withMessage(numeric, message)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Range
|
|
177
|
+
if (key === 'range' && schemaFieldRules.range) {
|
|
178
|
+
const message: string = translateMessage(schemaFieldRules.range)
|
|
179
|
+
regleRules['range'] =
|
|
180
|
+
message === ''
|
|
181
|
+
? between(schemaFieldRules.range.min, schemaFieldRules.range.max)
|
|
182
|
+
: withMessage(
|
|
183
|
+
between(schemaFieldRules.range.min, schemaFieldRules.range.max),
|
|
184
|
+
message
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Regex
|
|
189
|
+
if (key === 'regex' && schemaFieldRules.regex) {
|
|
190
|
+
const message: string = translateMessage(schemaFieldRules.regex)
|
|
191
|
+
regleRules['regex'] =
|
|
192
|
+
message === ''
|
|
193
|
+
? regex(new RegExp(schemaFieldRules.regex.regex))
|
|
194
|
+
: withMessage(regex(new RegExp(schemaFieldRules.regex.regex)), message)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// TODO : telephone
|
|
198
|
+
if (key === 'telephone' && schemaFieldRules.telephone) {
|
|
199
|
+
console.warn('Validation rule "telephone" not implemented yet')
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// uri
|
|
203
|
+
if (key === 'uri' && schemaFieldRules.uri) {
|
|
204
|
+
const message: string = translateMessage(schemaFieldRules.uri)
|
|
205
|
+
regleRules['uri'] = message === '' ? url : withMessage(url, message)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Username
|
|
209
|
+
if (key === 'username' && schemaFieldRules.username) {
|
|
210
|
+
const message: string = translateMessage(schemaFieldRules.username)
|
|
211
|
+
regleRules['username'] =
|
|
212
|
+
message === ''
|
|
213
|
+
? regex(/^([a-z0-9.\-_])+$/i)
|
|
214
|
+
: withMessage(regex(/^([a-z0-9.\-_])+$/i), message)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return { adapt, parse, translateMessage }
|
|
219
|
+
}
|
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
import { describe, test, expect, vi, afterEach } from 'vitest'
|
|
2
|
+
import { useRuleSchemaAdapter } from '../../composables/useRuleSchemaAdapter'
|
|
3
|
+
import { useRegle } from '@regle/core'
|
|
4
|
+
|
|
5
|
+
// Mock the translator store
|
|
6
|
+
const translateMock = vi.fn((key: string) => key || '')
|
|
7
|
+
vi.mock('@userfrosting/sprinkle-core/stores', () => ({
|
|
8
|
+
useTranslator: () => ({
|
|
9
|
+
translate: translateMock
|
|
10
|
+
})
|
|
11
|
+
}))
|
|
12
|
+
|
|
13
|
+
describe('useRuleSchemaAdapter', () => {
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
vi.clearAllMocks()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('should parse a basic schema', () => {
|
|
19
|
+
const yamlInput = `
|
|
20
|
+
foo:
|
|
21
|
+
validators:
|
|
22
|
+
length:
|
|
23
|
+
min: 1
|
|
24
|
+
max: 132
|
|
25
|
+
required: true
|
|
26
|
+
`
|
|
27
|
+
|
|
28
|
+
const { r$ } = useRegle(
|
|
29
|
+
{
|
|
30
|
+
foo: ''
|
|
31
|
+
},
|
|
32
|
+
useRuleSchemaAdapter().adapt(yamlInput)
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
expect(r$.foo).toBeDefined()
|
|
36
|
+
expect(r$.foo.$rules.required).toBeDefined()
|
|
37
|
+
expect(r$.foo.$rules.minLength).toBeDefined()
|
|
38
|
+
expect(r$.foo.$rules.maxLength).toBeDefined()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('should parse a schema with a custom message', () => {
|
|
42
|
+
const yamlInput = `
|
|
43
|
+
name:
|
|
44
|
+
validators:
|
|
45
|
+
length:
|
|
46
|
+
min: 2
|
|
47
|
+
max: 20
|
|
48
|
+
required: true
|
|
49
|
+
email:
|
|
50
|
+
validators:
|
|
51
|
+
length:
|
|
52
|
+
min: 1
|
|
53
|
+
max: 30
|
|
54
|
+
email: true
|
|
55
|
+
`
|
|
56
|
+
|
|
57
|
+
const { r$ } = useRegle(
|
|
58
|
+
{
|
|
59
|
+
name: '',
|
|
60
|
+
email: ''
|
|
61
|
+
},
|
|
62
|
+
useRuleSchemaAdapter().adapt(yamlInput)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
expect(r$.name).toBeDefined()
|
|
66
|
+
expect(r$.name.$rules.required).toBeDefined()
|
|
67
|
+
expect(r$.name.$rules.minLength).toBeDefined()
|
|
68
|
+
expect(r$.name.$rules.maxLength).toBeDefined()
|
|
69
|
+
expect(r$.email).toBeDefined()
|
|
70
|
+
expect(r$.email.$rules.required).not.toBeDefined()
|
|
71
|
+
expect(r$.email.$rules.minLength).toBeDefined()
|
|
72
|
+
expect(r$.email.$rules.maxLength).toBeDefined()
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('should parse a bad schema without errors', () => {
|
|
76
|
+
// N.B.: The schema is missing the 'validators' key
|
|
77
|
+
const yamlInput = `
|
|
78
|
+
foo:
|
|
79
|
+
length:
|
|
80
|
+
min: 1
|
|
81
|
+
max: 132
|
|
82
|
+
required: true
|
|
83
|
+
`
|
|
84
|
+
|
|
85
|
+
const { r$ } = useRegle(
|
|
86
|
+
{
|
|
87
|
+
foo: ''
|
|
88
|
+
},
|
|
89
|
+
useRuleSchemaAdapter().adapt(yamlInput)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
expect(r$.foo).toBeDefined()
|
|
93
|
+
expect(r$.foo.$rules.required).not.toBeDefined()
|
|
94
|
+
expect(r$.foo.$rules.minLength).not.toBeDefined()
|
|
95
|
+
expect(r$.foo.$rules.maxLength).not.toBeDefined()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('required rule', () => {
|
|
99
|
+
const yamlInput = `
|
|
100
|
+
first_name:
|
|
101
|
+
validators:
|
|
102
|
+
required:
|
|
103
|
+
label: "&FIRST_NAME"
|
|
104
|
+
message: VALIDATE.REQUIRED
|
|
105
|
+
last_name:
|
|
106
|
+
validators:
|
|
107
|
+
required: true
|
|
108
|
+
`
|
|
109
|
+
|
|
110
|
+
const { r$ } = useRegle(
|
|
111
|
+
{
|
|
112
|
+
first_name: '',
|
|
113
|
+
last_name: ''
|
|
114
|
+
},
|
|
115
|
+
useRuleSchemaAdapter().adapt(yamlInput)
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
// Set translator expectations
|
|
119
|
+
expect(translateMock).toHaveBeenCalledExactlyOnceWith('VALIDATE.REQUIRED', {
|
|
120
|
+
label: '&FIRST_NAME'
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
expect(r$.first_name.$rules.required.$message).toBe('VALIDATE.REQUIRED') // Custom message
|
|
124
|
+
expect(r$.last_name.$rules.required.$message).toBe('This field is required') // Default message
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test('email rule', () => {
|
|
128
|
+
const yamlInput = `
|
|
129
|
+
email:
|
|
130
|
+
validators:
|
|
131
|
+
email:
|
|
132
|
+
message: VALIDATE.INVALID_EMAIL
|
|
133
|
+
email2:
|
|
134
|
+
validators:
|
|
135
|
+
email: true
|
|
136
|
+
`
|
|
137
|
+
|
|
138
|
+
const { r$ } = useRegle(
|
|
139
|
+
{
|
|
140
|
+
email: '',
|
|
141
|
+
email2: ''
|
|
142
|
+
},
|
|
143
|
+
useRuleSchemaAdapter().adapt(yamlInput)
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
// Set translator expectations
|
|
147
|
+
expect(translateMock).toHaveBeenCalledExactlyOnceWith('VALIDATE.INVALID_EMAIL', {})
|
|
148
|
+
|
|
149
|
+
expect(r$.email.$rules.email.$message).toBe('VALIDATE.INVALID_EMAIL') // Custom message
|
|
150
|
+
expect(r$.email2.$rules.email.$message).toBe('This field is not valid') // Default message
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
test('length rule', () => {
|
|
154
|
+
const yamlInput = `
|
|
155
|
+
tooShort:
|
|
156
|
+
validators:
|
|
157
|
+
length:
|
|
158
|
+
min: 5
|
|
159
|
+
tooLong:
|
|
160
|
+
validators:
|
|
161
|
+
length:
|
|
162
|
+
max: 2
|
|
163
|
+
tooShortWithMessage:
|
|
164
|
+
validators:
|
|
165
|
+
length:
|
|
166
|
+
min: 5
|
|
167
|
+
message: VALIDATE.LENGTH_RANGE
|
|
168
|
+
tooLongWithMessage:
|
|
169
|
+
validators:
|
|
170
|
+
length:
|
|
171
|
+
max: 2
|
|
172
|
+
message: VALIDATE.LENGTH_RANGE
|
|
173
|
+
`
|
|
174
|
+
|
|
175
|
+
const { r$ } = useRegle(
|
|
176
|
+
{
|
|
177
|
+
tooShort: '1',
|
|
178
|
+
tooLong: '123',
|
|
179
|
+
tooShortWithMessage: '1',
|
|
180
|
+
tooLongWithMessage: '123'
|
|
181
|
+
},
|
|
182
|
+
useRuleSchemaAdapter().adapt(yamlInput)
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
// Set translator expectations
|
|
186
|
+
expect(translateMock).toHaveBeenNthCalledWith(1, 'VALIDATE.LENGTH_RANGE', { min: 5 })
|
|
187
|
+
expect(translateMock).toHaveBeenNthCalledWith(2, 'VALIDATE.LENGTH_RANGE', { max: 2 })
|
|
188
|
+
|
|
189
|
+
expect(r$.tooShort.$silentErrors).toEqual(['The value length should be at least 5']) // Custom message
|
|
190
|
+
expect(r$.tooLong.$silentErrors).toEqual(['The value length should not exceed 2']) // Default message
|
|
191
|
+
expect(r$.tooShortWithMessage.$silentErrors).toEqual(['VALIDATE.LENGTH_RANGE']) // Custom message
|
|
192
|
+
expect(r$.tooLongWithMessage.$silentErrors).toEqual(['VALIDATE.LENGTH_RANGE']) // Default message
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
test('integer rule', () => {
|
|
196
|
+
const yamlInput = `
|
|
197
|
+
foo:
|
|
198
|
+
validators:
|
|
199
|
+
integer: true
|
|
200
|
+
bar:
|
|
201
|
+
validators:
|
|
202
|
+
integer:
|
|
203
|
+
message: VALIDATE.INVALID_INTEGER
|
|
204
|
+
foobar:
|
|
205
|
+
validators:
|
|
206
|
+
integer: true
|
|
207
|
+
`
|
|
208
|
+
|
|
209
|
+
const { r$ } = useRegle(
|
|
210
|
+
{
|
|
211
|
+
foo: 'one',
|
|
212
|
+
bar: 'two',
|
|
213
|
+
foobar: 92
|
|
214
|
+
},
|
|
215
|
+
useRuleSchemaAdapter().adapt(yamlInput)
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
// Set translator expectations
|
|
219
|
+
expect(translateMock).toHaveBeenCalledExactlyOnceWith('VALIDATE.INVALID_INTEGER', {})
|
|
220
|
+
|
|
221
|
+
expect(r$.foo.$silentErrors).toEqual(['The value must be an integer']) // Custom message
|
|
222
|
+
expect(r$.bar.$silentErrors).toEqual(['VALIDATE.INVALID_INTEGER']) // Default message
|
|
223
|
+
expect(r$.foobar.$silentErrors).toEqual([]) // Valid
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
test('member_of rule', () => {
|
|
227
|
+
const yamlInput = `
|
|
228
|
+
genus:
|
|
229
|
+
validators:
|
|
230
|
+
member_of:
|
|
231
|
+
values:
|
|
232
|
+
- Megascops
|
|
233
|
+
- Bubo
|
|
234
|
+
- Glaucidium
|
|
235
|
+
- Tyto
|
|
236
|
+
- Athene
|
|
237
|
+
message: Sorry, that is not one of the permitted genuses.
|
|
238
|
+
owls:
|
|
239
|
+
validators:
|
|
240
|
+
member_of:
|
|
241
|
+
values:
|
|
242
|
+
- Foo
|
|
243
|
+
- Bar
|
|
244
|
+
valid:
|
|
245
|
+
validators:
|
|
246
|
+
member_of:
|
|
247
|
+
values:
|
|
248
|
+
- Foo
|
|
249
|
+
- Bar
|
|
250
|
+
`
|
|
251
|
+
|
|
252
|
+
const { r$ } = useRegle(
|
|
253
|
+
{
|
|
254
|
+
genus: 'Foo',
|
|
255
|
+
owls: 'Hedwig',
|
|
256
|
+
valid: 'Foo'
|
|
257
|
+
},
|
|
258
|
+
useRuleSchemaAdapter().adapt(yamlInput)
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
// Set translator expectations
|
|
262
|
+
expect(translateMock).toHaveBeenCalledExactlyOnceWith(
|
|
263
|
+
'Sorry, that is not one of the permitted genuses.',
|
|
264
|
+
{
|
|
265
|
+
values: ['Megascops', 'Bubo', 'Glaucidium', 'Tyto', 'Athene']
|
|
266
|
+
}
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
expect(r$.genus.$silentErrors).toEqual(['Sorry, that is not one of the permitted genuses.']) // Custom message
|
|
270
|
+
expect(r$.owls.$silentErrors).toEqual([
|
|
271
|
+
'The value should be one of those options: Foo, Bar.'
|
|
272
|
+
]) // Default message
|
|
273
|
+
expect(r$.valid.$silentErrors).toEqual([]) // Valid
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
test('not_member_of rule', () => {
|
|
277
|
+
const yamlInput = `
|
|
278
|
+
genus:
|
|
279
|
+
validators:
|
|
280
|
+
not_member_of:
|
|
281
|
+
values:
|
|
282
|
+
- Megascops
|
|
283
|
+
- Bubo
|
|
284
|
+
- Glaucidium
|
|
285
|
+
- Tyto
|
|
286
|
+
- Athene
|
|
287
|
+
message: VALIDATE.NOT_MEMBER_OF
|
|
288
|
+
owls:
|
|
289
|
+
validators:
|
|
290
|
+
not_member_of:
|
|
291
|
+
values:
|
|
292
|
+
- Foo
|
|
293
|
+
- Bar
|
|
294
|
+
valid:
|
|
295
|
+
validators:
|
|
296
|
+
not_member_of:
|
|
297
|
+
values:
|
|
298
|
+
- Foo
|
|
299
|
+
- Bar
|
|
300
|
+
`
|
|
301
|
+
|
|
302
|
+
const { r$ } = useRegle(
|
|
303
|
+
{
|
|
304
|
+
genus: 'Megascops',
|
|
305
|
+
owls: 'Foo',
|
|
306
|
+
valid: 'Hedwig'
|
|
307
|
+
},
|
|
308
|
+
useRuleSchemaAdapter().adapt(yamlInput)
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
// Set translator expectations
|
|
312
|
+
expect(translateMock).toHaveBeenCalledExactlyOnceWith('VALIDATE.NOT_MEMBER_OF', {
|
|
313
|
+
values: ['Megascops', 'Bubo', 'Glaucidium', 'Tyto', 'Athene']
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
expect(r$.genus.$silentErrors).toEqual(['VALIDATE.NOT_MEMBER_OF']) // Custom message
|
|
317
|
+
expect(r$.owls.$silentErrors).toEqual(['Error']) // Default message
|
|
318
|
+
expect(r$.valid.$silentErrors).toEqual([]) // Valid
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
test('no_leading_whitespace rule', () => {
|
|
322
|
+
const yamlInput = `
|
|
323
|
+
withMessage:
|
|
324
|
+
validators:
|
|
325
|
+
no_leading_whitespace:
|
|
326
|
+
label: "&USERNAME"
|
|
327
|
+
message: VALIDATE.NO_LEAD_WS
|
|
328
|
+
defaultMessage:
|
|
329
|
+
validators:
|
|
330
|
+
no_leading_whitespace: true
|
|
331
|
+
valid:
|
|
332
|
+
validators:
|
|
333
|
+
no_leading_whitespace: true
|
|
334
|
+
`
|
|
335
|
+
|
|
336
|
+
const { r$ } = useRegle(
|
|
337
|
+
{
|
|
338
|
+
withMessage: ' Foo',
|
|
339
|
+
defaultMessage: ' Foo',
|
|
340
|
+
valid: 'Foo'
|
|
341
|
+
},
|
|
342
|
+
useRuleSchemaAdapter().adapt(yamlInput)
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
// Set translator expectations
|
|
346
|
+
expect(translateMock).toHaveBeenCalledExactlyOnceWith('VALIDATE.NO_LEAD_WS', {
|
|
347
|
+
label: '&USERNAME'
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
expect(r$.withMessage.$silentErrors).toEqual(['VALIDATE.NO_LEAD_WS']) // Custom message
|
|
351
|
+
expect(r$.defaultMessage.$silentErrors).toEqual([
|
|
352
|
+
'The value does not match the required pattern'
|
|
353
|
+
]) // Default message
|
|
354
|
+
expect(r$.valid.$silentErrors).toEqual([]) // Valid
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
test('no_trailing_whitespace rule', () => {
|
|
358
|
+
const yamlInput = `
|
|
359
|
+
withMessage:
|
|
360
|
+
validators:
|
|
361
|
+
no_trailing_whitespace:
|
|
362
|
+
label: "&USERNAME"
|
|
363
|
+
message: VALIDATE.NO_TRAIL_WS
|
|
364
|
+
defaultMessage:
|
|
365
|
+
validators:
|
|
366
|
+
no_trailing_whitespace: true
|
|
367
|
+
valid:
|
|
368
|
+
validators:
|
|
369
|
+
no_trailing_whitespace: true
|
|
370
|
+
`
|
|
371
|
+
|
|
372
|
+
const { r$ } = useRegle(
|
|
373
|
+
{
|
|
374
|
+
withMessage: 'Foo ',
|
|
375
|
+
defaultMessage: 'Foo ',
|
|
376
|
+
valid: 'Foo'
|
|
377
|
+
},
|
|
378
|
+
useRuleSchemaAdapter().adapt(yamlInput)
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
// Set translator expectations
|
|
382
|
+
expect(translateMock).toHaveBeenCalledExactlyOnceWith('VALIDATE.NO_TRAIL_WS', {
|
|
383
|
+
label: '&USERNAME'
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
expect(r$.withMessage.$silentErrors).toEqual(['VALIDATE.NO_TRAIL_WS']) // Custom message
|
|
387
|
+
expect(r$.defaultMessage.$silentErrors).toEqual([
|
|
388
|
+
'The value does not match the required pattern'
|
|
389
|
+
]) // Default message
|
|
390
|
+
expect(r$.valid.$silentErrors).toEqual([]) // Valid
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
test('numeric rule', () => {
|
|
394
|
+
const yamlInput = `
|
|
395
|
+
withMessage:
|
|
396
|
+
validators:
|
|
397
|
+
numeric:
|
|
398
|
+
message: VALIDATE.INVALID_NUMERIC
|
|
399
|
+
defaultMessage:
|
|
400
|
+
validators:
|
|
401
|
+
numeric: true
|
|
402
|
+
valid:
|
|
403
|
+
validators:
|
|
404
|
+
numeric: true
|
|
405
|
+
`
|
|
406
|
+
|
|
407
|
+
const { r$ } = useRegle(
|
|
408
|
+
{
|
|
409
|
+
withMessage: 'Foo',
|
|
410
|
+
defaultMessage: 'Foo',
|
|
411
|
+
valid: '10.2'
|
|
412
|
+
},
|
|
413
|
+
useRuleSchemaAdapter().adapt(yamlInput)
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
// Set translator expectations
|
|
417
|
+
expect(translateMock).toHaveBeenCalledExactlyOnceWith('VALIDATE.INVALID_NUMERIC', {})
|
|
418
|
+
|
|
419
|
+
expect(r$.withMessage.$silentErrors).toEqual(['VALIDATE.INVALID_NUMERIC']) // Custom message
|
|
420
|
+
expect(r$.defaultMessage.$silentErrors).toEqual(['The value must be numeric']) // Default message
|
|
421
|
+
expect(r$.valid.$silentErrors).toEqual([]) // Valid
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
test('range rule', () => {
|
|
425
|
+
const yamlInput = `
|
|
426
|
+
withMessage:
|
|
427
|
+
validators:
|
|
428
|
+
range:
|
|
429
|
+
min: 0
|
|
430
|
+
max: 10
|
|
431
|
+
message: VALIDATE.INVALID_RANGE
|
|
432
|
+
defaultMessage:
|
|
433
|
+
validators:
|
|
434
|
+
range:
|
|
435
|
+
min: 0
|
|
436
|
+
max: 10
|
|
437
|
+
valid:
|
|
438
|
+
validators:
|
|
439
|
+
range:
|
|
440
|
+
min: 0
|
|
441
|
+
max: 10
|
|
442
|
+
`
|
|
443
|
+
|
|
444
|
+
const { r$ } = useRegle(
|
|
445
|
+
{
|
|
446
|
+
withMessage: 92,
|
|
447
|
+
defaultMessage: 92,
|
|
448
|
+
valid: 9
|
|
449
|
+
},
|
|
450
|
+
useRuleSchemaAdapter().adapt(yamlInput)
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
// Set translator expectations
|
|
454
|
+
expect(translateMock).toHaveBeenCalledExactlyOnceWith('VALIDATE.INVALID_RANGE', {
|
|
455
|
+
min: 0,
|
|
456
|
+
max: 10
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
expect(r$.withMessage.$silentErrors).toEqual(['VALIDATE.INVALID_RANGE']) // Custom message
|
|
460
|
+
expect(r$.defaultMessage.$silentErrors).toEqual(['The value must be between 0 and 10']) // Default message
|
|
461
|
+
expect(r$.valid.$silentErrors).toEqual([]) // Valid
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
test('regex rule', () => {
|
|
465
|
+
const yamlInput = `
|
|
466
|
+
withMessage:
|
|
467
|
+
validators:
|
|
468
|
+
regex:
|
|
469
|
+
regex: ^who(o*)$
|
|
470
|
+
message: VALIDATE.INVALID_VALUE
|
|
471
|
+
defaultMessage:
|
|
472
|
+
validators:
|
|
473
|
+
regex:
|
|
474
|
+
regex: ^who(o*)$
|
|
475
|
+
valid:
|
|
476
|
+
validators:
|
|
477
|
+
regex:
|
|
478
|
+
regex: ^who(o*)$
|
|
479
|
+
`
|
|
480
|
+
|
|
481
|
+
const { r$ } = useRegle(
|
|
482
|
+
{
|
|
483
|
+
withMessage: 'hum',
|
|
484
|
+
defaultMessage: 'hello',
|
|
485
|
+
valid: 'whooo'
|
|
486
|
+
},
|
|
487
|
+
useRuleSchemaAdapter().adapt(yamlInput)
|
|
488
|
+
)
|
|
489
|
+
r$.$validate()
|
|
490
|
+
|
|
491
|
+
// Set translator expectations
|
|
492
|
+
expect(translateMock).toHaveBeenCalledExactlyOnceWith('VALIDATE.INVALID_VALUE', {
|
|
493
|
+
regex: '^who(o*)$'
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
expect(r$.withMessage.$errors).toEqual(['VALIDATE.INVALID_VALUE']) // Custom message
|
|
497
|
+
expect(r$.defaultMessage.$errors).toEqual(['The value does not match the required pattern']) // Default message
|
|
498
|
+
expect(r$.valid.$correct).toEqual(true) // Valid
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
test('uri rule', () => {
|
|
502
|
+
const yamlInput = `
|
|
503
|
+
withMessage:
|
|
504
|
+
validators:
|
|
505
|
+
uri:
|
|
506
|
+
message: VALIDATE.INVALID_URL
|
|
507
|
+
defaultMessage:
|
|
508
|
+
validators:
|
|
509
|
+
uri: true
|
|
510
|
+
valid:
|
|
511
|
+
validators:
|
|
512
|
+
uri: true
|
|
513
|
+
`
|
|
514
|
+
|
|
515
|
+
const { r$ } = useRegle(
|
|
516
|
+
{
|
|
517
|
+
withMessage: 'foo',
|
|
518
|
+
defaultMessage: 'bar@example.com',
|
|
519
|
+
valid: 'http://example.com'
|
|
520
|
+
},
|
|
521
|
+
useRuleSchemaAdapter().adapt(yamlInput)
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
// Set translator expectations
|
|
525
|
+
expect(translateMock).toHaveBeenCalledExactlyOnceWith('VALIDATE.INVALID_URL', {})
|
|
526
|
+
|
|
527
|
+
expect(r$.withMessage.$silentErrors).toEqual(['VALIDATE.INVALID_URL']) // Custom message
|
|
528
|
+
expect(r$.defaultMessage.$silentErrors).toEqual(['The value is not a valid URL address']) // Default message
|
|
529
|
+
expect(r$.valid.$silentErrors).toEqual([]) // Valid
|
|
530
|
+
})
|
|
531
|
+
|
|
532
|
+
test('username rule', () => {
|
|
533
|
+
const yamlInput = `
|
|
534
|
+
withMessage:
|
|
535
|
+
validators:
|
|
536
|
+
username:
|
|
537
|
+
message: VALIDATE.INVALID_USERNAME
|
|
538
|
+
defaultMessage:
|
|
539
|
+
validators:
|
|
540
|
+
username: true
|
|
541
|
+
valid:
|
|
542
|
+
validators:
|
|
543
|
+
username: true
|
|
544
|
+
`
|
|
545
|
+
|
|
546
|
+
const { r$ } = useRegle(
|
|
547
|
+
{
|
|
548
|
+
withMessage: 'My Name',
|
|
549
|
+
defaultMessage: 'bar@example.com',
|
|
550
|
+
valid: 'foo.bar-bax_123'
|
|
551
|
+
},
|
|
552
|
+
useRuleSchemaAdapter().adapt(yamlInput)
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
// Set translator expectations
|
|
556
|
+
expect(translateMock).toHaveBeenCalledExactlyOnceWith('VALIDATE.INVALID_USERNAME', {})
|
|
557
|
+
|
|
558
|
+
expect(r$.withMessage.$silentErrors).toEqual(['VALIDATE.INVALID_USERNAME']) // Custom message
|
|
559
|
+
expect(r$.defaultMessage.$silentErrors).toEqual([
|
|
560
|
+
'The value does not match the required pattern'
|
|
561
|
+
]) // Default message
|
|
562
|
+
expect(r$.valid.$silentErrors).toEqual([]) // Valid
|
|
563
|
+
})
|
|
564
|
+
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@userfrosting/sprinkle-core",
|
|
3
|
-
"version": "6.0.0-
|
|
3
|
+
"version": "6.0.0-beta.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Core Sprinkle for UserFrosting",
|
|
6
6
|
"funding": "https://opencollective.com/userfrosting",
|
|
@@ -34,8 +34,11 @@
|
|
|
34
34
|
"app/assets/"
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
|
+
"@regle/core": "^1.6.0",
|
|
38
|
+
"@regle/rules": "^1.6.0",
|
|
37
39
|
"dot-prop": "^9.0.0",
|
|
38
|
-
"luxon": "^3.5.0"
|
|
40
|
+
"luxon": "^3.5.0",
|
|
41
|
+
"yaml": "^2.8.0"
|
|
39
42
|
},
|
|
40
43
|
"peerDependencies": {
|
|
41
44
|
"axios": "^1.5.0",
|