Skip to content

Commit 20f8b64

Browse files
Refactored schema to support running schema tests from other packages (#2899)
* Refactored schema to support runnings from other packages - Fixed the import of `types` in `mergeSchemas.ts` - Renamed all of the `xxx.test.ts` to `xxxTest.ts` and wrapped them in an outer `xxxTest()` function that takes a `TestValidatorType` - Added a `types.ts` file in the `schema` directory to provide the `TestValidatorType` and `TestValidatorParams` interfaces - Added an `index.ts` file in the `schema` directory to export all of the `xxxTest` functions - Updated `getTestValidator()` to import the types from the `schema/types` - Added a `test/schema.test.ts` to create a `TestValidator` and run all of the `xxxTest()` functions from `schema` * - Fixed the tests based on using the real validator * - Removed the use of `xxx.hasOwnProperty('y')` in favor `'y' in xxx` to eliminate lint warnings - Fixed bug in `stubExistingAdditionalProperties()` related to the `additionalProperties: true` case, adding new test to verify fix * - Removed console.log statement * - Moved `stubExistingAdditionalProperties()` into the `retrieveSchema.ts` file to eliminate a circular dependency - Did the same for the `stubExistingAdditionalPropertiesTest()` function - Updated the `index.ts` files to remove the `stubExistingAdditionalProperties[Test]()` functions - Updated `SchemaUtilsType` and `createSchemaUtils` to remove the `stubExistingAdditionalProperties()` function
1 parent ff277f9 commit 20f8b64

34 files changed

+3948
-3969
lines changed

packages/utils/src/createSchemaUtils.ts

-11
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
isMultiSelect,
88
isSelect,
99
retrieveSchema,
10-
stubExistingAdditionalProperties,
1110
toIdSchema,
1211
toPathSchema,
1312
} from './schema';
@@ -104,16 +103,6 @@ class SchemaUtils<T = any> implements SchemaUtilsType<T> {
104103
return retrieveSchema<T>(this.validator, schema, this.rootSchema, rawFormData);
105104
}
106105

107-
/** Creates new 'properties' items for each key in the `formData`
108-
*
109-
* @param schema - The schema for which the existing additional properties is desired
110-
* @param [formData] - The current formData, if any, to assist retrieving a schema
111-
* @returns - The updated schema with additional properties stubbed
112-
*/
113-
stubExistingAdditionalProperties(schema: RJSFSchema, formData?: T) {
114-
return stubExistingAdditionalProperties<T>(this.validator, schema, this.rootSchema, formData);
115-
}
116-
117106
/** Generates an `IdSchema` object for the `schema`, recursively
118107
*
119108
* @param schema - The schema for which the display label flag is desired

packages/utils/src/getWidget.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -108,17 +108,17 @@ export default function getWidget<T = any, F = any>(
108108
throw new Error(`Unsupported widget definition: ${typeof widget}`);
109109
}
110110

111-
if (registeredWidgets.hasOwnProperty(widget)) {
111+
if (widget in registeredWidgets) {
112112
const registeredWidget = registeredWidgets[widget];
113113
return getWidget<T, F>(schema, registeredWidget, registeredWidgets);
114114
}
115115

116116
if (typeof type === 'string') {
117-
if (!widgetMap.hasOwnProperty(type)) {
117+
if (!(type in widgetMap)) {
118118
throw new Error(`No widget for type '${type}'`);
119119
}
120120

121-
if (widgetMap[type].hasOwnProperty(widget)) {
121+
if (widget in widgetMap[type]) {
122122
const registeredWidget = registeredWidgets[widgetMap[type][widget]];
123123
return getWidget<T, F>(schema, registeredWidget, registeredWidgets);
124124
}

packages/utils/src/isConstant.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ import { RJSFSchema } from './types';
99
* @returns - True if the `schema` has a single constant value, false otherwise
1010
*/
1111
export default function isConstant(schema: RJSFSchema) {
12-
return (Array.isArray(schema.enum) && schema.enum.length === 1) || schema.hasOwnProperty(CONST_KEY);
12+
return (Array.isArray(schema.enum) && schema.enum.length === 1) || CONST_KEY in schema;
1313
}

