graphql-codegen-typescript-operations-tester
Version:
graphql-code-generator plugin to generate test functions on operations
108 lines (92 loc) • 3.1 kB
text/typescript
import { PluginFunction } from '@graphql-codegen/plugin-helpers'
import { pascalCase } from 'change-case-all'
import {
concatAST,
DocumentNode,
ExecutionResult,
FragmentDefinitionNode,
graphql,
GraphQLArgs,
GraphQLSchema,
OperationDefinitionNode,
print,
visit,
} from 'graphql'
function getOperationFragments(
node: OperationDefinitionNode | FragmentDefinitionNode,
allFragments: Map<string, FragmentDefinitionNode>,
operationFragments?: Map<string, FragmentDefinitionNode>
): Map<string, FragmentDefinitionNode> {
const fragments = operationFragments || new Map<string, FragmentDefinitionNode>()
visit(node, {
FragmentSpread: {
enter(node) {
const fragment = allFragments.get(node.name.value)
if (fragment) {
fragments.set(node.name.value, fragment)
getOperationFragments(fragment, allFragments, fragments)
} else {
throw new Error('Unknown fragment: ' + node.name.value)
}
},
},
})
return fragments
}
type Config = {
prefix?: string
}
export const plugin: PluginFunction<Config> = (schema, documents, config) => {
const imports = [`import { request, Args } from 'graphql-codegen-typescript-operations-tester'`]
const prefix = config.prefix || 'test'
const allAst = concatAST(
documents.reduce<DocumentNode[]>((acc, source) => {
if (source.document) {
acc.push(source.document)
}
return acc
}, [])
)
const allFragments = new Map<string, FragmentDefinitionNode>()
visit(allAst, {
FragmentDefinition(node) {
allFragments.set(node.name.value, node)
},
})
const lines: string[] = []
visit(allAst, {
OperationDefinition(node) {
if (!node.name) return
const type = node.operation === 'mutation' ? 'Mutation' : 'Query'
// Mimic the default naming convention.
// See https://www.graphql-code-generator.com/docs/getting-started/naming-convention#namingconvention
const name = pascalCase(`${node.name.value}${type}`)
const fragments = getOperationFragments(node, allFragments)
const fragmentsStr =
fragments.size > 0 ? `${Array.from(fragments.values()).map(print)}\n` : ''
lines.push(``)
lines.push(`export const ${name}Source: string = \``)
lines.push(`${fragmentsStr}${print(node)}\`;`)
lines.push(``)
lines.push(`export function ${prefix}${name}(`)
lines.push(` graphqlArgs: Args,`)
lines.push(` variables?: ${name}Variables`)
lines.push(`) {`)
lines.push(
` return request<${name}>({ ...graphqlArgs, source: ${name}Source, variableValues: variables })`
)
lines.push(`};`)
},
})
const content = lines.join('\n')
return {
prepend: imports,
content: content,
}
}
export type Args = { schema: GraphQLSchema } & Partial<GraphQLArgs>
export async function request<TData extends Record<string, unknown> = Record<string, unknown>>(
graphqlArgs: GraphQLArgs
): Promise<ExecutionResult<TData>> {
return graphql(graphqlArgs) as Promise<ExecutionResult<TData>>
}