ra-data-graphql-simple
Version:
A GraphQL simple data provider for react-admin
1,026 lines (975 loc) • 31.1 kB
text/typescript
import { TypeKind, print } from 'graphql';
import { gql } from '@apollo/client';
import {
GET_LIST,
GET_ONE,
GET_MANY,
GET_MANY_REFERENCE,
UPDATE,
CREATE,
DELETE,
DELETE_MANY,
UPDATE_MANY,
} from 'ra-core';
import buildGqlQuery, {
buildApolloArgs,
buildArgs,
buildFields,
} from './buildGqlQuery';
describe('buildArgs', () => {
it('returns an empty array when query does not have any arguments', () => {
expect(buildArgs({ args: [] }, {})).toEqual([]);
});
it('returns an array of args correctly filtered when query has arguments', () => {
expect(
print(
buildArgs(
{ args: [{ name: 'foo' }, { name: 'bar' }] },
{ foo: 'foo_value' }
)
)
).toEqual(['foo: $foo']);
});
});
describe('buildApolloArgs', () => {
it('returns an empty array when query does not have any arguments', () => {
expect(print(buildApolloArgs({ args: [] }, {}))).toEqual([]);
});
it('returns an array of args correctly filtered when query has arguments', () => {
expect(
print(
buildApolloArgs(
{
args: [
{
name: 'foo',
type: {
kind: TypeKind.NON_NULL,
ofType: {
kind: TypeKind.SCALAR,
name: 'Int',
},
},
},
{
name: 'barId',
type: { kind: TypeKind.SCALAR, name: 'ID' },
},
{
name: 'barIds',
type: {
kind: TypeKind.LIST,
ofType: {
kind: TypeKind.NON_NULL,
ofType: {
kind: TypeKind.SCALAR,
name: 'ID',
},
},
},
},
{ name: 'bar' },
],
},
{ foo: 'foo_value', barId: 100, barIds: [101, 102] }
)
)
).toEqual(['$foo: Int!', '$barId: ID', '$barIds: [ID!]']);
});
});
function buildGQLParamsWithSparseFieldsFactory() {
const introspectionResults = {
resources: [
{
type: {
name: 'resourceType',
fields: [
{
name: 'id',
type: { kind: TypeKind.SCALAR, name: 'ID' },
},
{
name: 'name',
type: { kind: TypeKind.SCALAR, name: 'String' },
},
{
name: 'foo',
type: { kind: TypeKind.SCALAR, name: 'String' },
},
],
},
},
],
types: [
{
name: 'linkedType',
fields: [
{
name: 'id',
type: { kind: TypeKind.SCALAR, name: 'ID' },
},
{
name: 'title',
type: { kind: TypeKind.SCALAR, name: 'String' },
},
{
name: 'nestedLink',
type: {
kind: TypeKind.OBJECT,
name: 'nestedLinkedType',
},
},
],
},
{
name: 'nestedLinkedType',
fields: [
{
name: 'id',
type: { kind: TypeKind.SCALAR, name: 'ID' },
},
{
name: 'bar',
type: { kind: TypeKind.SCALAR, name: 'String' },
},
],
},
],
};
const resource = {
type: {
fields: [
{ type: { kind: TypeKind.SCALAR, name: 'ID' }, name: 'id' },
{
type: { kind: TypeKind.SCALAR, name: 'String' },
name: 'address',
},
{
type: { kind: TypeKind.SCALAR, name: '_internalField' },
name: 'foo1',
},
{
type: { kind: TypeKind.OBJECT, name: 'linkedType' },
name: 'linked',
},
{
type: { kind: TypeKind.OBJECT, name: 'resourceType' },
name: 'resource',
},
],
},
};
const queryType = {
name: 'allCommand',
args: [
{
name: 'foo',
type: {
kind: TypeKind.NON_NULL,
ofType: { kind: TypeKind.SCALAR, name: 'Int' },
},
},
],
};
const params = {
foo: 'foo_value',
meta: {
sparseFields: [
'address',
{ linked: ['title'] },
{ resource: ['foo', 'name'] },
],
},
};
return {
introspectionResults,
queryType,
params,
resource,
};
}
describe('buildFields', () => {
it('returns an object with the fields to retrieve', () => {
const introspectionResults = {
resources: [
{
type: {
name: 'resourceType',
fields: [
{
name: 'id',
type: { kind: TypeKind.SCALAR, name: 'ID' },
},
],
},
},
],
types: [
{
name: 'linkedType',
fields: [
{
name: 'id',
type: { kind: TypeKind.SCALAR, name: 'ID' },
},
],
},
],
};
const fields = [
{ type: { kind: TypeKind.SCALAR, name: 'ID' }, name: 'id' },
{
type: { kind: TypeKind.SCALAR, name: '_internalField' },
name: 'foo1',
},
{
type: { kind: TypeKind.OBJECT, name: 'linkedType' },
name: 'linked',
},
{
type: { kind: TypeKind.OBJECT, name: 'resourceType' },
name: 'resource',
},
];
expect(print(buildFields(introspectionResults)(fields))).toEqual([
'id',
`linked {
id
}`,
`resource {
id
}`,
]);
});
describe('with sparse fields', () => {
it('returns an object with the fields to retrieve', () => {
const { introspectionResults, resource, params } =
buildGQLParamsWithSparseFieldsFactory();
// nested sparse params
params.meta.sparseFields[1].linked.push({ nestedLink: ['bar'] });
expect(
print(
buildFields(introspectionResults)(
resource.type.fields,
params.meta.sparseFields
)
)
).toEqual([
'address',
`linked {
title
nestedLink {
bar
}
}`,
`resource {
foo
name
}`,
]);
});
it('throws an error when sparse fields is requested but empty', () => {
const { introspectionResults, resource } =
buildGQLParamsWithSparseFieldsFactory();
expect(() =>
buildFields(introspectionResults)(resource.type.fields, [])
).toThrowError(
"Empty sparse fields. Specify at least one field or remove the 'sparseFields' param"
);
});
it('throws an error when requested sparse fields are not available', () => {
const { introspectionResults, resource } =
buildGQLParamsWithSparseFieldsFactory();
expect(() =>
buildFields(introspectionResults)(resource.type.fields, [
'unavailbleField',
])
).toThrowError(
"Requested sparse fields not found. Ensure sparse fields are available in the resource's type"
);
});
});
});
describe('buildFieldsWithCircularDependency', () => {
it('returns an object with the fields to retrieve', () => {
const introspectionResults = {
resources: [
{
type: {
name: 'resourceType',
fields: [
{
name: 'id',
type: { kind: TypeKind.SCALAR, name: 'ID' },
},
],
},
},
],
types: [
{
name: 'linkedType',
fields: [
{
name: 'id',
type: { kind: TypeKind.SCALAR, name: 'ID' },
},
{
name: 'child',
type: { kind: TypeKind.OBJECT, name: 'linkedType' },
},
],
},
],
};
const fields = [
{ type: { kind: TypeKind.SCALAR, name: 'ID' }, name: 'id' },
{
type: { kind: TypeKind.SCALAR, name: '_internalField' },
name: 'foo1',
},
{
type: { kind: TypeKind.OBJECT, name: 'linkedType' },
name: 'linked',
},
{
type: { kind: TypeKind.OBJECT, name: 'resourceType' },
name: 'resource',
},
];
expect(print(buildFields(introspectionResults)(fields))).toEqual([
'id',
`linked {
id
}`,
`resource {
id
}`,
]);
});
});
describe('buildFieldsWithSameType', () => {
it('returns an object with the fields to retrieve', () => {
const introspectionResults = {
resources: [
{
type: {
name: 'resourceType',
fields: [
{
name: 'id',
type: { kind: TypeKind.SCALAR, name: 'ID' },
},
],
},
},
],
types: [
{
name: 'linkedType',
fields: [
{
name: 'id',
type: { kind: TypeKind.SCALAR, name: 'ID' },
},
],
},
],
};
const fields = [
{ type: { kind: TypeKind.SCALAR, name: 'ID' }, name: 'id' },
{
type: { kind: TypeKind.SCALAR, name: '_internalField' },
name: 'foo1',
},
{
type: { kind: TypeKind.OBJECT, name: 'linkedType' },
name: 'linked',
},
{
type: { kind: TypeKind.OBJECT, name: 'linkedType' },
name: 'anotherLinked',
},
{
type: { kind: TypeKind.OBJECT, name: 'resourceType' },
name: 'resource',
},
];
expect(print(buildFields(introspectionResults)(fields))).toEqual([
'id',
`linked {
id
}`,
`anotherLinked {
id
}`,
`resource {
id
}`,
]);
});
});
describe('buildGqlQuery', () => {
const introspectionResults = {
resources: [
{
type: {
name: 'resourceType',
fields: [
{
name: 'id',
type: { kind: TypeKind.SCALAR, name: 'ID' },
},
],
},
},
],
types: [
{
name: 'linkedType',
fields: [
{
name: 'foo',
type: { kind: TypeKind.SCALAR, name: 'bar' },
},
],
},
],
};
const resource = {
type: {
fields: [
{ type: { kind: TypeKind.SCALAR, name: '' }, name: 'foo' },
{ type: { kind: TypeKind.SCALAR, name: '_foo' }, name: 'foo1' },
{
type: { kind: TypeKind.OBJECT, name: 'linkedType' },
name: 'linked',
},
{
type: { kind: TypeKind.OBJECT, name: 'resourceType' },
name: 'resource',
},
],
},
};
const queryType = {
name: 'allCommand',
args: [
{
name: 'foo',
type: {
kind: TypeKind.NON_NULL,
ofType: { kind: TypeKind.SCALAR, name: 'Int' },
},
},
{
name: 'barId',
type: { kind: TypeKind.SCALAR },
},
{
name: 'barIds',
type: { kind: TypeKind.SCALAR },
},
{ name: 'bar' },
],
};
const queryTypeDeleteMany = {
name: 'deleteCommands',
args: [
{
name: 'ids',
type: {
kind: TypeKind.LIST,
ofType: {
kind: TypeKind.NON_NULL,
ofType: {
kind: TypeKind.SCALAR,
name: 'ID',
},
},
},
},
],
};
const queryTypeUpdateMany = {
name: 'updateCommands',
args: [
{
name: 'ids',
type: {
kind: TypeKind.LIST,
ofType: {
kind: TypeKind.NON_NULL,
ofType: {
kind: TypeKind.SCALAR,
name: 'ID',
},
},
},
},
{
name: 'data',
type: { kind: TypeKind.OBJECT, name: 'CommandType' },
},
],
};
const params = { foo: 'foo_value' };
describe('GET_LIST', () => {
it('returns the correct query', () => {
expect(
print(
buildGqlQuery(introspectionResults)(
resource,
GET_LIST,
queryType,
params
)
)
).toEqual(
print(gql`
query allCommand($foo: Int!) {
items: allCommand(foo: $foo) {
foo
linked {
foo
}
resource {
id
}
}
total: _allCommandMeta(foo: $foo) {
count
}
}
`)
);
});
it('returns the correct query with sparse fields', () => {
const { introspectionResults, params, queryType, resource } =
buildGQLParamsWithSparseFieldsFactory();
expect(
print(
buildGqlQuery(introspectionResults)(
resource,
GET_LIST,
queryType,
params
)
)
).toEqual(
print(gql`
query allCommand($foo: Int!) {
items: allCommand(foo: $foo) {
address
linked {
title
}
resource {
foo
name
}
}
total: _allCommandMeta(foo: $foo) {
count
}
}
`)
);
});
});
describe('GET_MANY', () => {
it('returns the correct query', () => {
expect(
print(
buildGqlQuery(introspectionResults)(
resource,
GET_MANY,
queryType,
params
)
)
).toEqual(
print(gql`
query allCommand($foo: Int!) {
items: allCommand(foo: $foo) {
foo
linked {
foo
}
resource {
id
}
}
total: _allCommandMeta(foo: $foo) {
count
}
}
`)
);
});
it('returns the correct query with sparse fields', () => {
const { introspectionResults, params, queryType, resource } =
buildGQLParamsWithSparseFieldsFactory();
expect(
print(
buildGqlQuery(introspectionResults)(
resource,
GET_MANY,
queryType,
params
)
)
).toEqual(
print(gql`
query allCommand($foo: Int!) {
items: allCommand(foo: $foo) {
address
linked {
title
}
resource {
foo
name
}
}
total: _allCommandMeta(foo: $foo) {
count
}
}
`)
);
});
});
describe('GET_MANY_REFERENCE', () => {
it('returns the correct query', () => {
expect(
print(
buildGqlQuery(introspectionResults)(
resource,
GET_MANY_REFERENCE,
queryType,
params
)
)
).toEqual(
print(gql`
query allCommand($foo: Int!) {
items: allCommand(foo: $foo) {
foo
linked {
foo
}
resource {
id
}
}
total: _allCommandMeta(foo: $foo) {
count
}
}
`)
);
});
it('returns the correct query with sparse fields', () => {
const { introspectionResults, params, queryType, resource } =
buildGQLParamsWithSparseFieldsFactory();
expect(
print(
buildGqlQuery(introspectionResults)(
resource,
GET_MANY_REFERENCE,
queryType,
params
)
)
).toEqual(
print(gql`
query allCommand($foo: Int!) {
items: allCommand(foo: $foo) {
address
linked {
title
}
resource {
foo
name
}
}
total: _allCommandMeta(foo: $foo) {
count
}
}
`)
);
});
});
describe('GET_ONE', () => {
it('returns the correct query', () => {
expect(
print(
buildGqlQuery(introspectionResults)(
resource,
GET_ONE,
{ ...queryType, name: 'getCommand' },
params
)
)
).toEqual(
print(gql`
query getCommand($foo: Int!) {
data: getCommand(foo: $foo) {
foo
linked {
foo
}
resource {
id
}
}
}
`)
);
});
it('returns the correct query with sparse fields', () => {
const { introspectionResults, params, queryType, resource } =
buildGQLParamsWithSparseFieldsFactory();
expect(
print(
buildGqlQuery(introspectionResults)(
resource,
GET_ONE,
{ ...queryType, name: 'getCommand' },
params
)
)
).toEqual(
print(gql`
query getCommand($foo: Int!) {
data: getCommand(foo: $foo) {
address
linked {
title
}
resource {
foo
name
}
}
}
`)
);
});
});
describe('UPDATE', () => {
it('returns the correct query', () => {
expect(
print(
buildGqlQuery(introspectionResults)(
resource,
UPDATE,
{ ...queryType, name: 'updateCommand' },
params
)
)
).toEqual(
print(gql`
mutation updateCommand($foo: Int!) {
data: updateCommand(foo: $foo) {
foo
linked {
foo
}
resource {
id
}
}
}
`)
);
});
it('returns the correct query with sparse fields', () => {
const { introspectionResults, params, queryType, resource } =
buildGQLParamsWithSparseFieldsFactory();
expect(
print(
buildGqlQuery(introspectionResults)(
resource,
UPDATE,
{ ...queryType, name: 'updateCommand' },
params
)
)
).toEqual(
print(gql`
mutation updateCommand($foo: Int!) {
data: updateCommand(foo: $foo) {
address
linked {
title
}
resource {
foo
name
}
}
}
`)
);
});
});
describe('CREATE', () => {
it('returns the correct query', () => {
expect(
print(
buildGqlQuery(introspectionResults)(
resource,
CREATE,
{ ...queryType, name: 'createCommand' },
params
)
)
).toEqual(
print(gql`
mutation createCommand($foo: Int!) {
data: createCommand(foo: $foo) {
foo
linked {
foo
}
resource {
id
}
}
}
`)
);
});
it('returns the correct query with sparse fields', () => {
const { introspectionResults, params, queryType, resource } =
buildGQLParamsWithSparseFieldsFactory();
expect(
print(
buildGqlQuery(introspectionResults)(
resource,
CREATE,
{ ...queryType, name: 'createCommand' },
params
)
)
).toEqual(
print(gql`
mutation createCommand($foo: Int!) {
data: createCommand(foo: $foo) {
address
linked {
title
}
resource {
foo
name
}
}
}
`)
);
});
});
describe('DELETE', () => {
it('returns the correct query', () => {
expect(
print(
buildGqlQuery(introspectionResults)(
resource,
DELETE,
{ ...queryType, name: 'deleteCommand' },
params
)
)
).toEqual(
print(gql`
mutation deleteCommand($foo: Int!) {
data: deleteCommand(foo: $foo) {
foo
linked {
foo
}
resource {
id
}
}
}
`)
);
});
it('returns the correct query with sparse fields', () => {
const { introspectionResults, params, queryType, resource } =
buildGQLParamsWithSparseFieldsFactory();
expect(
print(
buildGqlQuery(introspectionResults)(
resource,
DELETE,
{ ...queryType, name: 'deleteCommand' },
params
)
)
).toEqual(
print(gql`
mutation deleteCommand($foo: Int!) {
data: deleteCommand(foo: $foo) {
address
linked {
title
}
resource {
foo
name
}
}
}
`)
);
});
});
it('returns the correct query for DELETE_MANY', () => {
expect(
print(
buildGqlQuery(introspectionResults)(
resource,
DELETE_MANY,
queryTypeDeleteMany,
{ ids: [1, 2, 3] }
)
)
).toEqual(
`mutation deleteCommands($ids: [ID!]) {
data: deleteCommands(ids: $ids) {
ids
}
}
`
);
});
it('returns the correct query for UPDATE_MANY', () => {
expect(
print(
buildGqlQuery(introspectionResults)(
resource,
UPDATE_MANY,
queryTypeUpdateMany,
{
ids: [1, 2, 3],
data: params,
}
)
)
).toEqual(
`mutation updateCommands($ids: [ID!], $data: CommandType) {
data: updateCommands(ids: $ids, data: $data) {
ids
}
}
`
);
});
});