diff --git a/packages/deno/packages/plugin-mocks/global-types.ts b/packages/deno/packages/plugin-mocks/global-types.ts index 40c35bebf..9c89f90cc 100644 --- a/packages/deno/packages/plugin-mocks/global-types.ts +++ b/packages/deno/packages/plugin-mocks/global-types.ts @@ -1,6 +1,6 @@ // @ts-nocheck -import { SchemaTypes } from '../core/index.ts'; -import { ResolverMap } from './types.ts'; +import { ObjectParam, SchemaTypes, ShapeFromTypeParam } from '../core/index.ts'; +import { Mock, ResolverMap } from './types.ts'; import { MocksPlugin } from './index.ts'; declare global { export namespace PothosSchemaTypes { @@ -9,6 +9,10 @@ declare global { } export interface BuildSchemaOptions { mocks?: ResolverMap; + typeMocks?: Mock[]; + } + export interface SchemaBuilder { + createMock: ? ShapeFromTypeParam : object, NameOrRef extends ObjectParam | string>(nameOrRef: NameOrRef, resolver: () => Partial) => Mock; } } } diff --git a/packages/deno/packages/plugin-mocks/types.ts b/packages/deno/packages/plugin-mocks/types.ts index 43e18e594..9c1b79608 100644 --- a/packages/deno/packages/plugin-mocks/types.ts +++ b/packages/deno/packages/plugin-mocks/types.ts @@ -1,7 +1,10 @@ // @ts-nocheck -import { Resolver, SchemaTypes, Subscriber } from '../core/index.ts'; +import { ObjectRef, Resolver, SchemaTypes, Subscriber } from '../core/index.ts'; export type Resolvers = Record | { resolve: Resolver; subscribe: Subscriber; }>; export type ResolverMap = Record>; +export interface Mock { + ref: ObjectRef; +} diff --git a/packages/plugin-mocks/src/global-types.ts b/packages/plugin-mocks/src/global-types.ts index 470279fbc..8e86a1a5a 100644 --- a/packages/plugin-mocks/src/global-types.ts +++ b/packages/plugin-mocks/src/global-types.ts @@ -1,5 +1,5 @@ -import { SchemaTypes } from '@pothos/core'; -import { ResolverMap } from './types'; +import { ObjectParam, Resolver, SchemaTypes, ShapeFromTypeParam } from '@pothos/core'; +import { Mock, ResolverMap } from './types'; import { MocksPlugin } from '.'; declare global { @@ -10,6 +10,19 @@ declare global { export interface BuildSchemaOptions { mocks?: ResolverMap; + typeMocks?: Mock[]; + } + + export interface SchemaBuilder { + createObjectMock: < + Shape extends NameOrRef extends ObjectParam + ? ShapeFromTypeParam + : object, + NameOrRef extends ObjectParam | string, + >( + nameOrRef: NameOrRef, + resolver: Resolver>, + ) => Mock; } } } diff --git a/packages/plugin-mocks/src/index.ts b/packages/plugin-mocks/src/index.ts index 3d4afa8a7..285d5dede 100644 --- a/packages/plugin-mocks/src/index.ts +++ b/packages/plugin-mocks/src/index.ts @@ -1,7 +1,14 @@ import './global-types'; +import './schema-builder'; import { GraphQLFieldResolver } from 'graphql'; -import SchemaBuilder, { BasePlugin, PothosOutputFieldConfig, SchemaTypes } from '@pothos/core'; -import { ResolverMap } from './types'; +import SchemaBuilder, { + BasePlugin, + PothosOutputFieldConfig, + PothosOutputFieldType, + Resolver, + SchemaTypes, +} from '@pothos/core'; +import { Mock, ResolverMap } from './types'; const pluginName = 'mocks' as const; @@ -11,13 +18,14 @@ export class MocksPlugin extends BasePlugin { resolver: GraphQLFieldResolver, fieldConfig: PothosOutputFieldConfig, ): GraphQLFieldResolver { - const { mocks } = this.options; + const { mocks, typeMocks = [] } = this.options; + const { parentType: typeName, name: fieldName, type: outputType } = fieldConfig; if (!mocks) { return resolver; } - const resolveMock = this.resolveMock(fieldConfig.parentType, fieldConfig.name, mocks); + const resolveMock = this.resolveMock(typeName, fieldName, outputType, mocks, typeMocks); return resolveMock ?? resolver; } @@ -37,18 +45,48 @@ export class MocksPlugin extends BasePlugin { return subscribeMock ?? subscribe; } - resolveMock(typename: string, fieldName: string, mocks: ResolverMap) { - const fieldMock = mocks[typename]?.[fieldName] || null; + resolveMock( + typeName: string, + fieldName: string, + outputType: PothosOutputFieldType, + mocks: ResolverMap, + typeMocks: Mock[], + ): Resolver | null { + const fieldMock = mocks[typeName]?.[fieldName] || null; + + if (fieldMock) { + if (typeof fieldMock === 'function') { + return fieldMock; + } + + return fieldMock.resolve ?? null; + } - if (!fieldMock) { - return null; + if (outputType.kind === 'Object') { + const outputName = (outputType.ref as { name: string }).name; + const mock = typeMocks.find((v) => v.name === outputName); + + return mock?.resolver ?? null; } - if (typeof fieldMock === 'function') { - return fieldMock; + if (outputType.kind === 'Interface') { + const outputName = (outputType.ref as { name: string }).name; + const implementers = this.builder.configStore + .getImplementers(outputName) + .map((implementer) => implementer.name); + const mock = typeMocks.find((v) => implementers.includes(v.name)); + + return mock?.resolver ?? null; + } + + if (outputType.kind === 'List') { + const result = this.resolveMock(typeName, fieldName, outputType.type, mocks, typeMocks); + if (result) { + return (...args) => [result(...args)]; + } } - return fieldMock.resolve || null; + return null; } subscribeMock(typename: string, fieldName: string, mocks: ResolverMap) { diff --git a/packages/plugin-mocks/src/schema-builder.ts b/packages/plugin-mocks/src/schema-builder.ts new file mode 100644 index 000000000..e7146dd2b --- /dev/null +++ b/packages/plugin-mocks/src/schema-builder.ts @@ -0,0 +1,12 @@ +import SchemaBuilder, { SchemaTypes } from '@pothos/core'; + +const schemaBuilderProto = SchemaBuilder.prototype as PothosSchemaTypes.SchemaBuilder; + +schemaBuilderProto.createObjectMock = function createMock(nameOrRef, resolver) { + const name = typeof nameOrRef === 'string' ? nameOrRef : (nameOrRef as { name: string }).name; + + return { + name, + resolver: resolver as never, + }; +}; diff --git a/packages/plugin-mocks/src/types.ts b/packages/plugin-mocks/src/types.ts index cd4851d1b..154bbe613 100644 --- a/packages/plugin-mocks/src/types.ts +++ b/packages/plugin-mocks/src/types.ts @@ -10,3 +10,8 @@ export type Resolvers = Record< >; export type ResolverMap = Record>; + +export interface Mock { + name: string; + resolver: Resolver; +} diff --git a/packages/plugin-mocks/tests/__snapshots__/index.test.ts.snap b/packages/plugin-mocks/tests/__snapshots__/index.test.ts.snap index c2486ccc1..07752107b 100644 --- a/packages/plugin-mocks/tests/__snapshots__/index.test.ts.snap +++ b/packages/plugin-mocks/tests/__snapshots__/index.test.ts.snap @@ -9,3 +9,28 @@ Object { }, } `; + +exports[`mocked type mocks queries lists 1`] = ` +Object { + "data": Object { + "r2d2": Object { + "friends": Array [ + Object { + "name": "C-3PO", + }, + ], + "name": "C-3PO", + }, + }, +} +`; + +exports[`mocked type mocks queries stuff 1`] = ` +Object { + "data": Object { + "r2d2": Object { + "name": "C-3PO", + }, + }, +} +`; diff --git a/packages/plugin-mocks/tests/index.test.ts b/packages/plugin-mocks/tests/index.test.ts index 7cb9bdd01..b13915de3 100644 --- a/packages/plugin-mocks/tests/index.test.ts +++ b/packages/plugin-mocks/tests/index.test.ts @@ -29,4 +29,63 @@ describe('mocked', () => { expect(result).toMatchSnapshot(); }); + + describe('type mocks', () => { + it('queries stuff', async () => { + const DroidMock = builder.createObjectMock('Droid', () => ({ + name: 'C-3PO', + })); + const mockedSchema = builder.toSchema({ + mocks: {}, + typeMocks: [DroidMock], + }); + + const query = gql` + query { + r2d2 { + name + } + } + `; + + const result = await execute({ + schema: mockedSchema, + document: query, + contextValue: {}, + }); + + expect(result).toMatchSnapshot(); + }); + + it('queries lists', async () => { + const DroidMock = builder.createObjectMock('Droid', () => ({ + type: 'Droid' as const, + name: 'C-3PO', + friends: ['1002', '1003'], + })); + const mockedSchema = builder.toSchema({ + mocks: {}, + typeMocks: [DroidMock], + }); + + const query = gql` + query { + r2d2 { + name + friends { + name + } + } + } + `; + + const result = await execute({ + schema: mockedSchema, + document: query, + contextValue: {}, + }); + + expect(result).toMatchSnapshot(); + }); + }); });