Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

WIP: feat(useQuery): forward networkStatus #68

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 183 additions & 6 deletions src/__tests__/useQuery-test.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { ApolloClient } from 'apollo-client';
import { ApolloClient, NetworkStatus } from 'apollo-client';
import { ApolloLink, DocumentNode, Observable } from 'apollo-link';
import gql from 'graphql-tag';
import { withProfiler } from 'jest-react-profiler';
import React, { Fragment, Suspense, SuspenseProps } from 'react';
import { cleanup, render } from 'react-testing-library';
import { cleanup, fireEvent, render } from 'react-testing-library';

import { ApolloProvider, QueryHookOptions, useQuery } from '..';
import createClient from '../__testutils__/createClient';
import { SAMPLE_TASKS } from '../__testutils__/data';
import { MORE_TASKS, SAMPLE_TASKS } from '../__testutils__/data';
import flushEffectsAndWait from '../__testutils__/flushEffectsAndWait';
import noop from '../__testutils__/noop';

Expand Down Expand Up @@ -81,6 +81,28 @@ const TASKS_MOCKS = [
},
},
},

{
request: {
query: gql`
query FetchMoreTasksQuery {
moreTasks {
id
text
completed
__typename
}
}
`,
variables: {},
},
result: {
data: {
__typename: 'Query',
tasks: [...MORE_TASKS],
},
},
},
];

const TASKS_QUERY = gql`
Expand Down Expand Up @@ -115,7 +137,9 @@ function createMockClient(link?: ApolloLink) {
}

interface TasksProps<TVariables = any> extends QueryHookOptions<TVariables> {
onNetworkStatus?: (networkStatus?: NetworkStatus) => void;
query: DocumentNode;
showFetchMore?: boolean;
}

function TaskList({ tasks }: { tasks: Array<{ id: number; text: string }> }) {
Expand All @@ -128,8 +152,20 @@ function TaskList({ tasks }: { tasks: Array<{ id: number; text: string }> }) {
);
}

function Tasks({ query, ...options }: TasksProps) {
const { data, error, errors, loading } = useQuery(query, options);
function Tasks({
onNetworkStatus,
query,
showFetchMore,
...options
}: TasksProps) {
const { data, error, errors, fetchMore, loading, ...rest } = useQuery(
query,
options
);

if (onNetworkStatus) {
onNetworkStatus(rest.networkStatus);
}

if (error) {
return <>{error.message}</>;
Expand All @@ -153,7 +189,38 @@ function Tasks({ query, ...options }: TasksProps) {
return <>Skipped loading of data</>;
}

return <TaskList tasks={data.tasks} />;
return (
<>
<TaskList tasks={data.tasks} />
{showFetchMore && (
<button
data-testid="fetch-more"
onClick={() => {
fetchMore({
query: gql`
query FetchMoreTasksQuery {
moreTasks {
id
text
completed
}
}
`,
updateQuery(previousResult, { fetchMoreResult }) {
const result = {
...previousResult,
tasks: [...previousResult.tasks, ...fetchMoreResult.tasks],
};
return result;
},
});
}}
>
Fetch more
</button>
)}
</>
);
}

interface TasksWrapperProps extends TasksProps {
Expand Down Expand Up @@ -753,3 +820,113 @@ it('starts skipped query in non-suspense mode', async () => {
</div>
`);
});

it('should forward `networkStatus`', async () => {
const client = createMockClient();
const onNetworkStatusSpy = jest.fn();

render(
<TasksWrapper
client={client}
onNetworkStatus={onNetworkStatusSpy}
query={TASKS_QUERY}
/>
);

expect(onNetworkStatusSpy.mock.calls).toEqual([]);
await flushEffectsAndWait();
expect(onNetworkStatusSpy.mock.calls).toEqual([
[NetworkStatus.ready],
[NetworkStatus.ready],
]);
});

it('should forward `networkStatus` in non-suspense mode', async () => {
const client = createMockClient();
const onNetworkStatusSpy = jest.fn();

render(
<TasksWrapper
client={client}
onNetworkStatus={onNetworkStatusSpy}
query={TASKS_QUERY}
suspend={false}
/>
);

expect(onNetworkStatusSpy.mock.calls).toEqual([[NetworkStatus.loading]]);
await flushEffectsAndWait();
expect(onNetworkStatusSpy.mock.calls).toEqual([
[NetworkStatus.loading],
[NetworkStatus.ready],
]);
});

