diff --git a/src/ApolloProvider.tsx b/src/ApolloProvider.tsx
index a5198a55f4..d185be8f80 100644
--- a/src/ApolloProvider.tsx
+++ b/src/ApolloProvider.tsx
@@ -1,12 +1,8 @@
import * as React from 'react';
import * as PropTypes from 'prop-types';
-import {
- Component,
-} from 'react';
+import { Component } from 'react';
-import {
- Store,
-} from 'redux';
+import { Store } from 'redux';
import ApolloClient, { ApolloStore } from 'apollo-client';
diff --git a/test/react-web/client/graphql/mutations.test.tsx b/test/react-web/client/graphql/mutations.test.tsx
deleted file mode 100644
index 57b0b922de..0000000000
--- a/test/react-web/client/graphql/mutations.test.tsx
+++ /dev/null
@@ -1,767 +0,0 @@
-
-import * as React from 'react';
-import * as renderer from 'react-test-renderer';
-import gql from 'graphql-tag';
-import assign = require('object-assign');
-
-import ApolloClient from 'apollo-client';
-
-declare function require(name: string);
-
-import { mockNetworkInterface } from '../../../../src/test-utils';
-
-
-import { ApolloProvider, graphql } from '../../../../src';
-
-describe('mutations', () => {
-
- it('binds a mutation to props', () => {
- const query = gql`mutation addPerson { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- const ContainerWithData = graphql(query)(({ mutate }) => {
- expect(mutate).toBeTruthy();
- expect(typeof mutate).toBe('function');
- return null;
- });
-
- renderer.create();
- });
-
- it('binds a mutation to custom props', () => {
- const query = gql`mutation addPerson { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- const props = ({ ownProps, addPerson }) => ({
- [ownProps.methodName]: (name: string) => addPerson({ variables: { name }}),
- });
-
- const ContainerWithData = graphql(query, { props })(({ test }) => {
- expect(test).toBeTruthy();
- expect(typeof test).toBe('function');
- return null;
- });
-
- renderer.create();
- });
-
- it('does not swallow children errors', () => {
- const query = gql`mutation addPerson { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
- let bar;
- const ContainerWithData = graphql(query)(() => {
- bar(); // this will throw
- return null;
- });
-
- try {
- renderer.create();
- throw new Error();
- } catch (e) {
- expect(e.name).toMatch(/TypeError/);
- }
-
- });
-
- it('can execute a mutation', (done) => {
- const query = gql`mutation addPerson { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query)
- class Container extends React.Component {
- componentDidMount() {
- this.props.mutate()
- .then(result => {
- expect(result.data).toEqual(data);
- done();
- })
- ;
- }
- render() {
- return null;
- }
- };
-
- renderer.create();
- });
-
- it('can execute a mutation with variables from props', (done) => {
- const query = gql`
- mutation addPerson($id: Int) {
- allPeople(id: $id) { people { name } }
- }
- `;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const variables = { id: 1 };
- const networkInterface = mockNetworkInterface({
- request: { query, variables },
- result: { data },
- });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query)
- class Container extends React.Component {
- componentDidMount() {
- this.props.mutate()
- .then(result => {
- expect(result.data).toEqual(data);
- done();
- })
- ;
- }
- render() {
- return null;
- }
- };
-
- renderer.create();
- });
-
- it('allows falsy values in the mapped variables from props', (done) => {
- const query = gql`
- mutation addPerson($id: Int) {
- allPeople(id: $id) { people { name } }
- }
- `;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const variables = { id: null };
- const networkInterface = mockNetworkInterface({
- request: { query, variables },
- result: { data },
- });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query)
- class Container extends React.Component {
- componentDidMount() {
- this.props.mutate()
- .then(result => {
- expect(result.data).toEqual(data);
- done();
- })
- ;
- }
- render() {
- return null;
- }
- };
-
- renderer.create();
- });
-
- it('errors if the passed props don\'t contain the needed variables', () => {
- const query = gql`
- mutation addPerson($first: Int) {
- allPeople(first: $first) { people { name } }
- }
- `;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const variables = { first: 1 };
- const networkInterface = mockNetworkInterface({
- request: { query, variables },
- result: { data },
- });
- const client = new ApolloClient({ networkInterface, addTypename: false });
- const Container = graphql(query)(() => null);
-
- try {
- renderer.create();
- } catch (e) {
- expect(e).toMatch(/Invariant Violation: The operation 'addPerson'/);
- }
-
- });
-
- it('rebuilds the mutation on prop change when using `options`', (done) => {
- const query = gql`mutation addPerson { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- function options(props) {
- // expect(props.listId).toBe(2);
- return {};
- };
-
- @graphql(query, { options })
- class Container extends React.Component {
- componentWillReceiveProps(props) {
- if (props.listId !== 2) return;
- props.mutate().then(x => done());
- }
- render() {
- return null;
- }
- };
- class ChangingProps extends React.Component {
- state = { listId: 1 };
-
- componentDidMount() {
- setTimeout(() => this.setState({ listId: 2 }), 50);
- }
-
- render() {
- return ;
- }
- }
-
- renderer.create();
- });
-
- it('can execute a mutation with custom variables', (done) => {
- const query = gql`
- mutation addPerson($id: Int) {
- allPeople(id: $id) { people { name } }
- }
- `;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const variables = { id: 1 };
- const networkInterface = mockNetworkInterface({
- request: { query, variables },
- result: { data },
- });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query)
- class Container extends React.Component {
- componentDidMount() {
- this.props.mutate({ variables: { id: 1 } })
- .then(result => {
- expect(result.data).toEqual(data);
- done();
- })
- ;
- }
- render() {
- return null;
- }
- };
-
- renderer.create();
- });
-
- it('allows for passing optimisticResponse for a mutation', (done) => {
- const query = gql`
- mutation createTodo {
- createTodo { id, text, completed, __typename }
- __typename
- }
- `;
-
- const data = {
- __typename: 'Mutation',
- createTodo: {
- __typename: 'Todo',
- id: '99',
- text: 'This one was created with a mutation.',
- completed: true,
- },
- };
-
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query)
- class Container extends React.Component {
- componentDidMount() {
- const optimisticResponse = {
- __typename: 'Mutation',
- createTodo: {
- __typename: 'Todo',
- id: '99',
- text: 'Optimistically generated',
- completed: true,
- },
- };
- this.props.mutate({ optimisticResponse })
- .then(result => {
- expect(result.data).toEqual(data);
- done();
- })
- ;
-
- const dataInStore = client.queryManager.getDataWithOptimisticResults();
- expect(dataInStore['Todo:99']).toEqual(
- optimisticResponse.createTodo
- );
-
- }
- render() {
- return null;
- }
- };
-
- renderer.create();
- });
-
- it('allows for updating queries from a mutation', (done) => {
- const mutation = gql`
- mutation createTodo {
- createTodo { id, text, completed }
- }
- `;
-
- const mutationData = {
- createTodo: {
- id: '99',
- text: 'This one was created with a mutation.',
- completed: true,
- },
- };
-
- const optimisticResponse = {
- createTodo: {
- id: '99',
- text: 'Optimistically generated',
- completed: true,
- },
- };
-
- const updateQueries = {
- todos: (previousQueryResult, { mutationResult, queryVariables }) => {
- if (queryVariables.id !== '123') {
- // this isn't the query we updated, so just return the previous result
- return previousQueryResult;
- }
- // otherwise, create a new object with the same shape as the
- // previous result with the mutationResult incorporated
- const originalList = previousQueryResult.todo_list;
- const newTask = mutationResult.data.createTodo;
- return {
- todo_list: assign(originalList, { tasks: [...originalList.tasks, newTask] }),
- };
- },
- };
-
- const query = gql`
- query todos($id: ID!) {
- todo_list(id: $id) {
- id, title, tasks { id, text, completed }
- }
- }
- `;
-
- const data = {
- todo_list: { id: '123', title: 'how to apollo', tasks: [] },
- };
-
- const networkInterface = mockNetworkInterface(
- { request: { query, variables: { id: '123' } }, result: { data } },
- { request: { query: mutation }, result: { data: mutationData } }
- );
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- let count = 0;
- @graphql(query)
- @graphql(mutation, { options: () => ({ optimisticResponse, updateQueries }) })
- class Container extends React.Component {
- componentWillReceiveProps(props) {
- if (!props.data.todo_list) return;
- if (!props.data.todo_list.tasks.length) {
- props.mutate()
- .then(result => {
- expect(result.data).toEqual(mutationData);
- });
-
- const dataInStore = client.queryManager.getDataWithOptimisticResults();
- expect(dataInStore['$ROOT_MUTATION.createTodo']).toEqual(
- optimisticResponse.createTodo
- );
- return;
- }
-
- if (count === 0) {
- count ++;
- expect(props.data.todo_list.tasks).toEqual([optimisticResponse.createTodo]);
- } else if (count === 1) {
- expect(props.data.todo_list.tasks).toEqual([mutationData.createTodo]);
- done();
- }
- }
- render() {
- return null;
- }
- };
-
- renderer.create();
- });
-
- // This is a long test that keeps track of a lot of stuff. It is testing
- // whether or not the `updateQueries` reducers will run even when a given
- // container component is unmounted.
- //
- // It does this with the following procedure:
- //
- // 1. Mount a mutation component.
- // 2. Mount a query component.
- // 3. Run the mutation in the mutation component.
- // 4. Check the props in the query component.
- // 5. Unmount the query component.
- // 6. Run the mutation in the mutation component again.
- // 7. Remount the query component.
- // 8. Check the props in the query component to confirm that the mutation
- // that was run while we were unmounted changed the query component’s
- // props.
- //
- // There are also a lot more assertions on the way to make sure everything is
- // going as smoothly as planned.
- it('will run `updateQueries` for a previously mounted component', () => new Promise((resolve, reject) => {
- const mutation = gql`
- mutation createTodo {
- createTodo { id, text, completed }
- }
- `;
-
- const mutationData = {
- createTodo: {
- id: '99',
- text: 'This one was created with a mutation.',
- completed: true,
- },
- };
-
- let todoUpdateQueryCount = 0;
-
- const updateQueries = {
- todos: (previousQueryResult, { mutationResult, queryVariables }) => {
- todoUpdateQueryCount++;
-
- if (queryVariables.id !== '123') {
- // this isn't the query we updated, so just return the previous result
- return previousQueryResult;
- }
- // otherwise, create a new object with the same shape as the
- // previous result with the mutationResult incorporated
- const originalList = previousQueryResult.todo_list;
- const newTask = mutationResult.data.createTodo;
- return {
- todo_list: assign(originalList, { tasks: [...originalList.tasks, newTask] }),
- };
- },
- };
-
- const query = gql`
- query todos($id: ID!) {
- todo_list(id: $id) {
- id, title, tasks { id, text, completed }
- }
- }
- `;
-
- const data = {
- todo_list: { id: '123', title: 'how to apollo', tasks: [] },
- };
-
- const networkInterface = mockNetworkInterface(
- { request: { query, variables: { id: '123' } }, result: { data } },
- { request: { query: mutation }, result: { data: mutationData } },
- { request: { query: mutation }, result: { data: mutationData } },
- );
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- let mutate;
-
- @graphql(mutation, { options: () => ({ updateQueries }) })
- class Mutation extends React.Component {
- componentDidMount () {
- mutate = this.props.mutate;
- }
-
- render () {
- return null;
- }
- }
-
- let queryMountCount = 0;
- let queryUnmountCount = 0;
- let queryRenderCount = 0;
-
- @graphql(query)
- class Query extends React.Component {
- componentWillMount () {
- queryMountCount++;
- }
-
- componentWillUnmount () {
- queryUnmountCount++;
- }
-
- render () {
- try {
- switch (queryRenderCount++) {
- case 0:
- expect(this.props.data.loading).toBe(true);
- expect(this.props.data.todo_list).toBeFalsy();
- break;
- case 1:
- expect(this.props.data.todo_list).toEqual({
- id: '123',
- title: 'how to apollo',
- tasks: [],
- });
- break;
- case 2:
- expect(queryMountCount).toBe(1);
- expect(queryUnmountCount).toBe(0);
- expect(this.props.data.todo_list).toEqual({
- id: '123',
- title: 'how to apollo',
- tasks: [
- {
- id: '99',
- text: 'This one was created with a mutation.',
- completed: true,
- },
- ],
- });
- break;
- case 3:
- expect(queryMountCount).toBe(2);
- expect(queryUnmountCount).toBe(1);
- expect(this.props.data.todo_list).toEqual({
- id: '123',
- title: 'how to apollo',
- tasks: [
- {
- id: '99',
- text: 'This one was created with a mutation.',
- completed: true,
- },
- {
- id: '99',
- text: 'This one was created with a mutation.',
- completed: true,
- },
- ],
- });
- break;
- case 4:
- expect(this.props.data.todo_list).toEqual({
- id: '123',
- title: 'how to apollo',
- tasks: [
- {
- id: '99',
- text: 'This one was created with a mutation.',
- completed: true,
- },
- {
- id: '99',
- text: 'This one was created with a mutation.',
- completed: true,
- },
- ],
- });
- break;
- default:
- throw new Error('Rendered too many times');
- }
- } catch (error) {
- reject(error);
- }
- return null;
- }
- }
-
- const wrapperMutation = renderer.create(
-
-
-
- );
-
- const wrapperQuery1 = renderer.create(
-
-
-
- );
-
- setTimeout(() => {
- mutate();
-
- setTimeout(() => {
- try {
- expect(queryUnmountCount).toBe(0);
- wrapperQuery1.unmount();
- expect(queryUnmountCount).toBe(1);
- } catch (error) {
- reject(error);
- throw error;
- }
-
- setTimeout(() => {
- mutate();
-
- setTimeout(() => {
- const wrapperQuery2 = renderer.create(
-
-
-
- );
-
- setTimeout(() => {
- wrapperMutation.unmount();
- wrapperQuery2.unmount();
-
- try {
- expect(todoUpdateQueryCount).toBe(2);
- expect(queryMountCount).toBe(2);
- expect(queryUnmountCount).toBe(2);
- expect(queryRenderCount).toBe(5);
- resolve();
- } catch (error) {
- reject(error);
- throw error;
- }
- }, 5);
- }, 5);
- }, 5);
- }, 5);
- }, 5);
- }));
-
- it('will run `refetchQueries` for a recycled queries', () => new Promise((resolve, reject) => {
- const mutation = gql`
- mutation createTodo {
- createTodo { id, text, completed }
- }
- `;
-
- const mutationData = {
- createTodo: {
- id: '99',
- text: 'This one was created with a mutation.',
- completed: true,
- },
- };
-
- const query = gql`
- query todos($id: ID!) {
- todo_list(id: $id) {
- id, title, tasks { id, text, completed }
- }
- }
- `;
-
- const data = {
- todo_list: { id: '123', title: 'how to apollo', tasks: [] },
- };
-
- const updatedData = {
- todo_list: { id: '123', title: 'how to apollo', tasks: [mutationData.createTodo] },
- };
-
- const networkInterface = mockNetworkInterface(
- { request: { query, variables: { id: '123' } }, result: { data } },
- { request: { query: mutation }, result: { data: mutationData } },
- {
- request: { query, variables: { id: '123' } },
- result: { data: updatedData }
- },
- );
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- let mutate;
-
- @graphql(mutation, {})
- class Mutation extends React.Component {
- componentDidMount () {
- mutate = this.props.mutate;
- }
-
- render () {
- return null;
- }
- }
-
- let queryMountCount = 0;
- let queryUnmountCount = 0;
- let queryRenderCount = 0;
-
- @graphql(query)
- class Query extends React.Component {
- componentWillMount () {
- queryMountCount++;
- }
-
- componentWillUnmount () {
- queryUnmountCount++;
- }
-
- render () {
- try {
- switch (queryRenderCount++) {
- case 0:
- expect(this.props.data.loading).toBe(true);
- expect(this.props.data.todo_list).toBeFalsy();
- break;
- case 1:
- expect(this.props.data.loading).toBe(false);
- expect(this.props.data.todo_list).toEqual({
- id: '123',
- title: 'how to apollo',
- tasks: [],
- });
- break;
- case 2:
- expect(queryMountCount).toBe(2);
- expect(queryUnmountCount).toBe(1);
- expect(this.props.data.todo_list).toEqual(updatedData.todo_list);
- break;
- case 3:
- expect(queryMountCount).toBe(2);
- expect(queryUnmountCount).toBe(1);
- expect(this.props.data.todo_list).toEqual(updatedData.todo_list);
- break;
- default:
- throw new Error('Rendered too many times');
- }
- } catch (error) {
- reject(error);
- }
- return null;
- }
- }
-
- const wrapperMutation = renderer.create(
-
-
-
- );
-
- const wrapperQuery1 = renderer.create(
-
-
-
- );
-
- setTimeout(() => {
- wrapperQuery1.unmount();
-
- mutate({ refetchQueries: ['todos'] })
- .then((...args) => {
- setTimeout(() => {
- // This re-renders the recycled query that should have been refetched while recycled.
- const wrapperQuery2 = renderer.create(
-
-
-
- );
- resolve();
- }, 5);
- })
- .catch((error) => {
- reject(error);
- throw error;
- });
- }, 5);
- }));
-
-});
diff --git a/test/react-web/client/graphql/mutations/index.test.tsx b/test/react-web/client/graphql/mutations/index.test.tsx
new file mode 100644
index 0000000000..a27be657ae
--- /dev/null
+++ b/test/react-web/client/graphql/mutations/index.test.tsx
@@ -0,0 +1,128 @@
+
+import * as React from 'react';
+import * as renderer from 'react-test-renderer';
+import gql from 'graphql-tag';
+import assign = require('object-assign');
+
+import ApolloClient from 'apollo-client';
+
+declare function require(name: string);
+
+import { mockNetworkInterface } from '../../../../../src/test-utils';
+
+
+import { ApolloProvider, graphql } from '../../../../../src';
+
+describe('[mutations]', () => {
+
+ it('binds a mutation to props', () => {
+ const query = gql`mutation addPerson { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ const ContainerWithData = graphql(query)(({ mutate }) => {
+ expect(mutate).toBeTruthy();
+ expect(typeof mutate).toBe('function');
+ return null;
+ });
+
+ renderer.create();
+ });
+
+ it('binds a mutation to custom props', () => {
+ const query = gql`mutation addPerson { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ const props = ({ ownProps, addPerson }) => ({
+ [ownProps.methodName]: (name: string) => addPerson({ variables: { name }}),
+ });
+
+ const ContainerWithData = graphql(query, { props })(({ test }) => {
+ expect(test).toBeTruthy();
+ expect(typeof test).toBe('function');
+ return null;
+ });
+
+ renderer.create();
+ });
+
+ it('does not swallow children errors', () => {
+ const query = gql`mutation addPerson { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+ let bar;
+ const ContainerWithData = graphql(query)(() => {
+ bar(); // this will throw
+ return null;
+ });
+
+ try {
+ renderer.create();
+ throw new Error();
+ } catch (e) {
+ expect(e.name).toMatch(/TypeError/);
+ }
+
+ });
+
+ it('can execute a mutation', (done) => {
+ const query = gql`mutation addPerson { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query)
+ class Container extends React.Component {
+ componentDidMount() {
+ this.props.mutate()
+ .then(result => {
+ expect(result.data).toEqual(data);
+ done();
+ })
+ ;
+ }
+ render() {
+ return null;
+ }
+ };
+
+ renderer.create();
+ });
+
+ it('can execute a mutation with variables from props', (done) => {
+ const query = gql`
+ mutation addPerson($id: Int) {
+ allPeople(id: $id) { people { name } }
+ }
+ `;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const variables = { id: 1 };
+ const networkInterface = mockNetworkInterface({
+ request: { query, variables },
+ result: { data },
+ });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query)
+ class Container extends React.Component {
+ componentDidMount() {
+ this.props.mutate()
+ .then(result => {
+ expect(result.data).toEqual(data);
+ done();
+ })
+ ;
+ }
+ render() {
+ return null;
+ }
+ };
+
+ renderer.create();
+ });
+
+});
diff --git a/test/react-web/client/graphql/mutations/lifecycle.test.tsx b/test/react-web/client/graphql/mutations/lifecycle.test.tsx
new file mode 100644
index 0000000000..636626bbfb
--- /dev/null
+++ b/test/react-web/client/graphql/mutations/lifecycle.test.tsx
@@ -0,0 +1,141 @@
+
+import * as React from 'react';
+import * as renderer from 'react-test-renderer';
+import gql from 'graphql-tag';
+import assign = require('object-assign');
+
+import ApolloClient from 'apollo-client';
+
+declare function require(name: string);
+
+import { mockNetworkInterface } from '../../../../../src/test-utils';
+
+
+import { ApolloProvider, graphql } from '../../../../../src';
+
+describe('[mutations] lifecycle', () => {
+
+ it('allows falsy values in the mapped variables from props', (done) => {
+ const query = gql`
+ mutation addPerson($id: Int) {
+ allPeople(id: $id) { people { name } }
+ }
+ `;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const variables = { id: null };
+ const networkInterface = mockNetworkInterface({
+ request: { query, variables },
+ result: { data },
+ });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query)
+ class Container extends React.Component {
+ componentDidMount() {
+ this.props.mutate()
+ .then(result => {
+ expect(result.data).toEqual(data);
+ done();
+ })
+ ;
+ }
+ render() {
+ return null;
+ }
+ };
+
+ renderer.create();
+ });
+
+ it('errors if the passed props don\'t contain the needed variables', () => {
+ const query = gql`
+ mutation addPerson($first: Int) {
+ allPeople(first: $first) { people { name } }
+ }
+ `;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const variables = { first: 1 };
+ const networkInterface = mockNetworkInterface({
+ request: { query, variables },
+ result: { data },
+ });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+ const Container = graphql(query)(() => null);
+
+ try {
+ renderer.create();
+ } catch (e) {
+ expect(e).toMatch(/Invariant Violation: The operation 'addPerson'/);
+ }
+
+ });
+
+ it('rebuilds the mutation on prop change when using `options`', (done) => {
+ const query = gql`mutation addPerson { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ function options(props) {
+ // expect(props.listId).toBe(2);
+ return {};
+ };
+
+ @graphql(query, { options })
+ class Container extends React.Component {
+ componentWillReceiveProps(props) {
+ if (props.listId !== 2) return;
+ props.mutate().then(x => done());
+ }
+ render() {
+ return null;
+ }
+ };
+ class ChangingProps extends React.Component {
+ state = { listId: 1 };
+
+ componentDidMount() {
+ setTimeout(() => this.setState({ listId: 2 }), 50);
+ }
+
+ render() {
+ return ;
+ }
+ }
+
+ renderer.create();
+ });
+
+ it('can execute a mutation with custom variables', (done) => {
+ const query = gql`
+ mutation addPerson($id: Int) {
+ allPeople(id: $id) { people { name } }
+ }
+ `;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const variables = { id: 1 };
+ const networkInterface = mockNetworkInterface({
+ request: { query, variables },
+ result: { data },
+ });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query)
+ class Container extends React.Component {
+ componentDidMount() {
+ this.props.mutate({ variables: { id: 1 } })
+ .then(result => {
+ expect(result.data).toEqual(data);
+ done();
+ })
+ ;
+ }
+ render() {
+ return null;
+ }
+ };
+
+ renderer.create();
+ });
+
+});
diff --git a/test/react-web/client/graphql/mutations/queries.test.tsx b/test/react-web/client/graphql/mutations/queries.test.tsx
new file mode 100644
index 0000000000..589715c037
--- /dev/null
+++ b/test/react-web/client/graphql/mutations/queries.test.tsx
@@ -0,0 +1,164 @@
+
+import * as React from 'react';
+import * as renderer from 'react-test-renderer';
+import gql from 'graphql-tag';
+import assign = require('object-assign');
+
+import ApolloClient from 'apollo-client';
+
+declare function require(name: string);
+
+import { mockNetworkInterface } from '../../../../../src/test-utils';
+
+
+import { ApolloProvider, graphql } from '../../../../../src';
+
+describe('[mutations] query integration', () => {
+
+ it('allows for passing optimisticResponse for a mutation', (done) => {
+ const query = gql`
+ mutation createTodo {
+ createTodo { id, text, completed, __typename }
+ __typename
+ }
+ `;
+
+ const data = {
+ __typename: 'Mutation',
+ createTodo: {
+ __typename: 'Todo',
+ id: '99',
+ text: 'This one was created with a mutation.',
+ completed: true,
+ },
+ };
+
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query)
+ class Container extends React.Component {
+ componentDidMount() {
+ const optimisticResponse = {
+ __typename: 'Mutation',
+ createTodo: {
+ __typename: 'Todo',
+ id: '99',
+ text: 'Optimistically generated',
+ completed: true,
+ },
+ };
+ this.props.mutate({ optimisticResponse })
+ .then(result => {
+ expect(result.data).toEqual(data);
+ done();
+ })
+ ;
+
+ const dataInStore = client.queryManager.getDataWithOptimisticResults();
+ expect(dataInStore['Todo:99']).toEqual(
+ optimisticResponse.createTodo
+ );
+
+ }
+ render() {
+ return null;
+ }
+ };
+
+ renderer.create();
+ });
+
+ it('allows for updating queries from a mutation', (done) => {
+ const mutation = gql`
+ mutation createTodo {
+ createTodo { id, text, completed }
+ }
+ `;
+
+ const mutationData = {
+ createTodo: {
+ id: '99',
+ text: 'This one was created with a mutation.',
+ completed: true,
+ },
+ };
+
+ const optimisticResponse = {
+ createTodo: {
+ id: '99',
+ text: 'Optimistically generated',
+ completed: true,
+ },
+ };
+
+ const updateQueries = {
+ todos: (previousQueryResult, { mutationResult, queryVariables }) => {
+ if (queryVariables.id !== '123') {
+ // this isn't the query we updated, so just return the previous result
+ return previousQueryResult;
+ }
+ // otherwise, create a new object with the same shape as the
+ // previous result with the mutationResult incorporated
+ const originalList = previousQueryResult.todo_list;
+ const newTask = mutationResult.data.createTodo;
+ return {
+ todo_list: assign(originalList, { tasks: [...originalList.tasks, newTask] }),
+ };
+ },
+ };
+
+ const query = gql`
+ query todos($id: ID!) {
+ todo_list(id: $id) {
+ id, title, tasks { id, text, completed }
+ }
+ }
+ `;
+
+ const data = {
+ todo_list: { id: '123', title: 'how to apollo', tasks: [] },
+ };
+
+ const networkInterface = mockNetworkInterface(
+ { request: { query, variables: { id: '123' } }, result: { data } },
+ { request: { query: mutation }, result: { data: mutationData } }
+ );
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ let count = 0;
+ @graphql(query)
+ @graphql(mutation, { options: () => ({ optimisticResponse, updateQueries }) })
+ class Container extends React.Component {
+ componentWillReceiveProps(props) {
+ if (!props.data.todo_list) return;
+ if (!props.data.todo_list.tasks.length) {
+ props.mutate()
+ .then(result => {
+ expect(result.data).toEqual(mutationData);
+ });
+
+ const dataInStore = client.queryManager.getDataWithOptimisticResults();
+ expect(dataInStore['$ROOT_MUTATION.createTodo']).toEqual(
+ optimisticResponse.createTodo
+ );
+ return;
+ }
+
+ if (count === 0) {
+ count ++;
+ expect(props.data.todo_list.tasks).toEqual([optimisticResponse.createTodo]);
+ } else if (count === 1) {
+ expect(props.data.todo_list.tasks).toEqual([mutationData.createTodo]);
+ done();
+ }
+ }
+ render() {
+ return null;
+ }
+ };
+
+ renderer.create();
+ });
+
+});
diff --git a/test/react-web/client/graphql/mutations/recycled-queries.test.tsx b/test/react-web/client/graphql/mutations/recycled-queries.test.tsx
new file mode 100644
index 0000000000..fc5c59ae44
--- /dev/null
+++ b/test/react-web/client/graphql/mutations/recycled-queries.test.tsx
@@ -0,0 +1,387 @@
+
+import * as React from 'react';
+import * as renderer from 'react-test-renderer';
+import gql from 'graphql-tag';
+import assign = require('object-assign');
+
+import ApolloClient from 'apollo-client';
+
+declare function require(name: string);
+
+import { mockNetworkInterface } from '../../../../../src/test-utils';
+
+
+import { ApolloProvider, graphql } from '../../../../../src';
+
+describe('[mutations] update queries', () => {
+ // This is a long test that keeps track of a lot of stuff. It is testing
+ // whether or not the `updateQueries` reducers will run even when a given
+ // container component is unmounted.
+ //
+ // It does this with the following procedure:
+ //
+ // 1. Mount a mutation component.
+ // 2. Mount a query component.
+ // 3. Run the mutation in the mutation component.
+ // 4. Check the props in the query component.
+ // 5. Unmount the query component.
+ // 6. Run the mutation in the mutation component again.
+ // 7. Remount the query component.
+ // 8. Check the props in the query component to confirm that the mutation
+ // that was run while we were unmounted changed the query component’s
+ // props.
+ //
+ // There are also a lot more assertions on the way to make sure everything is
+ // going as smoothly as planned.
+ it('will run `updateQueries` for a previously mounted component', () => new Promise((resolve, reject) => {
+ const mutation = gql`
+ mutation createTodo {
+ createTodo { id, text, completed }
+ }
+ `;
+
+ const mutationData = {
+ createTodo: {
+ id: '99',
+ text: 'This one was created with a mutation.',
+ completed: true,
+ },
+ };
+
+ let todoUpdateQueryCount = 0;
+
+ const updateQueries = {
+ todos: (previousQueryResult, { mutationResult, queryVariables }) => {
+ todoUpdateQueryCount++;
+
+ if (queryVariables.id !== '123') {
+ // this isn't the query we updated, so just return the previous result
+ return previousQueryResult;
+ }
+ // otherwise, create a new object with the same shape as the
+ // previous result with the mutationResult incorporated
+ const originalList = previousQueryResult.todo_list;
+ const newTask = mutationResult.data.createTodo;
+ return {
+ todo_list: assign(originalList, { tasks: [...originalList.tasks, newTask] }),
+ };
+ },
+ };
+
+ const query = gql`
+ query todos($id: ID!) {
+ todo_list(id: $id) {
+ id, title, tasks { id, text, completed }
+ }
+ }
+ `;
+
+ const data = {
+ todo_list: { id: '123', title: 'how to apollo', tasks: [] },
+ };
+
+ const networkInterface = mockNetworkInterface(
+ { request: { query, variables: { id: '123' } }, result: { data } },
+ { request: { query: mutation }, result: { data: mutationData } },
+ { request: { query: mutation }, result: { data: mutationData } },
+ );
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ let mutate;
+
+ @graphql(mutation, { options: () => ({ updateQueries }) })
+ class Mutation extends React.Component {
+ componentDidMount () {
+ mutate = this.props.mutate;
+ }
+
+ render () {
+ return null;
+ }
+ }
+
+ let queryMountCount = 0;
+ let queryUnmountCount = 0;
+ let queryRenderCount = 0;
+
+ @graphql(query)
+ class Query extends React.Component {
+ componentWillMount () {
+ queryMountCount++;
+ }
+
+ componentWillUnmount () {
+ queryUnmountCount++;
+ }
+
+ render () {
+ try {
+ switch (queryRenderCount++) {
+ case 0:
+ expect(this.props.data.loading).toBe(true);
+ expect(this.props.data.todo_list).toBeFalsy();
+ break;
+ case 1:
+ expect(this.props.data.todo_list).toEqual({
+ id: '123',
+ title: 'how to apollo',
+ tasks: [],
+ });
+ break;
+ case 2:
+ expect(queryMountCount).toBe(1);
+ expect(queryUnmountCount).toBe(0);
+ expect(this.props.data.todo_list).toEqual({
+ id: '123',
+ title: 'how to apollo',
+ tasks: [
+ {
+ id: '99',
+ text: 'This one was created with a mutation.',
+ completed: true,
+ },
+ ],
+ });
+ break;
+ case 3:
+ expect(queryMountCount).toBe(2);
+ expect(queryUnmountCount).toBe(1);
+ expect(this.props.data.todo_list).toEqual({
+ id: '123',
+ title: 'how to apollo',
+ tasks: [
+ {
+ id: '99',
+ text: 'This one was created with a mutation.',
+ completed: true,
+ },
+ {
+ id: '99',
+ text: 'This one was created with a mutation.',
+ completed: true,
+ },
+ ],
+ });
+ break;
+ case 4:
+ expect(this.props.data.todo_list).toEqual({
+ id: '123',
+ title: 'how to apollo',
+ tasks: [
+ {
+ id: '99',
+ text: 'This one was created with a mutation.',
+ completed: true,
+ },
+ {
+ id: '99',
+ text: 'This one was created with a mutation.',
+ completed: true,
+ },
+ ],
+ });
+ break;
+ default:
+ throw new Error('Rendered too many times');
+ }
+ } catch (error) {
+ reject(error);
+ }
+ return null;
+ }
+ }
+
+ const wrapperMutation = renderer.create(
+
+
+
+ );
+
+ const wrapperQuery1 = renderer.create(
+
+
+
+ );
+
+ setTimeout(() => {
+ mutate();
+
+ setTimeout(() => {
+ try {
+ expect(queryUnmountCount).toBe(0);
+ wrapperQuery1.unmount();
+ expect(queryUnmountCount).toBe(1);
+ } catch (error) {
+ reject(error);
+ throw error;
+ }
+
+ setTimeout(() => {
+ mutate();
+
+ setTimeout(() => {
+ const wrapperQuery2 = renderer.create(
+
+
+
+ );
+
+ setTimeout(() => {
+ wrapperMutation.unmount();
+ wrapperQuery2.unmount();
+
+ try {
+ expect(todoUpdateQueryCount).toBe(2);
+ expect(queryMountCount).toBe(2);
+ expect(queryUnmountCount).toBe(2);
+ expect(queryRenderCount).toBe(5);
+ resolve();
+ } catch (error) {
+ reject(error);
+ throw error;
+ }
+ }, 5);
+ }, 5);
+ }, 5);
+ }, 6);
+ }, 5);
+ }));
+
+ it('will run `refetchQueries` for a recycled queries', () => new Promise((resolve, reject) => {
+ const mutation = gql`
+ mutation createTodo {
+ createTodo { id, text, completed }
+ }
+ `;
+
+ const mutationData = {
+ createTodo: {
+ id: '99',
+ text: 'This one was created with a mutation.',
+ completed: true,
+ },
+ };
+
+ const query = gql`
+ query todos($id: ID!) {
+ todo_list(id: $id) {
+ id, title, tasks { id, text, completed }
+ }
+ }
+ `;
+
+ const data = {
+ todo_list: { id: '123', title: 'how to apollo', tasks: [] },
+ };
+
+ const updatedData = {
+ todo_list: { id: '123', title: 'how to apollo', tasks: [mutationData.createTodo] },
+ };
+
+ const networkInterface = mockNetworkInterface(
+ { request: { query, variables: { id: '123' } }, result: { data } },
+ { request: { query: mutation }, result: { data: mutationData } },
+ {
+ request: { query, variables: { id: '123' } },
+ result: { data: updatedData }
+ },
+ );
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ let mutate;
+
+ @graphql(mutation, {})
+ class Mutation extends React.Component {
+ componentDidMount () {
+ mutate = this.props.mutate;
+ }
+
+ render () {
+ return null;
+ }
+ }
+
+ let queryMountCount = 0;
+ let queryUnmountCount = 0;
+ let queryRenderCount = 0;
+
+ @graphql(query)
+ class Query extends React.Component {
+ componentWillMount () {
+ queryMountCount++;
+ }
+
+ componentWillUnmount () {
+ queryUnmountCount++;
+ }
+
+ render () {
+ try {
+ switch (queryRenderCount++) {
+ case 0:
+ expect(this.props.data.loading).toBe(true);
+ expect(this.props.data.todo_list).toBeFalsy();
+ break;
+ case 1:
+ expect(this.props.data.loading).toBe(false);
+ expect(this.props.data.todo_list).toEqual({
+ id: '123',
+ title: 'how to apollo',
+ tasks: [],
+ });
+ break;
+ case 2:
+ expect(queryMountCount).toBe(2);
+ expect(queryUnmountCount).toBe(1);
+ expect(this.props.data.todo_list).toEqual(updatedData.todo_list);
+ break;
+ case 3:
+ expect(queryMountCount).toBe(2);
+ expect(queryUnmountCount).toBe(1);
+ expect(this.props.data.todo_list).toEqual(updatedData.todo_list);
+ break;
+ default:
+ throw new Error('Rendered too many times');
+ }
+ } catch (error) {
+ reject(error);
+ }
+ return null;
+ }
+ }
+
+ const wrapperMutation = renderer.create(
+
+
+
+ );
+
+ const wrapperQuery1 = renderer.create(
+
+
+
+ );
+
+ setTimeout(() => {
+ wrapperQuery1.unmount();
+
+ mutate({ refetchQueries: ['todos'] })
+ .then((...args) => {
+ setTimeout(() => {
+ // This re-renders the recycled query that should have been refetched while recycled.
+ const wrapperQuery2 = renderer.create(
+
+
+
+ );
+ resolve();
+ }, 5);
+ })
+ .catch((error) => {
+ reject(error);
+ throw error;
+ });
+ }, 5);
+ }));
+
+});
diff --git a/test/react-web/client/graphql/queries.test.tsx b/test/react-web/client/graphql/queries.test.tsx
deleted file mode 100644
index d01f0cc584..0000000000
--- a/test/react-web/client/graphql/queries.test.tsx
+++ /dev/null
@@ -1,2633 +0,0 @@
-///
-
-import * as React from 'react';
-import * as PropTypes from 'prop-types';
-import * as ReactDOM from 'react-dom';
-import * as renderer from 'react-test-renderer';
-import { mount } from 'enzyme';
-import gql from 'graphql-tag';
-import ApolloClient, { ApolloError, ObservableQuery } from 'apollo-client';
-import { NetworkInterface } from 'apollo-client/transport/networkInterface';
-import { connect } from 'react-redux';
-import { withState } from 'recompose';
-
-declare function require(name: string);
-
-import { mockNetworkInterface } from '../../../../src/test-utils';
-import { ApolloProvider, graphql} from '../../../../src';
-
-// XXX: this is also defined in apollo-client
-// I'm not sure why mocha doesn't provide something like this, you can't
-// always use promises
-const wrap = (done: Function, cb: (...args: any[]) => any) => (...args: any[]) => {
- try {
- return cb(...args);
- } catch (e) {
- done(e);
- }
-};
-
-function wait(ms) {
- return new Promise(resolve => setTimeout(() => resolve(), ms));
-}
-
-describe('queries', () => {
-
- it('binds a query to props', () => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- const ContainerWithData = graphql(query)(({ data }) => { // tslint:disable-line
- expect(data).toBeTruthy();
- expect(data.ownProps).toBeFalsy();
- expect(data.loading).toBe(true);
- return null;
- });
-
- const output = renderer.create();
- output.unmount();
- });
-
- it("shouldn't warn about fragments", () => {
- const oldWarn = console.warn;
- const warnings = [];
- console.warn = (str) => warnings.push(str);
-
- try {
- graphql(gql`query foo { bar }`);
- expect(warnings.length).toEqual(0);
- } finally {
- console.warn = oldWarn;
- }
- });
-
- it('includes the variables in the props', () => {
- const query = gql`query people ($first: Int) { allPeople(first: $first) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const variables = { first: 1 };
- const networkInterface = mockNetworkInterface(
- { request: { query, variables }, result: { data } }
- );
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- const ContainerWithData = graphql(query)(({ data }) => { // tslint:disable-line
- expect(data).toBeTruthy();;
- expect(data.variables).toEqual(variables);
- return null;
- });
-
- renderer.create();
- });
-
- it('does not swallow children errors', () => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
- let bar;
- const ContainerWithData = graphql(query)(() => {
- bar(); // this will throw
- return null;
- });
-
- try {
- renderer.create();
- throw new Error();
- } catch (e) {
- expect(e.name).toMatch(/TypeError/);
- }
-
- });
-
- it('executes a query', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query)
- class Container extends React.Component {
- componentWillReceiveProps(props) {
- expect(props.data.loading).toBe(false);
- expect(props.data.allPeople).toEqual(data.allPeople);
- done();
- }
- render() {
- return null;
- }
- };
-
- renderer.create();
- });
-
- it('executes a query with two root fields', (done) => {
- const query = gql`query people {
- allPeople(first: 1) { people { name } }
- otherPeople(first: 1) { people { name } }
- }`;
- const data = {
- allPeople: { people: [ { name: 'Luke Skywalker' } ] },
- otherPeople: { people: [ { name: 'Luke Skywalker' } ] },
- };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query)
- class Container extends React.Component {
- componentWillReceiveProps(props) {
- expect(props.data.loading).toBe(false);
- expect(props.data.allPeople).toEqual(data.allPeople);
- expect(props.data.otherPeople).toEqual(data.otherPeople);
- done();
- }
- render() {
- return null;
- }
- };
-
- renderer.create();
- });
-
- it('can unmount without error', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- const ContainerWithData = graphql(query)(() => null);
-
- const wrapper = renderer.create(
-
- ) as any;
-
- try {
- wrapper.unmount();
- done();
- } catch (e) { throw new Error(e); }
- });
-
- it('passes any GraphQL errors in props', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const networkInterface = mockNetworkInterface({ request: { query }, error: new Error('boo') });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query)
- class ErrorContainer extends React.Component {
- componentWillReceiveProps({ data }) { // tslint:disable-line
- expect(data.error).toBeTruthy();
- expect(data.error.networkError).toBeTruthy();
- // expect(data.error instanceof ApolloError).toBe(true);
- done();
- }
- render() {
- return null;
- }
- };
-
- renderer.create();
- });
-
- describe('uncaught exceptions', () => {
- let unhandled = [];
- function handle(reason) {
- unhandled.push(reason);
- }
- beforeEach(() => {
- unhandled = [];
- process.on('unhandledRejection', handle);
- });
- afterEach(() => {
- process.removeListener('unhandledRejection', handle);
- });
-
- it('does not log when you change variables resulting in an error', (done) => {
- const query = gql`query people($var: Int) { allPeople(first: $var) { people { name } } }`;
- const var1 = { var: 1 };
- const data = { allPeople : { people: { name: 'Luke Skywalker' } } };
- const var2 = { var: 2 };
- const networkInterface = mockNetworkInterface({
- request: { query, variables: var1 }, result: { data },
- }, {
- request: { query, variables: var2 }, error: new Error('boo'),
- });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- let iteration = 0;
- @withState('var', 'setVar', 1)
- @graphql(query)
- class ErrorContainer extends React.Component {
- componentWillReceiveProps(props) { // tslint:disable-line
- iteration += 1;
- if (iteration === 1) {
- expect(props.data.allPeople).toEqual(data.allPeople)
- props.setVar(2);
- } else if (iteration === 2) {
- expect(props.data.loading).toBeTruthy();
- } else if (iteration === 3) {
- expect(props.data.error).toBeTruthy();
- expect(props.data.error.networkError).toBeTruthy();
- // We need to set a timeout to ensure the unhandled rejection is swept up
- setTimeout(() => {
- expect(unhandled.length).toEqual(0);
- done()
- }, 0);
- }
- }
- render() {
- return null;
- }
- };
-
- renderer.create();
- });
- });
-
- it('maps props as variables if they match', (done) => {
- const query = gql`
- query people($first: Int) {
- allPeople(first: $first) { people { name } }
- }
- `;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const variables = { first: 1 };
- const networkInterface = mockNetworkInterface({
- request: { query, variables },
- result: { data },
- });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query)
- class Container extends React.Component {
- componentWillReceiveProps(props) {
- expect(props.data.loading).toBe(false);
- expect(props.data.allPeople).toEqual(data.allPeople);
- expect(props.data.variables).toEqual(this.props.data.variables);
- done();
- }
- render() {
- return null;
- }
- };
-
- renderer.create();
- });
-
- it('allows falsy values in the mapped variables from props', (done) => {
- const query = gql`
- query people($first: Int) {
- allPeople(first: $first) { people { name } }
- }
- `;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const variables = { first: null };
- const networkInterface = mockNetworkInterface({
- request: { query, variables },
- result: { data },
- });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query)
- class Container extends React.Component {
- componentWillReceiveProps(props) {
- expect(props.data.loading).toBe(false);
- expect(props.data.allPeople).toEqual(data.allPeople);
- done();
- }
- render() {
- return null;
- }
- };
-
- renderer.create();
- });
-
- it('don\'t error on optional required props', () => {
- const query = gql`
- query people($first: Int) {
- allPeople(first: $first) { people { name } }
- }
- `;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const variables = { first: 1 };
- const networkInterface = mockNetworkInterface({
- request: { query, variables },
- result: { data },
- });
- const client = new ApolloClient({ networkInterface, addTypename: false });
- const Container = graphql(query)(() => null);
-
- let error = null;
- try {
- renderer.create();
- } catch (e) { error = e; }
-
- expect(error).toBeNull();
-
- });
-
- it('errors if the passed props don\'t contain the needed variables', () => {
- const query = gql`
- query people($first: Int!) {
- allPeople(first: $first) { people { name } }
- }
- `;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const variables = { first: 1 };
- const networkInterface = mockNetworkInterface({
- request: { query, variables },
- result: { data },
- });
- const client = new ApolloClient({ networkInterface, addTypename: false });
- const Container = graphql(query)(() => null);
-
- try {
- renderer.create();
- } catch (e) {
- expect(e.name).toMatch(/Invariant Violation/);
- expect(e.message).toMatch(/The operation 'people'/);
- }
-
- });
-
- it('rebuilds the queries on prop change when using `options`', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
-
- let firstRun = true;
- let isDone = false;
- function options(props) {
- if (!firstRun) {
- expect(props.listId).toBe(2);
- if (!isDone) done();
- isDone = true;
- }
- return {};
- };
-
- const Container = graphql(query, { options })((props) => null);
-
- class ChangingProps extends React.Component {
- state = { listId: 1 };
-
- componentDidMount() {
- setTimeout(() => {
- firstRun = false;
- this.setState({ listId: 2 });
- }, 50);
- }
-
- render() {
- return ;
- }
- }
-
- renderer.create();
- });
-
- it('allows you to skip a query (deprecated)', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- let queryExecuted;
- @graphql(query, { options: () => ({ skip: true }) })
- class Container extends React.Component {
- componentWillReceiveProps(props) {
- queryExecuted = true;
- }
- render() {
- expect(this.props.data).toBeUndefined();
- return null;
- }
- };
-
- renderer.create();
-
- setTimeout(() => {
- if (!queryExecuted) { done(); return; }
- fail(new Error('query ran even though skip present'));
- }, 25);
- });
-
- it('allows you to skip a query without running it', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- let queryExecuted;
- @graphql(query, { skip: ({ skip }) => skip })
- class Container extends React.Component {
- componentWillReceiveProps(props) {
- queryExecuted = true;
- }
- render() {
- expect(this.props.data).toBeUndefined();
- return null;
- }
- };
-
- renderer.create();
-
- setTimeout(() => {
- if (!queryExecuted) { done(); return; }
- fail(new Error('query ran even though skip present'));
- }, 25);
- });
-
- it('continues to not subscribe to a skipped query when props change', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const networkInterface = mockNetworkInterface();
- const oldQuery = networkInterface.query;
-
- networkInterface.query = function (request) {
- fail(new Error('query ran even though skip present'));
- return oldQuery.call(this, request);
- };
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query, { skip: true })
- class Container extends React.Component {8
- componentWillReceiveProps(props) {
- done();
- }
- render() {
- return null;
- }
- };
-
- class Parent extends React.Component {
- constructor() {
- super();
- this.state = { foo: 42 };
- }
- componentDidMount() {
- this.setState({ foo: 43 });
- }
- render() {
- return ;
- }
- };
-
- renderer.create();
- });
-
- it('doesn\'t run options or props when skipped', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- let queryExecuted;
- @graphql(query, {
- skip: ({ skip }) => skip,
- options: ({ willThrowIfAccesed }) => ({ pollInterval: willThrowIfAccesed.pollInterval }),
- props: ({ willThrowIfAccesed }) => ({ pollInterval: willThrowIfAccesed.pollInterval }),
- })
- class Container extends React.Component {
- componentWillReceiveProps(props) {
- queryExecuted = true;
- }
- render() {
- expect(this.props.data).toBeFalsy();
- return null;
- }
- };
-
- renderer.create();
-
- setTimeout(() => {
- if (!queryExecuted) { done(); return; }
- fail(new Error('query ran even though skip present'));
- }, 25);
- });
-
- it('allows you to skip a query without running it (alternate syntax)', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- let queryExecuted;
- @graphql(query, { skip: true })
- class Container extends React.Component {
- componentWillReceiveProps(props) {
- queryExecuted = true;
- }
- render() {
- expect(this.props.data).toBeFalsy();
- return null;
- }
- };
-
- renderer.create();
-
- setTimeout(() => {
- if (!queryExecuted) { done(); return; }
- fail(new Error('query ran even though skip present'));
- }, 25);
- });
-
- // test the case of skip:false -> skip:true -> skip:false to make sure things
- // are cleaned up properly
- it('allows you to skip then unskip a query with top-level syntax', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- let hasSkipped = false;
- @graphql(query, { skip: ({ skip }) => skip })
- class Container extends React.Component {8
- componentWillReceiveProps(newProps) {
- if (newProps.skip) {
- hasSkipped = true;
- this.props.setSkip(false);
- } else {
- if (hasSkipped) {
- done();
- } else {
- this.props.setSkip(true);
- }
- }
- }
- render() {
- return null;
- }
- };
-
- class Parent extends React.Component {
- constructor() {
- super();
- this.state = { skip: false };
- }
- render() {
- return this.setState({ skip })} />;
- }
- };
-
- renderer.create();
- });
-
- it('allows you to skip then unskip a query with new options (top-level syntax)', (done) => {
- const query = gql`query people($first: Int) { allPeople(first: $first) { people { name } } }`;
- const dataOne = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const dataTwo = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
- const networkInterface = mockNetworkInterface(
- { request: { query, variables: { first: 1 } }, result: { data: dataOne } },
- { request: { query, variables: { first: 2 } }, result: { data: dataTwo } },
- { request: { query, variables: { first: 2 } }, result: { data: dataTwo } },
- );
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- let hasSkipped = false;
- @graphql(query, { skip: ({ skip }) => skip })
- class Container extends React.Component {8
- componentWillReceiveProps(newProps) {
- if (newProps.skip) {
- hasSkipped = true;
- // change back to skip: false, with a different variable
- this.props.setState({ skip: false, first: 2 });
- } else {
- if (hasSkipped) {
- if (!newProps.data.loading) {
- expect(newProps.data.allPeople).toEqual(dataTwo.allPeople);
- done();
- }
- } else {
- expect(newProps.data.allPeople).toEqual(dataOne.allPeople);
- this.props.setState({ skip: true });
- }
- }
- }
- render() {
- return null;
- }
- };
-
- class Parent extends React.Component {
- constructor() {
- super();
- this.state = { skip: false, first: 1 };
- }
- render() {
- return (
- this.setState(state)}
- />
- );
- }
- };
-
- renderer.create();
- });
-
- it('allows you to skip then unskip a query with opts syntax', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const nextData = { allPeople: { people: [ { name: 'Anakin Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({
- request: { query }, result: { data }, newData: () => ({ data: nextData }) });
- const oldQuery = networkInterface.query;
-
- let ranQuery = 0;
- networkInterface.query = function (request) {
- ranQuery++;
- return oldQuery.call(this, request);
- };
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- let hasSkipped = false;
- let hasRequeried = false;
- @graphql(query, { options: ({ skip }) => ({ skip, fetchPolicy: 'network-only' }) })
- class Container extends React.Component {8
- componentWillReceiveProps(newProps) {
- if (newProps.skip) {
- // Step 2. We shouldn't query again.
- expect(ranQuery).toBe(1);
- hasSkipped = true;
- this.props.setSkip(false);
- } else if (hasRequeried) {
- // Step 4. We need to actually get the data from the query into the component!
- expect(newProps.data.loading).toBe(false);
- done();
- } else if (hasSkipped) {
- // Step 3. We need to query again!
- expect(newProps.data.loading).toBe(true);
- expect(ranQuery).toBe(2);
- hasRequeried = true;
- } else {
- // Step 1. We've queried once.
- expect(ranQuery).toBe(1);
- this.props.setSkip(true);
- }
- }
- render() {
- return null;
- }
- };
-
- class Parent extends React.Component {
- constructor() {
- super();
- this.state = { skip: false };
- }
- render() {
- return this.setState({ skip })} />;
- }
- };
-
- renderer.create();
- });
-
-
- it('removes the injected props if skip becomes true', (done) => {
- let count = 0;
- const query = gql`
- query people($first: Int) {
- allPeople(first: $first) { people { name } }
- }
- `;
-
- const data1 = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const variables1 = { first: 1 };
-
- const data2 = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
- const variables2 = { first: 2 };
-
-
- const data3 = { allPeople: { people: [ { name: 'Anakin Skywalker' } ] } };
- const variables3 = { first: 3 };
-
- const networkInterface = mockNetworkInterface(
- { request: { query, variables: variables1 }, result: { data: data1 } },
- { request: { query, variables: variables2 }, result: { data: data2 } },
- { request: { query, variables: variables3 }, result: { data: data2 } }
- );
-
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query, {
- skip: () => count === 1,
- options: (props) => ({ variables: props }),
- })
- class Container extends React.Component {
- componentWillReceiveProps({ data }) {
- // loading is true, but data still there
- if (count === 0) expect(data.allPeople).toEqual(data1.allPeople);
- if (count === 1 ) expect(data).toBeFalsy();
- if (count === 2 && data.loading) expect(data.allPeople).toBeFalsy();
- if (count === 2 && !data.loading) {
- expect(data.allPeople).toEqual(data2.allPeople);
- done();
- }
- }
- render() {
- return null;
- }
- };
-
- class ChangingProps extends React.Component {
- state = { first: 1 };
-
- componentDidMount() {
- setTimeout(() => {
- count++;
- this.setState({ first: 2 });
- }, 50);
-
- setTimeout(() => {
- count++;
- this.setState({ first: 3 });
- }, 100);
- }
-
- render() {
- return ;
- }
- }
-
- renderer.create();
- });
-
-
- it('allows you to unmount a skipped query', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const networkInterface = mockNetworkInterface();
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query, {
- skip: true,
- })
- class Container extends React.Component {
- componentDidMount() {
- this.props.hide();
- }
- componentWillUnmount() {
- done();
- }
- render() {
- return null;
- }
- };
-
- class Hider extends React.Component {
- constructor() {
- super();
- this.state = { hide: false };
- }
- render() {
- if (this.state.hide) {
- return null;
- }
- return this.setState({ hide: true })} />;
- }
- }
-
- renderer.create();
- });
-
-
- it('reruns the query if it changes', (done) => {
- let count = 0;
- const query = gql`
- query people($first: Int) {
- allPeople(first: $first) { people { name } }
- }
- `;
-
- const data1 = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const variables1 = { first: 1 };
-
- const data2 = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
- const variables2 = { first: 2 };
-
- const networkInterface = mockNetworkInterface(
- { request: { query, variables: variables1 }, result: { data: data1 } },
- { request: { query, variables: variables2 }, result: { data: data2 } }
- );
-
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query, {
- options: (props) => ({ variables: props, fetchPolicy: count === 0 ? 'cache-and-network' : 'cache-first' }),
- })
- class Container extends React.Component {
- componentWillReceiveProps({ data }) {
- // loading is true, but data still there
- if (count === 1 && data.loading) {
- expect(data.allPeople).toEqual(data1.allPeople);
- }
- if (count === 1 && !data.loading && this.props.data.loading) {
- expect(data.allPeople).toEqual(data2.allPeople);
- done();
- }
- }
- render() {
- return null;
- }
- };
-
- class ChangingProps extends React.Component {
- state = { first: 1 };
-
- componentDidMount() {
- setTimeout(() => {
- count++;
- this.setState({ first: 2 });
- }, 50);
- }
-
- render() {
- return ;
- }
- }
-
- renderer.create();
- });
-
- // XXX broken in AC 0.4.20
- // it('correctly unsubscribes', (done) => {
- // let count = 0;
- // const query = gql`
- // query people($first: Int) {
- // allPeople(first: $first) { people { name } }
- // }
- // `;
-
- // const data1 = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- // const variables1 = { first: 1 };
-
- // const data2 = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
- // const variables2 = { first: 2 };
-
- // const networkInterface = mockNetworkInterface(
- // { request: { query, variables: variables1 }, result: { data: data1 } },
- // { request: { query, variables: variables2 }, result: { data: data2 } }
- // );
-
- // const client = new ApolloClient({ networkInterface, addTypename: false });
- // const Container = graphql(query)(() => null);
-
- // @connect(state => ({ apollo: state.apollo }))
- // class ChangingProps extends React.Component {
- // state = { first: 1 };
-
- // componentDidMount() {
- // setTimeout(() => {
- // count++;
- // this.setState({ first: 0 });
- // }, 50);
-
- // setTimeout(() => {
- // count++;
- // this.setState({ first: 2 });
- // }, 50);
- // }
-
- // componentWillReceiveProps({ apollo: { queries } }) {
- // const queryNumber = Object.keys(queries).length;
- // if (count === 0) expect(queryNumber).toEqual(1);
- // if (count === 1) expect(queryNumber).toEqual(0);
- // if (count === 2) {
- // expect(queryNumber).toEqual(1);
- // done();
- // }
- // }
-
- // render() {
- // if (this.state.first === 0) return null;
- // return ;
- // }
- // }
-
- // renderer.create();
- // });
-
- it('reruns the query if just the variables change', (done) => {
- let count = 0;
- const query = gql`
- query people($first: Int) {
- allPeople(first: $first) { people { name } }
- }
- `;
-
- const data1 = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const variables1 = { first: 1 };
-
- const data2 = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
- const variables2 = { first: 2 };
-
- const networkInterface = mockNetworkInterface(
- { request: { query, variables: variables1 }, result: { data: data1 } },
- { request: { query, variables: variables2 }, result: { data: data2 } }
- );
-
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query, { options: (props) => ({ variables: props }) })
- class Container extends React.Component {
- componentWillReceiveProps({ data }) {
- // loading is true, but data still there
- if (count === 1 && data.loading) {
- expect(data.allPeople).toEqual(data1.allPeople);
- }
- if (count === 1 && !data.loading && this.props.data.loading) {
- expect(data.allPeople).toEqual(data2.allPeople);
- done();
- }
- }
- render() {
- return null;
- }
- };
-
- class ChangingProps extends React.Component {
- state = { first: 1 };
-
- componentDidMount() {
- setTimeout(() => {
- count++;
- this.setState({ first: 2 });
- }, 50);
- }
-
- render() {
- return ;
- }
- }
-
- renderer.create();
- });
-
- it('reruns the queries on prop change when using passed props', (done) => {
- let count = 0;
- const query = gql`
- query people($first: Int) {
- allPeople(first: $first) { people { name } }
- }
- `;
-
- const data1 = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const variables1 = { first: 1 };
-
- const data2 = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
- const variables2 = { first: 2 };
-
- const networkInterface = mockNetworkInterface(
- { request: { query, variables: variables1 }, result: { data: data1 } },
- { request: { query, variables: variables2 }, result: { data: data2 } }
- );
-
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query)
- class Container extends React.Component {
- componentWillReceiveProps({ data }) {
- // loading is true, but data still there
- if (count === 1 && data.loading) {
- expect(data.allPeople).toEqual(data1.allPeople);
- }
- if (count === 1 && !data.loading && this.props.data.loading) {
- expect(data.allPeople).toEqual(data2.allPeople);
- done();
- }
- }
- render() {
- return null;
- }
- };
-
- class ChangingProps extends React.Component {
- state = { first: 1 };
-
- componentDidMount() {
- setTimeout(() => {
- count++;
- this.setState({ first: 2 });
- }, 50);
- }
-
- render() {
- return ;
- }
- }
-
- renderer.create();
- });
-
- it('stays subscribed to updates after irrelevant prop changes', (done) => {
- const query = gql`query people($first: Int) { allPeople(first: $first) { people { name } } }`;
- const variables = { first: 1 };
- const data1 = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const data2 = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
- const networkInterface = mockNetworkInterface(
- { request: { query, variables }, result: { data: data1 } },
- { request: { query, variables }, result: { data: data2 } },
- );
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- let count = 0;
- @graphql(query, { options() { return { variables, notifyOnNetworkStatusChange: false }; } })
- class Container extends React.Component {8
- componentWillReceiveProps(props) {
- count += 1;
-
- if (count == 1) {
- expect(props.foo).toEqual(42);
- expect(props.data.loading).toEqual(false);
- expect(props.data.allPeople).toEqual(data1.allPeople);
- props.changeState();
- } else if (count == 2) {
- expect(props.foo).toEqual(43);
- expect(props.data.loading).toEqual(false);
- expect(props.data.allPeople).toEqual(data1.allPeople);
- props.data.refetch();
- } else if (count == 3) {
- expect(props.foo).toEqual(43);
- expect(props.data.loading).toEqual(false);
- expect(props.data.allPeople).toEqual(data2.allPeople);
- done();
- }
- }
- render() {
- return null;
- }
- };
-
- class Parent extends React.Component {
- constructor() {
- super();
- this.state = { foo: 42 };
- }
- render() {
- return this.setState({ foo: 43 })}/>;
- }
- };
-
- renderer.create();
- });
-
- it('exposes refetch as part of the props api', (done) => {
- const query = gql`query people($first: Int) { allPeople(first: $first) { people { name } } }`;
- const variables = { first: 1 };
- const data1 = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface(
- { request: { query, variables }, result: { data: data1 } },
- { request: { query, variables }, result: { data: data1 } },
- { request: { query, variables: { first: 2 } }, result: { data: data1 } }
- );
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- let hasRefetched, count = 0;
- @graphql(query)
- class Container extends React.Component {
- componentWillMount(){
- expect(this.props.data.refetch).toBeTruthy();
- expect(this.props.data.refetch instanceof Function).toBe(true);
- }
- componentWillReceiveProps({ data }) { // tslint:disable-line
- if (count === 0) expect(data.loading).toBe(false); // first data
- if (count === 1) expect(data.loading).toBe(true); // first refetch
- if (count === 2) expect(data.loading).toBe(false); // second data
- if (count === 3) expect(data.loading).toBe(true); // second refetch
- if (count === 4) expect(data.loading).toBe(false); // third data
- count ++;
- if (hasRefetched) return;
- hasRefetched = true;
- expect(data.refetch).toBeTruthy();
- expect(data.refetch instanceof Function).toBe(true);
- data.refetch()
- .then(result => {
- expect(result.data).toEqual(data1);
- data.refetch({ first: 2 }) // new variables
- .then(response => {
- expect(response.data).toEqual(data1);
- expect(data.allPeople).toEqual(data1.allPeople);
- done();
- });
- })
- .catch(done);
- }
- render() {
- return null;
- }
- };
-
- renderer.create();
- });
-
- // Failing because fetchMore is not bound w/ createBoundRefetch either,
- // so no loading state
- it('exposes fetchMore as part of the props api', (done) => {
- const query = gql`
- query people($skip: Int, $first: Int) { allPeople(first: $first, skip: $skip) { people { name } } }
- `;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const data1 = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
- const variables = { skip: 1, first: 1 };
- const variables2 = { skip: 2, first: 1 };
-
- const networkInterface = mockNetworkInterface(
- { request: { query, variables }, result: { data } },
- { request: { query, variables: variables2 }, result: { data: data1 } }
- );
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- let count = 0;
- @graphql(query, { options: () => ({ variables }) })
- class Container extends React.Component {
- componentWillMount(){
- expect(this.props.data.fetchMore).toBeTruthy();
- expect(this.props.data.fetchMore instanceof Function).toBe(true);
- }
- componentWillReceiveProps = wrap(done, (props) => {
- if (count === 0) {
- expect(props.data.fetchMore).toBeTruthy();
- expect(props.data.fetchMore instanceof Function).toBe(true);
- props.data.fetchMore({
- variables: { skip: 2 },
- updateQuery: (prev, { fetchMoreResult }) => ({
- allPeople: {
- people: prev.allPeople.people.concat(fetchMoreResult.allPeople.people),
- },
- }),
- }).then(wrap(done, result => {
- expect(result.data.allPeople.people).toEqual(data1.allPeople.people);
- }));
- } else if (count === 1) {
- expect(props.data.variables).toEqual(variables);
- expect(props.data.loading).toBe(false);
- expect(props.data.allPeople.people).toEqual(
- data.allPeople.people.concat(data1.allPeople.people)
- );
- done();
- } else {
- throw new Error('should not reach this point');
- }
- count++;
- })
- render() {
- return null;
- }
- };
-
- renderer.create();
- });
-
- it('exposes stopPolling as part of the props api', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query)
- class Container extends React.Component {
- componentWillReceiveProps({ data }) { // tslint:disable-line
- expect(data.stopPolling).toBeTruthy();
- expect(data.stopPolling instanceof Function).toBe(true);
- expect(data.stopPolling).not.toThrow();
- done();
- }
- render() {
- return null;
- }
- };
-
- renderer.create();
- });
-
- it('exposes subscribeToMore as part of the props api', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query)
- class Container extends React.Component {
- componentWillReceiveProps({ data }) { // tslint:disable-line
- expect(data.subscribeToMore).toBeTruthy();
- expect(data.subscribeToMore instanceof Function).toBe(true);
- done();
- }
- render() {
- return null;
- }
- };
-
- renderer.create();
- });
-
- it('exposes startPolling as part of the props api', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
- let wrapper;
-
- // @graphql(query)
- @graphql(query, { options: { pollInterval: 10 }})
- class Container extends React.Component {
- componentWillReceiveProps({ data }) { // tslint:disable-line
- expect(data.startPolling).toBeTruthy();
- expect(data.startPolling instanceof Function).toBe(true);
- // XXX this does throw because of no pollInterval
- // expect(data.startPolling).not.toThrow();
- setTimeout(() => {
- wrapper.unmount();
- done();
- }, 0);
- }
- render() {
- return null;
- }
- };
-
- wrapper = renderer.create();
- });
-
- it('exposes networkStatus as a part of the props api', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query, { options: { notifyOnNetworkStatusChange: true }})
- class Container extends React.Component {
- componentWillReceiveProps({ data }) {
- expect(data.networkStatus).toBeTruthy();
- done();
- }
- render() {
- return null;
- }
- }
-
- renderer.create(
-
-
-
- );
- });
-
- it('should set the initial networkStatus to 1 (loading)', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query, { options: { notifyOnNetworkStatusChange: true }})
- class Container extends React.Component {
- constructor({ data: { networkStatus } }) {
- super();
- expect(networkStatus).toBe(1);
- done();
- }
-
- render() {
- return null;
- }
- }
-
- renderer.create(
-
-
-
- );
- });
-
- it('should set the networkStatus to 7 (ready) when the query is loaded', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query, { options: { notifyOnNetworkStatusChange: true }})
- class Container extends React.Component {
- componentWillReceiveProps({ data: { networkStatus } }) {
- expect(networkStatus).toBe(7);
- done();
- }
-
- render() {
- return null;
- }
- }
-
- renderer.create(
-
-
-
- );
- });
-
- it('should set the networkStatus to 2 (setVariables) when the query variables are changed', (done) => {
- let count = 0;
- const query = gql`
- query people($first: Int) {
- allPeople(first: $first) { people { name } }
- }
- `;
-
- const data1 = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const variables1 = { first: 1 };
-
- const data2 = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
- const variables2 = { first: 2 };
-
- const networkInterface = mockNetworkInterface(
- { request: { query, variables: variables1 }, result: { data: data1 } },
- { request: { query, variables: variables2 }, result: { data: data2 } }
- );
-
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query, { options: (props) => ({ variables: props, notifyOnNetworkStatusChange: true }) })
- class Container extends React.Component {
- componentWillReceiveProps(nextProps) {
- // variables changed, new query is loading, but old data is still there
- if (count === 1 && nextProps.data.loading) {
- expect(nextProps.data.networkStatus).toBe(2);
- expect(nextProps.data.allPeople).toEqual(data1.allPeople);
- }
- // query with new variables is loaded
- if (count === 1 && !nextProps.data.loading && this.props.data.loading) {
- expect(nextProps.data.networkStatus).toBe(7);
- expect(nextProps.data.allPeople).toEqual(data2.allPeople);
- done();
- }
- }
- render() {
- return null;
- }
- }
-
- class ChangingProps extends React.Component {
- state = { first: 1 };
-
- componentDidMount() {
- setTimeout(() => {
- count++;
- this.setState({ first: 2 });
- }, 50);
- }
-
- render() {
- return ;
- }
- }
-
- renderer.create(
-
-
-
- );
- });
-
- it('resets the loading state after a refetched query', () => new Promise((resolve, reject) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const data2 = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
- const networkInterface = mockNetworkInterface(
- { request: { query }, result: { data } },
- { request: { query }, result: { data: data2 } }
- );
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- let count = 0;
- @graphql(query, { options: { notifyOnNetworkStatusChange: true } })
- class Container extends React.Component {
- componentWillReceiveProps = wrap(reject, (props) => {
- switch (count++) {
- case 0:
- expect(props.data.networkStatus).toBe(7);
- props.data.refetch();
- break;
- case 1:
- expect(props.data.loading).toBe(true);
- expect(props.data.networkStatus).toBe(4);
- expect(props.data.allPeople).toEqual(data.allPeople);
- break;
- case 2:
- expect(props.data.loading).toBe(false);
- expect(props.data.networkStatus).toBe(7);
- expect(props.data.allPeople).toEqual(data2.allPeople);
- resolve();
- break;
- default:
- reject(new Error('Too many props updates'));
- }
- });
-
- render() {
- return null;
- }
- };
-
- const wrapper = renderer.create();
- }));
-
- // XXX: this does not occur at the moment. When we add networkStatus, we should
- // see a few more states
- // it('resets the loading state after a refetched query even if the data doesn\'t change', (d) => {
- // const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- // const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- // const networkInterface = mockNetworkInterface(
- // { request: { query }, result: { data } },
- // { request: { query }, result: { data } }
- // );
- // const client = new ApolloClient({ networkInterface, addTypename: false });
- //
- // let isRefectching;
- // let refetched;
- // @graphql(query)
- // class Container extends React.Component {
- // componentWillReceiveProps(props) {
- // // get new data with no more loading state
- // if (refetched) {
- // expect(props.data.loading).toBe(false);
- // expect(props.data.allPeople).toEqual(data.allPeople);
- // d();
- // return;
- // }
- //
- // // don't remove old data
- // if (isRefectching) {
- // isRefectching = false;
- // refetched = true;
- // expect(props.data.loading).toBe(true);
- // expect(props.data.allPeople).toEqual(data.allPeople);
- // return;
- // }
- //
- // if (!isRefectching) {
- // isRefectching = true;
- // props.data.refetch();
- // }
- // }
- // render() {
- // return null;
- // }
- // };
- //
- // renderer.create();
- // });
-
- it('allows a polling query to be created', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const data2 = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
- const networkInterface = mockNetworkInterface(
- { request: { query }, result: { data } },
- { request: { query }, result: { data: data2 } },
- { request: { query }, result: { data: data2 } }
- );
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- let count = 0;
- const Container = graphql(query, { options: () => ({ pollInterval: 75, notifyOnNetworkStatusChange: false }) })(() => {
- count++;
- return null;
- });
-
- const wrapper = renderer.create();
-
- setTimeout(() => {
- expect(count).toBe(3);
- (wrapper as any).unmount();
- done();
- }, 160);
- });
-
- it('allows custom mapping of a result to props', () => {
- const query = gql`query thing { getThing { thing } }`;
- const data = { getThing: { thing: true } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- const props = ({ data }) => ({ showSpinner: data.loading });
- const ContainerWithData = graphql(query, { props })(({ showSpinner }) => {
- expect(showSpinner).toBe(true);
- return null;
- });
-
- const wrapper = renderer.create();
- (wrapper as any).unmount();
- });
-
- it('allows custom mapping of a result to props that includes the passed props', () => {
- const query = gql`query thing { getThing { thing } }`;
- const data = { getThing: { thing: true } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- const props = ({ data, ownProps }) => {
- expect(ownProps.sample).toBe(1);
- return { showSpinner: data.loading };
- };
- const ContainerWithData = graphql(query, { props })(({ showSpinner }) => {
- expect(showSpinner).toBe(true);
- return null;
- });
-
- const wrapper = renderer.create(
-
- );
- (wrapper as any).unmount();
- });
-
- it('allows custom mapping of a result to props', (done) => {
- const query = gql`query thing { getThing { thing } }`;
- const data = { getThing: { thing: true } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query, { props: ({ data }) => ({ thingy: data.getThing }) }) // tslint:disable-line
- class Container extends React.Component {
- componentWillReceiveProps(props) {
- expect(props.thingy).toEqual(data.getThing);
- done();
- }
- render() {
- return null;
- }
- };
-
- renderer.create();
- });
-
- it('allows context through updates', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query)
- class Container extends React.Component {
- componentWillReceiveProps(props) {
- expect(props.data.loading).toBe(false);
- expect(props.data.allPeople).toEqual(data.allPeople);
- }
- render() {
- return {this.props.children}
;
- }
- };
-
- class ContextContainer extends React.Component {
-
- constructor(props) {
- super(props);
- this.state = { color: 'purple' };
- }
-
- getChildContext() {
- return { color: this.state.color };
- }
-
- componentDidMount() {
- setTimeout(() => {
- this.setState({ color: 'green' });
- }, 50);
- }
-
- render() {
- return {this.props.children}
;
- }
- }
-
- (ContextContainer as any).childContextTypes = {
- color: PropTypes.string,
- };
-
- let count = 0;
- class ChildContextContainer extends React.Component {
- render() {
- const { color } = (this.context as any);
- if (count === 0) expect(color).toBe('purple');
- if (count === 1) {
- expect(color).toBe('green');
- done();
- }
-
- count++;
- return {this.props.children}
;
- }
- }
-
- (ChildContextContainer as any).contextTypes = {
- color: PropTypes.string,
- };
-
- renderer.create(
-
-
-
-
-
-
- );
- });
-
- it('exposes updateQuery as part of the props api', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query)
- class Container extends React.Component {
- componentWillReceiveProps({ data }) { // tslint:disable-line
- expect(data.updateQuery).toBeTruthy();
- expect(data.updateQuery instanceof Function).toBe(true);
- try {
- data.updateQuery(() => done());
- } catch (error) {
- // fail
- }
- }
- render() {
- return null;
- }
- };
-
- renderer.create();
- });
-
- it('exposes updateQuery as part of the props api during componentWillMount', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query)
- class Container extends React.Component {
- componentWillMount() { // tslint:disable-line
- expect(this.props.data.updateQuery).toBeTruthy()
- expect(this.props.data.updateQuery instanceof Function).toBe(true);
- done();
- }
- render() {
- return null;
- }
- };
-
- renderer.create();
- });
-
- it('updateQuery throws if called before data has returned', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query)
- class Container extends React.Component {
- componentWillMount() { // tslint:disable-line
- expect(this.props.data.updateQuery).toBeTruthy();
- expect(this.props.data.updateQuery instanceof Function).toBe(true);
- try {
- this.props.data.updateQuery();
- done();
- } catch (e) {
- expect(e.toString()).toMatch(/ObservableQuery with this id doesn't exist:/);
- done();
- }
- }
- render() {
- return null;
- }
- };
-
- renderer.create();
- });
-
- it('allows updating query results after query has finished (early binding)', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const data2 = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
- const networkInterface = mockNetworkInterface(
- { request: { query }, result: { data } },
- { request: { query }, result: { data: data2 } }
- );
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- let isUpdated;
- @graphql(query)
- class Container extends React.Component {
- public updateQuery: any;
- componentWillMount() {
- this.updateQuery = this.props.data.updateQuery;
- }
- componentWillReceiveProps(props) {
- if (isUpdated) {
- expect(props.data.allPeople).toEqual(data2.allPeople);
- done();
- return;
- } else {
- isUpdated = true;
- this.updateQuery((prev) => {
- return data2;
- });
- }
- }
- render() {
- return null;
- }
- };
-
- renderer.create();
- });
-
- it('allows updating query results after query has finished', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const data2 = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
- const networkInterface = mockNetworkInterface(
- { request: { query }, result: { data } },
- { request: { query }, result: { data: data2 } }
- );
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- let isUpdated;
- @graphql(query)
- class Container extends React.Component {
- componentWillReceiveProps(props) {
- if (isUpdated) {
- expect(props.data.allPeople).toEqual(data2.allPeople);
- done();
- return;
- } else {
- isUpdated = true;
- props.data.updateQuery((prev) => {
- return data2;
- });
- }
- }
- render() {
- return null;
- }
- };
-
- renderer.create();
- });
-
- it('reruns props function after query results change via fetchMore', (done) => {
- const query = gql`query people($cursor: Int) {
- allPeople(cursor: $cursor) { cursor, people { name } }
- }`;
- const vars1 = { cursor: null };
- const data1 = { allPeople: { cursor: 1, people: [ { name: 'Luke Skywalker' } ] } };
- const vars2 = { cursor: 1 };
- const data2 = { allPeople: { cursor: 2, people: [ { name: 'Leia Skywalker' } ] } };
- const networkInterface = mockNetworkInterface(
- { request: { query, variables: vars1 }, result: { data: data1 } },
- { request: { query, variables: vars2 }, result: { data: data2 } }
- );
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- let isUpdated = false;
- @graphql(query, {
- // XXX: I think we should be able to avoid this https://github.com/apollostack/react-apollo/issues/197
- options: { variables: { cursor: null } },
- props({ data: { loading, allPeople, fetchMore } }) {
- if (loading) return { loading };
-
- const { cursor, people } = allPeople;
- return {
- people,
- getMorePeople: () => fetchMore({
- variables: { cursor },
- updateQuery(prev, { fetchMoreResult }) {
- const { allPeople: { cursor, people } } = fetchMoreResult;
- return {
- allPeople: {
- cursor,
- people: [...people, ...prev.allPeople.people],
- },
- };
- }
- }),
- }
- }
- })
- class Container extends React.Component {
- componentWillReceiveProps(props) {
- if (props.loading) {
- return;
- } else if (isUpdated) {
- expect(props.people.length).toBe(2);
- done();
- return;
- } else {
- isUpdated = true;
- expect(props.people).toEqual(data1.allPeople.people);
- props.getMorePeople();
- }
- }
- render() {
- return null;
- }
- };
-
- renderer.create();
- });
-
- it('correctly rebuilds props on remount', (done) => {
- const query = gql`query pollingPeople { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Darth Skywalker' } ] } };
- const networkInterface = mockNetworkInterface(
- { request: { query }, result: { data }, newData: () => ({
- data: {
- allPeople: { people: [ { name: `Darth Skywalker - ${Math.random()}` } ] },
- },
- }) }
- );
- const client = new ApolloClient({ networkInterface, addTypename: false });
- let wrapper, app, count = 0;
-
- @graphql(query, { options: { pollInterval: 10, notifyOnNetworkStatusChange: false }})
- class Container extends React.Component {
- componentWillReceiveProps(props) {
- if (count === 1) { // has data
- wrapper.unmount();
- wrapper = mount(app);
- }
-
- if (count === 10) {
- wrapper.unmount();
- done();
- }
- count++;
- }
- render() {
- return null;
- }
- };
-
- app = ;
-
- wrapper = mount(app);
- });
-
- it('correctly sets loading state on remounted network-only query', (done) => {
- const query = gql`query pollingPeople { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Darth Skywalker' } ] } };
- const networkInterface = mockNetworkInterface(
- { request: { query }, result: { data }, delay: 10, newData: () => ({
- data: {
- allPeople: { people: [ { name: `Darth Skywalker - ${Math.random()}` } ] },
- },
- }) }
- );
- const client = new ApolloClient({ networkInterface, addTypename: false });
- let wrapper, app, count = 0;
-
- @graphql(query, { options: { fetchPolicy: 'network-only' }})
- class Container extends React.Component {
- componentWillMount() {
- if (count === 1) {
- expect(this.props.data.loading).toBe(true); // on remount
- count++;
- }
- }
- componentWillReceiveProps(props) {
- if (count === 0) { // has data
- wrapper.unmount();
- setTimeout(() => {
- wrapper = mount(app);
- }, 5);
- }
-
- if (count === 2) {
- // remounted data after fetch
- expect(props.data.loading).toBe(false);
- expect(props.data.allPeople).toBeTruthy();
- done();
- }
- count++;
- }
- render() {
- return null;
- }
- };
-
- app = ;
-
- wrapper = mount(app);
- });
-
- it('correctly sets loading state on remounted component with changed variables', (done) => {
- const query = gql`
- query remount($first: Int) { allPeople(first: $first) { people { name } } }
- `;
- const data = { allPeople: null };
- const variables = { first: 1 };
- const variables2 = { first: 2 };
- const networkInterface = mockNetworkInterface(
- { request: { query, variables }, result: { data }, delay: 10 },
- { request: { query, variables: variables2 }, result: { data }, delay: 10 }
- );
- const client = new ApolloClient({ networkInterface, addTypename: false });
- let wrapper, render, count = 0;
-
- @graphql(query, { options: ({ first }) => ({ variables: { first }})})
- class Container extends React.Component {
- componentWillMount() {
- if (count === 1) {
- expect(this.props.data.loading).toBe.true; // on remount
- count++;
- }
- }
- componentWillReceiveProps(props) {
- if (count === 0) { // has data
- wrapper.unmount();
- setTimeout(() => {
- wrapper = mount(render(2));
- }, 5);
- }
-
- if (count === 2) {
- // remounted data after fetch
- expect(props.data.loading).toBe.false;
- done();
- }
- count++;
- }
- render() {
- return null;
- }
- };
-
- render = (first) => (
-
- );
-
- wrapper = mount(render(1));
- });
-
- it('correctly sets loading state on remounted component with changed variables (alt)', (done) => {
- const query = gql`
- query remount($name: String) { allPeople(name: $name) { people { name } } }
- `;
- const data = { allPeople: null };
- const variables = { name: 'does-not-exist' };
- const variables2 = { name: 'nothing-either' };
- const networkInterface = mockNetworkInterface(
- { request: { query, variables }, result: { data }, delay: 10 },
- { request: { query, variables: variables2 }, result: { data }, delay: 10 }
- );
- const client = new ApolloClient({ networkInterface, addTypename: false });
- let count = 0;
-
- @graphql(query)
- class Container extends React.Component {
- render() {
- const { loading } = this.props.data;
- if (count === 0) expect(loading).toBe.true;
- if (count === 1) expect(loading).toBe.false;
- if (count === 2) expect(loading).toBe.true;
- if (count === 3) {
- expect(loading).toBe.false;
- done();
- }
- count ++;
- return null;
- }
- };
- const main = document.createElement('DIV');
- main.id = 'main';
- document.body.appendChild(main);
-
- const render = (props) => {
- ReactDOM.render((
-
-
-
- ), document.getElementById('main'));
- };
-
- // Initial render.
- render(variables);
-
- // Prop update: fetch.
- setTimeout(() => render(variables2), 1000);
- });
-
- it('correctly sets loading state on component with changed variables and unchanged result', (done) => {
- const query = gql`
- query remount($first: Int) { allPeople(first: $first) { people { name } } }
- `;
- const data = { allPeople: null };
- const variables = { first: 1 };
- const variables2 = { first: 2 };
- const networkInterface = mockNetworkInterface(
- { request: { query, variables }, result: { data }, delay: 10 },
- { request: { query, variables: variables2 }, result: { data }, delay: 10 }
- );
- const client = new ApolloClient({ networkInterface, addTypename: false });
- let count = 0;
-
- const connect = (component) : any => {
- return class Container extends React.Component {
- constructor(props) {
- super(props);
-
- this.state = {
- first: 1,
- };
- this.setFirst = this.setFirst.bind(this);
- }
-
- setFirst(first) {
- this.setState({first});
- }
-
- render() {
- return React.createElement(component, {
- first: this.state.first,
- setFirst: this.setFirst
- });
- }
- }
- }
-
- @connect
- @graphql(query, { options: ({ first }) => ({ variables: { first }})})
- class Container extends React.Component {
- componentWillReceiveProps(props) {
- if (count === 0) {
- expect(props.data.loading).toBe.false; // has initial data
- setTimeout(() => {
- this.props.setFirst(2);
- }, 5);
- }
-
- if (count === 1) {
- expect(props.data.loading).toBe.true; // on variables change
- }
-
- if (count === 2) {
- // new data after fetch
- expect(props.data.loading).toBe.false;
- done();
- }
- count++;
- }
- render() {
- return null;
- }
- };
- const output = renderer.create();
- });
-
- it('stores the component name in the query metadata', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query)
- class Container extends React.Component {
- componentWillReceiveProps(props) {
- const queries = client.queryManager.getApolloState().queries;
- const queryIds = Object.keys(queries);
- expect(queryIds.length).toEqual(1);
- const query = queries[queryIds[0]];
- expect(query.metadata).toEqual({
- reactComponent: {
- displayName: 'Apollo(Container)',
- },
- });
- done();
- }
- render() {
- return null;
- }
- }
-
- renderer.create(
-
-
-
- );
- });
-
- it('uses a custom wrapped component name when \'alias\' is specified', () => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- @graphql(query, {
- alias: 'withFoo',
- })
- class Container extends React.Component {
- render() {
- return null;
- }
- }
-
- // Not sure why I have to cast Container to any
- expect((Container as any).displayName).toEqual('withFoo(Container)');
- });
-
- it('will recycle `ObservableQuery`s when re-rendering the entire tree', () => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface(
- { request: { query }, result: { data } },
- { request: { query }, result: { data } },
- );
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query)
- class Container extends React.Component {
- render () {
- return null;
- }
- }
-
- const wrapper1 = renderer.create(
-
-
-
- );
-
- expect(Object.keys((client as any).queryManager.observableQueries)).toEqual(['1']);
- const queryObservable1: ObservableQuery = (client as any).queryManager.observableQueries['1'].observableQuery;
-
- const originalOptions = Object.assign({}, queryObservable1.options);
-
- wrapper1.unmount();
-
- expect(Object.keys((client as any).queryManager.observableQueries)).toEqual(['1']);
-
- const wrapper2 = renderer.create(
-
-
-
- );
-
- expect(Object.keys((client as any).queryManager.observableQueries)).toEqual(['1']);
- const queryObservable2: ObservableQuery = (client as any).queryManager.observableQueries['1'].observableQuery;
-
- const recycledOptions = queryObservable2.options;
-
- expect(queryObservable1).toBe(queryObservable2);
- expect(recycledOptions).toEqual(originalOptions);
-
- wrapper2.unmount();
-
- expect(Object.keys((client as any).queryManager.observableQueries)).toEqual(['1']);
- });
-
- it('will not try to refetch recycled `ObservableQuery`s when resetting the client store', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- let finish = () => {};
- const networkInterface = {
- query: jest.fn(() => {
- setTimeout(finish, 5);
- return Promise.resolve({ data: {} })
- }),
- } as NetworkInterface;
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- // make sure that the in flight query is done before resetting store
- finish = () => {
- client.resetStore();
-
- // The query should not have been fetch again
- expect(networkInterface.query).toHaveBeenCalledTimes(1);
-
- done();
- }
-
- @graphql(query)
- class Container extends React.Component {
- render () {
- return null;
- }
- }
-
- const wrapper1 = renderer.create(
-
-
-
- );
-
- expect(Object.keys((client as any).queryManager.observableQueries)).toEqual(['1']);
- const queryObservable1 = (client as any).queryManager.observableQueries['1'].observableQuery;
-
- // The query should only have been invoked when first mounting and not when resetting store
- expect(networkInterface.query).toHaveBeenCalledTimes(1);
-
- wrapper1.unmount();
-
- expect(Object.keys((client as any).queryManager.observableQueries)).toEqual(['1']);
- const queryObservable2 = (client as any).queryManager.observableQueries['1'].observableQuery;
-
- expect(queryObservable1).toBe(queryObservable2);
-
- });
-
- it('will refetch active `ObservableQuery`s when resetting the client store', () => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = {
- query: jest.fn(() => Promise.resolve({ data: {}})),
- } as NetworkInterface;
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- @graphql(query)
- class Container extends React.Component {
- render () {
- return null;
- }
- }
-
- const wrapper1 = renderer.create(
-
-
-
- );
-
- expect(Object.keys((client as any).queryManager.observableQueries)).toEqual(['1']);
-
- expect(networkInterface.query).toHaveBeenCalledTimes(1);
-
- client.resetStore();
-
- expect(networkInterface.query).toHaveBeenCalledTimes(2);
- });
-
- it('will recycle `ObservableQuery`s when re-rendering a portion of the tree', done => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface(
- { request: { query }, result: { data } },
- { request: { query }, result: { data } },
- );
- const client = new ApolloClient({ networkInterface, addTypename: false });
- let remount: any;
-
- class Remounter extends React.Component {
- state = {
- showChildren: true,
- };
-
- componentDidMount () {
- remount = () => {
- this.setState({ showChildren: false }, () => {
- setTimeout(() => {
- this.setState({ showChildren: true });
- }, 5);
- });
- }
- }
-
- render () {
- return this.state.showChildren ? this.props.children : null;
- }
- }
-
- @graphql(query)
- class Container extends React.Component {
- render () {
- return null;
- }
- }
-
- const wrapper = renderer.create(
-
-
-
-
-
- );
-
- expect(Object.keys((client as any).queryManager.observableQueries)).toEqual(['1']);
- const queryObservable1 = (client as any).queryManager.observableQueries['1'].observableQuery;
-
- remount();
-
- setTimeout(() => {
- expect(Object.keys((client as any).queryManager.observableQueries)).toEqual(['1']);
- const queryObservable2 = (client as any).queryManager.observableQueries['1'].observableQuery;
- expect(queryObservable1).toBe(queryObservable2);
-
- remount();
-
- setTimeout(() => {
- expect(Object.keys((client as any).queryManager.observableQueries)).toEqual(['1']);
- const queryObservable3 = (client as any).queryManager.observableQueries['1'].observableQuery;
- expect(queryObservable1).toBe(queryObservable3);
-
- wrapper.unmount();
- done();
- }, 10);
- }, 10);
- });
-
- it('will not recycle parallel GraphQL container `ObservableQuery`s', done => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface(
- { request: { query }, result: { data } },
- { request: { query }, result: { data } },
- );
- const client = new ApolloClient({ networkInterface, addTypename: false });
- let remount: any;
-
- class Remounter extends React.Component {
- state = {
- showChildren: true,
- };
-
- componentDidMount () {
- remount = () => {
- this.setState({ showChildren: false }, () => {
- setTimeout(() => {
- this.setState({ showChildren: true });
- }, 5);
- });
- }
- }
-
- render () {
- return this.state.showChildren ? this.props.children : null;
- }
- }
-
- @graphql(query)
- class Container extends React.Component {
- render () {
- return null;
- }
- }
-
- const wrapper = renderer.create(
-
-
-
-
-
-
-
-
- );
-
- expect(Object.keys((client as any).queryManager.observableQueries)).toEqual(['1', '2']);
- const queryObservable1 = (client as any).queryManager.observableQueries['1'].observableQuery;
- const queryObservable2 = (client as any).queryManager.observableQueries['2'].observableQuery;
- expect(queryObservable1).not.toBe(queryObservable2);
-
- remount();
-
- setTimeout(() => {
- expect(Object.keys((client as any).queryManager.observableQueries)).toEqual(['1', '2']);
- const queryObservable3 = (client as any).queryManager.observableQueries['1'].observableQuery;
- const queryObservable4 = (client as any).queryManager.observableQueries['2'].observableQuery;
-
- // What we really want to test here is if the `queryObservable` on
- // `Container`s are referentially equal. But because there is no way to
- // get the component instances we compare against the query manager
- // observable queries map isntead which shouldn’t change.
- expect(queryObservable3).not.toBeFalsy();
- expect(queryObservable4).not.toBeFalsy();
- expect(queryObservable3).toBe(queryObservable1);
- expect(queryObservable4).toBe(queryObservable2);
-
- wrapper.unmount();
- done();
- }, 10);
- });
-
- it('will not log a warning when there is an error that is caught in the render method', () => new Promise((resolve, reject) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const networkInterface = mockNetworkInterface({ request: { query }, error: new Error('oops') });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- const origError = console.error;
- const errorMock = jest.fn();
- console.error = errorMock;
-
- let renderCount = 0;
- @graphql(query)
- class HandledErrorComponent extends React.Component {
- render() {
- try {
- switch (renderCount++) {
- case 0:
- expect(this.props.data.loading).toEqual(true);
- break;
- case 1:
- expect(this.props.data.error.message).toEqual('Network error: oops');
- break;
- default:
- throw new Error('Too many renders.');
- }
- } catch (error) {
- console.error = origError;
- reject(error);
- }
- return null;
- }
- }
-
- renderer.create(
-
-
-
- );
-
- setTimeout(() => {
- try {
- expect(renderCount).toBe(2);
- expect(errorMock.mock.calls.length).toBe(0);
- resolve();
- } catch (error) {
- reject(error);
- } finally {
- console.error = origError;
- }
- }, 20);
- }));
-
- it('will log a warning when there is an error that is not caught in the render method', () => new Promise((resolve, reject) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const networkInterface = mockNetworkInterface({ request: { query }, error: new Error('oops') });
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- const origError = console.error;
- const errorMock = jest.fn();
- console.error = errorMock;
-
- let renderCount = 0;
- @graphql(query)
- class UnhandledErrorComponent extends React.Component {
- render() {
- try {
- switch (renderCount++) {
- case 0:
- expect(this.props.data.loading).toEqual(true);
- break;
- case 1:
- // Noop. Don’t handle the error so a warning will be logged to the console.
- break;
- default:
- throw new Error('Too many renders.');
- }
- } catch (error) {
- console.error = origError;
- reject(error);
- }
- return null;
- }
- }
-
- renderer.create(
-
-
-
- );
-
- setTimeout(() => {
- try {
- expect(renderCount).toBe(2);
- expect(errorMock.mock.calls.length).toBe(1);
- expect(errorMock.mock.calls[0][0]).toEqual('Unhandled (in react-apollo)');
- resolve();
- } catch (error) {
- reject(error);
- } finally {
- console.error = origError;
- }
- }, 20);
- }));
-
- it('will re-execute a query when the client changes', async () => {
- const query = gql`{ a b c }`;
- const networkInterface1 = { query: jest.fn(() => Promise.resolve({ data: { a: 1, b: 2, c: 3 } })) };
- const networkInterface2 = { query: jest.fn(() => Promise.resolve({ data: { a: 4, b: 5, c: 6 } })) };
- const networkInterface3 = { query: jest.fn(() => Promise.resolve({ data: { a: 7, b: 8, c: 9 } })) };
- const client1 = new ApolloClient({ networkInterface: networkInterface1 });
- const client2 = new ApolloClient({ networkInterface: networkInterface2 });
- const client3 = new ApolloClient({ networkInterface: networkInterface3 });
- const renders = [];
- let switchClient;
- let refetchQuery;
-
- class ClientSwitcher extends React.Component {
- state = {
- client: client1,
- };
-
- componentDidMount() {
- switchClient = newClient => {
- this.setState({ client: newClient });
- };
- }
-
- render() {
- return (
-
-
-
- );
- }
- }
-
- @graphql(query, { options: { notifyOnNetworkStatusChange: true } })
- class Query extends React.Component {
- componentDidMount() {
- refetchQuery = () => this.props.data.refetch();
- }
-
- render() {
- const { data: { loading, a, b, c } } = this.props;
- renders.push({ loading, a, b, c });
- return null;
- }
- }
-
- renderer.create();
-
- await wait(1);
- refetchQuery();
- await wait(1);
- switchClient(client2);
- await wait(1);
- refetchQuery();
- await wait(1);
- switchClient(client3);
- await wait(1);
- switchClient(client1);
- await wait(1);
- switchClient(client2);
- await wait(1);
- switchClient(client3);
- await wait(1);
-
- expect(renders).toEqual([
- { loading: true },
- { loading: false, a: 1, b: 2, c: 3 },
- { loading: true, a: 1, b: 2, c: 3 },
- { loading: false, a: 1, b: 2, c: 3 },
- { loading: true },
- { loading: false, a: 4, b: 5, c: 6 },
- { loading: true, a: 4, b: 5, c: 6 },
- { loading: false, a: 4, b: 5, c: 6 },
- { loading: true },
- { loading: false, a: 7, b: 8, c: 9 },
- { loading: false, a: 1, b: 2, c: 3 },
- { loading: false, a: 4, b: 5, c: 6 },
- { loading: false, a: 7, b: 8, c: 9 },
- ]);
- });
-
- it('passes any cached data when there is a GraphQL error', (done) => {
- const query = gql`query people { allPeople(first: 1) { people { name } } }`;
- const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
- const networkInterface = mockNetworkInterface(
- { request: { query }, result: { data } },
- { request: { query }, error: new Error('No Network Connection') }
- );
- const client = new ApolloClient({ networkInterface, addTypename: false });
-
- let count = 0;
- @graphql(query, { options: { notifyOnNetworkStatusChange: true } })
- class Container extends React.Component {
- componentWillReceiveProps = (props) => {
- switch (count++) {
- case 0:
- expect(props.data.allPeople).toEqual(data.allPeople);
- props.data.refetch();
- break;
- case 1:
- expect(props.data.loading).toBe(true);
- expect(props.data.allPeople).toEqual(data.allPeople);
- break;
- case 2:
- expect(props.data.loading).toBe(false);
- expect(props.data.error).toBeTruthy();
- expect(props.data.allPeople).toEqual(data.allPeople);
- done();
- break;
- }
- }
-
- render() {
- return null;
- }
- };
-
- const wrapper = renderer.create();
- }));
-
-
-});
diff --git a/test/react-web/client/graphql/queries/api.test.tsx b/test/react-web/client/graphql/queries/api.test.tsx
new file mode 100644
index 0000000000..afcb748b1a
--- /dev/null
+++ b/test/react-web/client/graphql/queries/api.test.tsx
@@ -0,0 +1,224 @@
+///
+
+import * as React from 'react';
+import * as PropTypes from 'prop-types';
+import * as ReactDOM from 'react-dom';
+import * as renderer from 'react-test-renderer';
+import { mount } from 'enzyme';
+import gql from 'graphql-tag';
+import ApolloClient, { ApolloError, ObservableQuery } from 'apollo-client';
+import { NetworkInterface } from 'apollo-client';
+import { connect } from 'react-redux';
+import { withState } from 'recompose';
+
+declare function require(name: string);
+
+import { mockNetworkInterface } from '../../../../../src/test-utils';
+import { ApolloProvider, graphql} from '../../../../../src';
+
+// XXX: this is also defined in apollo-client
+// I'm not sure why mocha doesn't provide something like this, you can't
+// always use promises
+const wrap = (done: Function, cb: (...args: any[]) => any) => (...args: any[]) => {
+ try {
+ return cb(...args);
+ } catch (e) {
+ done(e);
+ }
+};
+
+function wait(ms) {
+ return new Promise(resolve => setTimeout(() => resolve(), ms));
+}
+
+describe('[queries] api', () => {
+
+ // api
+ it('exposes refetch as part of the props api', (done) => {
+ const query = gql`query people($first: Int) { allPeople(first: $first) { people { name } } }`;
+ const variables = { first: 1 };
+ const data1 = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface(
+ { request: { query, variables }, result: { data: data1 } },
+ { request: { query, variables }, result: { data: data1 } },
+ { request: { query, variables: { first: 2 } }, result: { data: data1 } }
+ );
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ let hasRefetched, count = 0;
+ @graphql(query)
+ class Container extends React.Component {
+ componentWillMount(){
+ expect(this.props.data.refetch).toBeTruthy();
+ expect(this.props.data.refetch instanceof Function).toBe(true);
+ }
+ componentWillReceiveProps({ data }) { // tslint:disable-line
+ if (count === 0) expect(data.loading).toBe(false); // first data
+ if (count === 1) expect(data.loading).toBe(true); // first refetch
+ if (count === 2) expect(data.loading).toBe(false); // second data
+ if (count === 3) expect(data.loading).toBe(true); // second refetch
+ if (count === 4) expect(data.loading).toBe(false); // third data
+ count ++;
+ if (hasRefetched) return;
+ hasRefetched = true;
+ expect(data.refetch).toBeTruthy();
+ expect(data.refetch instanceof Function).toBe(true);
+ data.refetch()
+ .then(result => {
+ expect(result.data).toEqual(data1);
+ data.refetch({ first: 2 }) // new variables
+ .then(response => {
+ expect(response.data).toEqual(data1);
+ expect(data.allPeople).toEqual(data1.allPeople);
+ done();
+ });
+ })
+ .catch(done);
+ }
+ render() {
+ return null;
+ }
+ };
+
+ renderer.create();
+ });
+
+ it('exposes subscribeToMore as part of the props api', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query)
+ class Container extends React.Component {
+ componentWillReceiveProps({ data }) { // tslint:disable-line
+ expect(data.subscribeToMore).toBeTruthy();
+ expect(data.subscribeToMore instanceof Function).toBe(true);
+ done();
+ }
+ render() {
+ return null;
+ }
+ };
+
+ renderer.create();
+ });
+
+ it('exposes fetchMore as part of the props api', (done) => {
+ const query = gql`
+ query people($skip: Int, $first: Int) { allPeople(first: $first, skip: $skip) { people { name } } }
+ `;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const data1 = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
+ const variables = { skip: 1, first: 1 };
+ const variables2 = { skip: 2, first: 1 };
+
+ const networkInterface = mockNetworkInterface(
+ { request: { query, variables }, result: { data } },
+ { request: { query, variables: variables2 }, result: { data: data1 } }
+ );
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ let count = 0;
+ @graphql(query, { options: () => ({ variables }) })
+ class Container extends React.Component {
+ componentWillMount(){
+ expect(this.props.data.fetchMore).toBeTruthy();
+ expect(this.props.data.fetchMore instanceof Function).toBe(true);
+ }
+ componentWillReceiveProps = wrap(done, (props) => {
+ if (count === 0) {
+ expect(props.data.fetchMore).toBeTruthy();
+ expect(props.data.fetchMore instanceof Function).toBe(true);
+ props.data.fetchMore({
+ variables: { skip: 2 },
+ updateQuery: (prev, { fetchMoreResult }) => ({
+ allPeople: {
+ people: prev.allPeople.people.concat(fetchMoreResult.allPeople.people),
+ },
+ }),
+ }).then(wrap(done, result => {
+ expect(result.data.allPeople.people).toEqual(data1.allPeople.people);
+ }));
+ } else if (count === 1) {
+ expect(props.data.variables).toEqual(variables);
+ expect(props.data.loading).toBe(false);
+ expect(props.data.allPeople.people).toEqual(
+ data.allPeople.people.concat(data1.allPeople.people)
+ );
+ done();
+ } else {
+ throw new Error('should not reach this point');
+ }
+ count++;
+ })
+ render() {
+ return null;
+ }
+ };
+
+ renderer.create();
+ });
+
+ it('reruns props function after query results change via fetchMore', (done) => {
+ const query = gql`query people($cursor: Int) {
+ allPeople(cursor: $cursor) { cursor, people { name } }
+ }`;
+ const vars1 = { cursor: null };
+ const data1 = { allPeople: { cursor: 1, people: [ { name: 'Luke Skywalker' } ] } };
+ const vars2 = { cursor: 1 };
+ const data2 = { allPeople: { cursor: 2, people: [ { name: 'Leia Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface(
+ { request: { query, variables: vars1 }, result: { data: data1 } },
+ { request: { query, variables: vars2 }, result: { data: data2 } }
+ );
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ let isUpdated = false;
+ @graphql(query, {
+ // XXX: I think we should be able to avoid this https://github.com/apollostack/react-apollo/issues/197
+ options: { variables: { cursor: null } },
+ props({ data: { loading, allPeople, fetchMore } }) {
+ if (loading) return { loading };
+
+ const { cursor, people } = allPeople;
+ return {
+ people,
+ getMorePeople: () => fetchMore({
+ variables: { cursor },
+ updateQuery(prev, { fetchMoreResult }) {
+ const { allPeople: { cursor, people } } = fetchMoreResult;
+ return {
+ allPeople: {
+ cursor,
+ people: [...people, ...prev.allPeople.people],
+ },
+ };
+ }
+ }),
+ }
+ }
+ })
+ class Container extends React.Component {
+ componentWillReceiveProps(props) {
+ if (props.loading) {
+ return;
+ } else if (isUpdated) {
+ expect(props.people.length).toBe(2);
+ done();
+ return;
+ } else {
+ isUpdated = true;
+ expect(props.people).toEqual(data1.allPeople.people);
+ props.getMorePeople();
+ }
+ }
+ render() {
+ return null;
+ }
+ };
+
+ renderer.create();
+ });
+
+});
diff --git a/test/react-web/client/graphql/queries/errors.test.tsx b/test/react-web/client/graphql/queries/errors.test.tsx
new file mode 100644
index 0000000000..edcdde1cbc
--- /dev/null
+++ b/test/react-web/client/graphql/queries/errors.test.tsx
@@ -0,0 +1,293 @@
+///
+
+import * as React from 'react';
+import * as PropTypes from 'prop-types';
+import * as ReactDOM from 'react-dom';
+import * as renderer from 'react-test-renderer';
+import { mount } from 'enzyme';
+import gql from 'graphql-tag';
+import ApolloClient, { ApolloError, ObservableQuery } from 'apollo-client';
+import { NetworkInterface } from 'apollo-client';
+import { connect } from 'react-redux';
+import { withState } from 'recompose';
+
+declare function require(name: string);
+
+import { mockNetworkInterface } from '../../../../../src/test-utils';
+import { ApolloProvider, graphql} from '../../../../../src';
+
+// XXX: this is also defined in apollo-client
+// I'm not sure why mocha doesn't provide something like this, you can't
+// always use promises
+const wrap = (done: Function, cb: (...args: any[]) => any) => (...args: any[]) => {
+ try {
+ return cb(...args);
+ } catch (e) {
+ done(e);
+ }
+};
+
+function wait(ms) {
+ return new Promise(resolve => setTimeout(() => resolve(), ms));
+}
+
+describe('[queries] errors', () => {
+
+ // errors
+ it('does not swallow children errors', () => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+ let bar;
+ const ContainerWithData = graphql(query)(() => {
+ bar(); // this will throw
+ return null;
+ });
+
+ try {
+ renderer.create();
+ throw new Error();
+ } catch (e) {
+ expect(e.name).toMatch(/TypeError/);
+ }
+
+ });
+
+ it('can unmount without error', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ const ContainerWithData = graphql(query)(() => null);
+
+ const wrapper = renderer.create(
+
+ ) as any;
+
+ try {
+ wrapper.unmount();
+ done();
+ } catch (e) { throw new Error(e); }
+ });
+
+ it('passes any GraphQL errors in props', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const networkInterface = mockNetworkInterface({ request: { query }, error: new Error('boo') });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query)
+ class ErrorContainer extends React.Component {
+ componentWillReceiveProps({ data }) { // tslint:disable-line
+ expect(data.error).toBeTruthy();
+ expect(data.error.networkError).toBeTruthy();
+ // expect(data.error instanceof ApolloError).toBe(true);
+ done();
+ }
+ render() {
+ return null;
+ }
+ };
+
+ renderer.create();
+ });
+
+ describe('uncaught exceptions', () => {
+ let unhandled = [];
+ function handle(reason) {
+ unhandled.push(reason);
+ }
+ beforeEach(() => {
+ unhandled = [];
+ process.on('unhandledRejection', handle);
+ });
+ afterEach(() => {
+ process.removeListener('unhandledRejection', handle);
+ });
+
+ it('does not log when you change variables resulting in an error', (done) => {
+ const query = gql`query people($var: Int) { allPeople(first: $var) { people { name } } }`;
+ const var1 = { var: 1 };
+ const data = { allPeople : { people: { name: 'Luke Skywalker' } } };
+ const var2 = { var: 2 };
+ const networkInterface = mockNetworkInterface({
+ request: { query, variables: var1 }, result: { data },
+ }, {
+ request: { query, variables: var2 }, error: new Error('boo'),
+ });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ let iteration = 0;
+ @withState('var', 'setVar', 1)
+ @graphql(query)
+ class ErrorContainer extends React.Component {
+ componentWillReceiveProps(props) { // tslint:disable-line
+ iteration += 1;
+ if (iteration === 1) {
+ expect(props.data.allPeople).toEqual(data.allPeople)
+ props.setVar(2);
+ } else if (iteration === 2) {
+ expect(props.data.loading).toBeTruthy();
+ } else if (iteration === 3) {
+ expect(props.data.error).toBeTruthy();
+ expect(props.data.error.networkError).toBeTruthy();
+ // We need to set a timeout to ensure the unhandled rejection is swept up
+ setTimeout(() => {
+ expect(unhandled.length).toEqual(0);
+ done()
+ }, 0);
+ }
+ }
+ render() {
+ return null;
+ }
+ };
+
+ renderer.create();
+ });
+ });
+
+ it('will not log a warning when there is an error that is caught in the render method', () => new Promise((resolve, reject) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const networkInterface = mockNetworkInterface({ request: { query }, error: new Error('oops') });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ const origError = console.error;
+ const errorMock = jest.fn();
+ console.error = errorMock;
+
+ let renderCount = 0;
+ @graphql(query)
+ class HandledErrorComponent extends React.Component {
+ render() {
+ try {
+ switch (renderCount++) {
+ case 0:
+ expect(this.props.data.loading).toEqual(true);
+ break;
+ case 1:
+ expect(this.props.data.error.message).toEqual('Network error: oops');
+ break;
+ default:
+ throw new Error('Too many renders.');
+ }
+ } catch (error) {
+ console.error = origError;
+ reject(error);
+ }
+ return null;
+ }
+ }
+
+ renderer.create(
+
+
+
+ );
+
+ setTimeout(() => {
+ try {
+ expect(renderCount).toBe(2);
+ expect(errorMock.mock.calls.length).toBe(0);
+ resolve();
+ } catch (error) {
+ reject(error);
+ } finally {
+ console.error = origError;
+ }
+ }, 20);
+ }));
+
+ it('will log a warning when there is an error that is not caught in the render method', () => new Promise((resolve, reject) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const networkInterface = mockNetworkInterface({ request: { query }, error: new Error('oops') });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ const origError = console.error;
+ const errorMock = jest.fn();
+ console.error = errorMock;
+
+ let renderCount = 0;
+ @graphql(query)
+ class UnhandledErrorComponent extends React.Component {
+ render() {
+ try {
+ switch (renderCount++) {
+ case 0:
+ expect(this.props.data.loading).toEqual(true);
+ break;
+ case 1:
+ // Noop. Don’t handle the error so a warning will be logged to the console.
+ break;
+ default:
+ throw new Error('Too many renders.');
+ }
+ } catch (error) {
+ console.error = origError;
+ reject(error);
+ }
+ return null;
+ }
+ }
+
+ renderer.create(
+
+
+
+ );
+
+ setTimeout(() => {
+ try {
+ expect(renderCount).toBe(2);
+ expect(errorMock.mock.calls.length).toBe(1);
+ expect(errorMock.mock.calls[0][0]).toEqual('Unhandled (in react-apollo)');
+ resolve();
+ } catch (error) {
+ reject(error);
+ } finally {
+ console.error = origError;
+ }
+ }, 20);
+ }));
+
+ it('passes any cached data when there is a GraphQL error', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface(
+ { request: { query }, result: { data } },
+ { request: { query }, error: new Error('No Network Connection') }
+ );
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ let count = 0;
+ @graphql(query, { options: { notifyOnNetworkStatusChange: true } })
+ class Container extends React.Component {
+ componentWillReceiveProps = (props) => {
+ switch (count++) {
+ case 0:
+ expect(props.data.allPeople).toEqual(data.allPeople);
+ props.data.refetch();
+ break;
+ case 1:
+ expect(props.data.loading).toBe(true);
+ expect(props.data.allPeople).toEqual(data.allPeople);
+ break;
+ case 2:
+ expect(props.data.loading).toBe(false);
+ expect(props.data.error).toBeTruthy();
+ expect(props.data.allPeople).toEqual(data.allPeople);
+ done();
+ break;
+ }
+ }
+
+ render() {
+ return null;
+ }
+ };
+
+ const wrapper = renderer.create();
+ }));
+
+});
diff --git a/test/react-web/client/graphql/queries/index.test.tsx b/test/react-web/client/graphql/queries/index.test.tsx
new file mode 100644
index 0000000000..8b39db915f
--- /dev/null
+++ b/test/react-web/client/graphql/queries/index.test.tsx
@@ -0,0 +1,362 @@
+///
+
+import * as React from 'react';
+import * as PropTypes from 'prop-types';
+import * as ReactDOM from 'react-dom';
+import * as renderer from 'react-test-renderer';
+import { mount } from 'enzyme';
+import gql from 'graphql-tag';
+import ApolloClient, { ApolloError, ObservableQuery } from 'apollo-client';
+import { NetworkInterface } from 'apollo-client';
+import { connect } from 'react-redux';
+import { withState } from 'recompose';
+
+declare function require(name: string);
+
+import { mockNetworkInterface } from '../../../../../src/test-utils';
+import { ApolloProvider, graphql} from '../../../../../src';
+
+// XXX: this is also defined in apollo-client
+// I'm not sure why mocha doesn't provide something like this, you can't
+// always use promises
+const wrap = (done: Function, cb: (...args: any[]) => any) => (...args: any[]) => {
+ try {
+ return cb(...args);
+ } catch (e) {
+ done(e);
+ }
+};
+
+function wait(ms) {
+ return new Promise(resolve => setTimeout(() => resolve(), ms));
+}
+
+describe('queries', () => {
+
+ // general api
+ it('binds a query to props', () => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ const ContainerWithData = graphql(query)(({ data }) => { // tslint:disable-line
+ expect(data).toBeTruthy();
+ expect(data.ownProps).toBeFalsy();
+ expect(data.loading).toBe(true);
+ return null;
+ });
+
+ const output = renderer.create();
+ output.unmount();
+ });
+
+ it('includes the variables in the props', () => {
+ const query = gql`query people ($first: Int) { allPeople(first: $first) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const variables = { first: 1 };
+ const networkInterface = mockNetworkInterface(
+ { request: { query, variables }, result: { data } }
+ );
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ const ContainerWithData = graphql(query)(({ data }) => { // tslint:disable-line
+ expect(data).toBeTruthy();;
+ expect(data.variables).toEqual(variables);
+ return null;
+ });
+
+ renderer.create();
+ });
+
+ it("shouldn't warn about fragments", () => {
+ const oldWarn = console.warn;
+ const warnings = [];
+ console.warn = (str) => warnings.push(str);
+
+ try {
+ graphql(gql`query foo { bar }`);
+ expect(warnings.length).toEqual(0);
+ } finally {
+ console.warn = oldWarn;
+ }
+ });
+
+ it('executes a query', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query)
+ class Container extends React.Component {
+ componentWillReceiveProps(props) {
+ expect(props.data.loading).toBe(false);
+ expect(props.data.allPeople).toEqual(data.allPeople);
+ done();
+ }
+ render() {
+ return null;
+ }
+ };
+
+ renderer.create();
+ });
+
+ it('executes a query with two root fields', (done) => {
+ const query = gql`query people {
+ allPeople(first: 1) { people { name } }
+ otherPeople(first: 1) { people { name } }
+ }`;
+ const data = {
+ allPeople: { people: [ { name: 'Luke Skywalker' } ] },
+ otherPeople: { people: [ { name: 'Luke Skywalker' } ] },
+ };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query)
+ class Container extends React.Component {
+ componentWillReceiveProps(props) {
+ expect(props.data.loading).toBe(false);
+ expect(props.data.allPeople).toEqual(data.allPeople);
+ expect(props.data.otherPeople).toEqual(data.otherPeople);
+ done();
+ }
+ render() {
+ return null;
+ }
+ };
+
+ renderer.create();
+ });
+
+ it('maps props as variables if they match', (done) => {
+ const query = gql`
+ query people($first: Int) {
+ allPeople(first: $first) { people { name } }
+ }
+ `;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const variables = { first: 1 };
+ const networkInterface = mockNetworkInterface({
+ request: { query, variables },
+ result: { data },
+ });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query)
+ class Container extends React.Component {
+ componentWillReceiveProps(props) {
+ expect(props.data.loading).toBe(false);
+ expect(props.data.allPeople).toEqual(data.allPeople);
+ expect(props.data.variables).toEqual(this.props.data.variables);
+ done();
+ }
+ render() {
+ return null;
+ }
+ };
+
+ renderer.create();
+ });
+
+ it('allows falsy values in the mapped variables from props', (done) => {
+ const query = gql`
+ query people($first: Int) {
+ allPeople(first: $first) { people { name } }
+ }
+ `;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const variables = { first: null };
+ const networkInterface = mockNetworkInterface({
+ request: { query, variables },
+ result: { data },
+ });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query)
+ class Container extends React.Component {
+ componentWillReceiveProps(props) {
+ expect(props.data.loading).toBe(false);
+ expect(props.data.allPeople).toEqual(data.allPeople);
+ done();
+ }
+ render() {
+ return null;
+ }
+ };
+
+ renderer.create();
+ });
+
+ it('doesn\'t error on optional required props', () => {
+ const query = gql`
+ query people($first: Int) {
+ allPeople(first: $first) { people { name } }
+ }
+ `;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const variables = { first: 1 };
+ const networkInterface = mockNetworkInterface({
+ request: { query, variables },
+ result: { data },
+ });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+ const Container = graphql(query)(() => null);
+
+ let error = null;
+ try {
+ renderer.create();
+ } catch (e) { error = e; }
+
+ expect(error).toBeNull();
+
+ });
+
+ it('errors if the passed props don\'t contain the needed variables', () => {
+ const query = gql`
+ query people($first: Int!) {
+ allPeople(first: $first) { people { name } }
+ }
+ `;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const variables = { first: 1 };
+ const networkInterface = mockNetworkInterface({
+ request: { query, variables },
+ result: { data },
+ });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+ const Container = graphql(query)(() => null);
+
+ try {
+ renderer.create();
+ } catch (e) {
+ expect(e.name).toMatch(/Invariant Violation/);
+ expect(e.message).toMatch(/The operation 'people'/);
+ }
+
+ });
+
+ // context
+ it('allows context through updates', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query)
+ class Container extends React.Component {
+ componentWillReceiveProps(props) {
+ expect(props.data.loading).toBe(false);
+ expect(props.data.allPeople).toEqual(data.allPeople);
+ }
+ render() {
+ return {this.props.children}
;
+ }
+ };
+
+ class ContextContainer extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = { color: 'purple' };
+ }
+
+ getChildContext() {
+ return { color: this.state.color };
+ }
+
+ componentDidMount() {
+ setTimeout(() => {
+ this.setState({ color: 'green' });
+ }, 50);
+ }
+
+ render() {
+ return {this.props.children}
;
+ }
+ }
+
+ (ContextContainer as any).childContextTypes = {
+ color: PropTypes.string,
+ };
+
+ let count = 0;
+ class ChildContextContainer extends React.Component {
+ render() {
+ const { color } = (this.context as any);
+ if (count === 0) expect(color).toBe('purple');
+ if (count === 1) {
+ expect(color).toBe('green');
+ done();
+ }
+
+ count++;
+ return {this.props.children}
;
+ }
+ }
+
+ (ChildContextContainer as any).contextTypes = {
+ color: PropTypes.string,
+ };
+
+ renderer.create(
+
+
+
+
+
+
+ );
+ });
+
+ // meta
+ it('stores the component name in the query metadata', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query)
+ class Container extends React.Component {
+ componentWillReceiveProps(props) {
+ const queries = client.queryManager.getApolloState().queries;
+ const queryIds = Object.keys(queries);
+ expect(queryIds.length).toEqual(1);
+ const query = queries[queryIds[0]];
+ expect(query.metadata).toEqual({
+ reactComponent: {
+ displayName: 'Apollo(Container)',
+ },
+ });
+ done();
+ }
+ render() {
+ return null;
+ }
+ }
+
+ renderer.create(
+
+
+
+ );
+ });
+
+ it('uses a custom wrapped component name when \'alias\' is specified', () => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ @graphql(query, {
+ alias: 'withFoo',
+ })
+ class Container extends React.Component {
+ render() {
+ return null;
+ }
+ }
+
+ // Not sure why I have to cast Container to any
+ expect((Container as any).displayName).toEqual('withFoo(Container)');
+ });
+
+});
diff --git a/test/react-web/client/graphql/queries/lifecycle.test.tsx b/test/react-web/client/graphql/queries/lifecycle.test.tsx
new file mode 100644
index 0000000000..2f666918ba
--- /dev/null
+++ b/test/react-web/client/graphql/queries/lifecycle.test.tsx
@@ -0,0 +1,414 @@
+///
+
+import * as React from 'react';
+import * as PropTypes from 'prop-types';
+import * as ReactDOM from 'react-dom';
+import * as renderer from 'react-test-renderer';
+import { mount } from 'enzyme';
+import gql from 'graphql-tag';
+import ApolloClient, { ApolloError, ObservableQuery } from 'apollo-client';
+import { NetworkInterface } from 'apollo-client';
+import { connect } from 'react-redux';
+import { withState } from 'recompose';
+
+declare function require(name: string);
+
+import { mockNetworkInterface } from '../../../../../src/test-utils';
+import { ApolloProvider, graphql} from '../../../../../src';
+
+// XXX: this is also defined in apollo-client
+// I'm not sure why mocha doesn't provide something like this, you can't
+// always use promises
+const wrap = (done: Function, cb: (...args: any[]) => any) => (...args: any[]) => {
+ try {
+ return cb(...args);
+ } catch (e) {
+ done(e);
+ }
+};
+
+function wait(ms) {
+ return new Promise(resolve => setTimeout(() => resolve(), ms));
+}
+
+describe('[queries] lifecycle', () => {
+
+ // lifecycle
+ it('reruns the query if it changes', (done) => {
+ let count = 0;
+ const query = gql`
+ query people($first: Int) {
+ allPeople(first: $first) { people { name } }
+ }
+ `;
+
+ const data1 = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const variables1 = { first: 1 };
+
+ const data2 = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
+ const variables2 = { first: 2 };
+
+ const networkInterface = mockNetworkInterface(
+ { request: { query, variables: variables1 }, result: { data: data1 } },
+ { request: { query, variables: variables2 }, result: { data: data2 } }
+ );
+
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query, {
+ options: (props) => ({ variables: props, fetchPolicy: count === 0 ? 'cache-and-network' : 'cache-first' }),
+ })
+ class Container extends React.Component {
+ componentWillReceiveProps({ data }) {
+ // loading is true, but data still there
+ if (count === 1 && data.loading) {
+ expect(data.allPeople).toEqual(data1.allPeople);
+ }
+ if (count === 1 && !data.loading && this.props.data.loading) {
+ expect(data.allPeople).toEqual(data2.allPeople);
+ done();
+ }
+ }
+ render() {
+ return null;
+ }
+ };
+
+ class ChangingProps extends React.Component {
+ state = { first: 1 };
+
+ componentDidMount() {
+ setTimeout(() => {
+ count++;
+ this.setState({ first: 2 });
+ }, 50);
+ }
+
+ render() {
+ return ;
+ }
+ }
+
+ renderer.create();
+ });
+
+ it('rebuilds the queries on prop change when using `options`', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+
+ let firstRun = true;
+ let isDone = false;
+ function options(props) {
+ if (!firstRun) {
+ expect(props.listId).toBe(2);
+ if (!isDone) done();
+ isDone = true;
+ }
+ return {};
+ };
+
+ const Container = graphql(query, { options })((props) => null);
+
+ class ChangingProps extends React.Component {
+ state = { listId: 1 };
+
+ componentDidMount() {
+ setTimeout(() => {
+ firstRun = false;
+ this.setState({ listId: 2 });
+ }, 50);
+ }
+
+ render() {
+ return ;
+ }
+ }
+
+ renderer.create();
+ });
+
+ it('reruns the query if just the variables change', (done) => {
+ let count = 0;
+ const query = gql`
+ query people($first: Int) {
+ allPeople(first: $first) { people { name } }
+ }
+ `;
+
+ const data1 = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const variables1 = { first: 1 };
+
+ const data2 = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
+ const variables2 = { first: 2 };
+
+ const networkInterface = mockNetworkInterface(
+ { request: { query, variables: variables1 }, result: { data: data1 } },
+ { request: { query, variables: variables2 }, result: { data: data2 } }
+ );
+
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query, { options: (props) => ({ variables: props }) })
+ class Container extends React.Component {
+ componentWillReceiveProps({ data }) {
+ // loading is true, but data still there
+ if (count === 1 && data.loading) {
+ expect(data.allPeople).toEqual(data1.allPeople);
+ }
+ if (count === 1 && !data.loading && this.props.data.loading) {
+ expect(data.allPeople).toEqual(data2.allPeople);
+ done();
+ }
+ }
+ render() {
+ return null;
+ }
+ };
+
+ class ChangingProps extends React.Component {
+ state = { first: 1 };
+
+ componentDidMount() {
+ setTimeout(() => {
+ count++;
+ this.setState({ first: 2 });
+ }, 50);
+ }
+
+ render() {
+ return ;
+ }
+ }
+
+ renderer.create();
+ });
+
+ it('reruns the queries on prop change when using passed props', (done) => {
+ let count = 0;
+ const query = gql`
+ query people($first: Int) {
+ allPeople(first: $first) { people { name } }
+ }
+ `;
+
+ const data1 = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const variables1 = { first: 1 };
+
+ const data2 = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
+ const variables2 = { first: 2 };
+
+ const networkInterface = mockNetworkInterface(
+ { request: { query, variables: variables1 }, result: { data: data1 } },
+ { request: { query, variables: variables2 }, result: { data: data2 } }
+ );
+
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query)
+ class Container extends React.Component {
+ componentWillReceiveProps({ data }) {
+ // loading is true, but data still there
+ if (count === 1 && data.loading) {
+ expect(data.allPeople).toEqual(data1.allPeople);
+ }
+ if (count === 1 && !data.loading && this.props.data.loading) {
+ expect(data.allPeople).toEqual(data2.allPeople);
+ done();
+ }
+ }
+ render() {
+ return null;
+ }
+ };
+
+ class ChangingProps extends React.Component {
+ state = { first: 1 };
+
+ componentDidMount() {
+ setTimeout(() => {
+ count++;
+ this.setState({ first: 2 });
+ }, 50);
+ }
+
+ render() {
+ return ;
+ }
+ }
+
+ renderer.create();
+ });
+
+ it('stays subscribed to updates after irrelevant prop changes', (done) => {
+ const query = gql`query people($first: Int) { allPeople(first: $first) { people { name } } }`;
+ const variables = { first: 1 };
+ const data1 = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const data2 = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface(
+ { request: { query, variables }, result: { data: data1 } },
+ { request: { query, variables }, result: { data: data2 } },
+ );
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ let count = 0;
+ @graphql(query, { options() { return { variables, notifyOnNetworkStatusChange: false }; } })
+ class Container extends React.Component {8
+ componentWillReceiveProps(props) {
+ count += 1;
+
+ if (count == 1) {
+ expect(props.foo).toEqual(42);
+ expect(props.data.loading).toEqual(false);
+ expect(props.data.allPeople).toEqual(data1.allPeople);
+ props.changeState();
+ } else if (count == 2) {
+ expect(props.foo).toEqual(43);
+ expect(props.data.loading).toEqual(false);
+ expect(props.data.allPeople).toEqual(data1.allPeople);
+ props.data.refetch();
+ } else if (count == 3) {
+ expect(props.foo).toEqual(43);
+ expect(props.data.loading).toEqual(false);
+ expect(props.data.allPeople).toEqual(data2.allPeople);
+ done();
+ }
+ }
+ render() {
+ return null;
+ }
+ };
+
+ class Parent extends React.Component {
+ constructor() {
+ super();
+ this.state = { foo: 42 };
+ }
+ render() {
+ return this.setState({ foo: 43 })}/>;
+ }
+ };
+
+ renderer.create();
+ });
+
+ it('correctly rebuilds props on remount', (done) => {
+ const query = gql`query pollingPeople { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Darth Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface(
+ { request: { query }, result: { data }, newData: () => ({
+ data: {
+ allPeople: { people: [ { name: `Darth Skywalker - ${Math.random()}` } ] },
+ },
+ }) }
+ );
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+ let wrapper, app, count = 0;
+
+ @graphql(query, { options: { pollInterval: 10, notifyOnNetworkStatusChange: false }})
+ class Container extends React.Component {
+ componentWillReceiveProps(props) {
+ if (count === 1) { // has data
+ wrapper.unmount();
+ wrapper = mount(app);
+ }
+
+ if (count === 10) {
+ wrapper.unmount();
+ done();
+ }
+ count++;
+ }
+ render() {
+ return null;
+ }
+ };
+
+ app = ;
+
+ wrapper = mount(app);
+ });
+
+ it('will re-execute a query when the client changes', async () => {
+ const query = gql`{ a b c }`;
+ const networkInterface1 = { query: jest.fn(() => Promise.resolve({ data: { a: 1, b: 2, c: 3 } })) };
+ const networkInterface2 = { query: jest.fn(() => Promise.resolve({ data: { a: 4, b: 5, c: 6 } })) };
+ const networkInterface3 = { query: jest.fn(() => Promise.resolve({ data: { a: 7, b: 8, c: 9 } })) };
+ const client1 = new ApolloClient({ networkInterface: networkInterface1 });
+ const client2 = new ApolloClient({ networkInterface: networkInterface2 });
+ const client3 = new ApolloClient({ networkInterface: networkInterface3 });
+ const renders = [];
+ let switchClient;
+ let refetchQuery;
+
+ class ClientSwitcher extends React.Component {
+ state = {
+ client: client1,
+ };
+
+ componentDidMount() {
+ switchClient = newClient => {
+ this.setState({ client: newClient });
+ };
+ }
+
+ render() {
+ return (
+
+
+
+ );
+ }
+ }
+
+ @graphql(query, { options: { notifyOnNetworkStatusChange: true } })
+ class Query extends React.Component {
+ componentDidMount() {
+ refetchQuery = () => this.props.data.refetch();
+ }
+
+ render() {
+ const { data: { loading, a, b, c } } = this.props;
+ renders.push({ loading, a, b, c });
+ return null;
+ }
+ }
+
+ renderer.create();
+
+ await wait(1);
+ refetchQuery();
+ await wait(1);
+ switchClient(client2);
+ await wait(1);
+ refetchQuery();
+ await wait(1);
+ switchClient(client3);
+ await wait(1);
+ switchClient(client1);
+ await wait(1);
+ switchClient(client2);
+ await wait(1);
+ switchClient(client3);
+ await wait(1);
+
+ expect(renders).toEqual([
+ { loading: true },
+ { loading: false, a: 1, b: 2, c: 3 },
+ { loading: true, a: 1, b: 2, c: 3 },
+ { loading: false, a: 1, b: 2, c: 3 },
+ { loading: true },
+ { loading: false, a: 4, b: 5, c: 6 },
+ { loading: true, a: 4, b: 5, c: 6 },
+ { loading: false, a: 4, b: 5, c: 6 },
+ { loading: true },
+ { loading: false, a: 7, b: 8, c: 9 },
+ { loading: false, a: 1, b: 2, c: 3 },
+ { loading: false, a: 4, b: 5, c: 6 },
+ { loading: false, a: 7, b: 8, c: 9 },
+ ]);
+ });
+
+});
diff --git a/test/react-web/client/graphql/queries/loading.test.tsx b/test/react-web/client/graphql/queries/loading.test.tsx
new file mode 100644
index 0000000000..e1e324cb56
--- /dev/null
+++ b/test/react-web/client/graphql/queries/loading.test.tsx
@@ -0,0 +1,429 @@
+///
+
+import * as React from 'react';
+import * as PropTypes from 'prop-types';
+import * as ReactDOM from 'react-dom';
+import * as renderer from 'react-test-renderer';
+import { mount } from 'enzyme';
+import gql from 'graphql-tag';
+import ApolloClient, { ApolloError, ObservableQuery } from 'apollo-client';
+import { NetworkInterface } from 'apollo-client';
+import { connect } from 'react-redux';
+import { withState } from 'recompose';
+
+declare function require(name: string);
+
+import { mockNetworkInterface } from '../../../../../src/test-utils';
+import { ApolloProvider, graphql} from '../../../../../src';
+
+// XXX: this is also defined in apollo-client
+// I'm not sure why mocha doesn't provide something like this, you can't
+// always use promises
+const wrap = (done: Function, cb: (...args: any[]) => any) => (...args: any[]) => {
+ try {
+ return cb(...args);
+ } catch (e) {
+ done(e);
+ }
+};
+
+function wait(ms) {
+ return new Promise(resolve => setTimeout(() => resolve(), ms));
+}
+
+describe('[queries] loading', () => {
+
+ // networkStatus / loading
+ it('exposes networkStatus as a part of the props api', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query, { options: { notifyOnNetworkStatusChange: true }})
+ class Container extends React.Component {
+ componentWillReceiveProps({ data }) {
+ expect(data.networkStatus).toBeTruthy();
+ done();
+ }
+ render() {
+ return null;
+ }
+ }
+
+ renderer.create(
+
+
+
+ );
+ });
+
+ it('should set the initial networkStatus to 1 (loading)', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query, { options: { notifyOnNetworkStatusChange: true }})
+ class Container extends React.Component {
+ constructor({ data: { networkStatus } }) {
+ super();
+ expect(networkStatus).toBe(1);
+ done();
+ }
+
+ render() {
+ return null;
+ }
+ }
+
+ renderer.create(
+
+
+
+ );
+ });
+
+ it('should set the networkStatus to 7 (ready) when the query is loaded', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query, { options: { notifyOnNetworkStatusChange: true }})
+ class Container extends React.Component {
+ componentWillReceiveProps({ data: { networkStatus } }) {
+ expect(networkStatus).toBe(7);
+ done();
+ }
+
+ render() {
+ return null;
+ }
+ }
+
+ renderer.create(
+
+
+
+ );
+ });
+
+ it('should set the networkStatus to 2 (setVariables) when the query variables are changed', (done) => {
+ let count = 0;
+ const query = gql`
+ query people($first: Int) {
+ allPeople(first: $first) { people { name } }
+ }
+ `;
+
+ const data1 = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const variables1 = { first: 1 };
+
+ const data2 = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
+ const variables2 = { first: 2 };
+
+ const networkInterface = mockNetworkInterface(
+ { request: { query, variables: variables1 }, result: { data: data1 } },
+ { request: { query, variables: variables2 }, result: { data: data2 } }
+ );
+
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query, { options: (props) => ({ variables: props, notifyOnNetworkStatusChange: true }) })
+ class Container extends React.Component {
+ componentWillReceiveProps(nextProps) {
+ // variables changed, new query is loading, but old data is still there
+ if (count === 1 && nextProps.data.loading) {
+ expect(nextProps.data.networkStatus).toBe(2);
+ expect(nextProps.data.allPeople).toEqual(data1.allPeople);
+ }
+ // query with new variables is loaded
+ if (count === 1 && !nextProps.data.loading && this.props.data.loading) {
+ expect(nextProps.data.networkStatus).toBe(7);
+ expect(nextProps.data.allPeople).toEqual(data2.allPeople);
+ done();
+ }
+ }
+ render() {
+ return null;
+ }
+ }
+
+ class ChangingProps extends React.Component {
+ state = { first: 1 };
+
+ componentDidMount() {
+ setTimeout(() => {
+ count++;
+ this.setState({ first: 2 });
+ }, 50);
+ }
+
+ render() {
+ return ;
+ }
+ }
+
+ renderer.create(
+
+
+
+ );
+ });
+
+ it('resets the loading state after a refetched query', () => new Promise((resolve, reject) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const data2 = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface(
+ { request: { query }, result: { data } },
+ { request: { query }, result: { data: data2 } }
+ );
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ let count = 0;
+ @graphql(query, { options: { notifyOnNetworkStatusChange: true } })
+ class Container extends React.Component {
+ componentWillReceiveProps = wrap(reject, (props) => {
+ switch (count++) {
+ case 0:
+ expect(props.data.networkStatus).toBe(7);
+ props.data.refetch();
+ break;
+ case 1:
+ expect(props.data.loading).toBe(true);
+ expect(props.data.networkStatus).toBe(4);
+ expect(props.data.allPeople).toEqual(data.allPeople);
+ break;
+ case 2:
+ expect(props.data.loading).toBe(false);
+ expect(props.data.networkStatus).toBe(7);
+ expect(props.data.allPeople).toEqual(data2.allPeople);
+ resolve();
+ break;
+ default:
+ reject(new Error('Too many props updates'));
+ }
+ });
+
+ render() {
+ return null;
+ }
+ };
+
+ const wrapper = renderer.create();
+ }));
+
+ it('correctly sets loading state on remounted network-only query', (done) => {
+ const query = gql`query pollingPeople { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Darth Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface(
+ { request: { query }, result: { data }, delay: 10, newData: () => ({
+ data: {
+ allPeople: { people: [ { name: `Darth Skywalker - ${Math.random()}` } ] },
+ },
+ }) }
+ );
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+ let wrapper, app, count = 0;
+
+ @graphql(query, { options: { fetchPolicy: 'network-only' }})
+ class Container extends React.Component {
+ componentWillMount() {
+ if (count === 1) {
+ expect(this.props.data.loading).toBe(true); // on remount
+ count++;
+ }
+ }
+ componentWillReceiveProps(props) {
+ if (count === 0) { // has data
+ wrapper.unmount();
+ setTimeout(() => {
+ wrapper = mount(app);
+ }, 5);
+ }
+
+ if (count === 2) {
+ // remounted data after fetch
+ expect(props.data.loading).toBe(false);
+ expect(props.data.allPeople).toBeTruthy();
+ done();
+ }
+ count++;
+ }
+ render() {
+ return null;
+ }
+ };
+
+ app = ;
+
+ wrapper = mount(app);
+ });
+
+ it('correctly sets loading state on remounted component with changed variables', (done) => {
+ const query = gql`
+ query remount($first: Int) { allPeople(first: $first) { people { name } } }
+ `;
+ const data = { allPeople: null };
+ const variables = { first: 1 };
+ const variables2 = { first: 2 };
+ const networkInterface = mockNetworkInterface(
+ { request: { query, variables }, result: { data }, delay: 10 },
+ { request: { query, variables: variables2 }, result: { data }, delay: 10 }
+ );
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+ let wrapper, render, count = 0;
+
+ @graphql(query, { options: ({ first }) => ({ variables: { first }})})
+ class Container extends React.Component {
+ componentWillMount() {
+ if (count === 1) {
+ expect(this.props.data.loading).toBe.true; // on remount
+ count++;
+ }
+ }
+ componentWillReceiveProps(props) {
+ if (count === 0) { // has data
+ wrapper.unmount();
+ setTimeout(() => {
+ wrapper = mount(render(2));
+ }, 5);
+ }
+
+ if (count === 2) {
+ // remounted data after fetch
+ expect(props.data.loading).toBe.false;
+ done();
+ }
+ count++;
+ }
+ render() {
+ return null;
+ }
+ };
+
+ render = (first) => (
+
+ );
+
+ wrapper = mount(render(1));
+ });
+
+ it('correctly sets loading state on remounted component with changed variables (alt)', (done) => {
+ const query = gql`
+ query remount($name: String) { allPeople(name: $name) { people { name } } }
+ `;
+ const data = { allPeople: null };
+ const variables = { name: 'does-not-exist' };
+ const variables2 = { name: 'nothing-either' };
+ const networkInterface = mockNetworkInterface(
+ { request: { query, variables }, result: { data }, delay: 10 },
+ { request: { query, variables: variables2 }, result: { data }, delay: 10 }
+ );
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+ let count = 0;
+
+ @graphql(query)
+ class Container extends React.Component {
+ render() {
+ const { loading } = this.props.data;
+ if (count === 0) expect(loading).toBe.true;
+ if (count === 1) expect(loading).toBe.false;
+ if (count === 2) expect(loading).toBe.true;
+ if (count === 3) {
+ expect(loading).toBe.false;
+ done();
+ }
+ count ++;
+ return null;
+ }
+ };
+ const main = document.createElement('DIV');
+ main.id = 'main';
+ document.body.appendChild(main);
+
+ const render = (props) => {
+ ReactDOM.render((
+
+
+
+ ), document.getElementById('main'));
+ };
+
+ // Initial render.
+ render(variables);
+
+ // Prop update: fetch.
+ setTimeout(() => render(variables2), 1000);
+ });
+
+ it('correctly sets loading state on component with changed variables and unchanged result', (done) => {
+ const query = gql`
+ query remount($first: Int) { allPeople(first: $first) { people { name } } }
+ `;
+ const data = { allPeople: null };
+ const variables = { first: 1 };
+ const variables2 = { first: 2 };
+ const networkInterface = mockNetworkInterface(
+ { request: { query, variables }, result: { data }, delay: 10 },
+ { request: { query, variables: variables2 }, result: { data }, delay: 10 }
+ );
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+ let count = 0;
+
+ const connect = (component) : any => {
+ return class Container extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ first: 1,
+ };
+ this.setFirst = this.setFirst.bind(this);
+ }
+
+ setFirst(first) {
+ this.setState({first});
+ }
+
+ render() {
+ return React.createElement(component, {
+ first: this.state.first,
+ setFirst: this.setFirst
+ });
+ }
+ }
+ }
+
+ @connect
+ @graphql(query, { options: ({ first }) => ({ variables: { first }})})
+ class Container extends React.Component {
+ componentWillReceiveProps(props) {
+ if (count === 0) {
+ expect(props.data.loading).toBe.false; // has initial data
+ setTimeout(() => {
+ this.props.setFirst(2);
+ }, 5);
+ }
+
+ if (count === 1) {
+ expect(props.data.loading).toBe.true; // on variables change
+ }
+
+ if (count === 2) {
+ // new data after fetch
+ expect(props.data.loading).toBe.false;
+ done();
+ }
+ count++;
+ }
+ render() {
+ return null;
+ }
+ };
+ const output = renderer.create();
+ });
+
+});
diff --git a/test/react-web/client/graphql/queries/observableQuery.test.tsx b/test/react-web/client/graphql/queries/observableQuery.test.tsx
new file mode 100644
index 0000000000..25e23da2b7
--- /dev/null
+++ b/test/react-web/client/graphql/queries/observableQuery.test.tsx
@@ -0,0 +1,309 @@
+///
+
+import * as React from 'react';
+import * as PropTypes from 'prop-types';
+import * as ReactDOM from 'react-dom';
+import * as renderer from 'react-test-renderer';
+import { mount } from 'enzyme';
+import gql from 'graphql-tag';
+import ApolloClient, { ApolloError, ObservableQuery } from 'apollo-client';
+import { NetworkInterface } from 'apollo-client';
+import { connect } from 'react-redux';
+import { withState } from 'recompose';
+
+declare function require(name: string);
+
+import { mockNetworkInterface } from '../../../../../src/test-utils';
+import { ApolloProvider, graphql} from '../../../../../src';
+
+// XXX: this is also defined in apollo-client
+// I'm not sure why mocha doesn't provide something like this, you can't
+// always use promises
+const wrap = (done: Function, cb: (...args: any[]) => any) => (...args: any[]) => {
+ try {
+ return cb(...args);
+ } catch (e) {
+ done(e);
+ }
+};
+
+function wait(ms) {
+ return new Promise(resolve => setTimeout(() => resolve(), ms));
+}
+
+describe('[queries] observableQuery', () => {
+
+ // observableQuery
+ it('will recycle `ObservableQuery`s when re-rendering the entire tree', () => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface(
+ { request: { query }, result: { data } },
+ { request: { query }, result: { data } },
+ );
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query)
+ class Container extends React.Component {
+ render () {
+ return null;
+ }
+ }
+
+ const wrapper1 = renderer.create(
+
+
+
+ );
+
+ expect(Object.keys((client as any).queryManager.observableQueries)).toEqual(['1']);
+ const queryObservable1: ObservableQuery = (client as any).queryManager.observableQueries['1'].observableQuery;
+
+ const originalOptions = Object.assign({}, queryObservable1.options);
+
+ wrapper1.unmount();
+
+ expect(Object.keys((client as any).queryManager.observableQueries)).toEqual(['1']);
+
+ const wrapper2 = renderer.create(
+
+
+
+ );
+
+ expect(Object.keys((client as any).queryManager.observableQueries)).toEqual(['1']);
+ const queryObservable2: ObservableQuery = (client as any).queryManager.observableQueries['1'].observableQuery;
+
+ const recycledOptions = queryObservable2.options;
+
+ expect(queryObservable1).toBe(queryObservable2);
+ expect(recycledOptions).toEqual(originalOptions);
+
+ wrapper2.unmount();
+
+ expect(Object.keys((client as any).queryManager.observableQueries)).toEqual(['1']);
+ });
+
+ it('will not try to refetch recycled `ObservableQuery`s when resetting the client store', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ let finish = () => {};
+ const networkInterface = {
+ query: jest.fn(() => {
+ setTimeout(finish, 5);
+ return Promise.resolve({ data: {} })
+ }),
+ } as NetworkInterface;
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ // make sure that the in flight query is done before resetting store
+ finish = () => {
+ client.resetStore();
+
+ // The query should not have been fetch again
+ expect(networkInterface.query).toHaveBeenCalledTimes(1);
+
+ done();
+ }
+
+ @graphql(query)
+ class Container extends React.Component {
+ render () {
+ return null;
+ }
+ }
+
+ const wrapper1 = renderer.create(
+
+
+
+ );
+
+ expect(Object.keys((client as any).queryManager.observableQueries)).toEqual(['1']);
+ const queryObservable1 = (client as any).queryManager.observableQueries['1'].observableQuery;
+
+ // The query should only have been invoked when first mounting and not when resetting store
+ expect(networkInterface.query).toHaveBeenCalledTimes(1);
+
+ wrapper1.unmount();
+
+ expect(Object.keys((client as any).queryManager.observableQueries)).toEqual(['1']);
+ const queryObservable2 = (client as any).queryManager.observableQueries['1'].observableQuery;
+
+ expect(queryObservable1).toBe(queryObservable2);
+
+ });
+
+ it('will refetch active `ObservableQuery`s when resetting the client store', () => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = {
+ query: jest.fn(() => Promise.resolve({ data: {}})),
+ } as NetworkInterface;
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query)
+ class Container extends React.Component {
+ render () {
+ return null;
+ }
+ }
+
+ const wrapper1 = renderer.create(
+
+
+
+ );
+
+ expect(Object.keys((client as any).queryManager.observableQueries)).toEqual(['1']);
+
+ expect(networkInterface.query).toHaveBeenCalledTimes(1);
+
+ client.resetStore();
+
+ expect(networkInterface.query).toHaveBeenCalledTimes(2);
+ });
+
+ it('will recycle `ObservableQuery`s when re-rendering a portion of the tree', done => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface(
+ { request: { query }, result: { data } },
+ { request: { query }, result: { data } },
+ );
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+ let remount: any;
+
+ class Remounter extends React.Component {
+ state = {
+ showChildren: true,
+ };
+
+ componentDidMount () {
+ remount = () => {
+ this.setState({ showChildren: false }, () => {
+ setTimeout(() => {
+ this.setState({ showChildren: true });
+ }, 5);
+ });
+ }
+ }
+
+ render () {
+ return this.state.showChildren ? this.props.children : null;
+ }
+ }
+
+ @graphql(query)
+ class Container extends React.Component {
+ render () {
+ return null;
+ }
+ }
+
+ const wrapper = renderer.create(
+
+
+
+
+
+ );
+
+ expect(Object.keys((client as any).queryManager.observableQueries)).toEqual(['1']);
+ const queryObservable1 = (client as any).queryManager.observableQueries['1'].observableQuery;
+
+ remount();
+
+ setTimeout(() => {
+ expect(Object.keys((client as any).queryManager.observableQueries)).toEqual(['1']);
+ const queryObservable2 = (client as any).queryManager.observableQueries['1'].observableQuery;
+ expect(queryObservable1).toBe(queryObservable2);
+
+ remount();
+
+ setTimeout(() => {
+ expect(Object.keys((client as any).queryManager.observableQueries)).toEqual(['1']);
+ const queryObservable3 = (client as any).queryManager.observableQueries['1'].observableQuery;
+ expect(queryObservable1).toBe(queryObservable3);
+
+ wrapper.unmount();
+ done();
+ }, 10);
+ }, 10);
+ });
+
+ it('will not recycle parallel GraphQL container `ObservableQuery`s', done => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface(
+ { request: { query }, result: { data } },
+ { request: { query }, result: { data } },
+ );
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+ let remount: any;
+
+ class Remounter extends React.Component {
+ state = {
+ showChildren: true,
+ };
+
+ componentDidMount () {
+ remount = () => {
+ this.setState({ showChildren: false }, () => {
+ setTimeout(() => {
+ this.setState({ showChildren: true });
+ }, 5);
+ });
+ }
+ }
+
+ render () {
+ return this.state.showChildren ? this.props.children : null;
+ }
+ }
+
+ @graphql(query)
+ class Container extends React.Component {
+ render () {
+ return null;
+ }
+ }
+
+ const wrapper = renderer.create(
+
+
+
+
+
+
+
+
+ );
+
+ expect(Object.keys((client as any).queryManager.observableQueries)).toEqual(['1', '2']);
+ const queryObservable1 = (client as any).queryManager.observableQueries['1'].observableQuery;
+ const queryObservable2 = (client as any).queryManager.observableQueries['2'].observableQuery;
+ expect(queryObservable1).not.toBe(queryObservable2);
+
+ remount();
+
+ setTimeout(() => {
+ expect(Object.keys((client as any).queryManager.observableQueries)).toEqual(['1', '2']);
+ const queryObservable3 = (client as any).queryManager.observableQueries['1'].observableQuery;
+ const queryObservable4 = (client as any).queryManager.observableQueries['2'].observableQuery;
+
+ // What we really want to test here is if the `queryObservable` on
+ // `Container`s are referentially equal. But because there is no way to
+ // get the component instances we compare against the query manager
+ // observable queries map isntead which shouldn’t change.
+ expect(queryObservable3).not.toBeFalsy();
+ expect(queryObservable4).not.toBeFalsy();
+ expect(queryObservable3).toBe(queryObservable1);
+ expect(queryObservable4).toBe(queryObservable2);
+
+ wrapper.unmount();
+ done();
+ }, 10);
+ });
+
+});
diff --git a/test/react-web/client/graphql/queries/polling.test.tsx b/test/react-web/client/graphql/queries/polling.test.tsx
new file mode 100644
index 0000000000..fd8f883aa2
--- /dev/null
+++ b/test/react-web/client/graphql/queries/polling.test.tsx
@@ -0,0 +1,113 @@
+///
+
+import * as React from 'react';
+import * as PropTypes from 'prop-types';
+import * as ReactDOM from 'react-dom';
+import * as renderer from 'react-test-renderer';
+import { mount } from 'enzyme';
+import gql from 'graphql-tag';
+import ApolloClient, { ApolloError, ObservableQuery } from 'apollo-client';
+import { NetworkInterface } from 'apollo-client';
+import { connect } from 'react-redux';
+import { withState } from 'recompose';
+
+declare function require(name: string);
+
+import { mockNetworkInterface } from '../../../../../src/test-utils';
+import { ApolloProvider, graphql} from '../../../../../src';
+
+// XXX: this is also defined in apollo-client
+// I'm not sure why mocha doesn't provide something like this, you can't
+// always use promises
+const wrap = (done: Function, cb: (...args: any[]) => any) => (...args: any[]) => {
+ try {
+ return cb(...args);
+ } catch (e) {
+ done(e);
+ }
+};
+
+function wait(ms) {
+ return new Promise(resolve => setTimeout(() => resolve(), ms));
+}
+
+describe('[queries] polling', () => {
+
+ // polling
+ it('allows a polling query to be created', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const data2 = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface(
+ { request: { query }, result: { data } },
+ { request: { query }, result: { data: data2 } },
+ { request: { query }, result: { data: data2 } }
+ );
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ let count = 0;
+ const Container = graphql(query, { options: () => ({ pollInterval: 75, notifyOnNetworkStatusChange: false }) })(() => {
+ count++;
+ return null;
+ });
+
+ const wrapper = renderer.create();
+
+ setTimeout(() => {
+ expect(count).toBe(3);
+ (wrapper as any).unmount();
+ done();
+ }, 160);
+ });
+
+ it('exposes stopPolling as part of the props api', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query)
+ class Container extends React.Component {
+ componentWillReceiveProps({ data }) { // tslint:disable-line
+ expect(data.stopPolling).toBeTruthy();
+ expect(data.stopPolling instanceof Function).toBe(true);
+ expect(data.stopPolling).not.toThrow();
+ done();
+ }
+ render() {
+ return null;
+ }
+ };
+
+ renderer.create();
+ });
+
+ it('exposes startPolling as part of the props api', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+ let wrapper;
+
+ // @graphql(query)
+ @graphql(query, { options: { pollInterval: 10 }})
+ class Container extends React.Component {
+ componentWillReceiveProps({ data }) { // tslint:disable-line
+ expect(data.startPolling).toBeTruthy();
+ expect(data.startPolling instanceof Function).toBe(true);
+ // XXX this does throw because of no pollInterval
+ // expect(data.startPolling).not.toThrow();
+ setTimeout(() => {
+ wrapper.unmount();
+ done();
+ }, 0);
+ }
+ render() {
+ return null;
+ }
+ };
+
+ wrapper = renderer.create();
+ });
+
+});
diff --git a/test/react-web/client/graphql/queries/reducer.test.tsx b/test/react-web/client/graphql/queries/reducer.test.tsx
new file mode 100644
index 0000000000..355e306ba2
--- /dev/null
+++ b/test/react-web/client/graphql/queries/reducer.test.tsx
@@ -0,0 +1,95 @@
+///
+
+import * as React from 'react';
+import * as PropTypes from 'prop-types';
+import * as ReactDOM from 'react-dom';
+import * as renderer from 'react-test-renderer';
+import { mount } from 'enzyme';
+import gql from 'graphql-tag';
+import ApolloClient, { ApolloError, ObservableQuery } from 'apollo-client';
+import { NetworkInterface } from 'apollo-client';
+import { connect } from 'react-redux';
+import { withState } from 'recompose';
+
+declare function require(name: string);
+
+import { mockNetworkInterface } from '../../../../../src/test-utils';
+import { ApolloProvider, graphql} from '../../../../../src';
+
+// XXX: this is also defined in apollo-client
+// I'm not sure why mocha doesn't provide something like this, you can't
+// always use promises
+const wrap = (done: Function, cb: (...args: any[]) => any) => (...args: any[]) => {
+ try {
+ return cb(...args);
+ } catch (e) {
+ done(e);
+ }
+};
+
+function wait(ms) {
+ return new Promise(resolve => setTimeout(() => resolve(), ms));
+}
+
+describe('[queries] reducer', () => {
+
+ // props reducer
+ it('allows custom mapping of a result to props', () => {
+ const query = gql`query thing { getThing { thing } }`;
+ const data = { getThing: { thing: true } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ const props = ({ data }) => ({ showSpinner: data.loading });
+ const ContainerWithData = graphql(query, { props })(({ showSpinner }) => {
+ expect(showSpinner).toBe(true);
+ return null;
+ });
+
+ const wrapper = renderer.create();
+ (wrapper as any).unmount();
+ });
+
+ it('allows custom mapping of a result to props that includes the passed props', () => {
+ const query = gql`query thing { getThing { thing } }`;
+ const data = { getThing: { thing: true } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ const props = ({ data, ownProps }) => {
+ expect(ownProps.sample).toBe(1);
+ return { showSpinner: data.loading };
+ };
+ const ContainerWithData = graphql(query, { props })(({ showSpinner }) => {
+ expect(showSpinner).toBe(true);
+ return null;
+ });
+
+ const wrapper = renderer.create(
+
+ );
+ (wrapper as any).unmount();
+ });
+
+ it('allows custom mapping of a result to props', (done) => {
+ const query = gql`query thing { getThing { thing } }`;
+ const data = { getThing: { thing: true } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query, { props: ({ data }) => ({ thingy: data.getThing }) }) // tslint:disable-line
+ class Container extends React.Component {
+ componentWillReceiveProps(props) {
+ expect(props.thingy).toEqual(data.getThing);
+ done();
+ }
+ render() {
+ return null;
+ }
+ };
+
+ renderer.create();
+ });
+
+
+});
diff --git a/test/react-web/client/graphql/queries/skip.test.tsx b/test/react-web/client/graphql/queries/skip.test.tsx
new file mode 100644
index 0000000000..1e38cfd1df
--- /dev/null
+++ b/test/react-web/client/graphql/queries/skip.test.tsx
@@ -0,0 +1,441 @@
+///
+
+import * as React from 'react';
+import * as PropTypes from 'prop-types';
+import * as ReactDOM from 'react-dom';
+import * as renderer from 'react-test-renderer';
+import { mount } from 'enzyme';
+import gql from 'graphql-tag';
+import ApolloClient, { ApolloError, ObservableQuery } from 'apollo-client';
+import { NetworkInterface } from 'apollo-client';
+import { connect } from 'react-redux';
+import { withState } from 'recompose';
+
+declare function require(name: string);
+
+import { mockNetworkInterface } from '../../../../../src/test-utils';
+import { ApolloProvider, graphql} from '../../../../../src';
+
+// XXX: this is also defined in apollo-client
+// I'm not sure why mocha doesn't provide something like this, you can't
+// always use promises
+const wrap = (done: Function, cb: (...args: any[]) => any) => (...args: any[]) => {
+ try {
+ return cb(...args);
+ } catch (e) {
+ done(e);
+ }
+};
+
+function wait(ms) {
+ return new Promise(resolve => setTimeout(() => resolve(), ms));
+}
+
+describe('[queries] skip', () => {
+
+ // skip
+ it('allows you to skip a query (deprecated)', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ let queryExecuted;
+ @graphql(query, { options: () => ({ skip: true }) })
+ class Container extends React.Component {
+ componentWillReceiveProps(props) {
+ queryExecuted = true;
+ }
+ render() {
+ expect(this.props.data).toBeUndefined();
+ return null;
+ }
+ };
+
+ renderer.create();
+
+ setTimeout(() => {
+ if (!queryExecuted) { done(); return; }
+ fail(new Error('query ran even though skip present'));
+ }, 25);
+ });
+
+ it('allows you to skip a query without running it', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ let queryExecuted;
+ @graphql(query, { skip: ({ skip }) => skip })
+ class Container extends React.Component {
+ componentWillReceiveProps(props) {
+ queryExecuted = true;
+ }
+ render() {
+ expect(this.props.data).toBeUndefined();
+ return null;
+ }
+ };
+
+ renderer.create();
+
+ setTimeout(() => {
+ if (!queryExecuted) { done(); return; }
+ fail(new Error('query ran even though skip present'));
+ }, 25);
+ });
+
+ it('continues to not subscribe to a skipped query when props change', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const networkInterface = mockNetworkInterface();
+ const oldQuery = networkInterface.query;
+
+ networkInterface.query = function (request) {
+ fail(new Error('query ran even though skip present'));
+ return oldQuery.call(this, request);
+ };
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query, { skip: true })
+ class Container extends React.Component {8
+ componentWillReceiveProps(props) {
+ done();
+ }
+ render() {
+ return null;
+ }
+ };
+
+ class Parent extends React.Component {
+ constructor() {
+ super();
+ this.state = { foo: 42 };
+ }
+ componentDidMount() {
+ this.setState({ foo: 43 });
+ }
+ render() {
+ return ;
+ }
+ };
+
+ renderer.create();
+ });
+
+ it('doesn\'t run options or props when skipped', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ let queryExecuted;
+ @graphql(query, {
+ skip: ({ skip }) => skip,
+ options: ({ willThrowIfAccesed }) => ({ pollInterval: willThrowIfAccesed.pollInterval }),
+ props: ({ willThrowIfAccesed }) => ({ pollInterval: willThrowIfAccesed.pollInterval }),
+ })
+ class Container extends React.Component {
+ componentWillReceiveProps(props) {
+ queryExecuted = true;
+ }
+ render() {
+ expect(this.props.data).toBeFalsy();
+ return null;
+ }
+ };
+
+ renderer.create();
+
+ setTimeout(() => {
+ if (!queryExecuted) { done(); return; }
+ fail(new Error('query ran even though skip present'));
+ }, 25);
+ });
+
+ it('allows you to skip a query without running it (alternate syntax)', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ let queryExecuted;
+ @graphql(query, { skip: true })
+ class Container extends React.Component {
+ componentWillReceiveProps(props) {
+ queryExecuted = true;
+ }
+ render() {
+ expect(this.props.data).toBeFalsy();
+ return null;
+ }
+ };
+
+ renderer.create();
+
+ setTimeout(() => {
+ if (!queryExecuted) { done(); return; }
+ fail(new Error('query ran even though skip present'));
+ }, 25);
+ });
+
+ // test the case of skip:false -> skip:true -> skip:false to make sure things
+ // are cleaned up properly
+ it('allows you to skip then unskip a query with top-level syntax', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ let hasSkipped = false;
+ @graphql(query, { skip: ({ skip }) => skip })
+ class Container extends React.Component {8
+ componentWillReceiveProps(newProps) {
+ if (newProps.skip) {
+ hasSkipped = true;
+ this.props.setSkip(false);
+ } else {
+ if (hasSkipped) {
+ done();
+ } else {
+ this.props.setSkip(true);
+ }
+ }
+ }
+ render() {
+ return null;
+ }
+ };
+
+ class Parent extends React.Component {
+ constructor() {
+ super();
+ this.state = { skip: false };
+ }
+ render() {
+ return this.setState({ skip })} />;
+ }
+ };
+
+ renderer.create();
+ });
+
+ it('allows you to skip then unskip a query with new options (top-level syntax)', (done) => {
+ const query = gql`query people($first: Int) { allPeople(first: $first) { people { name } } }`;
+ const dataOne = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const dataTwo = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface(
+ { request: { query, variables: { first: 1 } }, result: { data: dataOne } },
+ { request: { query, variables: { first: 2 } }, result: { data: dataTwo } },
+ { request: { query, variables: { first: 2 } }, result: { data: dataTwo } },
+ );
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ let hasSkipped = false;
+ @graphql(query, { skip: ({ skip }) => skip })
+ class Container extends React.Component {8
+ componentWillReceiveProps(newProps) {
+ if (newProps.skip) {
+ hasSkipped = true;
+ // change back to skip: false, with a different variable
+ this.props.setState({ skip: false, first: 2 });
+ } else {
+ if (hasSkipped) {
+ if (!newProps.data.loading) {
+ expect(newProps.data.allPeople).toEqual(dataTwo.allPeople);
+ done();
+ }
+ } else {
+ expect(newProps.data.allPeople).toEqual(dataOne.allPeople);
+ this.props.setState({ skip: true });
+ }
+ }
+ }
+ render() {
+ return null;
+ }
+ };
+
+ class Parent extends React.Component {
+ constructor() {
+ super();
+ this.state = { skip: false, first: 1 };
+ }
+ render() {
+ return (
+ this.setState(state)}
+ />
+ );
+ }
+ };
+
+ renderer.create();
+ });
+
+ it('allows you to skip then unskip a query with opts syntax', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const nextData = { allPeople: { people: [ { name: 'Anakin Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({
+ request: { query }, result: { data }, newData: () => ({ data: nextData }) });
+ const oldQuery = networkInterface.query;
+
+ let ranQuery = 0;
+ networkInterface.query = function (request) {
+ ranQuery++;
+ return oldQuery.call(this, request);
+ };
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ let hasSkipped = false;
+ let hasRequeried = false;
+ @graphql(query, { options: ({ skip }) => ({ skip, fetchPolicy: 'network-only' }) })
+ class Container extends React.Component {8
+ componentWillReceiveProps(newProps) {
+ if (newProps.skip) {
+ // Step 2. We shouldn't query again.
+ expect(ranQuery).toBe(1);
+ hasSkipped = true;
+ this.props.setSkip(false);
+ } else if (hasRequeried) {
+ // Step 4. We need to actually get the data from the query into the component!
+ expect(newProps.data.loading).toBe(false);
+ done();
+ } else if (hasSkipped) {
+ // Step 3. We need to query again!
+ expect(newProps.data.loading).toBe(true);
+ expect(ranQuery).toBe(2);
+ hasRequeried = true;
+ } else {
+ // Step 1. We've queried once.
+ expect(ranQuery).toBe(1);
+ this.props.setSkip(true);
+ }
+ }
+ render() {
+ return null;
+ }
+ };
+
+ class Parent extends React.Component {
+ constructor() {
+ super();
+ this.state = { skip: false };
+ }
+ render() {
+ return this.setState({ skip })} />;
+ }
+ };
+
+ renderer.create();
+ });
+
+ it('removes the injected props if skip becomes true', (done) => {
+ let count = 0;
+ const query = gql`
+ query people($first: Int) {
+ allPeople(first: $first) { people { name } }
+ }
+ `;
+
+ const data1 = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const variables1 = { first: 1 };
+
+ const data2 = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
+ const variables2 = { first: 2 };
+
+
+ const data3 = { allPeople: { people: [ { name: 'Anakin Skywalker' } ] } };
+ const variables3 = { first: 3 };
+
+ const networkInterface = mockNetworkInterface(
+ { request: { query, variables: variables1 }, result: { data: data1 } },
+ { request: { query, variables: variables2 }, result: { data: data2 } },
+ { request: { query, variables: variables3 }, result: { data: data2 } }
+ );
+
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query, {
+ skip: () => count === 1,
+ options: (props) => ({ variables: props }),
+ })
+ class Container extends React.Component {
+ componentWillReceiveProps({ data }) {
+ // loading is true, but data still there
+ if (count === 0) expect(data.allPeople).toEqual(data1.allPeople);
+ if (count === 1 ) expect(data).toBeFalsy();
+ if (count === 2 && data.loading) expect(data.allPeople).toBeFalsy();
+ if (count === 2 && !data.loading) {
+ expect(data.allPeople).toEqual(data2.allPeople);
+ done();
+ }
+ }
+ render() {
+ return null;
+ }
+ };
+
+ class ChangingProps extends React.Component {
+ state = { first: 1 };
+
+ componentDidMount() {
+ setTimeout(() => {
+ count++;
+ this.setState({ first: 2 });
+ }, 50);
+
+ setTimeout(() => {
+ count++;
+ this.setState({ first: 3 });
+ }, 100);
+ }
+
+ render() {
+ return ;
+ }
+ }
+
+ renderer.create();
+ });
+
+ it('allows you to unmount a skipped query', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const networkInterface = mockNetworkInterface();
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query, {
+ skip: true,
+ })
+ class Container extends React.Component {
+ componentDidMount() {
+ this.props.hide();
+ }
+ componentWillUnmount() {
+ done();
+ }
+ render() {
+ return null;
+ }
+ };
+
+ class Hider extends React.Component {
+ constructor() {
+ super();
+ this.state = { hide: false };
+ }
+ render() {
+ if (this.state.hide) {
+ return null;
+ }
+ return this.setState({ hide: true })} />;
+ }
+ }
+
+ renderer.create();
+ });
+
+});
diff --git a/test/react-web/client/graphql/queries/updateQuery.test.tsx b/test/react-web/client/graphql/queries/updateQuery.test.tsx
new file mode 100644
index 0000000000..4c18c0dd5c
--- /dev/null
+++ b/test/react-web/client/graphql/queries/updateQuery.test.tsx
@@ -0,0 +1,180 @@
+///
+
+import * as React from 'react';
+import * as PropTypes from 'prop-types';
+import * as ReactDOM from 'react-dom';
+import * as renderer from 'react-test-renderer';
+import { mount } from 'enzyme';
+import gql from 'graphql-tag';
+import ApolloClient, { ApolloError, ObservableQuery } from 'apollo-client';
+import { NetworkInterface } from 'apollo-client';
+import { connect } from 'react-redux';
+import { withState } from 'recompose';
+
+declare function require(name: string);
+
+import { mockNetworkInterface } from '../../../../../src/test-utils';
+import { ApolloProvider, graphql} from '../../../../../src';
+
+// XXX: this is also defined in apollo-client
+// I'm not sure why mocha doesn't provide something like this, you can't
+// always use promises
+const wrap = (done: Function, cb: (...args: any[]) => any) => (...args: any[]) => {
+ try {
+ return cb(...args);
+ } catch (e) {
+ done(e);
+ }
+};
+
+function wait(ms) {
+ return new Promise(resolve => setTimeout(() => resolve(), ms));
+}
+
+describe('[queries] updateQuery', () => {
+
+ // updateQuery
+ it('exposes updateQuery as part of the props api', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query)
+ class Container extends React.Component {
+ componentWillReceiveProps({ data }) { // tslint:disable-line
+ expect(data.updateQuery).toBeTruthy();
+ expect(data.updateQuery instanceof Function).toBe(true);
+ try {
+ data.updateQuery(() => done());
+ } catch (error) {
+ // fail
+ }
+ }
+ render() {
+ return null;
+ }
+ };
+
+ renderer.create();
+ });
+
+ it('exposes updateQuery as part of the props api during componentWillMount', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query)
+ class Container extends React.Component {
+ componentWillMount() { // tslint:disable-line
+ expect(this.props.data.updateQuery).toBeTruthy()
+ expect(this.props.data.updateQuery instanceof Function).toBe(true);
+ done();
+ }
+ render() {
+ return null;
+ }
+ };
+
+ renderer.create();
+ });
+
+ it('updateQuery throws if called before data has returned', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ @graphql(query)
+ class Container extends React.Component {
+ componentWillMount() { // tslint:disable-line
+ expect(this.props.data.updateQuery).toBeTruthy();
+ expect(this.props.data.updateQuery instanceof Function).toBe(true);
+ try {
+ this.props.data.updateQuery();
+ done();
+ } catch (e) {
+ expect(e.toString()).toMatch(/ObservableQuery with this id doesn't exist:/);
+ done();
+ }
+ }
+ render() {
+ return null;
+ }
+ };
+
+ renderer.create();
+ });
+
+ it('allows updating query results after query has finished (early binding)', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const data2 = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface(
+ { request: { query }, result: { data } },
+ { request: { query }, result: { data: data2 } }
+ );
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ let isUpdated;
+ @graphql(query)
+ class Container extends React.Component {
+ public updateQuery: any;
+ componentWillMount() {
+ this.updateQuery = this.props.data.updateQuery;
+ }
+ componentWillReceiveProps(props) {
+ if (isUpdated) {
+ expect(props.data.allPeople).toEqual(data2.allPeople);
+ done();
+ return;
+ } else {
+ isUpdated = true;
+ this.updateQuery((prev) => {
+ return data2;
+ });
+ }
+ }
+ render() {
+ return null;
+ }
+ };
+
+ renderer.create();
+ });
+
+ it('allows updating query results after query has finished', (done) => {
+ const query = gql`query people { allPeople(first: 1) { people { name } } }`;
+ const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
+ const data2 = { allPeople: { people: [ { name: 'Leia Skywalker' } ] } };
+ const networkInterface = mockNetworkInterface(
+ { request: { query }, result: { data } },
+ { request: { query }, result: { data: data2 } }
+ );
+ const client = new ApolloClient({ networkInterface, addTypename: false });
+
+ let isUpdated;
+ @graphql(query)
+ class Container extends React.Component {
+ componentWillReceiveProps(props) {
+ if (isUpdated) {
+ expect(props.data.allPeople).toEqual(data2.allPeople);
+ done();
+ return;
+ } else {
+ isUpdated = true;
+ props.data.updateQuery((prev) => {
+ return data2;
+ });
+ }
+ }
+ render() {
+ return null;
+ }
+ };
+
+ renderer.create();
+ });
+
+});