Skip to content

Commit

Permalink
chore: improve graphql environment
Browse files Browse the repository at this point in the history
  • Loading branch information
unicornware committed Nov 14, 2023
1 parent afd107e commit ab18433
Show file tree
Hide file tree
Showing 42 changed files with 61,687 additions and 484 deletions.
1 change: 1 addition & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
".yarn/",
"__fixtures__/api.github.com/",
"dist/",
"github.schema.gql",
"patches/",
"yarn.lock"
],
Expand Down
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ yarn.lock
!.cspell.json
!.dprint.*
!.github/
!.graphqlrc.yml
!.grease*.*
!.lintstagedrc.json
!.markdownlint.jsonc
Expand Down
37 changes: 31 additions & 6 deletions .eslintrc.base.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const config = {
'promise',
'unicorn'
],
processor: '@graphql-eslint/graphql',
rules: {
'@typescript-eslint/adjacent-overload-signatures': 2,
'@typescript-eslint/array-type': [
Expand Down Expand Up @@ -794,6 +795,7 @@ const config = {
files: '**/*.d.+(cts|mts|ts)',
rules: {
'@typescript-eslint/ban-types': 0,
'@typescript-eslint/prefer-function-type': 0,
'@typescript-eslint/triple-slash-reference': 0,
'jsdoc/no-undefined-types': 0,
'jsdoc/require-file-overview': 0,
Expand Down Expand Up @@ -1181,12 +1183,6 @@ const config = {
]
}
},
{
files: ['docker*.yml', '**/*.md/*.+(yaml|yml)'],
rules: {
'yml/sort-keys': 0
}
},
{
files: ['.github/workflows/*.yml', '.yarnrc.yml', 'docker*.yml'],
rules: {
Expand Down Expand Up @@ -1218,6 +1214,35 @@ const config = {
}
]
}
},
{
files: ['docker*.yml', '**/*.md/*.+(yaml|yml)'],
rules: {
'yml/sort-keys': 0
}
},
{
extends: ['plugin:@graphql-eslint/operations-all'],
files: 'src/**/*.+(gql|graphql)',
parser: '@graphql-eslint/eslint-plugin',
plugins: ['@graphql-eslint'],
rules: {
'@graphql-eslint/alphabetize': [
2,
{
arguments: ['Field', 'Directive'],
definitions: true,
fields: [
'InputObjectTypeDefinition',
'InterfaceTypeDefinition',
'ObjectTypeDefinition'
],
selections: ['OperationDefinition', 'FragmentDefinition'],
values: true,
variables: true
}
]
}
}
],
plugins: [],
Expand Down
10 changes: 9 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,15 @@
*/
const config = {
extends: ['./.eslintrc.base.cjs'],
overrides: [...require('./.eslintrc.base.cjs').overrides],
overrides: [
...require('./.eslintrc.base.cjs').overrides,
{
files: 'github.schema.gql',
parser: '@graphql-eslint/eslint-plugin',
plugins: ['@graphql-eslint'],
rules: {}
}
],
root: true
}

