Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/lib/unstable/__tests__/helpers.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type {Tools} from 'final-form';

import {JsonSchemaType, SchemaRendererMode} from '../core/constants';
import type {
IndependentView,
Expand Down Expand Up @@ -194,6 +196,9 @@ export function createMockSchema<T extends JsonSchema>(
} as T;
}

export const mockTools = {} as Tools<{}, {}>;
export const mockServiceFieldName = 'mockServiceFieldName';

describe('helpers', () => {
test('just empty test', () => {
expect(true).toBe(true);
Expand Down
4 changes: 2 additions & 2 deletions src/lib/unstable/core/SchemaRendererServiceField/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,13 +278,13 @@ const processAjvValidateErrors = <Schema extends JsonSchema>({

w.promise.then((result) => {
setValidationCache({
name: serviceFieldName,
cache: {
[w.instancePath]: {
...w.params,
result,
},
},
serviceFieldName,
});
});
},
Expand Down Expand Up @@ -397,7 +397,7 @@ export const getValidate = <Schema extends JsonSchema>({
});

if (Object.keys(waiters).length) {
setValidationWaiters({name: serviceFieldName, waiters});
setValidationWaiters({serviceFieldName, waiters});
}

const result = processErrorItems({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
import type {InternalFormState, MutableState} from 'final-form';

import {mockServiceFieldName, mockTools} from '../../../../__tests__/helpers.test';
import {JsonSchemaType} from '../../../constants';
import {setValidationCache, setValidationWaiters} from '../async-validation';
import type {ValidationCache, ValidationWaiter} from '../types';

const createMockWaiterAndCache = (prefix = '') => {
const fieldName = `fieldName${prefix}`;
const waiter: ValidationWaiter = {
schema: {type: JsonSchemaType.String},
validator: jest.fn(),
value: `value${prefix}`,
};
const cache: ValidationCache = {...waiter, result: `result${prefix}`};

return {cache, fieldName, waiter};
};

const createMockMutableState = (fields: Record<string, any> = {}): MutableState<{}, {}> => {
const mockFormState = {} as InternalFormState;

return {
fields,
formState: mockFormState,
fieldSubscribers: {},
};
};

describe('async-validation', () => {
describe('setValidationWaiters', () => {
it('should not modify state if service field does not exist', () => {
const {fieldName, waiter} = createMockWaiterAndCache();
const mutableState = createMockMutableState();

setValidationWaiters(
[{serviceFieldName: mockServiceFieldName, waiters: {[fieldName]: waiter}}],
mutableState,
mockTools,
);

expect(mutableState.fields).toEqual({});
});

it('should not modify state if waiters are not provided', () => {
const mutableState = createMockMutableState({
[mockServiceFieldName]: {data: {}},
});

setValidationWaiters(
[{serviceFieldName: mockServiceFieldName, waiters: undefined as any}],
mutableState,
mockTools,
);

expect(mutableState.fields[mockServiceFieldName].data).toEqual({});
});

it('should add waiter to the validation state', () => {
const {fieldName, waiter} = createMockWaiterAndCache();
const mutableState = createMockMutableState({
[mockServiceFieldName]: {data: {}},
[fieldName]: {},
});

setValidationWaiters(
[{serviceFieldName: mockServiceFieldName, waiters: {[fieldName]: waiter}}],
mutableState,
mockTools,
);

expect(mutableState.fields[mockServiceFieldName].data.waiters).toEqual({
[fieldName]: waiter,
});
expect(mutableState.fields[fieldName].validating).toBe(true);
});

it('should add waiters to the validation state', () => {
const waiterAndCacheKit = createMockWaiterAndCache();
const waiterAndCacheKit2 = createMockWaiterAndCache('2');

const mutableState = createMockMutableState({
[mockServiceFieldName]: {data: {}},
[waiterAndCacheKit.fieldName]: {validating: false},
[waiterAndCacheKit2.fieldName]: {validating: false},
});

setValidationWaiters(
[
{
serviceFieldName: mockServiceFieldName,
waiters: {
[waiterAndCacheKit.fieldName]: waiterAndCacheKit.waiter,
[waiterAndCacheKit2.fieldName]: waiterAndCacheKit2.waiter,
},
},
],
mutableState,
mockTools,
);

expect(mutableState.fields[mockServiceFieldName].data.waiters).toEqual({
[waiterAndCacheKit.fieldName]: waiterAndCacheKit.waiter,
[waiterAndCacheKit2.fieldName]: waiterAndCacheKit2.waiter,
});
expect(mutableState.fields[waiterAndCacheKit.fieldName].validating).toBe(true);
expect(mutableState.fields[waiterAndCacheKit2.fieldName].validating).toBe(true);
});

it('should merge waiters with existing waiters', () => {
const waiterAndCacheKit = createMockWaiterAndCache();
const existingWaiterAndCacheKit = createMockWaiterAndCache('existing');
const mutableState = createMockMutableState({
[mockServiceFieldName]: {
data: {
waiters: {
[existingWaiterAndCacheKit.fieldName]: existingWaiterAndCacheKit.waiter,
},
},
},
[waiterAndCacheKit.fieldName]: {},
});

setValidationWaiters(
[
{
serviceFieldName: mockServiceFieldName,
waiters: {[waiterAndCacheKit.fieldName]: waiterAndCacheKit.waiter},
},
],
mutableState,
mockTools,
);

expect(mutableState.fields[mockServiceFieldName].data.waiters).toEqual({
[existingWaiterAndCacheKit.fieldName]: existingWaiterAndCacheKit.waiter,
[waiterAndCacheKit.fieldName]: waiterAndCacheKit.waiter,
});
expect(mutableState.fields[waiterAndCacheKit.fieldName].validating).toBe(true);
});
});

describe('setValidationCache', () => {
it('should not modify state if field does not exist', () => {
const {cache, fieldName} = createMockWaiterAndCache();
const mutableState = createMockMutableState();

setValidationCache(
[{serviceFieldName: mockServiceFieldName, cache: {[fieldName]: cache}}],
mutableState,
mockTools,
);

expect(mutableState.fields).toEqual({});
});

it('should not modify state if cache is not provided', () => {
const mutableState = createMockMutableState({
[mockServiceFieldName]: {data: {}},
});

setValidationCache(
[{serviceFieldName: mockServiceFieldName, cache: undefined as any}],
mutableState,
mockTools,
);

expect(mutableState.fields[mockServiceFieldName].data).toEqual({});
});

it('should add cache to the validation state', () => {
const {cache, fieldName} = createMockWaiterAndCache();
const mutableState = createMockMutableState({
[mockServiceFieldName]: {data: {}},
[fieldName]: {},
});

setValidationCache(
[{serviceFieldName: mockServiceFieldName, cache: {[fieldName]: cache}}],
mutableState,
mockTools,
);

expect(mutableState.fields[mockServiceFieldName].data.cache).toEqual({
[fieldName]: [cache],
});
});

it('should append to existing cache', () => {
const fieldName = 'fieldName';
const existingWaiterAndCacheKit = createMockWaiterAndCache('existing');
const waiterAndCacheKit = createMockWaiterAndCache();
const mutableState = createMockMutableState({
[mockServiceFieldName]: {
data: {
cache: {
[fieldName]: [existingWaiterAndCacheKit.cache],
},
},
},
[fieldName]: {},
});

setValidationCache(
[
{
serviceFieldName: mockServiceFieldName,
cache: {[fieldName]: waiterAndCacheKit.cache},
},
],
mutableState,
mockTools,
);

expect(mutableState.fields[mockServiceFieldName].data.cache).toEqual({
[fieldName]: [existingWaiterAndCacheKit.cache, waiterAndCacheKit.cache],
});
});

it('should clear waiter and set validating to false when waiter matches cache', () => {
const fieldName = 'fieldName';
const {cache, waiter} = createMockWaiterAndCache();
const mutableState = createMockMutableState({
[mockServiceFieldName]: {data: {waiters: {[fieldName]: waiter}}},
[fieldName]: {validating: true},
});

setValidationCache(
[{serviceFieldName: mockServiceFieldName, cache: {[fieldName]: cache}}],
mutableState,
mockTools,
);

expect(mutableState.fields[mockServiceFieldName].data.waiters).toEqual({});
expect(mutableState.fields[fieldName].validating).toBe(false);
});

it('should not clear waiter when waiter does not match cache', () => {
const fieldName = 'fieldName';
const {waiter} = createMockWaiterAndCache('1');
const {cache} = createMockWaiterAndCache('2');

const mutableState = createMockMutableState({
[mockServiceFieldName]: {data: {waiters: {[fieldName]: waiter}}},
[fieldName]: {validating: true},
});

setValidationCache(
[{serviceFieldName: mockServiceFieldName, cache: {[fieldName]: cache}}],
mutableState,
mockTools,
);

expect(mutableState.fields[mockServiceFieldName].data.waiters).toEqual({
[fieldName]: waiter,
});
expect(mutableState.fields[fieldName].validating).toBe(true);
});

it('should handle multiple cache entries', () => {
const fieldName = 'fieldName';
const waiterAndCacheKit = createMockWaiterAndCache(fieldName);

const fieldName2 = 'fieldName2';
const waiterAndCacheKit2 = createMockWaiterAndCache(fieldName2);

const mutableState = createMockMutableState({
[mockServiceFieldName]: {
data: {
waiters: {
[fieldName]: waiterAndCacheKit.waiter,
[fieldName2]: waiterAndCacheKit2.waiter,
},
},
},
[fieldName]: {validating: true},
[fieldName2]: {validating: true},
});

setValidationCache(
[
{
serviceFieldName: mockServiceFieldName,
cache: {
[fieldName]: waiterAndCacheKit.cache,
[fieldName2]: waiterAndCacheKit2.cache,
},
},
],
mutableState,
mockTools,
);

expect(mutableState.fields[mockServiceFieldName].data.waiters).toEqual({});
expect(mutableState.fields[fieldName].validating).toBe(false);
expect(mutableState.fields[fieldName2].validating).toBe(false);
expect(mutableState.fields[mockServiceFieldName].data.cache).toEqual({
[fieldName]: [waiterAndCacheKit.cache],
[fieldName2]: [waiterAndCacheKit2.cache],
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import type {
} from './types';

export const setValidationWaiters: SetValidationWaitersFunction = (
[{name, waiters}],
[{serviceFieldName, waiters}],
mutableState,
) => {
const validationState = mutableState.fields[name]?.data as ValidationState | undefined;
const validationState = mutableState.fields[serviceFieldName]?.data as
| ValidationState
| undefined;

if (validationState && waiters) {
Object.keys(waiters).forEach((waiterName) => {
Expand All @@ -29,8 +31,13 @@ export const setValidationWaiters: SetValidationWaitersFunction = (
}
};

export const setValidationCache: SetValidationCacheFunction = ([{cache, name}], mutableState) => {
const validationState = mutableState.fields[name]?.data as ValidationState | undefined;
export const setValidationCache: SetValidationCacheFunction = (
[{cache, serviceFieldName}],
mutableState,
) => {
const validationState = mutableState.fields[serviceFieldName]?.data as
| ValidationState
| undefined;

if (validationState && cache) {
Object.keys(cache).forEach((cacheName) => {
Expand Down
4 changes: 2 additions & 2 deletions src/lib/unstable/core/mutators/async-validation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface ValidationState {
}

export interface SetValidationWaitersParams {
name: string;
serviceFieldName: string;
waiters: {
[key: string]: ValidationWaiter;
};
Expand All @@ -43,7 +43,7 @@ export interface SetValidationCacheParams {
cache: {
[key: string]: ValidationCache;
};
name: string;
serviceFieldName: string;
}

export type SetValidationCacheFunction<
Expand Down