Skip to content

Commit

Permalink
Expose "only" config as public API after renaming from "validate" (#109)
Browse files Browse the repository at this point in the history
  • Loading branch information
timacdonald authored Jan 13, 2025
1 parent 5406457 commit a044af2
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 25 deletions.
40 changes: 22 additions & 18 deletions packages/core/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,24 +123,28 @@ const request = (userConfig: Config = {}): Promise<unknown> => {
/**
* Resolve the configuration.
*/
const resolveConfig = (config: Config): Config => ({
...config,
timeout: config.timeout ?? axiosClient.defaults['timeout'] ?? 30000,
precognitive: config.precognitive !== false,
fingerprint: typeof config.fingerprint === 'undefined'
? requestFingerprintResolver(config, axiosClient)
: config.fingerprint,
headers: {
...config.headers,
'Content-Type': resolveContentType(config),
...config.precognitive !== false ? {
Precognition: true,
} : {},
...config.validate ? {
'Precognition-Validate-Only': Array.from(config.validate).join(),
} : {},
},
})
const resolveConfig = (config: Config): Config => {
const only = config.only ?? config.validate

return {
...config,
timeout: config.timeout ?? axiosClient.defaults['timeout'] ?? 30000,
precognitive: config.precognitive !== false,
fingerprint: typeof config.fingerprint === 'undefined'
? requestFingerprintResolver(config, axiosClient)
: config.fingerprint,
headers: {
...config.headers,
'Content-Type': resolveContentType(config),
...config.precognitive !== false ? {
Precognition: true,
} : {},
...only ? {
'Precognition-Validate-Only': Array.from(only).join(),
} : {},
},
}
}

/**
* Determine if the status is successful.
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ export type SimpleValidationErrors = Record<string, string>

export type Config = AxiosRequestConfig & {
precognitive?: boolean,
/** @deprecated Use `only` instead */
validate?: Iterable<string> | ArrayLike<string>,
only?: Iterable<string> | ArrayLike<string>,
fingerprint?: string | null,
onBefore?: () => boolean | undefined,
onStart?: () => void,
Expand Down
18 changes: 11 additions & 7 deletions packages/core/src/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,37 +216,37 @@ export const createValidator = (callback: ValidationCallback, initialData: Recor
...instanceConfig,
}

const validate = Array.from(config.validate ?? touched)
const only = Array.from(config.only ?? config.validate ?? touched)

return {
...instanceConfig,
// Axios has special rules for merging global and local config. We
// use their merge function here to make sure things like headers
// merge in an expected way.
...mergeConfig(globalConfig, instanceConfig),
validate,
only,
timeout: config.timeout ?? 5000,
onValidationError: (response, axiosError) => {
[
...setValidated([...validated, ...validate]),
...setErrors(merge(omit({ ...errors }, validate), response.data.errors)),
...setValidated([...validated, ...only]),
...setErrors(merge(omit({ ...errors }, only), response.data.errors)),
].forEach((listener) => listener())

return config.onValidationError
? config.onValidationError(response, axiosError)
: Promise.reject(axiosError)
},
onSuccess: (response) => {
setValidated([...validated, ...validate]).forEach((listener) => listener())
setValidated([...validated, ...only]).forEach((listener) => listener())

return config.onSuccess
? config.onSuccess(response)
: response
},
onPrecognitionSuccess: (response) => {
[
...setValidated([...validated, ...validate]),
...setErrors(omit({ ...errors }, validate)),
...setValidated([...validated, ...only]),
...setErrors(omit({ ...errors }, only)),
].forEach((listener) => listener())

return config.onPrecognitionSuccess
Expand Down Expand Up @@ -294,6 +294,10 @@ export const createValidator = (callback: ValidationCallback, initialData: Recor
*/
const validate = (name?: string | NamedInputEvent, value?: unknown, config?: ValidationConfig): void => {
if (typeof name === 'undefined') {
const only = Array.from(config?.only ?? config?.validate ?? [])

setTouched([...touched, ...only]).forEach((listener) => listener())

validator(config ?? {})

return
Expand Down
16 changes: 16 additions & 0 deletions packages/core/tests/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,22 @@ it('can provide input names to validate via config', async () => {
return Promise.resolve({ headers: { precognition: 'true' } })
})

await client.get('https://laravel.com', {}, {
only: ['username', 'email'],
})

expect(config.headers['Precognition-Validate-Only']).toBe('username,email')
})

it('continues to support the deprecated "validate" key as fallback of "only"', async () => {
expect.assertions(1)

let config
axios.request.mockImplementationOnce((c) => {
config = c
return Promise.resolve({ headers: { precognition: 'true' } })
})

await client.get('https://laravel.com', {}, {
validate: ['username', 'email'],
})
Expand Down
71 changes: 71 additions & 0 deletions packages/core/tests/validator.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -674,3 +674,74 @@ it('does not cancel submit requests with custom abort signal', async () => {

await assertPendingValidateDebounceAndClear()
})

it('supports async validate with only key for untouched values', async () => {
let config
axios.request.mockImplementation((c) => {
config = c

return Promise.resolve({ headers: { precognition: 'true', 'precognition-success': 'true' }, status: 204, data: '' })
})
const validator = createValidator((client) => client.post('/foo', {}))

validator.validate({
only: ['name', 'email'],
})

expect(config.headers['Precognition-Validate-Only']).toBe('name,email')

await assertPendingValidateDebounceAndClear()
})

it('supports async validate with depricated validate key for untouched values', async () => {
let config
axios.request.mockImplementation((c) => {
config = c

return Promise.resolve({ headers: { precognition: 'true', 'precognition-success': 'true' }, status: 204, data: '' })
})
const validator = createValidator((client) => client.post('/foo', {}))

validator.validate({
validate: ['name', 'email'],
})

expect(config.headers['Precognition-Validate-Only']).toBe('name,email')

await assertPendingValidateDebounceAndClear()
})

it('does not include already touched keys when specifying keys via only', async () => {
let config
axios.request.mockImplementation((c) => {
config = c

return Promise.resolve({ headers: { precognition: 'true', 'precognition-success': 'true' }, status: 204, data: '' })
})
const validator = createValidator((client) => client.post('/foo', {}))


validator.touch(['email']).validate({
only: ['name'],
})

expect(config.headers['Precognition-Validate-Only']).toBe('name')

await assertPendingValidateDebounceAndClear()
})

it('marks fields as touched when the input has been included in validation', async () => {
axios.request.mockImplementation(() => {
return Promise.resolve({ headers: { precognition: 'true', 'precognition-success': 'true' }, status: 204, data: '' })
})
const validator = createValidator((client) => client.post('/foo', {}))


validator.touch(['email']).validate({
only: ['name'],
})

expect(validator.touched()).toEqual(['email', 'name'])

await assertPendingValidateDebounceAndClear()
})

0 comments on commit a044af2

Please sign in to comment.