Skip to content

Commit

Permalink
Add skip option to useQuery
Browse files Browse the repository at this point in the history
  • Loading branch information
Ács Dániel committed Aug 16, 2024
1 parent 602074e commit dd1538f
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 9 deletions.
4 changes: 4 additions & 0 deletions docs/fetching/queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@ Glimmer Apollo supports SSR with [FastBoot](http://ember-fastboot.com/) by defau

The `ssr` option allows disabling fetching of the query when running in SSR with FastBoot. It will skip the execution entirely in FastBoot but will execute when running in the Browser. This feature is useful if you are fetching secondary data to the page and can wait to be fetched.

### `skip`

The query will skip the execution while the `skip` option is true.

### `clientId`

This option specifies which Apollo Client should be used for the given query. Glimmer Apollo supports defining multiple Apollo Clients that are distinguished by a custom identifier while setting the client to Glimmer Apollo.
Expand Down
30 changes: 24 additions & 6 deletions packages/glimmer-apollo/src/-private/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type { TemplateArgs } from './types';

export interface QueryOptions<TData, TVariables extends OperationVariables>
extends Omit<WatchQueryOptions<TVariables>, 'query'> {
skip?: boolean;
ssr?: boolean;
clientId?: string;
onComplete?: (data: TData | undefined) => void;
Expand All @@ -41,7 +42,7 @@ export class QueryResource<
TVariables,
TemplateArgs<QueryPositionalArgs<TData, TVariables>>
> {
@tracked loading = true;
@tracked loading = false;
@tracked error?: ApolloError;
@tracked data: TData | undefined;
@tracked networkStatus: NetworkStatus = NetworkStatus.loading;
Expand All @@ -55,22 +56,39 @@ export class QueryResource<
/** @internal */
async setup(): Promise<void> {
this.#previousPositionalArgs = this.args.positional;
const [query, options] = this.args.positional;
const client = getClient(this, options?.clientId);
const [query, options = {}] = this.args.positional;
const client = getClient(this, options.clientId);

this.loading = true;
const fastboot = getFastboot(this);

if (fastboot && fastboot.isFastBoot && options && options.ssr === false) {
if (
fastboot &&
fastboot.isFastBoot &&
(options.ssr === false || options.skip === true)
) {
return;
}

let [promise, firstResolve, firstReject] = createPromise(); // eslint-disable-line prefer-const
this.#firstPromiseReject = firstReject;
this.promise = promise;

if (options.skip) {
options.fetchPolicy = 'standby';
}

if (options.fetchPolicy === 'standby') {
if (firstResolve) {
firstResolve();
firstResolve = undefined;
}
} else {
this.loading = true;
}

const observable = client.watchQuery({
query,
...(options || {})
...options
});

this._setObservable(observable);
Expand Down
19 changes: 19 additions & 0 deletions packages/test-app/app/components/playground/experiment.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,25 @@ firstName:
refetch
</button>

<hr />
<h2 data-test-id="experiment">
User Info w/ skip
</h2>

loading:
{{this.userInfoWithSkip.loading}}
<br />
firstName:
{{this.userInfoWithSkip.data.user.firstName}}

{{#if this.userInfoWithSkip.error}}
Error:
{{this.userInfoWithSkip.error.message}}
{{/if}}
<button type="button" {{on "click" this.userInfoWithSkip.refetch}}>
refetch
</button>

<hr />
<h2>
Login
Expand Down
10 changes: 10 additions & 0 deletions packages/test-app/app/components/playground/experiment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ export default class PlaygroundExperiment extends Component {
}
]);

userInfoWithSkip = useQuery(this, () => [
USER_INFO,
{
variables: { id: '1-with-delay' },
errorPolicy: 'all',
notifyOnNetworkStatusChange: true,
skip: true
}
]);

login = useMutation(this, () => [
LOGIN,
{
Expand Down
181 changes: 178 additions & 3 deletions packages/test-app/tests/unit/query-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import {
ApolloClient,
ApolloError,
InMemoryCache,
WatchQueryFetchPolicy,
createHttpLink
} from '@apollo/client/core';
import {
UserInfoQuery,
UserInfoQueryVariables
} from '../../app/mocks/handlers';
import sinon from 'sinon';
import { waitUntil } from '@ember/test-helpers';

const USER_INFO = gql`
query UserInfo($id: ID!) {
Expand All @@ -29,11 +31,13 @@ module('useQuery', function (hooks) {
let ctx = {};
const owner = {};

const link = createHttpLink({
uri: '/graphql'
});

const client = new ApolloClient({
cache: new InMemoryCache(),
link: createHttpLink({
uri: '/graphql'
})
link
});

hooks.beforeEach(() => {
Expand Down Expand Up @@ -141,6 +145,26 @@ module('useQuery', function (hooks) {
assert.deepEqual(onCompleteCalled, expectedData);
});

test('it does not call onCompleted if skip is true', async function (assert) {
const sandbox = sinon.createSandbox();
const onCompleteCallback = sandbox.fake();
const query = useQuery<UserInfoQuery, UserInfoQueryVariables>(ctx, () => [
USER_INFO,
{
variables: { id: '2' },
skip: true,
onComplete: onCompleteCallback
}
]);

await query.settled();
assert.equal(query.loading, false);
assert.equal(query.data, undefined);
assert.equal(onCompleteCallback.callCount, 0);

sandbox.restore();
});

test('it calls onError', async function (assert) {
let onErrorCalled: ApolloError;
const query = useQuery<UserInfoQuery, UserInfoQueryVariables>(ctx, () => [
Expand Down Expand Up @@ -267,4 +291,155 @@ module('useQuery', function (hooks) {

sandbox.restore();
});

test('it skips running a query when skip is true', async function (assert) {
class Obj {
@tracked skip = true;
}
const options = new Obj();

const query = useQuery<UserInfoQuery, UserInfoQueryVariables>(ctx, () => [
USER_INFO,
{
variables: { id: '1' },
skip: options.skip
}
]);

assert.equal(query.loading, false);
assert.equal(query.data, undefined);

options.skip = false;

assert.equal(query.loading, true);
assert.equal(query.data, undefined);
await query.settled();
assert.equal(query.loading, false);
assert.deepEqual(query.data, {
user: {
__typename: 'User',
firstName: 'Cathaline',
id: '1',
lastName: 'McCoy'
}
});
});

test('it does not make network requests when skip is true', async function (assert) {
const sandbox = sinon.createSandbox();

const requestSpy = sandbox.fake(link.request);
sandbox.replace(link, 'request', requestSpy);

class Obj {
@tracked skip = false;
@tracked id = '1';
}
const options = new Obj();

const query = useQuery<UserInfoQuery, UserInfoQueryVariables>(ctx, () => [
USER_INFO,
{
variables: { id: options.id },
skip: options.skip
}
]);

assert.equal(query.loading, true);
assert.equal(query.data, undefined);

await query.settled();
assert.equal(query.loading, false);
assert.deepEqual(query.data, {
user: {
__typename: 'User',
firstName: 'Cathaline',
id: '1',
lastName: 'McCoy'
}
});

options.skip = true;
options.id = '2';

await query.settled();
assert.equal(query.loading, false);
assert.deepEqual(query.data, {
user: {
__typename: 'User',
firstName: 'Cathaline',
id: '1',
lastName: 'McCoy'
}
});
assert.equal(requestSpy.callCount, 1);

sandbox.restore();
});

test('it treats fetchPolicy standby like skip', async function (assert) {
class Obj {
@tracked fetchPolicy: WatchQueryFetchPolicy = 'standby';
}
const options = new Obj();

const query = useQuery<UserInfoQuery, UserInfoQueryVariables>(ctx, () => [
USER_INFO,
{
variables: { id: '1' },
fetchPolicy: options.fetchPolicy
}
]);

assert.equal(query.loading, false);
assert.equal(query.data, undefined);

await query.settled();

options.fetchPolicy = 'cache-first';
assert.equal(query.data, undefined);

await query.settled();
assert.deepEqual(query.data, {
user: {
__typename: 'User',
firstName: 'Cathaline',
id: '1',
lastName: 'McCoy'
}
});
});

test('it refetches the query when skip is true', async function (assert) {
const sandbox = sinon.createSandbox();

const requestSpy = sandbox.fake(link.request);
sandbox.replace(link, 'request', requestSpy);

const query = useQuery<UserInfoQuery, UserInfoQueryVariables>(ctx, () => [
USER_INFO,
{
variables: { id: '1' },
skip: true
}
]);

query.refetch();

assert.equal(query.loading, false);
await waitUntil(() => query.data !== undefined);
assert.ok(requestSpy.calledOnce);
assert.equal(query.loading, false);
assert.equal(query.networkStatus, 7);
assert.deepEqual(query.data, {
user: {
__typename: 'User',
firstName: 'Cathaline',
id: '1',
lastName: 'McCoy'
}
});

sandbox.restore();
});
});

0 comments on commit dd1538f

Please sign in to comment.