Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Added better support for testing with AJV having discriminator option turned on #4257

Merged
Show file tree
Hide file tree
Changes from 2 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
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,21 @@ should change the heading of the (upcoming) version to include a major version b

-->

# 5.19.4

## @rjsf/utils

- Updated the `ValidatorType` interface to add an optional `reset?: () => void` prop that can be implemented to reset a validator back to initial constructed state
- Updated the `ParserValidator` to provide a `reset()` function that clears the schema map

## @rjsf/validator-ajv8

- Updated the `AJV8Validator` to implement the `reset()` function to remove cached schemas in the `ajv` instance

## Dev / docs / playground

- Updated the `Validator` dropdown to add `AJV8 (discriminator)` which sets the AJV validator [discriminator](https://ajv.js.org/json-schema.html#discriminator) option to `true` to support testing schemas with it in them
heath-freenome marked this conversation as resolved.
Show resolved Hide resolved

# 5.19.3

## @rjsf/antd
Expand Down
2 changes: 2 additions & 0 deletions packages/playground/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ import Playground, { PlaygroundProps } from './components';
const esV8Validator = customizeValidator({}, localize_es);
const AJV8_2019 = customizeValidator({ AjvClass: Ajv2019 });
const AJV8_2020 = customizeValidator({ AjvClass: Ajv2020 });
const AJV8_DISC = customizeValidator({ ajvOptionsOverrides: { discriminator: true } });

const validators: PlaygroundProps['validators'] = {
AJV8: v8Validator,
'AJV8 (discriminator)': AJV8_DISC,
AJV8_es: esV8Validator,
AJV8_2019,
AJV8_2020,
Expand Down
6 changes: 6 additions & 0 deletions packages/utils/src/parser/ParserValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ export default class ParserValidator<T = any, S extends StrictRJSFSchema = RJSFS
this.addSchema(rootSchema, hashForSchema<S>(rootSchema));
}

/** Resets the internal AJV validator to clear schemas from it. Can be helpful for resetting the validator for tests.
*/
reset() {
this.schemaMap = {};
}

/** Adds the given `schema` to the `schemaMap` keyed by the `hash` or `ID_KEY` if present on the `schema`. If the
* schema does not have an `ID_KEY`, then the `hash` will be added as the `ID_KEY` to allow the schema to be
* associated with it's `hash` for future use (by a schema compiler).
Expand Down
4 changes: 4 additions & 0 deletions packages/utils/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,10 @@ export interface ValidatorType<T = any, S extends StrictRJSFSchema = RJSFSchema,
* @param formData - The form data to validate
*/
rawValidation<Result = any>(schema: S, formData?: T): { errors?: Result[]; validationError?: Error };
/** An optional function that can be used to reset validator implementation. Useful for clear schemas in the AJV
* instance for tests.
*/
reset?: () => void;
}