it('should forward `networkStatus` when `fetchMore` is used', async () => {
const client = createMockClient();
const onNetworkStatusSpy = jest.fn();

const { getByTestId } = render(
<TasksWrapper
client={client}
notifyOnNetworkStatusChange
onNetworkStatus={onNetworkStatusSpy}
query={TASKS_QUERY}
showFetchMore
/>
);

expect(onNetworkStatusSpy.mock.calls).toEqual([]);
await flushEffectsAndWait();
expect(onNetworkStatusSpy.mock.calls).toEqual([
[NetworkStatus.ready],
[NetworkStatus.ready],
]);

onNetworkStatusSpy.mockClear();

const fetchMoreButton = getByTestId('fetch-more');
fireEvent.click(fetchMoreButton);
await flushEffectsAndWait();

expect(onNetworkStatusSpy.mock.calls).toEqual([
[NetworkStatus.fetchMore],
[NetworkStatus.ready],
[NetworkStatus.ready],
]);
});

it('should forward `networkStatus` in non-suspense mode when `fetchMore` is used', async () => {
const client = createMockClient();
const onNetworkStatusSpy = jest.fn();

const { getByTestId } = render(
<TasksWrapper
client={client}
notifyOnNetworkStatusChange
onNetworkStatus={onNetworkStatusSpy}
query={TASKS_QUERY}
showFetchMore
suspend={false}
/>
);

expect(onNetworkStatusSpy.mock.calls).toEqual([[NetworkStatus.loading]]);
await flushEffectsAndWait();
expect(onNetworkStatusSpy.mock.calls).toEqual([
[NetworkStatus.loading],
[NetworkStatus.ready],
]);

onNetworkStatusSpy.mockClear();

const fetchMoreButton = getByTestId('fetch-more');
fireEvent.click(fetchMoreButton);
await flushEffectsAndWait();

expect(onNetworkStatusSpy.mock.calls).toEqual([
[NetworkStatus.fetchMore],
[NetworkStatus.ready],
[NetworkStatus.ready],
]);
});
15 changes: 15 additions & 0 deletions src/__testutils__/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,18 @@ export const SAMPLE_TASKS = [
text: 'Learn Apollo',
},
];

export const MORE_TASKS = [
{
__typename: 'Task',
completed: true,
id: '4',
text: 'Learn Node.js',
},
{
__typename: 'Task',
completed: false,
id: '5',
text: 'Learn Postgres',
},
];
30 changes: 29 additions & 1 deletion src/useQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ import {
FetchMoreOptions,
FetchMoreQueryOptions,
FetchPolicy,
NetworkStatus,
ObservableQuery,
OperationVariables,
QueryOptions,
WatchQueryOptions,
} from 'apollo-client';
import { DocumentNode } from 'graphql';
import { useContext, useEffect, useMemo, useState } from 'react';
import { unstable_scheduleCallback } from 'scheduler';

import { useApolloClient } from './ApolloContext';
import { SSRContext } from './internal/SSRContext';
import {
Expand All @@ -25,6 +28,8 @@ export interface QueryHookState<TData>
'error' | 'errors' | 'loading' | 'partial'
> {
data?: TData;
// networkStatus is undefined for skipped queries
networkStatus: NetworkStatus | undefined;
}

export interface QueryHookOptions<TVariables>
Expand Down Expand Up @@ -129,6 +134,7 @@ export function useQuery<TData = any, TVariables = OperationVariables>(
error: result.error,
errors: result.errors,
loading: result.loading,
networkStatus: result.networkStatus,
partial: result.partial,
};
},
Expand Down Expand Up @@ -173,14 +179,36 @@ export function useQuery<TData = any, TVariables = OperationVariables>(
data: undefined,
error: undefined,
loading: false,
networkStatus: undefined,
};
}

if (currentResult.partial) {
if (suspend) {
// throw a promise - use the react suspense to wait until the data is
// available
throw observableQuery.result();
throw new Promise((resolve, reject) => {
const fakeSubscription = observableQuery.subscribe(() => {
// empty
});
const unsubscribe = () => {
unstable_scheduleCallback(() => {
setTimeout(() => {
fakeSubscription.unsubscribe();
});
});
};
observableQuery.result().then(
result => {
resolve(result);
unsubscribe();
},
error => {
reject(error);
unsubscribe();
}
);
});
}

if (ssrInUse) {
Expand Down