src/query/__tests__/RelayQueryPath-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');
jest.mock('warning');
const Relay = require('Relay');
const RelayMetaRoute = require('RelayMetaRoute');
const RelayQueryPath = require('RelayQueryPath');
const RelayRecordStore = require('RelayRecordStore');
const RelayRecordWriter = require('RelayRecordWriter');
const RelayTestUtils = require('RelayTestUtils');
describe('RelayQueryPath', () => {
const {getNode, getVerbatimNode} = RelayTestUtils;
let store;
let writer;
beforeEach(() => {
jest.resetModuleRegistry();
const records = {};
store = new RelayRecordStore({records});
writer = new RelayRecordWriter(records);
jasmine.addMatchers(RelayTestUtils.matchers);
});
it('creates root paths', () => {
const query = getNode(Relay.QL`
query {
node(id:"123") {
id
}
}
`);
const fragment = Relay.QL`
fragment on Node {
id
__typename
name
}
`;
const path = RelayQueryPath.create(query);
expect(RelayQueryPath.getName(path)).toBe(query.getName());
expect(RelayQueryPath.getRouteName(path)).toBe(query.getRoute().name);
writer.putRecord('123', 'User');
const pathQuery = RelayQueryPath.getQuery(store, path, getNode(fragment));
expect(pathQuery).toEqualQueryRoot(getVerbatimNode(Relay.QL`
query {
node(id:"123") {
... on User {
${fragment}
id
__typename
}
}
}
`));
});
it('creates root paths for argument-less root calls with IDs', () => {
const query = getNode(Relay.QL`
query {
me {
id
}
}
`);
const fragment = Relay.QL`
fragment on Actor {
name
}
`;
const path = RelayQueryPath.create(query);
const pathQuery = RelayQueryPath.getQuery(store, path, getNode(fragment));
expect(pathQuery).toEqualQueryRoot(getNode(Relay.QL`
query {
me {
id
${fragment}
}
}
`));
expect(RelayQueryPath.getName(path)).toBe(query.getName());
expect(RelayQueryPath.getRouteName(path)).toBe(query.getRoute().name);
});
it('creates root paths for argument-less root calls without IDs', () => {
const query = getNode(Relay.QL`
query {
viewer {
actor {
id
}
}
}
`);
const fragment = Relay.QL`
fragment on Viewer {
actor {
name
}
}
`;
const path = RelayQueryPath.create(query);
const pathQuery = RelayQueryPath.getQuery(store, path, getNode(fragment));
expect(pathQuery).toEqualQueryRoot(getNode(Relay.QL`
query {
viewer {
${fragment}
}
}
`));
expect(RelayQueryPath.getName(path)).toBe(query.getName());
expect(RelayQueryPath.getRouteName(path)).toBe(query.getRoute().name);
});
it('creates paths to non-refetchable fields', () => {
const query = getNode(Relay.QL`
query {
node(id:"123") {
id
}
}
`);
const address = getNode(Relay.QL`
fragment on Actor {
address {
city
}
}
`).getFieldByStorageKey('address');
const city = getNode(Relay.QL`
fragment on StreetAddress {
city
}
`).getFieldByStorageKey('city');
// address is not refetchable, has client ID
writer.putRecord('123', 'User');
const root = RelayQueryPath.create(query);
const path = RelayQueryPath.getPath(root, address, 'client:1');
const pathQuery = RelayQueryPath.getQuery(store, path, city);
expect(pathQuery).toEqualQueryRoot(getVerbatimNode(Relay.QL`
query {
node(id:"123") {
... on User {
id
__typename
address {
city
}
}
}
}
`));
expect(RelayQueryPath.getName(path)).toBe(query.getName());
expect(pathQuery.getName()).toBe(query.getName());
expect(pathQuery.getRoute().name).toBe(query.getRoute().name);
expect(pathQuery.isAbstract()).toBe(true);
});
it('creates roots with route from child', () => {
let query = getNode(Relay.QL`
query {
node(id:"123") {
id
}
}
`);
query = query.cloneWithRoute(
query.getChildren(),
RelayMetaRoute.get('FooRoute')
);
const fragment = Relay.QL`
fragment on Node {
id
__typename
name
}
`;
const path = RelayQueryPath.create(query);
writer.putRecord('123', 'User');
const pathQuery = RelayQueryPath.getQuery(store, path, getNode(fragment));
expect(pathQuery.getRoute()).toBe(getNode(fragment).getRoute());
expect(pathQuery).toEqualQueryRoot(getVerbatimNode(Relay.QL`
query {
node(id:"123") {
... on User {
${fragment}
id
__typename
}
}
}
`));
});
it('creates roots for refetchable fields', () => {
const query = getNode(Relay.QL`
query {
viewer {
actor {
id
}
}
}
`);
const actor = query.getFieldByStorageKey('actor');
const fragment = Relay.QL`
fragment on Node {
name
}
`;
// actor has an ID and is refetchable
writer.putRecord('123', 'User');
const root = RelayQueryPath.create(query);
const path = RelayQueryPath.getPath(root, actor, '123');
const pathQuery = RelayQueryPath.getQuery(store, path, getNode(fragment));
expect(pathQuery).toEqualQueryRoot(getVerbatimNode(Relay.QL`
query {
node(id:"123") {
... on User {
id
__typename
... on Node {
id
__typename
name
}
}
}
}
`));
expect(pathQuery.getName()).toBe(query.getName());
expect(pathQuery.getRoute().name).toBe(query.getRoute().name);
});
it('creates paths to non-refetchable connection fields', () => {
const query = getNode(Relay.QL`
query {
node(id:"123") {
id
}
}
`);
const friends = getNode(Relay.QL`
fragment on User {
friends(first:"1") {
edges {
cursor
}
}
}
`).getFieldByStorageKey('friends');
const edges = getNode(Relay.QL`
fragment on FriendsConnection {
edges {
cursor
}
}
`).getFieldByStorageKey('edges');
const cursor = getNode(Relay.QL`
fragment on FriendsEdge {
cursor
}
`).getFieldByStorageKey('cursor');
// edges is not refetchable because it is tied to a connection.
writer.putRecord('123', 'User');
const root = RelayQueryPath.create(query);
let path = RelayQueryPath.getPath(root, friends, 'client:1');
path = RelayQueryPath.getPath(path, edges, 'client:2');
const pathQuery = RelayQueryPath.getQuery(store, path, cursor);
expect(pathQuery).toEqualQueryRoot(getVerbatimNode(Relay.QL`
query {
node(id:"123") {
... on User {
__typename
id
friends(first:"1") {
edges {
cursor
}
}
}
}
}
`));
expect([
'RelayQueryPath.getQuery(): Cannot generate accurate query for ' +
'path with connection `%s`. Consider adding an `id` field to each ' +
'`node` to make them refetchable.',
'friends',
]).toBeWarnedNTimes(1);
expect(RelayQueryPath.getName(path)).toBe(query.getName());
expect(pathQuery.getName()).toBe(query.getName());
expect(pathQuery.getRoute().name).toBe(query.getRoute().name);
expect(pathQuery.isAbstract()).toBe(true);
});
it('warns if the root record\'s type is unknown', () => {
const query = getNode(Relay.QL`
query {
viewer {
actor {
id
}
}
}
`);
const actor = query.getFieldByStorageKey('actor');
const fragment = Relay.QL`
fragment on Node {
name
}
`;
// actor has an ID and is refetchable, but the type of actor is unknown.
const root = RelayQueryPath.create(query);
const path = RelayQueryPath.getPath(root, actor, '123');
const pathQuery = RelayQueryPath.getQuery(store, path, getNode(fragment));
expect(pathQuery).toEqualQueryRoot(getVerbatimNode(Relay.QL`
query {
node(id:"123") {
# not wrapped in a concrete fragment because the type is unknown.
... on Node {
name
id
__typename
}
id
__typename
}
}
`));
expect(pathQuery.getName()).toBe(query.getName());
expect(pathQuery.getRoute().name).toBe(query.getRoute().name);
expect([
'RelayQueryPath: No typename found for %s record `%s`. Generating a ' +
'possibly invalid query.',
'unknown',
'123',
]).toBeWarnedNTimes(1);
});
describe('getPath()', () => {
it('returns a client path given no `dataID`', () => {
const query = getNode(Relay.QL`
query {
viewer {
actor {
id
}
}
}
`);
const actor = query.getFieldByStorageKey('actor');
const path = RelayQueryPath.getPath(query, actor); // No `dataID`
expect(path).toEqual({
node: actor,
parent: query,
type: 'client',
});
});
});
});