Expand Down
7 changes: 4 additions & 3 deletions .lintstagedrc.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"*": ["dprint check", "cspell lint --color --no-progress --relative $@"],
"**/*.{cjs,cts,gql,js,json,json5,jsonc,jsx,md,mjs,mts,ts,tsx,yaml,yml}": [
"eslint --exit-on-fatal-error"
"*": [
"dprint check",
"eslint --exit-on-fatal-error",
"cspell lint --color --no-progress --relative $@"
],
"**/*.{cts,mts,ts}": "vitest run --changed --typecheck",
"**/yarn.lock": "yarn dedupe --check",
Expand Down
4 changes: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@
"extensions": [
"cjs",
"cts",
"gql",
"graphql",
"js",
"json",
"json5",
Expand All @@ -171,8 +173,8 @@
"overrideConfigFile": ".eslintrc.cjs"
},
"eslint.validate": [
"graphql",
"github-actions-workflow",
"graphql",
"javascript",
"json",
"json5",
Expand Down
2 changes: 1 addition & 1 deletion __tests__/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* @file Entry Point - Test Environment Interfaces
* @file Entry Point - Test Interfaces
* @module tests/interfaces
*/

Expand Down
2 changes: 1 addition & 1 deletion __tests__/interfaces/mock.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* @file Test Environment Interfaces - Mock
* @file Test Interfaces - Mock
* @module tests/interfaces/Mock
*/

Expand Down
2 changes: 1 addition & 1 deletion __tests__/interfaces/spy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* @file Test Environment Interfaces - Spy
* @file Test Interfaces - Spy
* @module tests/interfaces/Spy
*/

Expand Down
27 changes: 27 additions & 0 deletions __tests__/setup/graphql/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* @file Test Setup - schema
* @module tests/setup/graphql/schema
* @see https://mswjs.io/docs/recipes/mock-graphql-schema
*/

import * as mlly from '@flex-development/mlly'
import pathe from '@flex-development/pathe'
import { buildSchema, type GraphQLSchema } from 'graphql'

/**
* Absolute path to GraphQL schema file.
*
* @const {string} source
*/
const source: string = pathe.resolve('github.schema.gql')

/**
* GraphQL schema object.
*
* @see {@linkcode GraphQLSchema}
*
* @const {GraphQLSchema} schema
*/
const schema: GraphQLSchema = buildSchema(<string>await mlly.getSource(source))

export default schema
194 changes: 135 additions & 59 deletions __tests__/setup/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,33 @@
* @module tests/setup/server
*/

import data from '#fixtures/api.github.com/graphql.json' assert { type: 'json' }
import root from '#fixtures/api.github.com/graphql.json' assert { type: 'json' }
import CLIENT_MUTATION_ID from '#fixtures/client-mutation-id.fixture'
import type {
CreateLabelCommand,
DeleteLabelCommand,
UpdateLabelCommand
} from '#src/labels/commands'
import type { Label } from '#src/labels/types'
import type {
GQLPayload,
MutationVariables,
QueryVariables
} from '#tests/types'
CreateLabelCommand as CreateLabelInput,
Label,
UpdateLabelCommand as UpdateLabelInput
} from '#src/labels'
import connection from '#tests/utils/connection'
import gqh from '#tests/utils/gqh'
import GQLResponse from '#tests/utils/gql-response'
import { merge, pick, type Omit } from '@flex-development/tutils'
import {
assign,
includes,
pick,
select,
shake,
type Optional
} from '@flex-development/tutils'
import type { Connection } from '@octokit/graphql'
import type { RepositoryLabelsArgs } from '@octokit/graphql-schema'
import {
GraphQLError,
graphql as executeGraphql,
type ExecutionResult
} from 'graphql'
import { HttpResponse } from 'msw'
import { setupServer, type SetupServer } from 'msw/node'
import schema from './graphql/schema'

/**
* Mock server.
Expand All @@ -30,56 +40,122 @@ import { setupServer, type SetupServer } from 'msw/node'
* @const {SetupServer} server
*/
const server: SetupServer = setupServer(
gqh.mutation<
GQLPayload<'label', Label>,
MutationVariables<CreateLabelCommand>
>('CreateLabel', ({ variables: { input } }) => {
return GQLResponse.json({
data: {
payload: {
label: <Label>{
...pick(input, ['color', 'description', 'name']),
id: faker.string.nanoid()
gqh.operation<ExecutionResult>(async ({
operationName,
query,
variables
}) => {
const { data, errors } = await executeGraphql({
operationName,
rootValue: {
/**
* Mock `createLabel` mutation resolver.
*
* @see https://docs.github.com/graphql/reference/mutations#createlabel
*
* @param {Record<'input', CreateLabelInput>} args - Mutation arguments
* @return {{ label: Label }} Object containing new label
* @throws {GraphQLError} If label name is not unique
*/
createLabel(args: Record<'input', CreateLabelInput>): { label: Label } {
const { nodes } = root.data.repository.labels

// throw if label name is not unique
if (includes(nodes, args.input.name, 0, node => node!.name)) {
throw new GraphQLError('Name has already been taken', {
extensions: { type: 'UNPROCESSABLE' }
})
}
}
}
})
}),
gqh.mutation<
GQLPayload<'clientMutationId', string>,
MutationVariables<DeleteLabelCommand>
>('DeleteLabel', () => {
return GQLResponse.json({
data: { payload: { clientMutationId: CLIENT_MUTATION_ID } }
})
}),
gqh.mutation<
GQLPayload<'label', Label>,
MutationVariables<UpdateLabelCommand>
>('UpdateLabel', ({ variables: { input } }) => {
const { nodes } = data.data.repository.labels

return GQLResponse.json({
data: {
payload: {
label: <Label>merge(nodes.find(({ id }) => id === input.id)!, input)
return {
label: <Label>{
...pick(args.input, ['color', 'description', 'name']),
id: faker.string.nanoid()
}
}
},
/**
* Mock `deleteLabel` mutation resolver.
*
* @see https://docs.github.com/graphql/reference/mutations#deletelabel
*
* @return {{ clientMutationId: string }} Client mutation id object
*/
deleteLabel(): { clientMutationId: string } {
return { clientMutationId: CLIENT_MUTATION_ID }
},
/**
* Mock `repository` object.
*
* @see https://docs.github.com/graphql/reference/objects#repository
*/
repository: {
/**
* Node ID of repository.
*
* @const {string} id
*/
id: root.data.repository.id,
/**
* Mock repository `labels` query resolver.
*
* @param {RepositoryLabelsArgs} args - Query arguments
* @return {Connection<Label>} Label connection object
*/
labels(args: RepositoryLabelsArgs): Connection<Label> {
return connection('labels', args.after)
}
},
/**
* Mock `updateLabel` mutation resolver.
*
* @see https://docs.github.com/graphql/reference/mutations#updatelabel
*
* @param {Record<'input', UpdateLabelInput>} args - Mutation arguments
* @return {{ label: Label }} Object containing updated label
* @throws {GraphQLError} If label to update is not found
*/
updateLabel(args: Record<'input', UpdateLabelInput>): { label: Label } {
const { nodes } = root.data.repository.labels

/**
* Label to update.
*
* @const {Optional<Label>} node
*/
const node: Optional<Label> = nodes.find(({ id }) => {
return id === args.input.id
})

// throw if label was not found
if (!node) {
/**
* Error message.
*
* @const {string} message
*/
const message: string =
`Could not resolve to Label node with the global id of ${args.input.id}`

throw new GraphQLError(message, {
extensions: { type: 'NOT_FOUND' }
})
}

return { label: <Label>assign(node, args.input) }
}
}
})
}),
gqh.query<
GQLPayload<'id', string>,
Omit<QueryVariables, 'cursor'>
>('GetRepository', () => {
return GQLResponse.json({
data: { payload: pick(data.data.repository, ['id']) }
},
schema,
source: query,
variableValues: <Record<string, unknown>>variables
})
}),
gqh.query<
GQLPayload<'labels', Label[]>,
QueryVariables
>('Labels', ({ variables }) => {
return GQLResponse.paginate({ ...variables, key: 'labels' })

return HttpResponse.json(shake({
data: data!,
errors: errors
? select(errors, null, e => ({ ...e.toJSON(), ...e.extensions }))
: undefined
}))
})
)

Expand Down
Loading

0 comments on commit ab18433

Please sign in to comment.