Skip to content

Commit c1ec720

Browse files
committed
codegen: Add tests
1 parent b3fa988 commit c1ec720

File tree

2 files changed

+374
-0
lines changed

2 files changed

+374
-0
lines changed

src/codegen/schema.test.js

Lines changed: 367 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,367 @@
1+
const prettier = require('prettier')
2+
const graphql = require('graphql/language')
3+
const immutable = require('immutable')
4+
const SchemaCodeGenerator = require('./schema')
5+
const {
6+
Class,
7+
Method,
8+
StaticMethod,
9+
Param,
10+
NamedType,
11+
NullableType,
12+
ArrayType,
13+
} = require('./typescript')
14+
15+
const formatTS = code =>
16+
prettier.format(
17+
code,
18+
{ parser: 'typescript', semi: false }
19+
)
20+
21+
const createSchemaCodeGen = schema =>
22+
new SchemaCodeGenerator({
23+
ast: immutable.fromJS(graphql.parse(schema)),
24+
})
25+
26+
const testEntity = (generatedTypes, expectedEntity) => {
27+
const entity = generatedTypes.find(type => type.name === expectedEntity.name)
28+
29+
expect(entity instanceof Class).toBe(true)
30+
expect(entity.extends).toBe('Entity')
31+
expect(entity.export).toBe(true)
32+
33+
const { members, methods } = entity
34+
35+
expect(members).toStrictEqual(expectedEntity.members)
36+
37+
for (const expectedMethod of expectedEntity.methods) {
38+
const method = methods.find(method => method.name === expectedMethod.name)
39+
40+
expectedMethod.static
41+
? expect(method instanceof StaticMethod).toBe(true)
42+
: expect(method instanceof Method).toBe(true)
43+
44+
expect(method.params).toStrictEqual(expectedMethod.params)
45+
expect(method.returnType).toStrictEqual(expectedMethod.returnType)
46+
expect(formatTS(method.body)).toBe(formatTS(expectedMethod.body))
47+
}
48+
49+
expect(methods.length).toBe(expectedEntity.methods.length)
50+
}
51+
52+
describe('Schema code generator', () => {
53+
test('Should generate nothing for non entity types', () => {
54+
const codegen = createSchemaCodeGen(`
55+
type Foo {
56+
foobar: Int
57+
}
58+
59+
type Bar {
60+
barfoo: Int
61+
}
62+
`)
63+
64+
expect(codegen.generateTypes().size).toBe(0)
65+
})
66+
67+
describe('Should generate correct classes for each entity', () => {
68+
const codegen = createSchemaCodeGen(`
69+
# just to be sure nothing will be generated from non-entity types alongside regular ones
70+
type Foo {
71+
foobar: Int
72+
}
73+
74+
type Account @entity {
75+
id: ID!
76+
77+
# two non primitive types
78+
description: String
79+
name: String!
80+
81+
# two primitive types (i32)
82+
age: Int
83+
count: Int!
84+
85+
# derivedFrom
86+
wallets: [Wallet!] @derivedFrom(field: "account")
87+
}
88+
89+
type Wallet @entity {
90+
id: ID!
91+
amount: BigInt!
92+
account: Account!
93+
}
94+
`)
95+
96+
const generatedTypes = codegen.generateTypes()
97+
98+
test('Foo is NOT an entity', () => {
99+
const foo = generatedTypes.find(type => type.name === 'Foo')
100+
expect(foo).toBe(undefined)
101+
// Account and Wallet
102+
expect(generatedTypes.size).toBe(2)
103+
})
104+
105+
test('Account is an entity with the correct methods', () => {
106+
testEntity(generatedTypes, {
107+
name: 'Account',
108+
members: [],
109+
methods: [
110+
{
111+
name: 'constructor',
112+
params: [new Param('id', new NamedType('string'))],
113+
returnType: undefined,
114+
body: `
115+
super()
116+
this.set('id', Value.fromString(id))
117+
118+
this.set('name', Value.fromString(''))
119+
`,
120+
},
121+
{
122+
name: 'save',
123+
params: [],
124+
returnType: new NamedType('void'),
125+
body: `
126+
let id = this.get('id')
127+
assert(id != null, 'Cannot save Account entity without an ID')
128+
if (id) {
129+
assert(
130+
id.kind == ValueKind.STRING,
131+
'Cannot save Account entity with non-string ID. ' +
132+
'Considering using .toHex() to convert the "id" to a string.'
133+
)
134+
store.set('Account', id.toString(), this)
135+
}
136+
`,
137+
},
138+
{
139+
name: 'load',
140+
static: true,
141+
params: [new Param('id', new NamedType('string'))],
142+
returnType: new NullableType(new NamedType('Account')),
143+
body: `
144+
return changetype<Account | null>(store.get('Account', id))
145+
`,
146+
},
147+
{
148+
name: 'get id',
149+
params: [],
150+
returnType: new NamedType('string'),
151+
body: `
152+
let value = this.get('id')
153+
return value!.toString()
154+
`,
155+
},
156+
{
157+
name: 'set id',
158+
params: [new Param('value', new NamedType('string'))],
159+
returnType: undefined,
160+
body: `
161+
this.set('id', Value.fromString(value))
162+
`,
163+
},
164+
{
165+
name: 'get description',
166+
params: [],
167+
returnType: new NullableType(new NamedType('string')),
168+
body: `
169+
let value = this.get('description')
170+
if (!value || value.kind == ValueKind.NULL) {
171+
return null
172+
} else {
173+
return value.toString()
174+
}
175+
`,
176+
},
177+
{
178+
name: 'set description',
179+
params: [new Param('value', new NullableType(new NamedType('string')))],
180+
returnType: undefined,
181+
body: `
182+
if (!value) {
183+
this.unset('description')
184+
} else {
185+
this.set('description', Value.fromString(<string>value))
186+
}
187+
`,
188+
},
189+
{
190+
name: 'get name',
191+
params: [],
192+
returnType: new NamedType('string'),
193+
body: `
194+
let value = this.get('name')
195+
return value!.toString()
196+
`,
197+
},
198+
{
199+
name: 'set name',
200+
params: [new Param('value', new NamedType('string'))],
201+
returnType: undefined,
202+
body: `
203+
this.set('name', Value.fromString(value))
204+
`,
205+
},
206+
{
207+
name: 'get age',
208+
params: [],
209+
returnType: new NamedType('i32'),
210+
body: `
211+
let value = this.get('age')
212+
return value!.toI32()
213+
`,
214+
},
215+
{
216+
name: 'set age',
217+
params: [new Param('value', new NamedType('i32'))],
218+
returnType: undefined,
219+
body: `
220+
this.set('age', Value.fromI32(value))
221+
`,
222+
},
223+
{
224+
name: 'get count',
225+
params: [],
226+
returnType: new NamedType('i32'),
227+
body: `
228+
let value = this.get('count')
229+
return value!.toI32()
230+
`,
231+
},
232+
{
233+
name: 'set count',
234+
params: [new Param('value', new NamedType('i32'))],
235+
returnType: undefined,
236+
body: `
237+
this.set('count', Value.fromI32(value))
238+
`,
239+
},
240+
{
241+
name: 'get wallets',
242+
params: [],
243+
returnType: new NullableType(new ArrayType(new NamedType('string'))),
244+
body: `
245+
let value = this.get('wallets')
246+
if (!value || value.kind == ValueKind.NULL) {
247+
return null
248+
} else {
249+
return value.toStringArray()
250+
}
251+
`,
252+
},
253+
{
254+
name: 'set wallets',
255+
params: [new Param('value', new NullableType(new ArrayType(new NamedType('string'))))],
256+
returnType: undefined,
257+
body: `
258+
if (!value) {
259+
this.unset('wallets')
260+
} else {
261+
this.set('wallets', Value.fromStringArray(<Array<string>>value))
262+
}
263+
`,
264+
},
265+
],
266+
})
267+
})
268+
269+
test('Wallet is an entity with the correct methods', () => {
270+
testEntity(generatedTypes, {
271+
name: 'Wallet',
272+
members: [],
273+
methods: [
274+
{
275+
name: 'constructor',
276+
params: [new Param('id', new NamedType('string'))],
277+
returnType: undefined,
278+
body: `
279+
super()
280+
this.set('id', Value.fromString(id))
281+
282+
this.set('amount', Value.fromBigInt(BigInt.zero()))
283+
this.set('account', Value.fromString(''))
284+
`,
285+
},
286+
{
287+
name: 'save',
288+
params: [],
289+
returnType: new NamedType('void'),
290+
body: `
291+
let id = this.get('id')
292+
assert(id != null, 'Cannot save Wallet entity without an ID')
293+
if (id) {
294+
assert(
295+
id.kind == ValueKind.STRING,
296+
'Cannot save Wallet entity with non-string ID. ' +
297+
'Considering using .toHex() to convert the "id" to a string.'
298+
)
299+
store.set('Wallet', id.toString(), this)
300+
}
301+
`,
302+
},
303+
{
304+
name: 'load',
305+
static: true,
306+
params: [new Param('id', new NamedType('string'))],
307+
returnType: new NullableType(new NamedType('Wallet')),
308+
body: `
309+
return changetype<Wallet | null>(store.get('Wallet', id))
310+
`,
311+
},
312+
{
313+
name: 'get id',
314+
params: [],
315+
returnType: new NamedType('string'),
316+
body: `
317+
let value = this.get('id')
318+
return value!.toString()
319+
`,
320+
},
321+
{
322+
name: 'set id',
323+
params: [new Param('value', new NamedType('string'))],
324+
returnType: undefined,
325+
body: `
326+
this.set('id', Value.fromString(value))
327+
`,
328+
},
329+
{
330+
name: 'get amount',
331+
params: [],
332+
returnType: new NamedType('BigInt'),
333+
body: `
334+
let value = this.get('amount')
335+
return value!.toBigInt()
336+
`,
337+
},
338+
{
339+
name: 'set amount',
340+
params: [new Param('value', new NamedType('BigInt'))],
341+
returnType: undefined,
342+
body: `
343+
this.set('amount', Value.fromBigInt(value))
344+
`,
345+
},
346+
{
347+
name: 'get account',
348+
params: [],
349+
returnType: new NamedType('string'),
350+
body: `
351+
let value = this.get('account')
352+
return value!.toString()
353+
`,
354+
},
355+
{
356+
name: 'set account',
357+
params: [new Param('value', new NamedType('string'))],
358+
returnType: undefined,
359+
body: `
360+
this.set('account', Value.fromString(value))
361+
`,
362+
},
363+
],
364+
})
365+
})
366+
})
367+
})

src/codegen/typescript.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,15 @@ const GENERATED_FILE_NOTE = `
174174

175175
module.exports = {
176176
// Types
177+
Param,
178+
Method,
179+
StaticMethod,
180+
Class,
181+
ClassMember,
182+
NamedType,
177183
NullableType,
178184
ArrayType,
185+
ModuleImports,
179186

180187
// Code generators
181188
namedType,

0 commit comments

Comments
 (0)