Mirror: The small sibling of the graphql package, slimmed down for client-side libraries.

Add tests for printer

Changed files
+246 -12
alias
language
+8 -8
alias/language/__tests__/parser.js
···
});
it('parses kitchen sink', () => {
-
expect(() => parse(kitchenSinkQuery)).not.toThrow();
+
let query;
+
expect(() => {
+
return (query = parse(kitchenSinkQuery));
+
}).not.toThrow();
+
+
expect(query.definitions.length).toBe(6);
});
it('parses anonymous mutation operations', () => {
···
});
});
-
const kitchenSinkQuery: string = String.raw`
+
const kitchenSinkQuery = String.raw`
query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery {
whoever123is: node(id: [123, 456]) {
id
···
foo(
size: $size
bar: $b
-
obj: {
-
key: "value"
-
block: """
-
block string uses \"""
-
"""
-
}
+
obj: { key: "value" }
)
}
{
+236
alias/language/__tests__/printer.js
···
+
// See: https://github.com/graphql/graphql-js/blob/976d64b/src/language/__tests__/printer-test.ts
+
+
import { parse } from '../parser';
+
import { print } from '../printer';
+
+
describe('Printer: Query document', () => {
+
it('prints minimal ast', () => {
+
const ast = {
+
kind: 'Field',
+
name: { kind: 'Name', value: 'foo' },
+
};
+
expect(print(ast)).toBe('foo');
+
});
+
+
// NOTE: The shim won't throw for invalid AST nodes
+
it('returns empty strings for invalid AST', () => {
+
const badAST = { random: 'Data' };
+
expect(print(badAST)).toBe('');
+
});
+
+
it('correctly prints non-query operations without name', () => {
+
const queryASTShorthanded = parse('query { id, name }');
+
expect(print(queryASTShorthanded)).toBe(dedent`
+
{
+
id
+
name
+
}
+
`);
+
+
const mutationAST = parse('mutation { id, name }');
+
expect(print(mutationAST)).toBe(dedent`
+
mutation {
+
id
+
name
+
}
+
`);
+
+
const queryASTWithArtifacts = parse(
+
'query ($foo: TestType) @testDirective { id, name }'
+
);
+
expect(print(queryASTWithArtifacts)).toBe(dedent`
+
query ($foo: TestType) @testDirective {
+
id
+
name
+
}
+
`);
+
+
const mutationASTWithArtifacts = parse(
+
'mutation ($foo: TestType) @testDirective { id, name }'
+
);
+
expect(print(mutationASTWithArtifacts)).toBe(dedent`
+
mutation ($foo: TestType) @testDirective {
+
id
+
name
+
}
+
`);
+
});
+
+
it('prints query with variable directives', () => {
+
const queryASTWithVariableDirective = parse(
+
'query ($foo: TestType = {a: 123} @testDirective(if: true) @test) { id }'
+
);
+
expect(print(queryASTWithVariableDirective)).toBe(dedent`
+
query ($foo: TestType = {a: 123} @testDirective(if: true) @test) {
+
id
+
}
+
`);
+
});
+
+
it('keeps arguments on one line if line is short (<= 80 chars)', () => {
+
const printed = print(
+
parse('{trip(wheelchair:false arriveBy:false){dateTime}}')
+
);
+
+
expect(printed).toBe(
+
dedent`
+
{
+
trip(wheelchair: false, arriveBy: false) {
+
dateTime
+
}
+
}
+
`
+
);
+
});
+
+
it('prints kitchen sink without altering ast', () => {
+
const ast = parse(kitchenSinkQuery, { noLocation: true });
+
+
const astBeforePrintCall = JSON.stringify(ast);
+
const printed = print(ast);
+
const printedAST = parse(printed, { noLocation: true });
+
+
expect(printedAST).toEqual(ast);
+
expect(JSON.stringify(ast)).toBe(astBeforePrintCall);
+
+
expect(printed).toBe(
+
dedentString(String.raw`
+
query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery {
+
whoever123is: node(id: [123, 456]) {
+
id
+
... on User @onInlineFragment {
+
field2 {
+
id
+
alias: field1(first: 10, after: $foo) @include(if: $foo) {
+
id
+
...frag @onFragmentSpread
+
}
+
}
+
}
+
... @skip(unless: $foo) {
+
id
+
}
+
... {
+
id
+
}
+
}
+
}
+
+
mutation likeStory @onMutation {
+
like(story: 123) @onField {
+
story {
+
id @onField
+
}
+
}
+
}
+
+
subscription StoryLikeSubscription($input: StoryLikeSubscribeInput @onVariableDefinition) @onSubscription {
+
storyLikeSubscribe(input: $input) {
+
story {
+
likers {
+
count
+
}
+
likeSentence {
+
text
+
}
+
}
+
}
+
}
+
+
fragment frag on Friend @onFragmentDefinition {
+
foo(size: $size, bar: $b, obj: {key: "value"})
+
}
+
+
{
+
unnamed(truthy: true, falsy: false, nullish: null)
+
query
+
}
+
+
{
+
__typename
+
}
+
`) + '\n'
+
);
+
});
+
});
+
+
const kitchenSinkQuery = String.raw`
+
query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery {
+
whoever123is: node(id: [123, 456]) {
+
id
+
... on User @onInlineFragment {
+
field2 {
+
id
+
alias: field1(first: 10, after: $foo) @include(if: $foo) {
+
id
+
...frag @onFragmentSpread
+
}
+
}
+
}
+
... @skip(unless: $foo) {
+
id
+
}
+
... {
+
id
+
}
+
}
+
}
+
mutation likeStory @onMutation {
+
like(story: 123) @onField {
+
story {
+
id @onField
+
}
+
}
+
}
+
subscription StoryLikeSubscription(
+
$input: StoryLikeSubscribeInput @onVariableDefinition
+
)
+
@onSubscription {
+
storyLikeSubscribe(input: $input) {
+
story {
+
likers {
+
count
+
}
+
likeSentence {
+
text
+
}
+
}
+
}
+
}
+
fragment frag on Friend @onFragmentDefinition {
+
foo(
+
size: $size
+
bar: $b
+
obj: { key: "value" }
+
)
+
}
+
{
+
unnamed(truthy: true, falsy: false, nullish: null)
+
query
+
}
+
query {
+
__typename
+
}
+
`;
+
+
function dedentString(string) {
+
const trimmedStr = string
+
.replace(/^\n*/m, '') // remove leading newline
+
.replace(/[ \t\n]*$/, ''); // remove trailing spaces and tabs
+
// fixes indentation by removing leading spaces and tabs from each line
+
let indent = '';
+
for (const char of trimmedStr) {
+
if (char !== ' ' && char !== '\t') {
+
break;
+
}
+
indent += char;
+
}
+
+
return trimmedStr.replace(RegExp('^' + indent, 'mg'), ''); // remove indent
+
}
+
+
function dedent(strings, ...values) {
+
let str = strings[0];
+
for (let i = 1; i < strings.length; ++i) str += values[i - 1] + strings[i]; // interpolation
+
return dedentString(str) + '\n';
+
}
+2 -4
alias/language/parser.mjs
···
${/[eE][+-]?\d+/}?
`;
-
// 2.9.4: Notably, this skips checks for unicode escape sequences and escaped
-
// quotes. This is mainly meant for client-side use, so we won't have to be strict.
+
// 2.9.4: Notably, this skips checks for unicode escape sequences and escaped quotes.
const string = match(Kind.STRING, (x) => ({
kind: x.tag,
value: x[0],
···
const root = match(Kind.DOCUMENT, (x) =>
x.length ? { kind: x.tag, definitions: x.slice() } : undefined
)`
-
${queryShorthand}
-
| (${operationDefinition} | ${fragmentDefinition})+
+
(${queryShorthand} | ${operationDefinition} | ${fragmentDefinition})*
`;
const _parse = makeParser(root);