Skip to content
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

WIP on better codegen types & inference #19

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 36 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,24 @@ Adds commands for executing a mocked GraphQL server using only the client
in Cypress' `commands.js` add:

```js
import "cypress-graphql-mock";
import { setBaseGraphqlMocks } from "cypress-graphql-mock";

// Set all of your base mocks, as described in
// https://www.apollographql.com/docs/graphql-tools/mocking/#default-mock-example
setBaseGraphqlMocks({
User: () => ({
id: () => randomId(),
name: "Test User"
}),
DateTime(obj, args, context, field) {
if (obj[field.fieldName]) return obj[field.fieldName];
return new Date("2019-01-01");
}
});
```

See the "code generation" section below for more info on how to generate types for this.

## Instructions

Adds `.mockGraphql()` and `.mockGraphqlOps()` methods to the cypress chain.
Expand Down Expand Up @@ -147,6 +162,23 @@ cy.mockGraphqlOps({
});
```

#### Code Generation

This plugin ships with a [graphql-code-generator](https://graphql-code-generator.com/) plugin to generate typings for the "RootTypes" and the mock operations.

See the [graphql-code-generator](https://graphql-code-generator.com/docs/plugins/) docs around plugins, and the example app / `codegen.yml` in the root for an example use:

```yml
test/cypress/generated/generated-mock-types.d.ts:
documents: "test/test-graphql-app/**/*.jsx"
plugins:
- typescript
- typescript-operations
- cypress-graphql-mock/codegen
config:
enumsAsTypes: true
```

#### Error handling

```ts
Expand All @@ -165,15 +197,15 @@ It is also possible to throw error from the function. Just `return` or `throw` a
```ts
cy.mockGraphqlOps({
operations: {
userNameChange: (variables) => {
userNameChange: variables => {
if (!variables.name) {
throw new GraphQLError("Name is required")
throw new GraphQLError("Name is required");
}
}
}
});
```

### License

MIT
```
1 change: 1 addition & 0 deletions codegen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require("./dist/codegen/plugin");
14 changes: 14 additions & 0 deletions codegen.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
overwrite: true
hooks:
afterAllFileWrite:
- prettier --write
schema: "test/cypress/test-schema.graphql"
generates:
test/cypress/graphql/generatedMockTypes.d.ts:
documents: "test/test-graphql-app/**/*.jsx"
plugins:
- typescript
- typescript-operations
- codegen.js
config:
enumsAsTypes: true
19 changes: 14 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
{
"name": "cypress-graphql-mock",
"description": "Mock out a GraphQL schema from the client",
"version": "0.3.1",
"version": "0.5.0-alpha.4",
"scripts": {
"dev": "tsc --declarationMap -w",
"dev": "rm -rf dist && tsc -w",
"cypress:open": "yarn cypress open --project ./test",
"cypress:run": "yarn cypress run --project ./test",
"test-app:run": "cd ./test/test-graphql-app && SKIP_PREFLIGHT_CHECK=true yarn start",
"test-app:build": "cd ./test/test-graphql-app && SKIP_PREFLIGHT_CHECK=true yarn build",
"test-app:serve": "serve -s -l 3000 test/test-graphql-app/build",
"prepublish": "tsc --declarationMap"
"prepublish": "rm -rf dist && tsc"
},
"main": "dist",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"codegen.js",
"dist/",
"src/",
"yarn.lock",
Expand All @@ -38,19 +39,27 @@
"tslib": "^1.9.3"
},
"peerDependencies": {
"@graphql-codegen/plugin-helpers": "^1.6.1",
"cypress": "^3.1.3",
"graphql": "^14.0.2"
},
"devDependencies": {
"@cypress/webpack-preprocessor": "^4.1.0",
"@graphql-codegen/add": "^1.6.1",
"@graphql-codegen/cli": "^1.6.1",
"@graphql-codegen/plugin-helpers": "^1.6.1",
"@graphql-codegen/typescript": "^1.6.1",
"@graphql-codegen/typescript-document-nodes": "^1.6.1",
"@graphql-codegen/typescript-operations": "^1.6.1",
"@graphql-codegen/typescript-react-apollo": "^1.6.1",
"@types/graphql": "^14.0.3",
"cypress": "^3.4.1",
"graphql": "^14.0.2",
"husky": "^1.2.0",
"lint-staged": "^8.1.0",
"prettier": "^1.15.3",
"serve": "^11.1.0",
"ts-loader": "^6.0.4",
"graphql": "^14.0.2",
"typescript": "^3.2.2",
"wait-on": "^3.3.0",
"webpack": "^4.39.2"
Expand Down
103 changes: 103 additions & 0 deletions src/codegen/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { CodegenPlugin, toPascalCase } from "@graphql-codegen/plugin-helpers";
import {
isExecutableDefinitionNode,
Kind,
isScalarType,
isInterfaceType,
isObjectType,
isUnionType,
OperationTypeNode,
isEnumType
} from "graphql";

export = {
plugin(schema, documents, config) {
let additionalImports = "";
let tPrefix = "";
if (config && config.typesFile) {
additionalImports += `import * as t from ${JSON.stringify(
config.typesFile
)}`;
tPrefix = "t.";
}
const allTypes = schema.getTypeMap();
const documentOperations: { name: string; op: OperationTypeNode }[] = [];
const MockOperationTypes: string[] = [];
documents.forEach(d =>
d.content.definitions.forEach(node => {
if (
isExecutableDefinitionNode(node) &&
node.kind === Kind.OPERATION_DEFINITION &&
node.name
) {
documentOperations.push({
name: node.name.value,
op: node.operation
});
}
})
);
documentOperations.forEach(o => {
const typeVal = `${tPrefix}${toPascalCase(`${o.name}_${o.op}`)}`;
MockOperationTypes.push(
`${o.name}: ErrorOrValue<${typeVal} | PartialDeep<${typeVal}>>`
);
});
const MockBaseTypesBody = Object.keys(allTypes)
.sort()
.map(typeName => {
if (typeName.startsWith("__")) {
return "";
}
const pascalName = toPascalCase(typeName);
const type = allTypes[typeName];
let typeVal = "unknown";
if (isScalarType(type)) {
typeVal = `MockResolve<${tPrefix}Scalars['${typeName}']>;`;
} else if (isInterfaceType(type) || isUnionType(type)) {
typeVal = schema
.getPossibleTypes(type)
.map(t => `TypeMock<${tPrefix}${t.name}>`)
.join(" | ");
} else if (isObjectType(type)) {
typeVal = `TypeMock<${tPrefix}${pascalName}>`;
} else if (isEnumType(type)) {
typeVal = `MockResolve<${tPrefix}${pascalName}>`;
}
return `${typeName}?: ${typeVal}`;
});

return `
${additionalImports}
import { GraphQLResolveInfo, GraphQLError } from "graphql";

export type MockResolve<T> = (
obj: any,
args: any,
ctx: any,
info: GraphQLResolveInfo
) => T;

export type ErrorOrValue<T> = T | GraphQLError;

export type ResolverOrValue<T> = T | MockResolve<T>;

export type PartialDeep<T> = { [P in keyof T]?: PartialDeep<T[P]> };

export type PartialResolveDeep<T> = T extends object ? {
[P in keyof T]?: ResolverOrValue<PartialResolveDeep<T[P]>>
} : T;

export type TypeMock<T> = () => PartialResolveDeep<T>;

declare global {
interface CypressMockBaseTypes {
${MockBaseTypesBody.join("\n")}
}
interface CypressMockOperationTypes {
${MockOperationTypes.join("\n")}
}
}
`;
}
} as CodegenPlugin;
Loading