Skip to content

Commit d31de65

Browse files
committed
feat(discriminator): add ability to merge customization options
Options on baseModel are copied to child, if child is undefined
1 parent 922a302 commit d31de65

File tree

9 files changed

+724
-2
lines changed

9 files changed

+724
-2
lines changed

src/composeWithMongooseDiscriminators.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { Model } from 'mongoose';
1717
import type { TypeConverterOpts } from './composeWithMongoose';
1818
import { composeWithMongoose } from './composeWithMongoose';
1919
import { composeChildTC } from './discriminators';
20+
import { mergeCustomizationOptions } from './discriminators/merge-customization-options';
2021
import { prepareBaseResolvers } from './discriminators/prepare-resolvers/prepareBaseResolvers';
2122
import { reorderFields } from './discriminators/utils';
2223

@@ -122,7 +123,9 @@ export class DiscriminatorTypeComposer extends TypeComposer {
122123
baseModel: Model,
123124
opts: Options = {
124125
reorderFields: true,
125-
customizationOptions: {},
126+
customizationOptions: {
127+
schemaComposer,
128+
},
126129
}
127130
) {
128131
if (!baseModel || !(baseModel: any).discriminators) {
@@ -229,7 +232,9 @@ export class DiscriminatorTypeComposer extends TypeComposer {
229232

230233
/* eslint no-use-before-define: 0 */
231234
discriminator(childModel: Model, opts?: TypeConverterOpts): TypeComposer {
232-
let childTC = composeWithMongoose(childModel, opts || this.opts.customizationOptions);
235+
const customizationOpts = mergeCustomizationOptions(this.opts.customizationOptions, opts);
236+
237+
let childTC = composeWithMongoose(childModel, customizationOpts);
233238

234239
childTC = composeChildTC(this, childTC, this.opts);
235240

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
/* @flow */
2+
3+
import type { TypeConverterOpts } from '../../../composeWithMongoose';
4+
import {
5+
mergeCustomizationOptions,
6+
mergeFieldMaps,
7+
mergeStringAndStringArraysFields,
8+
} from '../index';
9+
10+
const baseFields = {
11+
remove: ['id', 'friends', 'health', 'appearsIn'],
12+
only: ['id'],
13+
};
14+
15+
const childFields = {
16+
remove: ['id', 'appearsIn', 'dob', 'health'],
17+
only: ['id'],
18+
};
19+
20+
const expectedFields = {
21+
remove: ['id', 'friends', 'health', 'appearsIn', 'dob'],
22+
only: ['id'],
23+
};
24+
25+
const baseInputTypeFields = {
26+
only: ['id', 'appearsIn'],
27+
remove: ['dob', 'gender'],
28+
};
29+
30+
const childInputTypeFields = {
31+
only: ['id', 'friends', 'appearsIn'],
32+
remove: ['dob'],
33+
required: ['id', 'dob', 'gender'],
34+
};
35+
36+
const expectedInputTypes = {
37+
only: ['id', 'appearsIn', 'friends'],
38+
remove: ['dob', 'gender'],
39+
required: ['id', 'dob', 'gender'],
40+
};
41+
42+
const optsTypes = ['string', 'string[]'];
43+
44+
describe('mergeStringAndStringArraysFields()', () => {
45+
it('should concat two Arrays', () => {
46+
expect(
47+
mergeStringAndStringArraysFields(baseInputTypeFields.remove, childFields.only, optsTypes[0])
48+
).toEqual([...baseInputTypeFields.remove, ...childFields.only]);
49+
});
50+
51+
it('should combine two input strings into an array', () => {
52+
expect(
53+
mergeStringAndStringArraysFields(
54+
baseInputTypeFields.remove[0],
55+
baseInputTypeFields.remove[1],
56+
optsTypes[1]
57+
)
58+
).toEqual(baseInputTypeFields.remove);
59+
});
60+
61+
it('should combine an array and a string into an array', () => {
62+
expect(
63+
mergeStringAndStringArraysFields(
64+
childInputTypeFields.required[0],
65+
baseInputTypeFields.remove,
66+
optsTypes[0]
67+
)
68+
).toEqual(childInputTypeFields.required);
69+
70+
expect(
71+
mergeStringAndStringArraysFields(
72+
baseInputTypeFields.only,
73+
childInputTypeFields.only[1],
74+
optsTypes[0]
75+
)
76+
).toEqual(expectedInputTypes.only);
77+
});
78+
79+
it('should remove repeated fields from the array', () => {
80+
expect(
81+
mergeStringAndStringArraysFields(baseFields.remove, childFields.remove, optsTypes[0])
82+
).toEqual(expectedFields.remove);
83+
84+
expect(
85+
mergeStringAndStringArraysFields(undefined, childInputTypeFields.required, optsTypes[0])
86+
).toEqual(expectedInputTypes.required);
87+
88+
expect(
89+
mergeStringAndStringArraysFields(
90+
baseInputTypeFields.only,
91+
childInputTypeFields.only,
92+
optsTypes[0]
93+
)
94+
).toEqual(expectedInputTypes.only);
95+
});
96+
97+
it('should return an ARRAY of the defined if other one is undefined', () => {
98+
expect(mergeStringAndStringArraysFields(baseFields.remove, undefined, optsTypes[0])).toEqual(
99+
baseFields.remove
100+
);
101+
102+
expect(mergeStringAndStringArraysFields(undefined, childFields.only, optsTypes[0])).toEqual(
103+
childFields.only
104+
);
105+
});
106+
107+
it('should operate normally with ARRAY of optsTypes', () => {
108+
expect(mergeStringAndStringArraysFields(baseFields.remove, undefined, optsTypes)).toEqual(
109+
baseFields.remove
110+
);
111+
112+
expect(mergeStringAndStringArraysFields(undefined, childFields.only, optsTypes)).toEqual(
113+
childFields.only
114+
);
115+
});
116+
117+
it('should return child field if not amongst opsType', () => {
118+
expect(mergeStringAndStringArraysFields(baseFields.remove, undefined, 'boolean')).toEqual(
119+
undefined
120+
);
121+
122+
expect(mergeStringAndStringArraysFields(undefined, childFields.only, 'number')).toEqual(
123+
childFields.only
124+
);
125+
});
126+
});
127+
128+
describe('mergeFieldMaps()', () => {
129+
it('should merge fields', () => {
130+
expect(mergeFieldMaps(baseFields, childFields)).toEqual(expectedFields);
131+
expect(mergeFieldMaps(baseInputTypeFields, childInputTypeFields)).toEqual(expectedInputTypes);
132+
});
133+
});
134+
135+
describe('mergeCustomizationOptions()', () => {
136+
const baseCustomOptions: TypeConverterOpts = {
137+
fields: baseFields,
138+
inputType: {
139+
name: 'BaseInput',
140+
description: 'Hello Base',
141+
fields: baseInputTypeFields,
142+
},
143+
resolvers: {
144+
findMany: {
145+
limit: { defaultValue: 20 },
146+
// sort: false,
147+
skip: false,
148+
filter: {
149+
isRequired: true,
150+
removeFields: ['id', 'dob'],
151+
operators: {
152+
one: ['gt', 'gte', 'lt'],
153+
two: ['gt', 'gte', 'lt', 'in[]', 'nin[]'],
154+
},
155+
},
156+
},
157+
findById: false,
158+
},
159+
};
160+
161+
const childCustomOptions: TypeConverterOpts = {
162+
fields: childFields,
163+
inputType: {
164+
name: 'ChildInputs',
165+
description: 'Hello Child',
166+
fields: childInputTypeFields,
167+
},
168+
resolvers: {
169+
findMany: {
170+
limit: { defaultValue: 50 },
171+
sort: false,
172+
// skip: false,
173+
filter: {
174+
removeFields: ['gender', 'dob', 'age'],
175+
operators: {
176+
one: ['gt', 'lte', 'ne', 'in[]', 'nin[]'],
177+
two: ['gt', 'gte', 'lt', 'lte', 'ne'],
178+
three: ['gte', 'lt'],
179+
},
180+
},
181+
},
182+
updateById: {
183+
input: {
184+
removeFields: ['one', 'two', 'five'],
185+
requiredFields: ['eight', 'two', 'five'],
186+
},
187+
},
188+
},
189+
};
190+
191+
const expected: TypeConverterOpts = {
192+
fields: expectedFields,
193+
inputType: {
194+
name: 'ChildInputs',
195+
description: 'Hello Child',
196+
fields: expectedInputTypes,
197+
},
198+
resolvers: {
199+
findMany: {
200+
limit: { defaultValue: 50 },
201+
sort: false,
202+
skip: false,
203+
filter: {
204+
isRequired: true,
205+
removeFields: ['id', 'dob', 'gender', 'age'],
206+
operators: {
207+
one: ['gt', 'gte', 'lt', 'lte', 'ne', 'in[]', 'nin[]'],
208+
two: ['gt', 'gte', 'lt', 'in[]', 'nin[]', 'lte', 'ne'],
209+
three: ['gte', 'lt'],
210+
},
211+
},
212+
},
213+
findById: false,
214+
updateById: {
215+
input: {
216+
removeFields: ['one', 'two', 'five'],
217+
requiredFields: ['eight', 'two', 'five'],
218+
},
219+
},
220+
},
221+
};
222+
223+
it('should merge customisation Options', () => {
224+
expect(mergeCustomizationOptions(baseCustomOptions, childCustomOptions)).toEqual(expected);
225+
});
226+
227+
/* it('should produce error if using different schema composers', () => {
228+
expect( fixme: error is not caught.
229+
mergeCustomizationOptions(
230+
{ schemaComposer: new SchemaComposer() },
231+
{ schemaComposer: new SchemaComposer() }
232+
)
233+
).toThrow('[Discriminators] ChildModel should have same schemaComposer as its BaseModels');
234+
}); */
235+
});
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/* @flow */
2+
3+
import type { TypeConverterResolversOpts } from '../../../composeWithMongoose';
4+
import {} from '../index';
5+
import {
6+
mergeTypeConverterResolverOpts,
7+
mergePrimitiveTypeFields,
8+
mergeMapTypeFields,
9+
mergeFilterOperatorsOptsMap,
10+
} from '../utils/mergeTypeConverterResolversOpts';
11+
12+
const baseConverterResolverOpts: TypeConverterResolversOpts = {
13+
findMany: {
14+
limit: { defaultValue: 20 },
15+
// sort: false,
16+
skip: false,
17+
filter: {
18+
isRequired: true,
19+
removeFields: ['id', 'dob'],
20+
operators: {
21+
one: ['gt', 'gte', 'lt'],
22+
two: ['gt', 'gte', 'lt', 'in[]', 'nin[]'],
23+
},
24+
},
25+
},
26+
findById: false,
27+
};
28+
29+
const childConverterResolverOpts: TypeConverterResolversOpts = {
30+
findMany: {
31+
limit: { defaultValue: 50 },
32+
sort: false,
33+
// skip: false,
34+
filter: {
35+
removeFields: ['gender', 'dob', 'age'],
36+
operators: {
37+
one: ['gt', 'lte', 'ne', 'in[]', 'nin[]'],
38+
two: ['gt', 'gte', 'lt', 'lte', 'ne'],
39+
three: ['gte', 'lt'],
40+
},
41+
},
42+
},
43+
};
44+
45+
const expectedConverterResolverOpts: TypeConverterResolversOpts = {
46+
findMany: {
47+
limit: { defaultValue: 50 },
48+
sort: false,
49+
skip: false,
50+
filter: {
51+
isRequired: true,
52+
removeFields: ['id', 'dob', 'gender', 'age'],
53+
operators: {
54+
one: ['gt', 'gte', 'lt', 'lte', 'ne', 'in[]', 'nin[]'],
55+
two: ['gt', 'gte', 'lt', 'in[]', 'nin[]', 'lte', 'ne'],
56+
three: ['gte', 'lt'],
57+
},
58+
},
59+
},
60+
findById: false,
61+
};
62+
63+
describe('mergeTypeConverterResolverOpts()', () => {
64+
it('should merge TypeConverterResolverOpts', () => {
65+
expect(
66+
mergeTypeConverterResolverOpts(baseConverterResolverOpts, childConverterResolverOpts)
67+
).toEqual(expectedConverterResolverOpts);
68+
});
69+
70+
describe('mergeFilterOperatorsOptsMap()', () => {
71+
it('should merge FilterOperatorsOptsMap', () => {
72+
expect(
73+
mergeFilterOperatorsOptsMap(
74+
((baseConverterResolverOpts.findMany: any).filter.operators: any),
75+
((childConverterResolverOpts.findMany: any).filter.operators: any)
76+
)
77+
).toEqual((expectedConverterResolverOpts.findMany: any).filter.operators);
78+
});
79+
});
80+
81+
describe('mergePrimitiveTypeFields()', () => {
82+
const opsTypes = ['string', 'boolean'];
83+
84+
it('should merge [base false] and [child undefined] to [false]', () => {
85+
expect(mergePrimitiveTypeFields(false, undefined, opsTypes[1])).toEqual(false);
86+
});
87+
88+
it('should merge [base true] and [child undefined] to [true]', () => {
89+
expect(mergePrimitiveTypeFields(true, undefined, opsTypes[1])).toEqual(true);
90+
});
91+
92+
it('should merge [base undefined] and [child true] to [true]', () => {
93+
expect(mergePrimitiveTypeFields(undefined, true, opsTypes[1])).toEqual(true);
94+
});
95+
96+
it('should merge [base undefined] and [child false] to [false]', () => {
97+
expect(mergePrimitiveTypeFields(undefined, false, opsTypes[1])).toEqual(false);
98+
});
99+
100+
it('should merge [base undefined] and [child undefined] to [undefined]', () => {
101+
expect(mergePrimitiveTypeFields(undefined, undefined, opsTypes[1])).toEqual(undefined);
102+
});
103+
104+
it('should merge with correct results if opsTypes is an array containing "boolean"', () => {
105+
expect(mergePrimitiveTypeFields(false, undefined, opsTypes)).toEqual(false);
106+
expect(mergePrimitiveTypeFields(undefined, false, opsTypes)).toEqual(false);
107+
expect(mergePrimitiveTypeFields(undefined, undefined, opsTypes)).toEqual(undefined);
108+
});
109+
110+
it('should return input child field if optsTypes does not have "boolean"', () => {
111+
expect(mergePrimitiveTypeFields(undefined, undefined, opsTypes[0])).toEqual(undefined);
112+
expect(mergePrimitiveTypeFields(undefined, false, opsTypes[0])).toEqual(false);
113+
expect(mergePrimitiveTypeFields(undefined, true, opsTypes)).toEqual(true);
114+
});
115+
});
116+
117+
describe('mergeMapTypeFields()', () => {
118+
const optsTypes = {
119+
isRequired: 'boolean',
120+
};
121+
122+
it('should merge Map type fields', () => {
123+
expect(
124+
mergeMapTypeFields(
125+
(baseConverterResolverOpts.findMany: any).filter,
126+
(childConverterResolverOpts.findMany: any).filter,
127+
optsTypes
128+
)
129+
).toEqual((expectedConverterResolverOpts.findMany: any).filter);
130+
});
131+
});
132+
});

0 commit comments

Comments
 (0)