diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index cbd4b29..15bc16e 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -123,24 +123,28 @@ const request = (userConfig: Config = {}): Promise => { /** * 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. diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 44c22dc..585f016 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -8,7 +8,9 @@ export type SimpleValidationErrors = Record export type Config = AxiosRequestConfig & { precognitive?: boolean, + /** @deprecated Use `only` instead */ validate?: Iterable | ArrayLike, + only?: Iterable | ArrayLike, fingerprint?: string | null, onBefore?: () => boolean | undefined, onStart?: () => void, diff --git a/packages/core/src/validator.ts b/packages/core/src/validator.ts index 58a74be..d11495d 100644 --- a/packages/core/src/validator.ts +++ b/packages/core/src/validator.ts @@ -216,7 +216,7 @@ 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, @@ -224,12 +224,12 @@ export const createValidator = (callback: ValidationCallback, initialData: Recor // 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 @@ -237,7 +237,7 @@ export const createValidator = (callback: ValidationCallback, initialData: Recor : Promise.reject(axiosError) }, onSuccess: (response) => { - setValidated([...validated, ...validate]).forEach((listener) => listener()) + setValidated([...validated, ...only]).forEach((listener) => listener()) return config.onSuccess ? config.onSuccess(response) @@ -245,8 +245,8 @@ export const createValidator = (callback: ValidationCallback, initialData: Recor }, onPrecognitionSuccess: (response) => { [ - ...setValidated([...validated, ...validate]), - ...setErrors(omit({ ...errors }, validate)), + ...setValidated([...validated, ...only]), + ...setErrors(omit({ ...errors }, only)), ].forEach((listener) => listener()) return config.onPrecognitionSuccess @@ -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 diff --git a/packages/core/tests/client.test.js b/packages/core/tests/client.test.js index e7993fe..a28fba0 100644 --- a/packages/core/tests/client.test.js +++ b/packages/core/tests/client.test.js @@ -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'], }) diff --git a/packages/core/tests/validator.test.js b/packages/core/tests/validator.test.js index cd07cc2..c84d368 100644 --- a/packages/core/tests/validator.test.js +++ b/packages/core/tests/validator.test.js @@ -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() +})