src/traversal/__tests__/printRelayOSSQuery-test.js
/**
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @emails oncall+relay
*/
'use strict';
require('configureForRelayOSS');
const QueryBuilder = require('QueryBuilder');
const Relay = require('Relay');
const RelayNodeInterface = require('RelayNodeInterface');
const RelayQuery = require('RelayQuery');
const RelayTestUtils = require('RelayTestUtils');
const generateRQLFieldAlias = require('generateRQLFieldAlias');
const printRelayOSSQuery = require('printRelayOSSQuery');
describe('printRelayOSSQuery', () => {
const {getNode} = RelayTestUtils;
beforeEach(() => {
jest.resetModuleRegistry();
jasmine.addMatchers(RelayTestUtils.matchers);
});
describe('roots', () => {
it('prints a query with no root arguments', () => {
const query = getNode(Relay.QL`
query {
me {
firstName
lastName
}
}
`);
const {text, variables} = printRelayOSSQuery(query);
expect(text).toEqualPrintedQuery(`
query PrintRelayOSSQuery {
me {
firstName,
lastName,
id
}
}
`);
expect(variables).toEqual({});
});
it('prints a generated query with one root argument', () => {
const query = RelayQuery.Root.build(
'FooQuery',
'node',
'123',
[
RelayQuery.Field.build({
fieldName: 'id',
type: 'String',
}),
],
{
identifyingArgName: RelayNodeInterface.ID,
identifyingArgType: RelayNodeInterface.ID_TYPE,
isAbstract: true,
isDeferred: false,
isPlural: false,
},
'Node'
);
const {text, variables} = printRelayOSSQuery(query);
expect(text).toEqualPrintedQuery(`
query FooQuery($id_0: ID!) {
node(id: $id_0) {
id
}
}
`);
expect(variables).toEqual({
id_0: '123',
});
});
it('prints a query with one root argument', () => {
const query = getNode(Relay.QL`
query {
node(id:"123") {
name
}
}
`);
const {text, variables} = printRelayOSSQuery(query);
expect(text).toEqualPrintedQuery(`
query PrintRelayOSSQuery($id_0: ID!) {
node(id: $id_0) {
name,
id,
__typename
}
}
`);
expect(variables).toEqual({
id_0: '123',
});
});
it('prints a query with one root numeric argument', () => {
const query = getNode(Relay.QL`
query FooQuery {
node(id: 123) {
name
id
}
}
`);
const {text, variables} = printRelayOSSQuery(query);
expect(text).toEqualPrintedQuery(`
query FooQuery($id_0: ID!) {
node(id: $id_0) {
name,
id,
__typename
}
}
`);
expect(variables).toEqual({
id_0: 123,
});
});
it('prints a query with multiple root arguments', () => {
const query = getNode(Relay.QL`
query {
usernames(names:["a","b","c"]) {
firstName
lastName
}
}
`);
const {text, variables} = printRelayOSSQuery(query);
expect(text).toEqualPrintedQuery(`
query PrintRelayOSSQuery($names_0: [String!]!) {
usernames(names: $names_0) {
firstName,
lastName,
id,
__typename
}
}
`);
expect(variables).toEqual({
names_0: ['a', 'b', 'c'],
});
});
it('prints a query with multiple numeric arguments', () => {
const query = getNode(Relay.QL`
query FooQuery {
nodes(ids: [123, 456]) {
name
id
}
}
`);
const {text, variables} = printRelayOSSQuery(query);
expect(text).toEqualPrintedQuery(`
query FooQuery($ids_0: [ID!]!) {
nodes(ids: $ids_0) {
name,
id,
__typename
}
}
`);
expect(variables).toEqual({
ids_0: [123, 456],
});
});
it('prints enum call values', () => {
const enumValue = 'WEB';
const query = getNode(Relay.QL`
query FooQuery {
settings(environment: $env) {
notificationSounds
}
}
`, {
env: enumValue,
});
const {text, variables} = printRelayOSSQuery(query);
expect(text).toEqualPrintedQuery(`
query FooQuery($environment_0: Environment!) {
settings(environment: $environment_0) {
notificationSounds
}
}
`);
expect(variables).toEqual({
environment_0: enumValue,
});
});
it('prints object call values', () => {
const objectValue = {query: 'Menlo Park'};
const query = getNode(Relay.QL`
query {
checkinSearchQuery(query: $q) {
query
}
}
`, {
q: objectValue,
});
const {text, variables} = printRelayOSSQuery(query);
expect(text).toEqualPrintedQuery(`
query PrintRelayOSSQuery($query_0: CheckinSearchInput!) {
checkinSearchQuery(query: $query_0) {
query
}
}
`);
expect(variables).toEqual({
query_0: objectValue,
});
});
it('prints literal object call values', () => {
const query = getNode(Relay.QL`
query {
checkinSearchQuery(query: {query: "Menlo Park"}) {
query
}
}
`);
const {text, variables} = printRelayOSSQuery(query);
expect(text).toEqualPrintedQuery(`
query PrintRelayOSSQuery($query_0: CheckinSearchInput!) {
checkinSearchQuery(query: $query_0) {
query
}
}
`);
expect(variables).toEqual({
query_0: {
query: 'Menlo Park',
},
});
});
it('dedupes enum variables', () => {
const enumValue = 'WEB';
const query = getNode(Relay.QL`
query FooQuery {
defaultSettings {
env: notifications(environment: $env)
web: notifications(environment: WEB)
}
}
`, {
env: enumValue,
});
const envAlias =
generateRQLFieldAlias('notifications.env.environment(WEB)');
const webAlias =
generateRQLFieldAlias('notifications.web.environment(WEB)');
const {text, variables} = printRelayOSSQuery(query);
expect(text).toEqualPrintedQuery(`
query FooQuery($environment_0: Environment!) {
defaultSettings {
${envAlias}: notifications(environment: $environment_0),
${webAlias}: notifications(environment: $environment_0)
}
}
`);
expect(variables).toEqual({
environment_0: enumValue,
});
});
it('dedupes object variables', () => {
const query1 = {query: 'foo'};
const query2 = {query: 'foo'};
const query = getNode(Relay.QL`
query FooQuery {
node(id: "123") {
... on User {
foo: storySearch(query: $query1) {
id
}
bar: storySearch(query: $query2) {
id
}
}
}
}
`, {
query1,
query2,
});
const fooAlias =
generateRQLFieldAlias('storySearch.foo.query({"query":"foo"})');
const barAlias =
generateRQLFieldAlias('storySearch.bar.query({"query":"foo"})');
const {text, variables} = printRelayOSSQuery(query);
expect(text).toEqualPrintedQuery(`
query FooQuery($id_0: ID!, $query_1: StorySearchInput!) {
node(id: $id_0) {
id,
__typename,
...F0
}
}
fragment F0 on User {
${fooAlias}: storySearch(query: $query_1) {
id
},
${barAlias}: storySearch(query: $query_1) {
id
},
id
}
`);
expect(variables).toEqual({
id_0: '123',
query_1: query1,
});
});
it('creates distinct variables for values of different types', () => {
// Relay allows the same variable at both locations, regardless of type:
const query = getNode(Relay.QL`
query DistinctVars {
node(id: "123") {
... on User {
storySearch(query: $query) {id}
storyCommentSearch(query: $query) {id}
}
}
}`,
{
query: {text: 'foo'},
},
);
const storySearchAlias =
generateRQLFieldAlias('storySearch.query({"text":"foo"})');
const storyCommentSearchAlias =
generateRQLFieldAlias('storyCommentSearch.query({"text":"foo"})');
const {text, variables} = printRelayOSSQuery(query);
// GraphQL requires that a different variable be used for values of
// different types:
expect(text).toEqualPrintedQuery(`
query DistinctVars(
$id_0: ID!,
$query_1: StorySearchInput!,
$query_2: StoryCommentSearchInput!
) {
node(id: $id_0) {
id,
__typename,
...F0
}
}
fragment F0 on User {
${storySearchAlias}: storySearch(query: $query_1) {
id
},
${storyCommentSearchAlias}: storyCommentSearch(query: $query_2) {
id
},
id
}
`);
expect(variables).toEqual({
id_0: '123',
query_1: {text: 'foo'},
query_2: {text: 'foo'},
});
});
it('throws for ref queries', () => {
const query = RelayQuery.Root.build(
'RefQueryName',
RelayNodeInterface.NODE,
QueryBuilder.createBatchCallVariable('q0', '$.*.actor.id'),
[
RelayQuery.Field.build({fieldName: 'id', type: 'String'}),
RelayQuery.Field.build({fieldName: 'name', type: 'String'}),
],
{
isDeferred: true,
identifyingArgName: RelayNodeInterface.ID,
type: RelayNodeInterface.NODE_TYPE,
}
);
expect(() => printRelayOSSQuery(query)).toFailInvariant(
'printRelayOSSQuery(): Deferred queries are not supported.'
);
});
});
describe('fragments', () => {
it('prints fragments', () => {
const fragment = getNode(Relay.QL`
fragment on Viewer {
actor {
id
}
}
`);
const {text, variables} = printRelayOSSQuery(fragment);
expect(text).toEqualPrintedQuery(`
fragment PrintRelayOSSQueryRelayQL on Viewer {
actor {
id,
__typename
}
}
`);
expect(variables).toEqual({});
});
it('prints inline fragments', () => {
const fragment = getNode(Relay.QL`
fragment on Viewer {
actor {
id
... on User {
name
}
... on User {
profilePicture {
uri
}
}
}
}
`);
const {text, variables} = printRelayOSSQuery(fragment);
expect(text).toEqualPrintedQuery(`
fragment PrintRelayOSSQueryRelayQL on Viewer {
actor {
id,
__typename,
...F0,
...F1
}
}
fragment F0 on User {
name,
id
}
fragment F1 on User {
profilePicture {
uri
},
id
}
`);
expect(variables).toEqual({});
});
it('prints fragments with incrementing names', () => {
const fragmentA = Relay.QL`fragment on User { firstName }`;
const fragmentB = Relay.QL`fragment on User { lastName }`;
const fragment = getNode(Relay.QL`
fragment on Node {
${fragmentA}
${fragmentB}
}
`);
const {text, variables} = printRelayOSSQuery(fragment);
expect(text).toEqualPrintedQuery(`
fragment PrintRelayOSSQueryRelayQL on Node {
id,
__typename,
...F0,
...F1
}
fragment F0 on User {
firstName,
id
}
fragment F1 on User {
lastName,
id
}
`);
expect(variables).toEqual({});
});
it('prints fragments with identical children only once', () => {
const fragmentA = Relay.QL`fragment on User { name }`;
const fragmentB = Relay.QL`fragment on User { name }`;
const fragment = getNode(Relay.QL`
fragment on Node {
${fragmentA}
${fragmentB}
}
`);
const {text, variables} = printRelayOSSQuery(fragment);
expect(text).toEqualPrintedQuery(`
fragment PrintRelayOSSQueryRelayQL on Node {
id,
__typename,
...F0
}
fragment F0 on User {
name,
id
}
`);
expect(variables).toEqual({});
});
it('prints fragments with different variables separately', () => {
const concreteFragment = Relay.QL`
fragment on User {
profilePicture(size: [$width, $height]) {
uri
}
}
`;
const fragment = getNode(Relay.QL`fragment on User { id }`).clone([
getNode(concreteFragment, {width: 32, height: 32}),
getNode(concreteFragment, {width: 64, height: 64}),
]);
const {text, variables} = printRelayOSSQuery(fragment);
expect(text).toEqualPrintedQuery(`
fragment PrintRelayOSSQueryRelayQL on User {
...F0,
...F1
}
fragment F0 on User {
${generateRQLFieldAlias('profilePicture.size(32,32)')}:
profilePicture(size: [32, 32]) {
uri
},
id
}
fragment F1 on User {
${generateRQLFieldAlias('profilePicture.size(64,64)')}:
profilePicture(size: [64, 64]) {
uri
},
id
}
`);
expect(variables).toEqual({});
});
it('prints fragments with different runtime children separately', () => {
let child;
child = Relay.QL`fragment on User { name }`;
const fragmentA = Relay.QL`fragment on User { ${child} }`;
child = Relay.QL`fragment on User { profilePicture { uri } }`;
const fragmentB = Relay.QL`fragment on User { ${child} }`;
const fragment = getNode(Relay.QL`
fragment on Node {
${fragmentA}
${fragmentB}
}
`);
const {text, variables} = printRelayOSSQuery(fragment);
expect(text).toEqualPrintedQuery(`
fragment PrintRelayOSSQueryRelayQL on Node {
id,
__typename,
...F1,
...F3
}
fragment F0 on User {
name,
id
}
fragment F1 on User {
id,
...F0
}
fragment F2 on User {
profilePicture {
uri
},
id
}
fragment F3 on User {
id,
...F2
}
`);
expect(variables).toEqual({});
});
it('prints fragments with different IDs but identical output once', () => {
const concreteFragment = Relay.QL`fragment on User { name }`;
const fragment = getNode(Relay.QL`fragment on User { id }`).clone([
getNode(concreteFragment, {value: 123}),
getNode(concreteFragment, {value: 456}),
]);
const {text, variables} = printRelayOSSQuery(fragment);
expect(text).toEqualPrintedQuery(`
fragment PrintRelayOSSQueryRelayQL on User {
...F0
}
fragment F0 on User {
name,
id
}
`);
expect(variables).toEqual({});
});
it('omits empty fragments', () => {
const fragment = getNode(Relay.QL`
fragment on Viewer {
actor {
id
}
... on Viewer {
actor @include(if: $false) {
name
}
}
}
`, {false: false});
const {text} = printRelayOSSQuery(fragment);
expect(text).toEqualPrintedQuery(`
fragment PrintRelayOSSQueryRelayQL on Viewer {
actor {
id,
__typename
}
}
`);
});
});
describe('fields', () => {
it('prints a field with one argument', () => {
const alias = generateRQLFieldAlias('newsFeed.first(10)');
const fragment = getNode(Relay.QL`
fragment on Viewer {
newsFeed(first:$first) {
edges {
node {
id
}
}
}
}
`, {first: 10});
const {text, variables} = printRelayOSSQuery(fragment);
expect(text).toEqualPrintedQuery(`
fragment PrintRelayOSSQueryRelayQL on Viewer {
${alias}:newsFeed(first:10) {
edges {
node {
id,
__typename
},
cursor
},
pageInfo {
hasNextPage,
hasPreviousPage
}
}
}
`);
expect(variables).toEqual({});
});
it('prints a field with multiple arguments', () => {
const alias = generateRQLFieldAlias('profilePicture.size(32,64)');
const fragment = getNode(Relay.QL`
fragment on Actor {
profilePicture(size:["32","64"]) {
uri
}
}
`);
const {text, variables} = printRelayOSSQuery(fragment);
expect(text).toEqualPrintedQuery(`
fragment PrintRelayOSSQueryRelayQL on Actor {
${alias}:profilePicture(size:["32","64"]) {
uri
},
id,
__typename
}
`);
expect(variables).toEqual({});
});
it('prints a field with multiple variable arguments', () => {
const alias = generateRQLFieldAlias('profilePicture.size(32,64)');
const fragment = getNode(Relay.QL`
fragment on Actor {
profilePicture(size:[$width,$height]) {
uri
}
}
`, {
height: 64,
width: 32,
});
const {text, variables} = printRelayOSSQuery(fragment);
expect(text).toEqualPrintedQuery(`
fragment PrintRelayOSSQueryRelayQL on Actor {
${alias}:profilePicture(size:[32,64]) {
uri
},
id,
__typename
}
`);
expect(variables).toEqual({});
});
it('prints scalar arguments', () => {
const fragment = getNode(Relay.QL`
fragment on Actor {
friends(
first: $first
orderby: $orderby
isViewerFriend: $isViewerFriend
) {
edges {
node {
id
}
}
}
}
`, {
first: 10,
orderby: ['name'],
isViewerFriend: false,
});
const alias = fragment.getChildren()[0].getSerializationKey();
const {text, variables} = printRelayOSSQuery(fragment);
expect(text).toEqualPrintedQuery(`
fragment PrintRelayOSSQueryRelayQL on Actor {
${alias}:friends(first:10,orderby:["name"],isViewerFriend:false) {
edges {
node {
id
},
cursor
},
pageInfo {
hasNextPage,
hasPreviousPage
}
},
id,
__typename
}
`);
expect(variables).toEqual({});
});
it('prints object call values', () => {
const enumValue = 'WEB';
const fragment = Relay.QL`
fragment on Settings {
notifications(environment: $env)
}
`;
const query = getNode(Relay.QL`
query {
defaultSettings {
${fragment}
}
}
`, {
env: enumValue,
});
const alias = generateRQLFieldAlias('notifications.environment(WEB)');
const {text, variables} = printRelayOSSQuery(query);
expect(text).toEqualPrintedQuery(`
query PrintRelayOSSQuery($environment_0: Environment!) {
defaultSettings {
...F0
}
}
fragment F0 on Settings {
${alias}:notifications(environment: $environment_0)
}
`);
expect(variables).toEqual({
environment_0: enumValue,
});
});
it('prints inline fragments as references', () => {
// these fragments have different types and cannot be flattened
const nestedFragment = Relay.QL`fragment on User { name }`;
const fragment = getNode(Relay.QL`
fragment on Viewer {
actor {
id
${nestedFragment}
${nestedFragment}
}
}
`);
const {text, variables} = printRelayOSSQuery(fragment);
expect(text).toEqualPrintedQuery(`
fragment PrintRelayOSSQueryRelayQL on Viewer {
actor {
id,
__typename,
...F0
}
}
fragment F0 on User {
name,
id
}
`);
expect(variables).toEqual({});
});
});
it('prints a mutation', () => {
const inputValue = {
clientMutationId: '123',
foo: 'bar',
};
const mutation = getNode(Relay.QL`
mutation {
feedbackLike(input: $input) {
clientMutationId
feedback {
id
actor {
profilePicture(preset: SMALL) {
uri
}
}
likeSentence
likers
}
}
}
`, {input: inputValue});
const alias = generateRQLFieldAlias('profilePicture.preset(SMALL)');
const {text, variables} = printRelayOSSQuery(mutation);
expect(text).toEqualPrintedQuery(`
mutation PrintRelayOSSQuery(
$input_0: FeedbackLikeInput!,
$preset_1: PhotoSize!
) {
feedbackLike(input: $input_0) {
clientMutationId,
feedback {
id,
actor {
${alias}: profilePicture(preset: $preset_1) {
uri
},
id,
__typename
},
likeSentence,
likers
}
}
}
`);
expect(variables).toEqual({
input_0: inputValue,
preset_1: 'SMALL',
});
});
it('prints a subscription', () => {
const inputValue = {
foo: 'bar',
};
const subscription = getNode(Relay.QL`
subscription {
feedbackLikeSubscribe(input: $input) {
clientSubscriptionId
feedback {
id
actor {
profilePicture(preset: SMALL) {
uri
}
}
likeSentence
likers
}
}
}
`, {input: inputValue});
const alias = generateRQLFieldAlias('profilePicture.preset(SMALL)');
const {text, variables} = printRelayOSSQuery(subscription);
expect(text).toEqualPrintedQuery(`
subscription PrintRelayOSSQuery(
$input_0: FeedbackLikeInput!,
$preset_1: PhotoSize!
) {
feedbackLikeSubscribe(input: $input_0) {
clientSubscriptionId,
feedback {
id,
actor {
${alias}: profilePicture(preset: $preset_1) {
uri
},
id,
__typename
},
likeSentence,
likers
}
}
}
`);
expect(variables).toEqual({
input_0: inputValue,
preset_1: 'SMALL',
});
});
it('prints directives', () => {
const params = {cond: true};
const nestedFragment = Relay.QL`
fragment on User @include(if: $cond) {
name @skip(if: $cond)
}
`;
const query = getNode(Relay.QL`
query {
node(id: 123) @skip(if: true) {
${nestedFragment}
}
}
`, params);
const {text, variables} = printRelayOSSQuery(query);
expect(text).toEqualPrintedQuery(`
query PrintRelayOSSQuery($id_0: ID!) {
node(id: $id_0) @skip(if: true) {
id,
__typename,
...F0
}
}
fragment F0 on User @include(if: true) {
id
}
`);
expect(variables).toEqual({
id_0: 123,
});
});
it('throws for directives with complex values', () => {
const params = {data: {foo: 'bar'}};
const query = getNode(Relay.QL`
query {
node(id: 123) @include(if: $data) {
id
}
}
`, params);
expect(() => printRelayOSSQuery(query)).toFailInvariant(
'printRelayOSSQuery(): Relay only supports directives with scalar ' +
'values (boolean, number, or string), got `if: [object Object]`.'
);
});
});