New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: add "ctx.field()" for GraphQL responses #1257
fix: add "ctx.field()" for GraphQL responses #1257
Conversation
src/context/field.ts
Outdated
} | ||
|
||
function assertFieldNameIsValid(fieldName: string) { | ||
if (forbiddenFieldNamesList.includes(fieldName as ForbiddenFieldNames)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using invariant
here:
import { devUtils } from '../utils/internals/devUtils'
invariant(
fieldName !== '',
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)
)
I'd also drop the forbidden names array and tackle each scenario individually. It may seem repetitive but we gain more contextual error messages for the developer this way.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the examples, I wasn't sure about using invariant when checking for a specific value. I see the value of more explicit error message, I will update the PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's really cool that, being a dev tool, MSW can be as explicit about error messages and error behaviors as it wants. Yeah, I'd generally recommend being detailed when it comes to error messages, and if we structure each individual name check separately, we can produce a separate message for each failed state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅
I made these changes.
Do you think we could change errors messages generation a bit to reduce duplication ? Or is it better to have the message in the code without formatting to make the search in the code easier ?
It could be something like this
invariant(
fieldName !== 'data' && fieldName !== 'errors' && 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.%s()" instead?',
fieldName, fieldName,
),
)
What bothers me with the current solution is that we use string formatting to create the error message, but we could either:
- use it more to reduce duplication (like in the example above)
- or not use it a all and keep the same number of lines of code (we use formatting in cases where we already know the value of
fieldName
). Instead have string without formatting to make search in the code easier.
Do you think we should change it for one of these propositions or keep it as it is ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is worth investing our time at the moment. Repetition is not inherently bad, and I'd much more prefer to see
invariant(a, messageA)
invariant(b, messageB)
invariant(c, messageC)
Even if it means all messages are almost the same. Yes, you're right, it improves discoverability of the code but also allows for custom cases without having to extend the abstraction. If we decide to give a better message for the b
case then we just do that. We know a
and c
are not affected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok ! So we keep it as it is ?
src/handlers/GraphQLHandler.ts
Outdated
@@ -48,6 +50,7 @@ export const graphqlContext: GraphQLContext<any> = { | |||
extensions, | |||
errors, | |||
cookie, | |||
field: field, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use shorthand assignment here!
field: field, | |
field, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey, @Poivey! Thank you for preparing this, an outstanding work!
I've left a few suggestions, would love to know what you think.
I've renamed the pull request to be a |
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit eb1d59e:
|
… forbiden names values
acf5644
to
24d20d6
Compare
There was a mistake in the forbidden values for field name of the initial commit. It is now corrected (I wrote |
} | ||
} | ||
|
||
function validateFieldName(fieldName: string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should export this function and unit test it.
Also, let's include an explicit return type:
function validateFieldName(fieldName: string) { | |
function validateFieldName(fieldName: string): void { |
src/context/field.test.ts
Outdated
) | ||
}) | ||
|
||
test.each(['data', 'errors', 'extensions'])( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is absolutely biased but I don't like any logic around test execution. It always makes tests smarter than they should be. If a test is repetitive, then I recommend tackling that on the test setup level but not test orchestration/definition level.
it('throws an error when using forbidden field names', async () => {
const tryWithFieldName = (fieldName: string) => {
return response(ctx.field(fieldName, 'value'))
}
expect(() => tryWithFieldName('data')).toThrow('...')
expect(() => tryWithFieldName('extensions')).toThrow('...')
})
- A single test case because input validation is a single piece of logic (even though we validate against multiple forbidden names—it's fine; each name does not equal new behavior).
- Clear assertions.
- Repetition is abstracted into
tryWithFieldName
, which is both scoped and readable.
field: { errorCode: 'value' }, | ||
errors: [ | ||
{ | ||
message: 'exceeds the limit of awesomeness', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❤️
I've pushed a few polish items, the feature should be ready to go! 🎉 |
@Poivey, I'm so thankful for your contributions. You're the best 🥳 |
Happy to contribute ! I planned to do the last changes later this week but it's great if you found the time to update it yourself :) |
Released: v0.42.1 🎉This has been released in v0.42.1! Make sure to always update to the latest version ( Predictable release automation by @ossjs/release. |
As discussed in the issues, this PR adds a
field(fieldName, fieldValue)
method to graphQL response context.fieldName
is typed in a way to prevent usingerrors
,data
,extensions
and<empty string>
as a value. Is then any other value we should forbid ?fieldName
is also validated at runtime level.@kentcdodds In the issue you mentionedinvariant
as a solution to make sure this field is valid, but I am not sure how it could be use here as we need to check for specific values, not if the value is undefined. Could you tell me how you thought this coule be used here ?TODO
https://mswjs.io/docs/api/context/field