import { describe, it } from 'node:test';
import { expect } from 'chai';
import { dedentString } from '../../__testUtils__/dedent.ts';
import { assertObjectType, GraphQLObjectType } from '../../type/definition.ts';
import type { GraphQLSchemaNormalizedConfig } from '../../type/schema.ts';
import { GraphQLSchema } from '../../type/schema.ts';
import { buildSchema } from '../buildASTSchema.ts';
import type {
ConfigMapperMap,
MappedSchemaContext,
} from '../mapSchemaConfig.ts';
import { mapSchemaConfig, SchemaElementKind } from '../mapSchemaConfig.ts';
import { printSchema } from '../printSchema.ts';
function expectSchemaMapping(
schemaConfig: GraphQLSchemaNormalizedConfig,
configMapperMapFn: (context: MappedSchemaContext) => ConfigMapperMap,
expected: string,
) {
const newSchemaConfig = mapSchemaConfig(schemaConfig, configMapperMapFn);
expect(printSchema(new GraphQLSchema(newSchemaConfig))).to.equal(
dedentString(expected),
);
}
describe('mapSchemaConfig', () => {
it('returns the original config when no mappers are included', () => {
const sdl = 'type Query';
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(schemaConfig, () => ({}), sdl);
});
describe('scalar mapping', () => {
it('can map scalar types', () => {
const sdl = 'scalar SomeScalar';
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(
schemaConfig,
() => ({
[SchemaElementKind.SCALAR]: (config) => ({
...config,
description: 'Some description',
}),
}),
`
"""Some description"""
scalar SomeScalar
`,
);
});
});
describe('argument mapping', () => {
it('can map arguments', () => {
const sdl = `
type SomeType {
field(arg: String): String
}
`;
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(
schemaConfig,
() => ({
[SchemaElementKind.ARGUMENT]: (config) => ({
...config,
description: 'Some description',
}),
}),
`
type SomeType {
field(
"""Some description"""
arg: String
): String
}
`,
);
});
it('provides field and parent type names when mapping field arguments', () => {
const sdl = `
type SomeType {
field(arg: String): String
}
`;
const schemaConfig = buildSchema(sdl).toConfig();
const visits: Array<string> = [];
const newSchemaConfig = mapSchemaConfig(schemaConfig, () => ({
[SchemaElementKind.ARGUMENT]: (
config,
fieldOrDirectiveName,
parentTypeName,
) => {
visits.push(`${parentTypeName}.${fieldOrDirectiveName}`);
return config;
},
}));
const schema = new GraphQLSchema(newSchemaConfig);
const type = assertObjectType(schema.getType('SomeType'));
expect(type.getFields().field.args).to.have.lengthOf(1);
expect(visits).to.deep.equal(['SomeType.field']);
});
});
describe('field mapping', () => {
it('can map fields', () => {
const sdl = `
type SomeType {
field: String
}
`;
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(
schemaConfig,
() => ({
[SchemaElementKind.FIELD]: (config) => ({
...config,
description: 'Some description',
}),
}),
`
type SomeType {
"""Some description"""
field: String
}
`,
);
});
it('can map fields with non-null return type', () => {
const sdl = `
type SomeType {
field: String!
}
`;
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(
schemaConfig,
() => ({
[SchemaElementKind.FIELD]: (config) => ({
...config,
description: 'Some description',
}),
}),
`
type SomeType {
"""Some description"""
field: String!
}
`,
);
});
it('can map fields with list return type', () => {
const sdl = `
type SomeType {
field: [String]
}
`;
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(
schemaConfig,
() => ({
[SchemaElementKind.FIELD]: (config) => ({
...config,
description: 'Some description',
}),
}),
`
type SomeType {
"""Some description"""
field: [String]
}
`,
);
});
it('maps fields after mapping arguments', () => {
const sdl = `
type SomeType {
field(arg: String): String
}
`;
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(
schemaConfig,
() => ({
[SchemaElementKind.ARGUMENT]: (config) => ({
...config,
description: 'Some argument description',
}),
[SchemaElementKind.FIELD]: (config) => {
expect(config.args.arg.description).to.equal(
'Some argument description',
);
return {
...config,
description: 'Some field description',
};
},
}),
`
type SomeType {
"""Some field description"""
field(
"""Some argument description"""
arg: String
): String
}
`,
);
});
});
describe('object type mapping', () => {
it('can map object types', () => {
const sdl = 'type SomeType';
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(
schemaConfig,
() => ({
[SchemaElementKind.OBJECT]: (config) => ({
...config,
description: 'Some description',
}),
}),
`
"""Some description"""
type SomeType
`,
);
});
it('maps object types after mapping fields', () => {
const sdl = `
type SomeType {
field: String
}
`;
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(
schemaConfig,
() => ({
[SchemaElementKind.FIELD]: (config) => ({
...config,
description: 'Some field description',
}),
[SchemaElementKind.OBJECT]: (config) => {
expect(config.fields().field.description).to.equal(
'Some field description',
);
return {
...config,
description: 'Some object description',
};
},
}),
`
"""Some object description"""
type SomeType {
"""Some field description"""
field: String
}
`,
);
});
it('maps object types after mapping interfaces', () => {
const sdl = `
interface SomeInterface
type SomeType implements SomeInterface
`;
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(
schemaConfig,
() => ({
[SchemaElementKind.INTERFACE]: (config) => ({
...config,
description: 'Some interface description',
}),
[SchemaElementKind.OBJECT]: (config) => {
expect(config.interfaces()[0].description).to.equal(
'Some interface description',
);
return {
...config,
description: 'Some object description',
};
},
}),
`
"""Some interface description"""
interface SomeInterface
"""Some object description"""
type SomeType implements SomeInterface
`,
);
});
});
describe('interface type mapping', () => {
it('can map interface types', () => {
const sdl = 'interface SomeInterface';
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(
schemaConfig,
() => ({
[SchemaElementKind.INTERFACE]: (config) => ({
...config,
description: 'Some description',
}),
}),
`
"""Some description"""
interface SomeInterface
`,
);
});
it('maps interface types after mapping fields', () => {
const sdl = `
interface SomeInterface {
field: String
}
`;
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(
schemaConfig,
() => ({
[SchemaElementKind.FIELD]: (config) => ({
...config,
description: 'Some field description',
}),
[SchemaElementKind.INTERFACE]: (config) => {
expect(config.fields().field.description).to.equal(
'Some field description',
);
return {
...config,
description: 'Some interface description',
};
},
}),
`
"""Some interface description"""
interface SomeInterface {
"""Some field description"""
field: String
}
`,
);
});
it('maps interface types after mapping parent interfaces', () => {
const sdl = `
interface SomeParentInterface
interface SomeInterface implements SomeParentInterface
`;
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(
schemaConfig,
() => ({
[SchemaElementKind.INTERFACE]: (config) => {
if (config.name === 'SomeInterface') {
expect(config.interfaces()[0].description).to.equal(
'Some interface description',
);
}
return {
...config,
description: 'Some interface description',
};
},
}),
`
"""Some interface description"""
interface SomeParentInterface
"""Some interface description"""
interface SomeInterface implements SomeParentInterface
`,
);
});
});
describe('union type mapping', () => {
it('can map union types', () => {
const sdl = 'union SomeUnion';
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(
schemaConfig,
() => ({
[SchemaElementKind.UNION]: (config) => ({
...config,
description: 'Some description',
}),
}),
`
"""Some description"""
union SomeUnion
`,
);
});
it('maps union types after mapping types', () => {
const sdl = `
type SomeType
union SomeUnion = SomeType
`;
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(
schemaConfig,
() => ({
[SchemaElementKind.OBJECT]: (config) => ({
...config,
description: 'Some type description',
}),
[SchemaElementKind.UNION]: (config) => {
expect(config.types()[0].description).to.equal(
'Some type description',
);
return {
...config,
description: 'Some union description',
};
},
}),
`
"""Some type description"""
type SomeType
"""Some union description"""
union SomeUnion = SomeType
`,
);
});
});
describe('enum value mapping', () => {
it('can map enum values', () => {
const sdl = `
enum SomeEnum {
SOME_VALUE
}
`;
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(
schemaConfig,
() => ({
[SchemaElementKind.ENUM_VALUE]: (config) => ({
...config,
description: 'Some description',
}),
}),
`
enum SomeEnum {
"""Some description"""
SOME_VALUE
}
`,
);
});
});
describe('enum type mapping', () => {
it('can map enum types', () => {
const sdl = 'enum SomeEnum';
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(
schemaConfig,
() => ({
[SchemaElementKind.ENUM]: (config) => ({
...config,
description: 'Some description',
}),
}),
`
"""Some description"""
enum SomeEnum
`,
);
});
it('maps enum types after mapping values', () => {
const sdl = `
enum SomeEnum {
SOME_VALUE
}
`;
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(
schemaConfig,
() => ({
[SchemaElementKind.ENUM_VALUE]: (config) => ({
...config,
description: 'Some value description',
}),
[SchemaElementKind.ENUM]: (config) => {
expect(config.values().SOME_VALUE.description).to.equal(
'Some value description',
);
return {
...config,
description: 'Some enum description',
};
},
}),
`
"""Some enum description"""
enum SomeEnum {
"""Some value description"""
SOME_VALUE
}
`,
);
});
});
describe('input field mapping', () => {
it('can map input fields', () => {
const sdl = `
input SomeInput {
field: String
}
`;
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(
schemaConfig,
() => ({
[SchemaElementKind.INPUT_FIELD]: (config) => ({
...config,
description: 'Some description',
}),
}),
`
input SomeInput {
"""Some description"""
field: String
}
`,
);
});
});
describe('input object type mapping', () => {
it('can map input object types', () => {
const sdl = 'input SomeInput';
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(
schemaConfig,
() => ({
[SchemaElementKind.INPUT_OBJECT]: (config) => ({
...config,
description: 'Some description',
}),
}),
`
"""Some description"""
input SomeInput
`,
);
});
it('maps input object types after mapping input fields', () => {
const sdl = `
input SomeInput {
field: String
}
`;
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(
schemaConfig,
() => ({
[SchemaElementKind.INPUT_FIELD]: (config) => ({
...config,
description: 'Some input field description',
}),
[SchemaElementKind.INPUT_OBJECT]: (config) => {
expect(config.fields().field.description).to.equal(
'Some input field description',
);
return {
...config,
description: 'Some input object description',
};
},
}),
`
"""Some input object description"""
input SomeInput {
"""Some input field description"""
field: String
}
`,
);
});
});
describe('directive mapping', () => {
it('can map directives', () => {
const sdl = `
directive @SomeDirective on FIELD
`;
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(
schemaConfig,
() => ({
[SchemaElementKind.DIRECTIVE]: (config) => ({
...config,
description: 'Some description',
}),
}),
`
"""Some description"""
directive @SomeDirective on FIELD
`,
);
});
it('maps directives after mapping arguments', () => {
const sdl = `
directive @SomeDirective(arg: String) on FIELD
`;
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(
schemaConfig,
() => ({
[SchemaElementKind.ARGUMENT]: (config) => ({
...config,
description: 'Some argument description',
}),
[SchemaElementKind.DIRECTIVE]: (config) => {
expect(config.args.arg.description).to.equal(
'Some argument description',
);
return {
...config,
description: 'Some directive description',
};
},
}),
`
"""Some directive description"""
directive @SomeDirective(
"""Some argument description"""
arg: String
) on FIELD
`,
);
});
});
describe('schema mapping', () => {
it('can map the schema', () => {
const sdl = `
type Query
type Mutation
type Subscription
directive @SomeDirective on FIELD
scalar SomeScalar
type SomeType {
field: String
}
interface SomeInterface
union SomeUnion
enum SomeEnum {
SOME_VALUE
}
input SomeInput {
field: String
}
`;
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(
schemaConfig,
() => ({
[SchemaElementKind.SCHEMA]: (config) => ({
...config,
description: 'Some description',
}),
}),
`
"""Some description"""
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
directive @SomeDirective on FIELD
type Query
type Mutation
type Subscription
scalar SomeScalar
type SomeType {
field: String
}
interface SomeInterface
union SomeUnion
enum SomeEnum {
SOME_VALUE
}
input SomeInput {
field: String
}
`,
);
});
it('maps the schema after mapping types and directives', () => {
const sdl = `
type Query
type Mutation
type Subscription
directive @SomeDirective on FIELD
scalar SomeScalar
type SomeType {
field: String
}
interface SomeInterface
union SomeUnion
enum SomeEnum {
SOME_VALUE
}
input SomeInput {
field: String
}
`;
const schemaConfig = buildSchema(sdl).toConfig();
expectSchemaMapping(
schemaConfig,
() => ({
[SchemaElementKind.DIRECTIVE]: (config) => ({
...config,
description: 'Some directive description',
}),
[SchemaElementKind.SCALAR]: (config) => ({
...config,
description: 'Some scalar description',
}),
[SchemaElementKind.OBJECT]: (config) => ({
...config,
description: 'Some object description',
}),
[SchemaElementKind.INTERFACE]: (config) => ({
...config,
description: 'Some interface description',
}),
[SchemaElementKind.UNION]: (config) => ({
...config,
description: 'Some union description',
}),
[SchemaElementKind.ENUM]: (config) => ({
...config,
description: 'Some enum description',
}),
[SchemaElementKind.INPUT_OBJECT]: (config) => ({
...config,
description: 'Some input object description',
}),
[SchemaElementKind.SCHEMA]: (config) => {
for (const directive of config.directives) {
if (directive.name === 'SomeDirective') {
expect(directive.description).to.equal(
'Some directive description',
);
}
}
for (const type of config.types) {
switch (type.name) {
case 'SomeScalar':
expect(type.description).to.equal('Some scalar description');
break;
case 'SomeType':
expect(type.description).to.equal('Some object description');
break;
case 'SomeInterface':
expect(type.description).to.equal(
'Some interface description',
);
break;
case 'SomeUnion':
expect(type.description).to.equal('Some union description');
break;
case 'SomeEnum':
expect(type.description).to.equal('Some enum description');
break;
case 'SomeInput':
expect(type.description).to.equal(
'Some input object description',
);
break;
}
}
return {
...config,
description: 'Some schema description',
};
},
}),
`
"""Some schema description"""
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
"""Some directive description"""
directive @SomeDirective on FIELD
"""Some object description"""
type Query
"""Some object description"""
type Mutation
"""Some object description"""
type Subscription
"""Some scalar description"""
scalar SomeScalar
"""Some object description"""
type SomeType {
field: String
}
"""Some interface description"""
interface SomeInterface
"""Some union description"""
union SomeUnion
"""Some enum description"""
enum SomeEnum {
SOME_VALUE
}
"""Some input object description"""
input SomeInput {
field: String
}
`,
);
});
});
describe('schema context', () => {
it('allows access to the final mapped named type via getNamedType()', () => {
const sdl = `
"""Some description"""
type SomeType
`;
const schema = buildSchema(sdl);
const schemaConfig = schema.toConfig();
const someType = schema.getType('SomeType') as GraphQLObjectType;
expectSchemaMapping(
schemaConfig,
({ getNamedType }) => {
return {
[SchemaElementKind.OBJECT]: (config) => ({
...config,
fields: () => {
expectMappedSomeType();
return config.fields();
},
}),
[SchemaElementKind.SCHEMA]: (config) => {
expectMappedSomeType();
return config;
},
};
function expectMappedSomeType() {
const mappedType = getNamedType(someType.name);
expect(mappedType).not.to.equal(someType);
expect(mappedType.description).to.equal(someType.description);
}
},
sdl,
);
});
it('allows adding a named type via setNamedType() and retrieving the new list via getNamedTypes', () => {
const sdl = 'type SomeType';
const schema = buildSchema(sdl);
const schemaConfig = schema.toConfig();
expectSchemaMapping(
schemaConfig,
({ setNamedType, getNamedTypes }) => ({
[SchemaElementKind.SCHEMA]: (config) => {
setNamedType(
new GraphQLObjectType({ name: 'AnotherType', fields: {} }),
);
return {
...config,
types: getNamedTypes(),
};
},
}),
`
type SomeType
type AnotherType
`,
);
});
});
});