Skip to content

Commit e5f9485

Browse files
committed
add(relay): connection.resolveEdge as a tiny helper for mutations
1 parent 19ce124 commit e5f9485

File tree

5 files changed

+216
-12
lines changed

5 files changed

+216
-12
lines changed

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"lint": "eslint src",
1313
"build": "rm -rf lib/* && babel src --ignore test --optional runtime --out-dir lib",
1414
"test": "npm run test-unit && npm run test-docker",
15-
"test-unit": "mocha $npm_package_options_mocha test/unit/*.test.js",
15+
"test-unit": "mocha $npm_package_options_mocha test/unit/*.test.js test/unit/**/*.test.js",
1616
"build-docker": "docker-compose build",
1717
"test-docker": "docker-compose run dev /bin/sh -c \"npm run test-integration\"",
1818
"test-integration": "mocha $npm_package_options_mocha test/integration/*.test.js test/integration/**/*.test.js"
@@ -46,12 +46,13 @@
4646
"pg-hstore": "^2.3.2",
4747
"sequelize": "^3.16.0",
4848
"sinon": "^1.15.4",
49+
"sinon-as-promised": "^4.0.0",
4950
"sinon-chai": "^2.8.0",
5051
"sqlite3": "^3.0.9"
5152
},
5253
"dependencies": {
5354
"babel-runtime": "^6.0.8",
54-
"graphql-relay": "^0.3.3",
55+
"graphql-relay": "^0.3.6",
5556
"lodash": "^3.10.0"
5657
}
5758
}