/** The `SchemaUtilsType` interface provides a wrapper around the publicly exported APIs in the `@rjsf/utils/schema`
Expand Down
4 changes: 4 additions & 0 deletions packages/utils/test/parser/ParserValidator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,8 @@ describe('ParserValidator', () => {
JSON.stringify({ ...DUPLICATE_SCHEMA, [ID_KEY]: DUPLICATE_HASH }, null, 2)
);
});
it('reset clears the map', () => {
validator.reset();
expect(validator.schemaMap).toEqual({});
});
});
8 changes: 0 additions & 8 deletions packages/utils/test/schema/getClosestMatchingOptionTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,10 +236,6 @@ export default function getClosestMatchingOptionTest(testValidator: TestValidato
},
discriminator: {
propertyName: 'code',
mapping: {
foo_coding: '#/definitions/Foo',
bar_coding: '#/definitions/Bar',
},
},
oneOf: [{ $ref: '#/definitions/Foo' }, { $ref: '#/definitions/Bar' }],
};
Expand Down Expand Up @@ -269,10 +265,6 @@ export default function getClosestMatchingOptionTest(testValidator: TestValidato
},
discriminator: {
propertyName: 'code',
mapping: {
foo_coding: '#/definitions/Foo',
bar_coding: '#/definitions/Bar',
},
},
oneOf: [{ $ref: '#/definitions/Foo' }, { $ref: '#/definitions/Bar' }],
};
Expand Down
29 changes: 20 additions & 9 deletions packages/utils/test/schema/getFirstMatchingOptionTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,9 @@ export default function getFirstMatchingOptionTest(testValidator: TestValidatorT
},
discriminator: {
propertyName: 'code',
mapping: {
foo_coding: '#/definitions/Foo',
bar_coding: '#/definitions/Bar',
},
},
oneOf: [{ $ref: '#/definitions/Foo' }, { $ref: '#/definitions/Bar' }],
required: ['code'],
};
const options = [schema.definitions!.Foo, schema.definitions!.Bar] as RJSFSchema[];
expect(getFirstMatchingOption(testValidator, null, options, schema, 'code')).toEqual(0);
Expand Down Expand Up @@ -156,12 +153,9 @@ export default function getFirstMatchingOptionTest(testValidator: TestValidatorT
},
discriminator: {
propertyName: 'code',
mapping: {
foo_coding: '#/definitions/Foo',
bar_coding: '#/definitions/Bar',
},
},
oneOf: [{ $ref: '#/definitions/Foo' }, { $ref: '#/definitions/Bar' }],
required: ['code'],
};
const formData = { code: 'bar_coding' };
const options = [schema.definitions!.Foo, schema.definitions!.Bar] as RJSFSchema[];
Expand All @@ -172,6 +166,7 @@ export default function getFirstMatchingOptionTest(testValidator: TestValidatorT

// simple in the sense of getOptionMatchingSimpleDiscriminator
it('should return Bar when schema has non-simple discriminator for bar', () => {
const consoleWarnSpy = jest.spyOn(console, 'warn');
// Mock isValid to pass the second value
testValidator.setReturnValues({ isValid: [false, true] });
const schema: RJSFSchema = {
Expand All @@ -196,12 +191,28 @@ export default function getFirstMatchingOptionTest(testValidator: TestValidatorT
propertyName: 'code',
},
oneOf: [{ $ref: '#/definitions/Foo' }, { $ref: '#/definitions/Bar' }],
required: ['code'],
};
const formData = { code: ['bar_coding'] };
const options = [schema.definitions!.Foo, schema.definitions!.Bar] as RJSFSchema[];
// Use the schemaUtils to verify the discriminator prop gets passed
const schemaUtils = createSchemaUtils(testValidator, schema);
expect(schemaUtils.getFirstMatchingOption(formData, options, 'code')).toEqual(1);
const result = schemaUtils.getFirstMatchingOption(formData, options, 'code');
const wasWarned = consoleWarnSpy.mock.calls.length > 0;
if (wasWarned) {
// According to the docs https://ajv.js.org/json-schema.html#discriminator, with ajv8 discrimator turned on the
// schema in this test will fail because of the limitations of AJV implementation
expect(consoleWarnSpy).toHaveBeenCalledWith(
'Error encountered compiling schema:',
expect.objectContaining({
message: 'discriminator: "properties/code" must have "const" or "enum"',
})
);
expect(result).toEqual(0);
} else {
expect(result).toEqual(1);
}
consoleWarnSpy.mockRestore();
});
});
}
1 change: 1 addition & 0 deletions packages/utils/test/schema/retrieveSchemaTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export default function retrieveSchemaTest(testValidator: TestValidatorType) {
});
afterEach(() => {
consoleWarnSpy.mockClear();
testValidator.reset?.();
});
it('returns empty object when schema is not an object', () => {
expect(retrieveSchema(testValidator, [] as RJSFSchema)).toEqual({});
Expand Down
5 changes: 5 additions & 0 deletions packages/utils/test/testUtils/getTestValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ export default function getTestValidator<T = any>({
testValidator._errorList = errorList;
}
},
reset() {
testValidator._data = [];
testValidator._isValid = [];
testValidator._errorList = [];
},
},
};
return testValidator.validator;
Expand Down
1 change: 1 addition & 0 deletions packages/validator-ajv8/src/createAjvInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const AJV_CONFIG: Options = {
multipleOfPrecision: 8,
strict: false,
verbose: true,
discriminator: false, // TODO enable this in V6
} as const;
export const COLOR_FORMAT_REGEX =
/^(#?([0-9A-Fa-f]{3}){1,2}\b|aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow|(rgb\(\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*,\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*,\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*\))|(rgb\(\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*\)))$/;
Expand Down
6 changes: 6 additions & 0 deletions packages/validator-ajv8/src/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ export default class AJV8Validator<T = any, S extends StrictRJSFSchema = RJSFSch
this.localizer = localizer;
}

/** Resets the internal AJV validator to clear schemas from it. Can be helpful for resetting the validator for tests.
*/
reset() {
this.ajv.removeSchema();
}

/** Converts an `errorSchema` into a list of `RJSFValidationErrors`
*
* @param errorSchema - The `ErrorSchema` instance to convert
Expand Down
3 changes: 3 additions & 0 deletions packages/validator-ajv8/test/utilsTests/getTestValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,8 @@ export default function getTestValidator<T = any>(options: CustomValidatorOption
},
// This is intentionally a no-op as we are using the real validator here
setReturnValues() {},
reset() {
validator.reset?.();
},
};
}
16 changes: 16 additions & 0 deletions packages/validator-ajv8/test/utilsTests/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,22 @@ sanitizeDataForNewSchemaTest(testValidator);
toIdSchemaTest(testValidator);
toPathSchemaTest(testValidator);

const testValidatorDiscriminated = getTestValidator({ ajvOptionsOverrides: { discriminator: true } });

// NOTE: to restrict which tests to run, you can temporarily comment out any tests you aren't needing
getDefaultFormStateTest(testValidatorDiscriminated);
getDisplayLabelTest(testValidatorDiscriminated);
getClosestMatchingOptionTest(testValidatorDiscriminated);
getFirstMatchingOptionTest(testValidatorDiscriminated);
isFilesArrayTest(testValidatorDiscriminated);
isMultiSelectTest(testValidatorDiscriminated);
isSelectTest(testValidatorDiscriminated);
mergeValidationDataTest(testValidatorDiscriminated);
retrieveSchemaTest(testValidatorDiscriminated);
sanitizeDataForNewSchemaTest(testValidatorDiscriminated);
toIdSchemaTest(testValidatorDiscriminated);
toPathSchemaTest(testValidatorDiscriminated);

const testValidator2019 = getTestValidator({ AjvClass: Ajv2019 });

// NOTE: to restrict which tests to run, you can temporarily comment out any tests you aren't needing
Expand Down