packages/utils/src/mergeObjects.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default function mergeObjects(obj1: GenericObjectType, obj2: GenericObjec
1212
return Object.keys(obj2).reduce((acc, key) => {
1313
const left = obj1 ? obj1[key] : {},
1414
right = obj2[key];
15-
if (obj1 && obj1.hasOwnProperty(key) && isObject(right)) {
15+
if (obj1 && key in obj1 && isObject(right)) {
1616
acc[key] = mergeObjects(left, right, concatArrays);
1717
} else if (concatArrays && Array.isArray(left) && Array.isArray(right)) {
1818
acc[key] = left.concat(right);

packages/utils/src/mergeSchemas.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import union from 'lodash/union';
22

33
import getSchemaType from './getSchemaType';
44
import isObject from './isObject';
5-
import { GenericObjectType } from 'types';
5+
import { GenericObjectType } from './types';
66

77
/** Recursively merge deeply nested schemas. The difference between mergeSchemas and mergeObjects is that mergeSchemas
88
* only concats arrays for values under the 'required' keyword, and when it does, it doesn't include duplicate values.
@@ -16,7 +16,7 @@ export default function mergeSchemas(obj1: GenericObjectType, obj2: GenericObjec
1616
return Object.keys(obj2).reduce((acc, key) => {
1717
const left = obj1 ? obj1[key] : {},
1818
right = obj2[key];
19-
if (obj1 && obj1.hasOwnProperty(key) && isObject(right)) {
19+
if (obj1 && key in obj1 && isObject(right)) {
2020
acc[key] = mergeSchemas(left, right);
2121
} else if (
2222
obj1 &&

packages/utils/src/schema/index.ts

-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import isFilesArray from './isFilesArray';
55
import isMultiSelect from './isMultiSelect';
66
import isSelect from './isSelect';
77
import retrieveSchema from './retrieveSchema';
8-
import stubExistingAdditionalProperties from './stubExistingAdditionalProperties';
98
import toIdSchema from './toIdSchema';
109
import toPathSchema from './toPathSchema';
1110

@@ -17,7 +16,6 @@ export {
1716
isMultiSelect,
1817
isSelect,
1918
retrieveSchema,
20-
stubExistingAdditionalProperties,
2119
toIdSchema,
2220
toPathSchema,
2321
};

packages/utils/src/schema/retrieveSchema.ts

+70-8
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
import get from 'lodash/get';
2+
import set from 'lodash/set';
23
import mergeAllOf from 'json-schema-merge-allof';
34

4-
import { ADDITIONAL_PROPERTIES_KEY, ALL_OF_KEY, DEPENDENCIES_KEY, REF_KEY } from '../constants';
5+
import {
6+
ADDITIONAL_PROPERTIES_KEY,
7+
ADDITIONAL_PROPERTY_FLAG,
8+
ALL_OF_KEY,
9+
DEPENDENCIES_KEY,
10+
REF_KEY
11+
} from '../constants';
512
import findSchemaDefinition, { splitKeyElementFromObject } from '../findSchemaDefinition';
13+
import guessType from '../guessType';
614
import isObject from '../isObject';
715
import mergeSchemas from '../mergeSchemas';
816
import { GenericObjectType, RJSFSchema, RJSFSchemaDefinition, ValidatorType } from '../types';
917
import getMatchingOption from './getMatchingOption';
10-
import stubExistingAdditionalProperties from './stubExistingAdditionalProperties';
1118

1219
/** Resolves a conditional block (if/else/then) by removing the condition and merging the appropriate conditional branch
1320
* with the rest of the schema
@@ -58,14 +65,14 @@ export function resolveCondition<T = any>(
5865
export function resolveSchema<T = any>(
5966
validator: ValidatorType, schema: RJSFSchema, rootSchema: RJSFSchema = {}, formData?: T
6067
): RJSFSchema {
61-
if (schema.hasOwnProperty(REF_KEY)) {
68+
if (REF_KEY in schema) {
6269
return resolveReference<T>(validator, schema, rootSchema, formData);
6370
}
64-
if (schema.hasOwnProperty(DEPENDENCIES_KEY)) {
71+
if (DEPENDENCIES_KEY in schema) {
6572
const resolvedSchema = resolveDependencies<T>(validator, schema, rootSchema, formData);
6673
return retrieveSchema<T>(validator, resolvedSchema, rootSchema, formData);
6774
}
68-
if (schema.hasOwnProperty(ALL_OF_KEY)) {
75+
if (ALL_OF_KEY in schema) {
6976
return {
7077
...schema,
7178
allOf: schema.allOf!.map((allOfSubschema) =>
@@ -101,6 +108,61 @@ export function resolveReference<T = any>(
101108
);
102109
}
103110

111+
/** Creates new 'properties' items for each key in the `formData`
112+
*
113+
* @param validator - An implementation of the `ValidatorType` interface that will be used when necessary
114+
* @param theSchema - The schema for which the existing additional properties is desired
115+
* @param [rootSchema] - The root schema, used to primarily to look up `$ref`s * @param validator
116+
* @param [aFormData] - The current formData, if any, to assist retrieving a schema
117+
* @returns - The updated schema with additional properties stubbed
118+
*/
119+
export function stubExistingAdditionalProperties<T = any>(
120+
validator: ValidatorType,
121+
theSchema: RJSFSchema,
122+
rootSchema?: RJSFSchema,
123+
aFormData?: T
124+
): RJSFSchema {
125+
// Clone the schema so we don't ruin the consumer's original
126+
const schema = {
127+
...theSchema,
128+
properties: { ...theSchema.properties },
129+
};
130+
131+
// make sure formData is an object
132+
const formData: GenericObjectType = aFormData && isObject(aFormData) ? aFormData : {};
133+
Object.keys(formData).forEach((key) => {
134+
if (key in schema.properties) {
135+
// No need to stub, our schema already has the property
136+
return;
137+
}
138+
139+
let additionalProperties: RJSFSchema = {};
140+
if (typeof schema.additionalProperties !== 'boolean') {
141+
if (REF_KEY in schema.additionalProperties!) {
142+
additionalProperties = retrieveSchema<T>(
143+
validator,
144+
{ $ref: get(schema.additionalProperties, [REF_KEY]) },
145+
rootSchema,
146+
formData as T
147+
);
148+
} else if ('type' in schema.additionalProperties!) {
149+
additionalProperties = { ...schema.additionalProperties };
150+
} else {
151+
additionalProperties = { type: guessType(get(formData, [key])) };
152+
}
153+
} else {
154+
additionalProperties = { type: guessType(get(formData, [key])) };
155+
}
156+
157+
// The type of our new key should match the additionalProperties value;
158+
schema.properties[key] = additionalProperties;
159+
// Set our additional property flag so we know it was dynamically added
160+
set(schema.properties, [key, ADDITIONAL_PROPERTY_FLAG], true);
161+
});
162+
163+
return schema;
164+
}
165+
104166
/** Retrieves an expanded schema that has had all of its conditions, additional properties, references and dependencies
105167
* resolved and merged into the `schema` given a `validator`, `rootSchema` and `rawFormData` that is used to do the
106168
* potentially recursive resolution.
@@ -119,7 +181,7 @@ export default function retrieveSchema<T = any>(
119181
}
120182
let resolvedSchema = resolveSchema<T>(validator, schema, rootSchema, rawFormData);
121183

122-
if (schema.hasOwnProperty('if')) {
184+
if ('if' in schema) {
123185
return resolveCondition<T>(validator, schema, rootSchema, rawFormData as T);
124186
}
125187

@@ -165,7 +227,7 @@ export default function retrieveSchema<T = any>(
165227
}
166228
}
167229
const hasAdditionalProperties =
168-
resolvedSchema.hasOwnProperty(ADDITIONAL_PROPERTIES_KEY) &&
230+
ADDITIONAL_PROPERTIES_KEY in resolvedSchema &&
169231
resolvedSchema.additionalProperties !== false;
170232
if (hasAdditionalProperties) {
171233
return stubExistingAdditionalProperties<T>(
@@ -309,7 +371,7 @@ export function withDependentSchema<T>(
309371
}
310372
// Resolve $refs inside oneOf.
311373
const resolvedOneOf = oneOf.map((subschema) => {
312-
if (typeof subschema === 'boolean' || !subschema.hasOwnProperty(REF_KEY)) {
374+
if (typeof subschema === 'boolean' || !(REF_KEY in subschema)) {
313375
return subschema;
314376
}
315377
return resolveReference<T>(validator, subschema as RJSFSchema, rootSchema, formData);

packages/utils/src/schema/stubExistingAdditionalProperties.ts

-62
This file was deleted.

packages/utils/src/schema/toPathSchema.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ export default function toPathSchema<T = any>(
3737
$name: name.replace(/^\./, ''),
3838
} as PathSchema;
3939

40-
if (schema.hasOwnProperty(ADDITIONAL_PROPERTIES_KEY)) {
40+
if (ADDITIONAL_PROPERTIES_KEY in schema) {
4141
set(pathSchema, '__rjsf_additionalProperties', true);
4242
}
4343

44-
if (schema.hasOwnProperty(ITEMS_KEY) && Array.isArray(formData)) {
44+
if (ITEMS_KEY in schema && Array.isArray(formData)) {
4545
formData.forEach((element, i: number) => {
4646
pathSchema[i] = toPathSchema<T>(
4747
validator,
@@ -51,7 +51,7 @@ export default function toPathSchema<T = any>(
5151
element
5252
);
5353
});
54-
} else if (schema.hasOwnProperty(PROPERTIES_KEY)) {
54+
} else if (PROPERTIES_KEY in schema) {
5555
for (const property in schema.properties) {
5656
const field = get(schema, [PROPERTIES_KEY, property]);
5757
pathSchema[property] = toPathSchema<T>(

packages/utils/src/toConstant.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export default function toConstant(schema: RJSFSchema) {
55
if (Array.isArray(schema.enum) && schema.enum.length === 1) {
66
return schema.enum[0];
77
}
8-
if (schema.hasOwnProperty(CONST_KEY)) {
8+
if (CONST_KEY in schema) {
99
return schema.const;
1010
}
1111
throw new Error('schema cannot be inferred as a constant');

packages/utils/src/types.ts

-1
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,6 @@ export interface SchemaUtilsType<T = any> {
308308
isMultiSelect(schema: RJSFSchema): boolean;
309309
isSelect(schema: RJSFSchema): boolean;
310310
retrieveSchema(schema: RJSFSchema, formData: T): RJSFSchema;
311-
stubExistingAdditionalProperties(schema: RJSFSchema, formData: T): RJSFSchema;
312311
toIdSchema(schema: RJSFSchema, id?: string, formData?: T, idPrefix?: string, idSeparator?: string): IdSchema<T>;
313312
toPathSchema(schema: RJSFSchema, name?: string, formData?: T): PathSchema<T>;
314313
}

packages/utils/test/schema.test.ts

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import getTestValidator from './testUtils/getTestValidator';
2+
import {
3+
getDefaultFormStateTest,
4+
getDisplayLabelTest,
5+
getMatchingOptionTest,
6+
isFilesArrayTest,
7+
isMultiSelectTest,
8+
isSelectTest,
9+
retrieveSchemaTest,
10+
toIdSchemaTest,
11+
toPathSchemaTest,
12+
} from './schema';
13+
14+
const testValidator = getTestValidator({});
15+
16+
getDefaultFormStateTest(testValidator);
17+
getDisplayLabelTest(testValidator);
18+
getMatchingOptionTest(testValidator);
19+
isFilesArrayTest(testValidator);
20+
isMultiSelectTest(testValidator);
21+
isSelectTest(testValidator);
22+
retrieveSchemaTest(testValidator);
23+
toIdSchemaTest(testValidator);
24+
toPathSchemaTest(testValidator);

0 commit comments

Comments
 (0)