Skip to content

Commit 7d49d06

Browse files
authored
Merge pull request #31 from mringer/mr-feature-composeApi
Compose functionality and tests
2 parents 0cbe329 + 42083af commit 7d49d06

File tree

6 files changed

+191
-5
lines changed

6 files changed

+191
-5
lines changed

.babelrc

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2-
"presets": ["es2015"],
3-
"sourceMaps": true
2+
"presets": ["env"],
3+
"sourceMaps": true,
4+
"plugins": ["transform-object-rest-spread"]
45
}

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@
3636
"babel-cli": "^6.18.0",
3737
"babel-core": "^6.17.0",
3838
"babel-eslint": "^7.0.0",
39-
"babel-preset-es2015": "^6.16.0",
39+
"babel-plugin-transform-object-rest-spread": "^6.26.0",
40+
"babel-preset-env": "^1.7.0",
4041
"babel-register": "^6.18.0",
4142
"bluebird": "^3.5.0",
4243
"chai": "^3.5.0",

src/helper.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { createResolver, ResultFunction, ErrorFunction, Resolver } from "./resolver";
12
import * as merge from "deepmerge";
3+
import { isFunction } from "./util";
24

35
// Helper function to combine multiple resolver definition hashes into a single hash for consumption by Apollostack's graphql-server
46
export const combineResolvers = (resolvers = []) => resolvers
@@ -26,4 +28,5 @@ export const or = (...conditions) => resolver => (...query) => {
2628
});
2729
attempt(0);
2830
});
29-
}
31+
}
32+

src/resolver.ts

+19
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,14 @@ export interface CreateResolverFunction {
1515
<R, E>(resFn: ResultFunction<R>, errFn?: ErrorFunction<E>): Resolver<R>
1616
}
1717

18+
export interface ComposeResolversFunction {
19+
( resolvers: any ): {} // { [name: string]: Resolver<R> | {} }
20+
}
21+
1822
export interface Resolver<ResulType> {
1923
(root, args: {}, context: {}, info: {}): Promise<ResulType>
2024
createResolver?: CreateResolverFunction
25+
compose?: ComposeResolversFunction
2126
}
2227

2328
export const createResolver: CreateResolverFunction = <R, E>(resFn: ResultFunction<R>, errFn: ErrorFunction<E>) => {
@@ -38,6 +43,7 @@ export const createResolver: CreateResolverFunction = <R, E>(resFn: ResultFuncti
3843
});
3944
});
4045
};
46+
4147
baseResolver.createResolver = (cResFn, cErrFn) => {
4248
const Promise = getPromise();
4349

@@ -74,5 +80,18 @@ export const createResolver: CreateResolverFunction = <R, E>(resFn: ResultFuncti
7480
return createResolver(childResFn, childErrFn);
7581
}
7682

83+
baseResolver.compose = ( resolvers: {} ) => {
84+
const composed = {};
85+
Object.keys(resolvers).forEach(key => {
86+
const _resolver = resolvers[key];
87+
composed[key] = (_resolver.resolve || _resolver.error)
88+
// supports syntax: compose( { myResolver: { resolve: resFn, error: errFn } } )
89+
? baseResolver.createResolver(_resolver.resolve, _resolver.error)
90+
// supports syntax: compose( { myResolver: resolver } )
91+
: baseResolver.createResolver(_resolver);
92+
});
93+
return composed;
94+
}
95+
7796
return baseResolver;
7897
};

test/unit/helper_spec.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import { expect } from 'chai';
22
import { stub } from 'sinon';
33

44
import {
5-
combineResolvers, and, or,
5+
combineResolvers, and, or, compose, composable
66
} from '../../dist/helper';
77
import { createResolver } from '../../dist/resolver';
8+
import { resolveAll } from 'jspm-config';
89

