Skip to content

Commit

Permalink
fix: include extensions in objectKeys results
Browse files Browse the repository at this point in the history
This fixes requiredness checks for required fields in subschemas where
the field was added through a later extension
of the subschema.
  • Loading branch information
aldeed committed Jul 21, 2022
1 parent b80e636 commit 489b75d
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 57 deletions.
53 changes: 26 additions & 27 deletions package/lib/SimpleSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,32 @@ class SimpleSchema {
*/
objectKeys(keyPrefix) {
if (!keyPrefix) return this._firstLevelSchemaKeys;
return this._objectKeys[`${keyPrefix}.`] || [];

const objectKeys = [];
const setObjectKeys = (curSchema, schemaParentKey) => {
Object.keys(curSchema).forEach((fieldName) => {
const definition = curSchema[fieldName];
fieldName = schemaParentKey ? `${schemaParentKey}.${fieldName}` : fieldName;
if (fieldName.indexOf('.') > -1 && fieldName.slice(-2) !== '.$') {
const parentKey = fieldName.slice(0, fieldName.lastIndexOf('.'));
const parentKeyWithDot = `${parentKey}.`;
objectKeys[parentKeyWithDot] = objectKeys[parentKeyWithDot] || [];
objectKeys[parentKeyWithDot].push(fieldName.slice(fieldName.lastIndexOf('.') + 1));
}

// If the current field is a nested SimpleSchema,
// iterate over the child fields and cache their properties as well
definition.type.definitions.forEach(({ type }) => {
if (SimpleSchema.isSimpleSchema(type)) {
setObjectKeys(type._schema, fieldName);
}
});
});
};

setObjectKeys(this._schema);

return objectKeys[`${keyPrefix}.`] || [];
}

/**
Expand Down Expand Up @@ -536,7 +561,6 @@ class SimpleSchema {
this._autoValues = [];
this._blackboxKeys = new Set();
this._firstLevelSchemaKeys = [];
this._objectKeys = {};

// Update all of the information cached on the instance
this._schemaKeys.forEach((fieldName) => {
Expand Down Expand Up @@ -573,31 +597,6 @@ class SimpleSchema {
}
});

// Store child keys keyed by parent. This needs to be done recursively to handle
// subschemas.
const setObjectKeys = (curSchema, schemaParentKey) => {
Object.keys(curSchema).forEach((fieldName) => {
const definition = curSchema[fieldName];
fieldName = schemaParentKey ? `${schemaParentKey}.${fieldName}` : fieldName;
if (fieldName.indexOf('.') > -1 && fieldName.slice(-2) !== '.$') {
const parentKey = fieldName.slice(0, fieldName.lastIndexOf('.'));
const parentKeyWithDot = `${parentKey}.`;
this._objectKeys[parentKeyWithDot] = this._objectKeys[parentKeyWithDot] || [];
this._objectKeys[parentKeyWithDot].push(fieldName.slice(fieldName.lastIndexOf('.') + 1));
}

// If the current field is a nested SimpleSchema,
// iterate over the child fields and cache their properties as well
definition.type.definitions.forEach(({ type }) => {
if (SimpleSchema.isSimpleSchema(type)) {
setObjectKeys(type._schema, fieldName);
}
});
});
};

setObjectKeys(this._schema);

return this;
}

Expand Down
122 changes: 92 additions & 30 deletions package/lib/SimpleSchema.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,48 @@ describe('SimpleSchema', function () {

expect(schema._schema.myArray.type.definitions[0].minCount).toBe(1);
});

it('tests requiredness on fields added through extension', function () {
const subitemSchema = new SimpleSchema({
name: String,
});

const itemSchema = new SimpleSchema({
name: String,
subitems: {
type: Array,
optional: true,
},
'subitems.$': {
type: subitemSchema,
},
});

const schema = new SimpleSchema({
name: String,
items: {
type: Array,
optional: true,
},
'items.$': {
type: itemSchema,
},
});

subitemSchema.extend({
other: String,
});

expectErrorOfTypeLength(SimpleSchema.ErrorTypes.REQUIRED, schema, {
name: 'foo',
items: [{
name: 'foo',
subitems: [{
name: 'foo',
}],
}],
}).toBe(1);
});
});

it('empty required array is valid', function () {
Expand Down Expand Up @@ -1026,41 +1068,61 @@ describe('SimpleSchema', function () {
expect(context.validationErrors()).toEqual(errorArray);
});

it('sets _objectKeys', function () {
const schema = new SimpleSchema({
a: Object,
'a.b': Object,
'a.b.c': Array,
'a.b.c.$': Object,
'a.b.c.$.d': Object,
'a.b.c.$.d.e': String,
});
describe('objectKeys', function () {
it('gets objectKeys', function () {
const schema = new SimpleSchema({
a: Object,
'a.b': Object,
'a.b.c': Array,
'a.b.c.$': Object,
'a.b.c.$.d': Object,
'a.b.c.$.d.e': String,
});

expect(schema._objectKeys).toEqual({
'a.': ['b'],
'a.b.': ['c'],
'a.b.c.$.': ['d'],
'a.b.c.$.d.': ['e'],
expect(schema.objectKeys()).toEqual(['a']);
expect(schema.objectKeys('a')).toEqual(['b']);
expect(schema.objectKeys('a.b')).toEqual(['c']);
expect(schema.objectKeys('a.b.c')).toEqual([]);
expect(schema.objectKeys('a.b.c.$')).toEqual(['d']);
expect(schema.objectKeys('a.b.c.$.d')).toEqual(['e']);
});
});

it('gets subschema objectKeys', function () {
const schema = new SimpleSchema({
a: {
type: new SimpleSchema({
b: {
type: new SimpleSchema({
c: {
type: String,
},
}),
},
}),
},
it('gets subschema objectKeys', function () {
const schema = new SimpleSchema({
a: {
type: new SimpleSchema({
b: {
type: new SimpleSchema({
c: {
type: String,
},
}),
},
}),
},
});

expect(schema.objectKeys('a')).toEqual(['b']);
expect(schema.objectKeys('a.b')).toEqual(['c']);
});

expect(schema.objectKeys('a')).toEqual(['b']);
expect(schema.objectKeys('a.b')).toEqual(['c']);
it('gets correct objectKeys from extended subschemas', function () {
const itemSchema = new SimpleSchema({
name: String,
});

const schema = new SimpleSchema({
name: String,
item: itemSchema,
});

itemSchema.extend({
other: String,
});

expect(schema.objectKeys()).toEqual(['name', 'item']);
expect(schema.objectKeys('item')).toEqual(['name', 'other']);
});
});

it('gets schema property by key', function () {
Expand Down

0 comments on commit 489b75d

Please sign in to comment.