scripts/mocha-bootload

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ require('babel/register')({
55
var chai = require('chai');
66

77
chai.use(require('chai-as-promised'));
8-
chai.use(require('sinon-chai'));
8+
chai.use(require('sinon-chai'));
9+
require('sinon-as-promised')(require('bluebird'));

src/relay.js

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,11 @@ export function sequelizeConnection({name, nodeType, target, orderBy: orderByEnu
9292
const {
9393
edgeType,
9494
connectionType
95-
} = connectionDefinitions({name, nodeType, connectionFields});
95+
} = connectionDefinitions({
96+
name,
97+
nodeType,
98+
connectionFields
99+
});
96100

97101
const model = target.target ? target.target : target;
98102
const SEPERATOR = '$';
@@ -107,6 +111,8 @@ export function sequelizeConnection({name, nodeType, target, orderBy: orderByEnu
107111
});
108112
}
109113

114+
let defaultOrderBy = orderByEnum._values[0].value;
115+
110116
before = before || ((options) => options);
111117

112118
let $connectionArgs = {
@@ -148,6 +154,17 @@ export function sequelizeConnection({name, nodeType, target, orderBy: orderByEnu
148154
return result;
149155
};
150156

157+
let resolveEdge = function (item, args = {}) {
158+
if (!args.orderBy) {
159+
args.orderBy = [defaultOrderBy];
160+
}
161+
162+
return {
163+
cursor: toCursor(item, args.orderBy),
164+
node: item
165+
};
166+
};
167+
151168
let $resolver = require('./resolver')(target, {
152169
handleConnection: false,
153170
include: true,
@@ -224,15 +241,8 @@ export function sequelizeConnection({name, nodeType, target, orderBy: orderByEnu
224241
return before(options, args, root);
225242
},
226243
after: function (values, args, root, {source}) {
227-
if (!args.orderBy) {
228-
args.orderBy = [orderByEnum._values[0].value];
229-
}
230-
231244
let edges = values.map((value) => {
232-
return {
233-
cursor: toCursor(value, args.orderBy),
234-
node: value
235-
};
245+
return resolveEdge(value, args);
236246
});
237247

238248
let firstEdge = edges[0];
@@ -282,6 +292,7 @@ export function sequelizeConnection({name, nodeType, target, orderBy: orderByEnu
282292
connectionType,
283293
edgeType,
284294
nodeType,
295+
resolveEdge,
285296
connectionArgs: $connectionArgs,
286297
resolve: resolver
287298
};

test/integration/relay/connection.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ if (helper.sequelize.dialect.name === 'postgres') {
340340
user(id: ${this.userA.id}) {
341341
tasks(first: 2, completed: true, orderBy: LATEST) {
342342
edges {
343+
cursor
343344
node {
344345
id
345346
name

test/unit/relay/mutation.test.js

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
'use strict';
2+
3+
import {expect} from 'chai';
4+
import helper from '../helper';
5+
import Sequelize from 'sequelize';
6+
import sinon from 'sinon';
7+
import attributeFields from '../../../src/attributeFields';
8+
9+
const {
10+
sequelize,
11+
Promise
12+
} = helper;
13+
14+
import {
15+
sequelizeConnection
16+
} from '../../../src/relay';
17+
18+
import {
19+
GraphQLString,
20+
GraphQLInt,
21+
GraphQLFloat,
22+
GraphQLNonNull,
23+
GraphQLBoolean,
24+
GraphQLEnumType,
25+
GraphQLList,
26+
GraphQLObjectType,
27+
GraphQLSchema,
28+
GraphQLID,
29+
graphql
30+
} from 'graphql';
31+
32+
import {
33+
globalIdField,
34+
toGlobalId,
35+
fromGlobalId,
36+
mutationWithClientMutationId
37+
} from 'graphql-relay';
38+
39+
describe('relay', function () {
40+
describe('mutation', function () {
41+
describe('connections', function () {
42+
before(function () {
43+
this.User = sequelize.define('user', {}, {timestamps: false});
44+
this.Task = sequelize.define('task', {title: Sequelize.STRING}, {timestamps: false});
45+
46+
this.User.Tasks = this.User.hasMany(this.Task, {as: 'tasks', foreignKey: 'userId'});
47+
48+
this.taskType = new GraphQLObjectType({
49+
name: this.Task.name,
50+
fields: {
51+
...attributeFields(this.Task),
52+
id: globalIdField(this.Task.name)
53+
}
54+
});
55+
56+
this.viewerTaskConnection = sequelizeConnection({
57+
name: 'Viewer' + this.Task.name,
58+
nodeType: this.taskType,
59+
target: this.User.Tasks,
60+
orderBy: new GraphQLEnumType({
61+
name: 'Viewer' + this.Task.name + 'ConnectionOrder',
62+
values: {
63+
ID: {value: [this.Task.primaryKeyAttribute, 'ASC']},
64+
}
65+
})
66+
});
67+
68+
this.viewerType = new GraphQLObjectType({
69+
name: 'Viewer',
70+
fields: {
71+
tasks: {
72+
type: this.viewerTaskConnection.connectionType,
73+
args: this.viewerTaskConnection.connectionArgs,
74+
resolve: this.viewerTaskConnection.resolve
75+
}
76+
}
77+
});
78+
79+
const addTaskMutation = mutationWithClientMutationId({
80+
name: 'addTask',
81+
inputFields: {
82+
title: {
83+
type: new GraphQLNonNull(GraphQLString)
84+
}
85+
},
86+
outputFields: () => ({
87+
viewer: {
88+
type: this.viewerType,
89+
resolve: (payload, {rootValue}) => {
90+
return rootValue.viewer;
91+
}
92+
},
93+
task: {
94+
type: this.taskType,
95+
resolve: (payload) => payload.task
96+
},
97+
newTaskEdge: {
98+
type: this.viewerTaskConnection.edgeType,
99+
resolve: (payload) => this.viewerTaskConnection.resolveEdge(payload.task)
100+
}
101+
}),
102+
mutateAndGetPayload: async ({title}, {rootValue}) => {
103+
let task = await this.Task.create({
104+
title: title,
105+
userId: rootValue.viewer.id
106+
});
107+
108+
return {
109+
task: task
110+
};
111+
}
112+
});
113+
114+
this.schema = new GraphQLSchema({
115+
query: new GraphQLObjectType({
116+
name: 'RootQueryType',
117+
fields: {
118+
viewer: {
119+
type: this.viewerType,
120+
resolve: function (source, args, info) {
121+
return info.rootValue.viewer;
122+
}
123+
}
124+
}
125+
}),
126+
mutation: new GraphQLObjectType({
127+
name: 'Mutation',
128+
fields: {
129+
addTask: addTaskMutation
130+
}
131+
})
132+
});
133+
});
134+
135+
beforeEach(function () {
136+
this.sinon = sinon.sandbox.create();
137+
138+
this.viewer = this.User.build({
139+
id: Math.ceil(Math.random() * 999)
140+
});
141+
142+
this.sinon.stub(this.Task, 'create').resolves();
143+
});
144+
145+
afterEach(function () {
146+
this.sinon.restore();
147+
});
148+
149+
describe('addEdgeMutation', function () {
150+
it('should return a appropriate cursor and node', async function () {
151+
let title = Math.random().toString()
152+
, id = Math.ceil(Math.random() * 999);
153+
154+
this.Task.create.resolves(this.Task.build({
155+
id: id,
156+
title: title,
157+
userId: this.viewer.get('id')
158+
}));
159+
160+
let result = await graphql(this.schema, `
161+
mutation {
162+
addTask(input: {title: "${title}", clientMutationId: "${Math.random().toString()}"}) {
163+
task {
164+
id
165+
}
166+
167+
newTaskEdge {
168+
cursor
169+
node {
170+
id
171+
title
172+
}
173+
}
174+
}
175+
}
176+
`, {
177+
viewer: this.viewer
178+
});
179+
180+
if (result.errors) throw new Error(result.errors[0].stack);
181+
182+
expect(result.data.addTask.task.id).to.equal(toGlobalId(this.Task.name, id));
183+
expect(result.data.addTask.newTaskEdge.cursor).to.be.ok;
184+
expect(result.data.addTask.newTaskEdge.node.id).to.equal(toGlobalId(this.Task.name, id));
185+
expect(result.data.addTask.newTaskEdge.node.title).to.equal(title);
186+
});
187+
});
188+
});
189+
});
190+
});

0 commit comments

Comments
 (0)