910
describe('(unit) src/helper.js', () => {
1011
describe('combineResolvers', () => {

test/unit/resolver_spec.js

+161
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,165 @@ describe('(unit) dist/resolver.js', () => {
197197
childResolver(null, null, null, { info: 'info' })
198198
})
199199
})
200+
201+
describe('Compose resolvers', () => {
202+
const compositionErr = new Error('composition error');
203+
const successResolver = createResolver(() => null, () => null);
204+
const failureResolver = createResolver(() => { throw compositionErr; }, () => null);
205+
206+
it('composed resolvers are chained, and base resolver is called for each', () => {
207+
208+
const b = {
209+
resolve: () => {},
210+
error: d => compositionErr
211+
};
212+
213+
stub(b, 'resolve', b.resolve);
214+
215+
const base = createResolver(b.resolve, b.error);
216+
const comp = base.compose({
217+
r1: () => true,
218+
r2: () => true,
219+
r3: () => true,
220+
});
221+
222+
return Promise.all([
223+
224+
comp.r1().then(r => {
225+
expect(b.resolve.calledThrice).to.be.true;
226+
expect(r).to.be.true;
227+
}),
228+
229+
comp.r2().then(r => {
230+
expect(b.resolve.calledThrice).to.be.true;
231+
expect(r).to.be.true;
232+
}),
233+
234+
comp.r3().then(r => {
235+
expect(r).to.be.true;
236+
expect(b.resolve.calledThrice).to.be.true;
237+
})
238+
239+
]);
240+
});
241+
242+
it('when base throws, child is not called ', () => {
243+
244+
const b = {
245+
resolve: null,
246+
error: d => compositionErr
247+
};
248+
249+
const r1 = {
250+
resolve: () => true,
251+
error: () => compositionErr
252+
};
253+
254+
stub(b, 'error', b.error);
255+
stub(r1, 'error', r1.error);
256+
257+
const base = createResolver(b.resolve, b.error);
258+
const comp = base.compose( { r1: r1 } );
259+
260+
comp.r1()
261+
.catch( e => {
262+
expect(b.error.calledOnce).to.be.true;
263+
expect(r1.resolve.notCalled).to.be.true;
264+
expect(r1.error.notCalled).to.be.true;
265+
expect(e).to.equal(compositionErr);
266+
});
267+
});
268+
269+
it('when child throws, parent error is called ', () => {
270+
const b = {
271+
resolve: null,
272+
error: d => null
273+
};
274+
275+
const r1 = {
276+
resolve: () => true,
277+
error: () => compositionErr
278+
};
279+
280+
stub(b, 'error', b.error);
281+
stub(r1, 'error', r1.error);
282+
283+
const base = createResolver(b.resolve, b.error);
284+
const comp = base.compose( { r1: r1 } );
285+
286+
comp.r1()
287+
.catch( e => {
288+
expect(b.error.calledOnce).to.be.true;
289+
expect(r1.error.calledOnce).to.be.true;
290+
expect(e).to.equal(compositionErr);
291+
});
292+
});
293+
294+
it('composed resolvers with { resolve: resFn, error: resFn } syntax, resolve and bubble errors correctly', () => {
295+
296+
const b = {
297+
resolve: () => {},
298+
error: d => compositionErr
299+
};
300+
301+
const r1 = {
302+
resolve: () => { throw Error('some other error') },
303+
error: () => compositionErr };
304+
305+
const r2 = { resolve: () => 'r2Result', error: () => compositionErr };
306+
307+
stub(b, 'resolve', b.resolve);
308+
stub(r1, 'error', r1.error);
309+
stub(r1, 'resolve', r1.resolve);
310+
stub(r2, 'resolve', r2.resolve);
311+
stub(r2, 'error', r2.error);
312+
313+
const base = createResolver(b.resolve, b.error);
314+
const comp = base.compose({
315+
r1: r1,
316+
r2: r2,
317+
});
318+
319+
return Promise.all([
320+
comp.r1().catch(e => {
321+
expect(e).to.equal(compositionErr);
322+
}),
323+
comp.r2().then(r => {
324+
expect(r).to.equal('r2Result');
325+
}),
326+
327+
]).then(()=> {
328+
expect(r1.resolve.calledOnce).to.be.true;
329+
expect(r1.error.calledOnce).to.be.true;
330+
expect(r2.resolve.calledOnce).to.be.true;
331+
expect(r2.error.notCalled).to.be.true;
332+
});
333+
});
334+
335+
it('composed result has correct structure', () => {
336+
337+
const b = {
338+
resolve: () => {},
339+
error: d => compositionErr
340+
};
341+
342+
stub(b, 'resolve', b.resolve);
343+
344+
const base = createResolver(b.resolve, b.error);
345+
const comp = base.compose({
346+
r1: { resolve: () => { throw Error('some other error') }, error: () => compositionErr },
347+
r2: { resolve: () => 'r2Result', error: () => compositionErr },
348+
r3: {} // should we throw an exception since it is not a resolver or createResolver params?
349+
});
350+
351+
const composed = { r0: () => {}, ...comp };
352+
353+
expect(composed.r0).to.be.a(typeof Function);
354+
expect(composed.r1).to.be.a(typeof Function);
355+
expect(composed.r2).to.be.a(typeof Function);
356+
expect(composed.r3).to.be.a(typeof Function);
357+
358+
});
359+
360+
});
200361
});

0 commit comments

Comments
 (0)