Skip to content

Commit 44878a5

Browse files
authored
Context typings improvements and tests (#1775)
1 parent 8df34c1 commit 44878a5

File tree

3 files changed

+189
-2
lines changed

3 files changed

+189
-2
lines changed

.changeset/ten-panthers-cover.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'graphql-yoga': patch
3+
---
4+
5+
Context typings improvements
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import { createSchema } from '../src/schema'
2+
import { createYoga } from '../src/server'
3+
import { YogaInitialContext } from '../src/types'
4+
import { Plugin } from '../src/plugins/types'
5+
6+
describe('Context', () => {
7+
interface UserContext {
8+
hi: 'there'
9+
}
10+
11+
const userContext: UserContext = { hi: 'there' }
12+
13+
const schema = createSchema<YogaInitialContext & UserContext>({
14+
typeDefs: /* GraphQL */ `
15+
type Query {
16+
hello: String!
17+
}
18+
type Subscription {
19+
greetings: String!
20+
}
21+
`,
22+
resolvers: {
23+
Query: {
24+
hello: () => 'world',
25+
},
26+
Subscription: {
27+
greetings: {
28+
async *subscribe() {
29+
yield { greetings: 'Hi' }
30+
},
31+
},
32+
},
33+
},
34+
})
35+
36+
it('should provide intial and user context to onExecute', async () => {
37+
const onExecuteFn = jest.fn((() => {}) as Plugin<
38+
{},
39+
{},
40+
UserContext
41+
>['onExecute'])
42+
43+
const yoga = createYoga({
44+
schema,
45+
context: userContext,
46+
plugins: [
47+
{
48+
onExecute: onExecuteFn,
49+
},
50+
],
51+
})
52+
53+
const response = await yoga.fetch('/graphql?query={hello}')
54+
expect(response.status).toBe(200)
55+
56+
expect(onExecuteFn.mock.lastCall[0].args.contextValue.hi).toBe(
57+
userContext.hi,
58+
)
59+
expect(onExecuteFn.mock.lastCall[0].args.contextValue.params)
60+
.toMatchInlineSnapshot(`
61+
{
62+
"extensions": undefined,
63+
"operationName": undefined,
64+
"query": "{hello}",
65+
"variables": undefined,
66+
}
67+
`)
68+
expect(onExecuteFn.mock.lastCall[0].args.contextValue.request).toBeDefined()
69+
})
70+
71+
it('should provide intial and user context to onSubscribe', async () => {
72+
const onSubscribeFn = jest.fn((() => {}) as Plugin<
73+
{},
74+
{},
75+
UserContext
76+
>['onSubscribe'])
77+
78+
const yoga = createYoga({
79+
schema,
80+
context: userContext,
81+
plugins: [
82+
{
83+
onSubscribe: onSubscribeFn,
84+
},
85+
],
86+
})
87+
88+
const response = await yoga.fetch(
89+
'/graphql?query=subscription{greetings}',
90+
{
91+
headers: {
92+
Accept: 'text/event-stream',
93+
},
94+
},
95+
)
96+
97+
expect(response.status).toBe(200)
98+
99+
expect(onSubscribeFn.mock.lastCall[0].args.contextValue.hi).toBe(
100+
userContext.hi,
101+
)
102+
expect(onSubscribeFn.mock.lastCall[0].args.contextValue.params)
103+
.toMatchInlineSnapshot(`
104+
{
105+
"extensions": undefined,
106+
"operationName": undefined,
107+
"query": "subscription{greetings}",
108+
"variables": undefined,
109+
}
110+
`)
111+
expect(
112+
onSubscribeFn.mock.lastCall[0].args.contextValue.request,
113+
).toBeDefined()
114+
})
115+
116+
it('should provide intial context to rest of envelop hooks', async () => {
117+
const onEnvelopedFn = jest.fn((() => {}) as Plugin['onEnveloped'])
118+
const onParseFn = jest.fn((() => {}) as Plugin['onParse'])
119+
const onValidateFn = jest.fn((() => {}) as Plugin['onValidate'])
120+
const onContextBuildingFn = jest.fn(
121+
(() => {}) as Plugin['onContextBuilding'],
122+
)
123+
const onResolverCalledFn = jest.fn((() => {}) as Plugin['onResolverCalled'])
124+
125+
const yoga = createYoga({
126+
schema,
127+
context: userContext,
128+
plugins: [
129+
{
130+
onEnveloped: onEnvelopedFn,
131+
onParse: onParseFn,
132+
onValidate: onValidateFn,
133+
onContextBuilding: onContextBuildingFn,
134+
onResolverCalled: onResolverCalledFn,
135+
},
136+
],
137+
})
138+
139+
const response = await yoga.fetch('/graphql?query={hello}')
140+
expect(response.status).toBe(200)
141+
142+
const params = {
143+
extensions: undefined,
144+
operationName: undefined,
145+
query: '{hello}',
146+
variables: undefined,
147+
}
148+
149+
expect(onEnvelopedFn.mock.lastCall[0].context?.params).toEqual(params)
150+
expect(onEnvelopedFn.mock.lastCall[0].context?.request).toBeDefined()
151+
152+
expect(onParseFn.mock.lastCall[0].context.params).toEqual(params)
153+
expect(onParseFn.mock.lastCall[0].context.request).toBeDefined()
154+
155+
expect(onValidateFn.mock.lastCall[0].context.params).toEqual(params)
156+
expect(onValidateFn.mock.lastCall[0].context.request).toBeDefined()
157+
158+
expect(onContextBuildingFn.mock.lastCall[0].context.params).toEqual(params)
159+
expect(onContextBuildingFn.mock.lastCall[0].context.request).toBeDefined()
160+
161+
expect(onResolverCalledFn.mock.lastCall[0].context.params).toEqual(params)
162+
expect(onResolverCalledFn.mock.lastCall[0].context.request).toBeDefined()
163+
})
164+
})

packages/graphql-yoga/src/plugins/types.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,35 @@
1-
import { Plugin as EnvelopPlugin, PromiseOrValue } from '@envelop/core'
1+
import {
2+
Plugin as EnvelopPlugin,
3+
PromiseOrValue,
4+
OnExecuteHook,
5+
OnSubscribeHook,
6+
} from '@envelop/core'
27
import { ExecutionResult } from 'graphql'
38
import {
49
ExecutionPatchResult,
510
FetchAPI,
611
GraphQLParams,
712
MaybeArray,
13+
YogaInitialContext,
814
} from '../types.js'
915

1016
export type Plugin<
1117
PluginContext extends Record<string, any> = {},
1218
TServerContext = {},
1319
TUserContext = {},
14-
> = EnvelopPlugin<PluginContext> & {
20+
> = EnvelopPlugin<YogaInitialContext & PluginContext> & {
21+
/**
22+
* onExecute hook that is invoked before the execute function is invoked.
23+
*/
24+
onExecute?: OnExecuteHook<YogaInitialContext & PluginContext & TUserContext>
25+
/**
26+
* onSubscribe hook that is invoked before the subscribe function is called.
27+
* Return a OnSubscribeHookResult for hooking into phase after the subscribe function has been called.
28+
*/
29+
onSubscribe?: OnSubscribeHook<
30+
YogaInitialContext & PluginContext & TUserContext
31+
>
32+
} & {
1533
/**
1634
* Use this hook with your own risk. It is still experimental and may change in the future.
1735
* @internal

0 commit comments

Comments
 (0)