Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: add "ctx.field()" for GraphQL responses (#1257)
* feat: add field() method to graphQL response context fix #1200 * style(src/context/field.ts): rename assertFieldNameIsValid to validateFieldName * style(src/handlers/graphqlhandler.ts): use shorthand assignemnt * refactor(ctx.field()): update filename validation exceptions, correct forbiden names values * test(field): expand error unit tests * chore(field): add whitespace between name validations Co-authored-by: Artem Zakharchenko <kettanaito@gmail.com>
- Loading branch information
1 parent
1b9dde0
commit 442f48d
Showing
3 changed files
with
180 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
/** | ||
* @jest-environment jsdom | ||
*/ | ||
import { field } from './field' | ||
import { response } from '../response' | ||
import { data } from './data' | ||
import { errors } from './errors' | ||
|
||
test('sets a given field value string on the response JSON body', async () => { | ||
const result = await response(field('field', 'value')) | ||
|
||
expect(result.headers.get('content-type')).toBe('application/json') | ||
expect(result).toHaveProperty('body', JSON.stringify({ field: 'value' })) | ||
}) | ||
|
||
test('sets a given field value object on the response JSON body', async () => { | ||
const result = await response( | ||
field('metadata', { | ||
date: new Date('2022-05-27'), | ||
comment: 'nice metadata', | ||
}), | ||
) | ||
|
||
expect(result.headers.get('content-type')).toBe('application/json') | ||
expect(result).toHaveProperty( | ||
'body', | ||
JSON.stringify({ | ||
metadata: { date: new Date('2022-05-27'), comment: 'nice metadata' }, | ||
}), | ||
) | ||
}) | ||
|
||
test('combines with data, errors and other field in the response JSON body', async () => { | ||
const result = await response( | ||
data({ name: 'msw' }), | ||
errors([{ message: 'exceeds the limit of awesomeness' }]), | ||
field('field', { errorCode: 'value' }), | ||
field('field2', 123), | ||
) | ||
|
||
expect(result.headers.get('content-type')).toEqual('application/json') | ||
expect(result).toHaveProperty( | ||
'body', | ||
JSON.stringify({ | ||
field2: 123, | ||
field: { errorCode: 'value' }, | ||
errors: [ | ||
{ | ||
message: 'exceeds the limit of awesomeness', | ||
}, | ||
], | ||
data: { | ||
name: 'msw', | ||
}, | ||
}), | ||
) | ||
}) | ||
|
||
test('throws when trying to set non-serializable values', async () => { | ||
await expect(response(field('metadata', BigInt(1)))).rejects.toThrow( | ||
'Do not know how to serialize a BigInt', | ||
) | ||
}) | ||
|
||
test('throws when passing an empty string as field name', async () => { | ||
await expect(response(field('' as string, 'value'))).rejects.toThrow( | ||
`[MSW] Failed to set a custom field on a GraphQL response: field name cannot be empty.`, | ||
) | ||
}) | ||
|
||
test('throws when passing an empty string (when trimmed) as field name', async () => { | ||
await expect(response(field(' ' as string, 'value'))).rejects.toThrow( | ||
`[MSW] Failed to set a custom field on a GraphQL response: field name cannot be empty.`, | ||
) | ||
}) | ||
|
||
test('throws when using "data" as the field name', async () => { | ||
await expect( | ||
response( | ||
field( | ||
// @ts-expect-error Test runtime value. | ||
'data', | ||
'value', | ||
), | ||
), | ||
).rejects.toThrow( | ||
'[MSW] Failed to set a custom "data" field on a mocked GraphQL response: forbidden field name. Did you mean to call "ctx.data()" instead?', | ||
) | ||
}) | ||
|
||
test('throws when using "errors" as the field name', async () => { | ||
await expect( | ||
response( | ||
field( | ||
// @ts-expect-error Test runtime value. | ||
'errors', | ||
'value', | ||
), | ||
), | ||
).rejects.toThrow( | ||
'[MSW] Failed to set a custom "errors" field on a mocked GraphQL response: forbidden field name. Did you mean to call "ctx.errors()" instead?', | ||
) | ||
}) | ||
|
||
test('throws when using "extensions" as the field name', async () => { | ||
await expect( | ||
response( | ||
field( | ||
// @ts-expect-error Test runtime value. | ||
'extensions', | ||
'value', | ||
), | ||
), | ||
).rejects.toThrow( | ||
'[MSW] Failed to set a custom "extensions" field on a mocked GraphQL response: forbidden field name. Did you mean to call "ctx.extensions()" instead?', | ||
) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { invariant } from 'outvariant' | ||
import { ResponseTransformer } from '../response' | ||
import { devUtils } from '../utils/internal/devUtils' | ||
import { jsonParse } from '../utils/internal/jsonParse' | ||
import { mergeRight } from '../utils/internal/mergeRight' | ||
import { json } from './json' | ||
|
||
type ForbiddenFieldNames = '' | 'data' | 'errors' | 'extensions' | ||
|
||
/** | ||
* Set a custom field on the GraphQL mocked response. | ||
* @example res(ctx.fields('customField', value)) | ||
* @see {@link https://mswjs.io/docs/api/context/field} | ||
*/ | ||
export const field = <FieldNameType extends string, FieldValueType>( | ||
fieldName: FieldNameType extends ForbiddenFieldNames ? never : FieldNameType, | ||
fieldValue: FieldValueType, | ||
): ResponseTransformer<string> => { | ||
return (res) => { | ||
validateFieldName(fieldName) | ||
|
||
const prevBody = jsonParse(res.body) || {} | ||
const nextBody = mergeRight(prevBody, { [fieldName]: fieldValue }) | ||
|
||
return json(nextBody)(res as any) as any | ||
} | ||
} | ||
|
||
function validateFieldName(fieldName: string) { | ||
invariant( | ||
fieldName.trim() !== '', | ||
devUtils.formatMessage( | ||
'Failed to set a custom field on a GraphQL response: field name cannot be empty.', | ||
), | ||
) | ||
|
||
invariant( | ||
fieldName !== 'data', | ||
devUtils.formatMessage( | ||
'Failed to set a custom "%s" field on a mocked GraphQL response: forbidden field name. Did you mean to call "ctx.data()" instead?', | ||
fieldName, | ||
), | ||
) | ||
|
||
invariant( | ||
fieldName !== 'errors', | ||
devUtils.formatMessage( | ||
'Failed to set a custom "%s" field on a mocked GraphQL response: forbidden field name. Did you mean to call "ctx.errors()" instead?', | ||
fieldName, | ||
), | ||
) | ||
|
||
invariant( | ||
fieldName !== 'extensions', | ||
devUtils.formatMessage( | ||
'Failed to set a custom "%s" field on a mocked GraphQL response: forbidden field name. Did you mean to call "ctx.extensions()" instead?', | ||
fieldName, | ||
), | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters