Skip to content
This repository has been archived by the owner on Jul 6, 2020. It is now read-only.

Commit

Permalink
Improve test coverage on AST helpers (#82)
Browse files Browse the repository at this point in the history
* Add tests for ast/traversal helpers

* Replace evaluateValueNode with valueFromASTUntyped from graphql

* Add tests for normalizeVariables in ast/variables
  • Loading branch information
kitten committed Sep 18, 2019
1 parent 7f6d5d4 commit c8d746f
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 35 deletions.
82 changes: 82 additions & 0 deletions src/ast/traversal.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import gql from 'graphql-tag';
import { getSelectionSet } from './node';
import { getMainOperation, shouldInclude } from './traversal';

describe('getMainOperation', () => {
it('retrieves the first operation', () => {
const doc = gql`
query Query {
field
}
`;
const operation = getMainOperation(doc);
expect(operation).toBe(doc.definitions[0]);
});

it('throws when no operation is found', () => {
const doc = gql`
fragment _ on Query {
field
}
`;
expect(() => getMainOperation(doc)).toThrow();
});
});

describe('shouldInclude', () => {
it('should include fields with truthy @include or falsy @skip directives', () => {
const doc = gql`
{
fieldA @include(if: true)
fieldB @skip(if: false)
}
`;
const fieldA = getSelectionSet(getMainOperation(doc))[0];
const fieldB = getSelectionSet(getMainOperation(doc))[1];
expect(shouldInclude(fieldA, {})).toBe(true);
expect(shouldInclude(fieldB, {})).toBe(true);
});

it('should exclude fields with falsy @include or truthy @skip directives', () => {
const doc = gql`
{
fieldA @include(if: false)
fieldB @skip(if: true)
}
`;
const fieldA = getSelectionSet(getMainOperation(doc))[0];
const fieldB = getSelectionSet(getMainOperation(doc))[1];
expect(shouldInclude(fieldA, {})).toBe(false);
expect(shouldInclude(fieldB, {})).toBe(false);
});

it('ignore other directives', () => {
const doc = gql`
{
field @test(if: false)
}
`;
const field = getSelectionSet(getMainOperation(doc))[0];
expect(shouldInclude(field, {})).toBe(true);
});

it('ignore unknown arguments on directives', () => {
const doc = gql`
{
field @skip(if: true, other: false)
}
`;
const field = getSelectionSet(getMainOperation(doc))[0];
expect(shouldInclude(field, {})).toBe(false);
});

it('ignore directives with invalid first arguments', () => {
const doc = gql`
{
field @skip(other: true)
}
`;
const field = getSelectionSet(getMainOperation(doc))[0];
expect(shouldInclude(field, {})).toBe(true);
});
});
4 changes: 2 additions & 2 deletions src/ast/traversal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import {
DocumentNode,
FragmentDefinitionNode,
OperationDefinitionNode,
valueFromASTUntyped,
Kind,
} from 'graphql';

import { getName } from './node';
import { evaluateValueNode } from './variables';
import { Fragments, Variables } from '../types';

const isFragmentNode = (
Expand Down Expand Up @@ -65,7 +65,7 @@ export const shouldInclude = (
const arg = directive.arguments ? directive.arguments[0] : null;
if (!arg || getName(arg) !== 'if') continue;

const value = evaluateValueNode(arg.value, vars);
const value = valueFromASTUntyped(arg.value, vars);
if (typeof value !== 'boolean' && value !== null) continue;

// Return whether this directive forces us to skip
Expand Down
57 changes: 57 additions & 0 deletions src/ast/variables.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import gql from 'graphql-tag';
import { getMainOperation } from './traversal';
import { normalizeVariables } from './variables';

describe('normalizeVariables', () => {
it('normalizes variables', () => {
const input = { x: 42 };
const operation = getMainOperation(
gql`
query($x: Int!) {
field
}
`
);
const normalized = normalizeVariables(operation, input);
expect(normalized).toEqual({ x: 42 });
});

it('normalizes variables with defaults', () => {
const input = { x: undefined };
const operation = getMainOperation(
gql`
query($x: Int! = 42) {
field
}
`
);
const normalized = normalizeVariables(operation, input);
expect(normalized).toEqual({ x: 42 });
});

it('normalizes variables even with missing fields', () => {
const input = { x: undefined };
const operation = getMainOperation(
gql`
query($x: Int!) {
field
}
`
);
const normalized = normalizeVariables(operation, input);
expect(normalized).toEqual({});
});

it('skips normalizing for queries without variables', () => {
const operation = getMainOperation(
gql`
query {
field
}
`
);
(operation as any).variableDefinitions = undefined;
const normalized = normalizeVariables(operation, {});
expect(normalized).toEqual({});
});
});
40 changes: 7 additions & 33 deletions src/ast/variables.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,12 @@
import { FieldNode, ValueNode, OperationDefinitionNode, Kind } from 'graphql';
import {
FieldNode,
OperationDefinitionNode,
valueFromASTUntyped,
} from 'graphql';

import { getName } from './node';
import { Variables } from '../types';

/** Evaluates a given ValueNode to a JSON value taking vars into account */
export const evaluateValueNode = (node: ValueNode, vars: Variables) => {
switch (node.kind) {
case Kind.NULL:
return null;
case Kind.INT:
return parseInt(node.value, 10);
case Kind.FLOAT:
return parseFloat(node.value);
case Kind.LIST:
const values = new Array(node.values.length);
for (let i = 0, l = node.values.length; i < l; i++)
values[i] = evaluateValueNode(node.values[i], vars);
return values;
case Kind.OBJECT:
const fields = Object.create(null);
for (let i = 0, l = node.fields.length; i < l; i++) {
const field = node.fields[i];
fields[getName(field)] = evaluateValueNode(field.value, vars);
}

return fields;
case Kind.VARIABLE:
const varValue = vars[getName(node)];
return varValue !== undefined ? varValue : null;
default:
return node.value;
}
};

/** Evaluates a fields arguments taking vars into account */
export const getFieldArguments = (
node: FieldNode,
Expand All @@ -45,7 +19,7 @@ export const getFieldArguments = (
const args = Object.create(null);
for (let i = 0, l = node.arguments.length; i < l; i++) {
const arg = node.arguments[i];
args[getName(arg)] = evaluateValueNode(arg.value, vars);
args[getName(arg)] = valueFromASTUntyped(arg.value, vars);
}

return args;
Expand All @@ -67,7 +41,7 @@ export const normalizeVariables = (
let value = args[name];
if (value === undefined) {
if (def.defaultValue !== undefined) {
value = evaluateValueNode(def.defaultValue, args);
value = valueFromASTUntyped(def.defaultValue, args);
} else {
return vars;
}
Expand Down

0 comments on commit c8d746f

Please sign in to comment.