Commit 39a2dbf2 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '282440-mlunoe-improve-graphql-local-resolver-tests-follow-up' into 'master'

Improve Apollo/GraphQL docs on fetchMore/pagination and tests

See merge request gitlab-org/gitlab!48245
parents c0e4f65d ca99b41b
...@@ -439,9 +439,11 @@ parameter, indicating a starting or ending point of our pagination. They should ...@@ -439,9 +439,11 @@ parameter, indicating a starting or ending point of our pagination. They should
followed with `first` or `last` parameter respectively to indicate _how many_ items followed with `first` or `last` parameter respectively to indicate _how many_ items
we want to fetch after or before a given endpoint. we want to fetch after or before a given endpoint.
For example, here we're fetching 10 designs after a cursor: For example, here we're fetching 10 designs after a cursor (let us call this `projectQuery`):
```javascript ```javascript
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
query { query {
project(fullPath: "root/my-project") { project(fullPath: "root/my-project") {
id id
...@@ -453,6 +455,9 @@ query { ...@@ -453,6 +455,9 @@ query {
id id
} }
} }
pageInfo {
...PageInfo
}
} }
} }
} }
...@@ -460,21 +465,31 @@ query { ...@@ -460,21 +465,31 @@ query {
} }
``` ```
Note that we are using the [`pageInfo.fragment.graphql`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/assets/javascripts/graphql_shared/fragments/pageInfo.fragment.graphql) to populate the `pageInfo` information.
#### Using `fetchMore` method in components #### Using `fetchMore` method in components
This approach makes sense to use with user-handled pagination (e.g. when the scrolls to fetch more data or explicitly clicks a "Next Page"-button).
When we need to fetch all the data initially, it is recommended to use [a (non-smart) query, instead](#using-a-recursive-query-in-components).
When making an initial fetch, we usually want to start a pagination from the beginning. When making an initial fetch, we usually want to start a pagination from the beginning.
In this case, we can either: In this case, we can either:
- Skip passing a cursor. - Skip passing a cursor.
- Pass `null` explicitly to `after`. - Pass `null` explicitly to `after`.
After data is fetched, we should save a `pageInfo` object. Let's assume we're storing After data is fetched, we can use the `update`-hook as an opportunity [to customize
it to Vue component `data`: the data that is set in the Vue component property](https://apollo.vuejs.org/api/smart-query.html#options), getting a hold of the `pageInfo` object among other data.
In the `result`-hook, we can inspect the `pageInfo` object to see if we need to fetch
the next page. Note that we also keep a `requestCount` to ensure that the application
does not keep requesting the next page, indefinitely:
```javascript ```javascript
data() { data() {
return { return {
pageInfo: null, pageInfo: null,
requestCount: 0,
} }
}, },
apollo: { apollo: {
...@@ -482,13 +497,29 @@ apollo: { ...@@ -482,13 +497,29 @@ apollo: {
query: projectQuery, query: projectQuery,
variables() { variables() {
return { return {
// rest of design variables // ... The rest of the design variables
...
first: 10, first: 10,
}; };
}, },
result(res) { update(data) {
this.pageInfo = res.data?.project?.issue?.designCollection?.designs?.pageInfo; const { id = null, issue = {} } = data.project || {};
const { edges = [], pageInfo } = issue.designCollection?.designs || {};
return {
id,
edges,
pageInfo,
};
},
result() {
const { pageInfo } = this.designs;
// Increment the request count with each new result
this.requestCount += 1;
// Only fetch next page if we have more requests and there is a next page to fetch
if (this.requestCount < MAX_REQUEST_COUNT && pageInfo?.hasNextPage) {
this.fetchNextPage(pageInfo.endCursor);
}
}, },
}, },
}, },
...@@ -497,34 +528,102 @@ apollo: { ...@@ -497,34 +528,102 @@ apollo: {
When we want to move to the next page, we use an Apollo `fetchMore` method, passing a When we want to move to the next page, we use an Apollo `fetchMore` method, passing a
new cursor (and, optionally, new variables) there. In the `updateQuery` hook, we have new cursor (and, optionally, new variables) there. In the `updateQuery` hook, we have
to return a result we want to see in the Apollo cache after fetching the next page. to return a result we want to see in the Apollo cache after fetching the next page.
[`Immer`s `produce`](#immutability-and-cache-updates)-function can help us with the immutability here:
```javascript ```javascript
fetchNextPage() { fetchNextPage(endCursor) {
// as a first step, we're checking if we have more pages to move forward this.$apollo.queries.designs.fetchMore({
if (this.pageInfo?.hasNextPage) { variables: {
this.$apollo.queries.designs.fetchMore({ // ... The rest of the design variables
variables: { first: 10,
// rest of design variables after: endCursor,
... },
first: 10, updateQuery(previousResult, { fetchMoreResult }) {
after: this.pageInfo?.endCursor, // Here we can implement the logic of adding new designs to existing ones
}, // (for example, if we use infinite scroll) or replacing old result
updateQuery(previousResult, { fetchMoreResult }) { // with the new one if we use numbered pages
// here we can implement the logic of adding new designs to fetched one (for example, if we use infinite scroll)
// or replacing old result with the new one if we use numbered pages const { designs: previousDesigns } = previousResult.project.issue.designCollection;
const { designs: newDesigns } = fetchMoreResult.project.issue.designCollection
return produce(previousResult, draftData => {
// `produce` gives us a working copy, `draftData`, that we can modify
// as we please and from it will produce the next immutable result for us
draftData.project.issue.designCollection.designs = [...previousDesigns, ...newDesigns];
});
},
});
}
```
const newDesigns = fetchMoreResult.project.issue.designCollection.designs; #### Using a recursive query in components
previousResult.project.issue.designCollection.designs.push(...newDesigns)
return previousResult; When it is necessary to fetch all paginated data initially an Apollo query can do the trick for us.
}, If we need to fetch the next page based on user interactions, it is recommend to use a [`smartQuery`](https://apollo.vuejs.org/api/smart-query.html) along with the [`fetchMore`-hook](#using-fetchmore-method-in-components).
});
When the query resolves we can update the component data and inspect the `pageInfo` object
to see if we need to fetch the next page, i.e. call the method recursively.
Note that we also keep a `requestCount` to ensure that the application does not keep
requesting the next page, indefinitely.
```javascript
data() {
return {
requestCount: 0,
isLoading: false,
designs: {
edges: [],
pageInfo: null,
},
} }
} },
``` created() {
this.fetchDesigns();
},
methods: {
handleError(error) {
this.isLoading = false;
// Do something with `error`
},
fetchDesigns(endCursor) {
this.isLoading = true;
return this.$apollo
.query({
query: projectQuery,
variables() {
return {
// ... The rest of the design variables
first: 10,
endCursor,
};
},
})
.then(({ data }) => {
const { id = null, issue = {} } = data.project || {};
const { edges = [], pageInfo } = issue.designCollection?.designs || {};
// Update data
this.designs = {
id,
edges: [...this.designs.edges, ...edges];
pageInfo: pageInfo;
};
Please note we don't have to save `pageInfo` one more time; `fetchMore` triggers a query // Increment the request count with each new result
`result` hook as well. this.requestCount += 1;
// Only fetch next page if we have more requests and there is a next page to fetch
if (this.requestCount < MAX_REQUEST_COUNT && pageInfo?.hasNextPage) {
this.fetchDesigns(pageInfo.endCursor);
} else {
this.isLoading = false;
}
})
.catch(this.handleError);
},
},
```
### Managing performance ### Managing performance
...@@ -666,34 +765,51 @@ it('calls mutation on submitting form ', () => { ...@@ -666,34 +765,51 @@ it('calls mutation on submitting form ', () => {
To test the logic of Apollo cache updates, we might want to mock an Apollo Client in our unit tests. We use [`mock-apollo-client`](https://www.npmjs.com/package/mock-apollo-client) library to mock Apollo client and [`createMockApollo` helper](https://gitlab.com/gitlab-org/gitlab/-/blob/master/spec/frontend/helpers/mock_apollo_helper.js) we created on top of it. To test the logic of Apollo cache updates, we might want to mock an Apollo Client in our unit tests. We use [`mock-apollo-client`](https://www.npmjs.com/package/mock-apollo-client) library to mock Apollo client and [`createMockApollo` helper](https://gitlab.com/gitlab-org/gitlab/-/blob/master/spec/frontend/helpers/mock_apollo_helper.js) we created on top of it.
To separate tests with mocked client from 'usual' unit tests, it's recommended to create an additional component factory. This way we only create Apollo Client instance when it's necessary: To separate tests with mocked client from 'usual' unit tests, it's recommended to create an additional factory and pass the created `mockApollo` as an option to the `createComponent`-factory. This way we only create Apollo Client instance when it's necessary.
```javascript We need to inject `VueApollo` to the Vue local instance and, likewise, it is recommended to call `localVue.use()` within `createMockApolloProvider()` to only load it when it is necessary.
function createComponent() {...}
function createComponentWithApollo() {...}
```
Then we need to inject `VueApollo` to Vue local instance (`localVue.use()` can also be called within `createComponentWithApollo()`)
```javascript ```javascript
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import { createLocalVue } from '@vue/test-utils'; import { createLocalVue } from '@vue/test-utils';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(VueApollo);
function createMockApolloProvider() {
localVue.use(VueApollo);
return createMockApollo(requestHandlers);
}
function createComponent(options = {}) {
const { mockApollo } = options;
...
return shallowMount(..., {
localVue,
apolloProvider: mockApollo,
...
});
}
``` ```
After this, on the global `describe`, we should create a variable for `fakeApollo`: After this, you can control whether you need a variable for `mockApollo` and assign it in the appropriate `describe`-scope:
```javascript ```javascript
describe('Some component with Apollo mock', () => { describe('Some component', () => {
let wrapper; let wrapper;
let fakeApollo
}) describe('with Apollo mock', () => {
let mockApollo;
beforeEach(() => {
mockApollo = createMockApolloProvider();
wrapper = createComponent({ mockApollo });
});
});
});
``` ```
Within component factory, we need to define an array of _handlers_ for every query or mutation: Within `createMockApolloProvider`-factory, we need to define an array of _handlers_ for every query or mutation:
```javascript ```javascript
import getDesignListQuery from '~/design_management/graphql/queries/get_design_list.query.graphql'; import getDesignListQuery from '~/design_management/graphql/queries/get_design_list.query.graphql';
...@@ -702,13 +818,16 @@ import moveDesignMutation from '~/design_management/graphql/mutations/move_desig ...@@ -702,13 +818,16 @@ import moveDesignMutation from '~/design_management/graphql/mutations/move_desig
describe('Some component with Apollo mock', () => { describe('Some component with Apollo mock', () => {
let wrapper; let wrapper;
let fakeApollo; let mockApollo;
function createMockApolloProvider() {
Vue.use(VueApollo);
function createComponentWithApollo() {
const requestHandlers = [ const requestHandlers = [
[getDesignListQuery, jest.fn().mockResolvedValue(designListQueryResponse)], [getDesignListQuery, jest.fn().mockResolvedValue(designListQueryResponse)],
[permissionsQuery, jest.fn().mockResolvedValue(permissionsQueryResponse)], [permissionsQuery, jest.fn().mockResolvedValue(permissionsQueryResponse)],
]; ];
...
} }
}) })
``` ```
...@@ -718,23 +837,38 @@ After this, we need to create a mock Apollo Client instance using a helper: ...@@ -718,23 +837,38 @@ After this, we need to create a mock Apollo Client instance using a helper:
```javascript ```javascript
import createMockApollo from 'jest/helpers/mock_apollo_helper'; import createMockApollo from 'jest/helpers/mock_apollo_helper';
describe('Some component with Apollo mock', () => { describe('Some component', () => {
let wrapper; let wrapper;
let fakeApollo;
function createComponentWithApollo() { function createMockApolloProvider() {
Vue.use(VueApollo);
const requestHandlers = [ const requestHandlers = [
[getDesignListQuery, jest.fn().mockResolvedValue(designListQueryResponse)], [getDesignListQuery, jest.fn().mockResolvedValue(designListQueryResponse)],
[permissionsQuery, jest.fn().mockResolvedValue(permissionsQueryResponse)], [permissionsQuery, jest.fn().mockResolvedValue(permissionsQueryResponse)],
]; ];
fakeApollo = createMockApollo(requestHandlers); return createMockApollo(requestHandlers);
wrapper = shallowMount(Index, { }
function createComponent(options = {}) {
const { mockApollo } = options;
return shallowMount(Index, {
localVue, localVue,
apolloProvider: fakeApollo, apolloProvider: mockApollo,
}); });
} }
})
describe('with Apollo mock', () => {
let mockApollo;
beforeEach(() => {
mockApollo = createMockApolloProvider();
wrapper = createComponent({ mockApollo });
});
});
});
``` ```
When mocking resolved values, ensure the structure of the response is the same When mocking resolved values, ensure the structure of the response is the same
...@@ -744,13 +878,15 @@ When testing queries, please keep in mind they are promises, so they need to be ...@@ -744,13 +878,15 @@ When testing queries, please keep in mind they are promises, so they need to be
```javascript ```javascript
it('renders a loading state', () => { it('renders a loading state', () => {
createComponentWithApollo(); const mockApollo = createMockApolloProvider();
const wrapper = createComponent({ mockApollo });
expect(wrapper.find(LoadingSpinner).exists()).toBe(true) expect(wrapper.find(LoadingSpinner).exists()).toBe(true)
}); });
it('renders designs list', async () => { it('renders designs list', async () => {
createComponentWithApollo(); const mockApollo = createMockApolloProvider();
const wrapper = createComponent({ mockApollo });
jest.runOnlyPendingTimers(); jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
...@@ -762,7 +898,7 @@ it('renders designs list', async () => { ...@@ -762,7 +898,7 @@ it('renders designs list', async () => {
If we need to test a query error, we need to mock a rejected value as request handler: If we need to test a query error, we need to mock a rejected value as request handler:
```javascript ```javascript
function createComponentWithApollo() { function createMockApolloProvider() {
... ...
const requestHandlers = [ const requestHandlers = [
[getDesignListQuery, jest.fn().mockRejectedValue(new Error('GraphQL error')], [getDesignListQuery, jest.fn().mockRejectedValue(new Error('GraphQL error')],
...@@ -772,7 +908,7 @@ function createComponentWithApollo() { ...@@ -772,7 +908,7 @@ function createComponentWithApollo() {
... ...
it('renders error if query fails', async () => { it('renders error if query fails', async () => {
createComponent() const wrapper = createComponent();
jest.runOnlyPendingTimers(); jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
...@@ -786,9 +922,11 @@ Request handlers can also be passed to component factory as a parameter. ...@@ -786,9 +922,11 @@ Request handlers can also be passed to component factory as a parameter.
Mutations could be tested the same way with a few additional `nextTick`s to get the updated result: Mutations could be tested the same way with a few additional `nextTick`s to get the updated result:
```javascript ```javascript
function createComponentWithApollo({ function createMockApolloProvider({
moveHandler = jest.fn().mockResolvedValue(moveDesignMutationResponse), moveHandler = jest.fn().mockResolvedValue(moveDesignMutationResponse),
}) { }) {
Vue.use(VueApollo);
moveDesignHandler = moveHandler; moveDesignHandler = moveHandler;
const requestHandlers = [ const requestHandlers = [
...@@ -797,15 +935,21 @@ function createComponentWithApollo({ ...@@ -797,15 +935,21 @@ function createComponentWithApollo({
[moveDesignMutation, moveDesignHandler], [moveDesignMutation, moveDesignHandler],
]; ];
fakeApollo = createMockApollo(requestHandlers); return createMockApollo(requestHandlers);
wrapper = shallowMount(Index, { }
function createComponent(options = {}) {
const { mockApollo } = options;
return shallowMount(Index, {
localVue, localVue,
apolloProvider: fakeApollo, apolloProvider: mockApollo,
}); });
} }
... ...
it('calls a mutation with correct parameters and reorders designs', async () => { it('calls a mutation with correct parameters and reorders designs', async () => {
createComponentWithApollo({}); const mockApollo = createMockApolloProvider({});
const wrapper = createComponent({ mockApollo });
wrapper.find(VueDraggable).vm.$emit('change', { wrapper.find(VueDraggable).vm.$emit('change', {
moved: { moved: {
...@@ -833,7 +977,7 @@ If your application contains `@client` queries, most probably you will have an A ...@@ -833,7 +977,7 @@ If your application contains `@client` queries, most probably you will have an A
```javascript ```javascript
import createMockApollo from 'jest/helpers/mock_apollo_helper'; import createMockApollo from 'jest/helpers/mock_apollo_helper';
... ...
mockApollo = createMockApollo(requestHandlers, resolvers); const mockApollo = createMockApollo(requestHandlers, resolvers);
``` ```
Sometimes we want to test a `result` hook of the local query. In order to have it triggered, we need to populate a cache with correct data to be fetched with this query: Sometimes we want to test a `result` hook of the local query. In order to have it triggered, we need to populate a cache with correct data to be fetched with this query:
...@@ -850,12 +994,14 @@ query fetchLocalUser { ...@@ -850,12 +994,14 @@ query fetchLocalUser {
import fetchLocalUserQuery from '~/design_management/graphql/queries/fetch_local_user.query.graphql'; import fetchLocalUserQuery from '~/design_management/graphql/queries/fetch_local_user.query.graphql';
function createMockApolloProvider() { function createMockApolloProvider() {
Vue.use(VueApollo);
const requestHandlers = [ const requestHandlers = [
[getDesignListQuery, jest.fn().mockResolvedValue(designListQueryResponse)], [getDesignListQuery, jest.fn().mockResolvedValue(designListQueryResponse)],
[permissionsQuery, jest.fn().mockResolvedValue(permissionsQueryResponse)], [permissionsQuery, jest.fn().mockResolvedValue(permissionsQueryResponse)],
]; ];
mockApollo = createMockApollo(requestHandlers, {}); const mockApollo = createMockApollo(requestHandlers, {});
mockApollo.clients.defaultClient.cache.writeQuery({ mockApollo.clients.defaultClient.cache.writeQuery({
query: fetchLocalUserQuery, query: fetchLocalUserQuery,
data: { data: {
...@@ -885,9 +1031,10 @@ Sometimes it is necessary to control what the local resolver returns and inspect ...@@ -885,9 +1031,10 @@ Sometimes it is necessary to control what the local resolver returns and inspect
import fetchLocalUserQuery from '~/design_management/graphql/queries/fetch_local_user.query.graphql'; import fetchLocalUserQuery from '~/design_management/graphql/queries/fetch_local_user.query.graphql';
function createMockApolloProvider(options = {}) { function createMockApolloProvider(options = {}) {
Vue.use(VueApollo);
const { fetchLocalUserSpy } = options; const { fetchLocalUserSpy } = options;
mockApollo = createMockApollo([], { const mockApollo = createMockApollo([], {
Query: { Query: {
fetchLocalUser: fetchLocalUserSpy, fetchLocalUser: fetchLocalUserSpy,
}, },
......
...@@ -19,52 +19,53 @@ export default { ...@@ -19,52 +19,53 @@ export default {
}, },
data() { data() {
return { return {
requestCount: MAX_REQUEST_COUNT, requestCount: 0,
loadingError: false, loadingError: false,
isLoading: false,
selectedSegmentId: null, selectedSegmentId: null,
}; groups: {
}, nodes: [],
apollo: { pageInfo: null,
groups: {
query: getGroupsQuery,
loadingKey: 'loading',
result() {
this.requestCount -= 1;
if (this.requestCount > 0 && this.groups?.pageInfo?.nextPage) {
this.fetchNextPage();
}
}, },
error(error) { };
this.handleError(error);
},
},
}, },
computed: { computed: {
hasGroupData() { hasGroupData() {
return Boolean(this.groups?.nodes?.length); return Boolean(this.groups?.nodes?.length);
}, },
isLoading() { },
return this.$apollo.queries.groups.loading; created() {
}, this.fetchGroups();
}, },
methods: { methods: {
handleError(error) { handleError(error) {
this.loadingError = true; this.loadingError = true;
Sentry.captureException(error); Sentry.captureException(error);
}, },
fetchNextPage() { fetchGroups(nextPage) {
this.$apollo.queries.groups this.isLoading = true;
.fetchMore({ this.$apollo
.query({
query: getGroupsQuery,
variables: { variables: {
nextPage: this.groups.pageInfo.nextPage, nextPage,
}, },
updateQuery: (previousResult, { fetchMoreResult }) => { })
const { nodes, ...rest } = fetchMoreResult.groups; .then(({ data }) => {
const { nodes: previousNodes } = previousResult.groups; const { pageInfo, nodes } = data.groups;
return { groups: { ...rest, nodes: [...previousNodes, ...nodes] } }; // Update data
}, this.groups = {
pageInfo,
nodes: [...this.groups.nodes, ...nodes],
};
this.requestCount += 1;
if (this.requestCount < MAX_REQUEST_COUNT && pageInfo?.nextPage) {
this.fetchGroups(pageInfo.nextPage);
} else {
this.isLoading = false;
}
}) })
.catch(this.handleError); .catch(this.handleError);
}, },
......
...@@ -102,7 +102,6 @@ describe('DevopsAdoptionApp', () => { ...@@ -102,7 +102,6 @@ describe('DevopsAdoptionApp', () => {
groupsSpy = jest.fn().mockResolvedValueOnce({ ...initialResponse, pageInfo: null }); groupsSpy = jest.fn().mockResolvedValueOnce({ ...initialResponse, pageInfo: null });
const mockApollo = createMockApolloProvider({ groupsSpy }); const mockApollo = createMockApolloProvider({ groupsSpy });
wrapper = createComponent({ mockApollo }); wrapper = createComponent({ mockApollo });
jest.spyOn(wrapper.vm.$apollo.queries.groups, 'fetchMore');
await waitForPromises(); await waitForPromises();
}); });
...@@ -117,21 +116,16 @@ describe('DevopsAdoptionApp', () => { ...@@ -117,21 +116,16 @@ describe('DevopsAdoptionApp', () => {
it('should fetch data once', () => { it('should fetch data once', () => {
expect(groupsSpy).toHaveBeenCalledTimes(1); expect(groupsSpy).toHaveBeenCalledTimes(1);
}); });
it('should not fetch more data', () => {
expect(wrapper.vm.$apollo.queries.groups.fetchMore).not.toHaveBeenCalled();
});
}); });
describe('when error is thrown in the initial request', () => { describe('when error is thrown in the initial request', () => {
const error = 'Error: foo!'; const error = new Error('foo!');
beforeEach(async () => { beforeEach(async () => {
jest.spyOn(Sentry, 'captureException'); jest.spyOn(Sentry, 'captureException');
groupsSpy = jest.fn().mockRejectedValueOnce(error); groupsSpy = jest.fn().mockRejectedValueOnce(error);
const mockApollo = createMockApolloProvider({ groupsSpy }); const mockApollo = createMockApolloProvider({ groupsSpy });
wrapper = createComponent({ mockApollo }); wrapper = createComponent({ mockApollo });
jest.spyOn(wrapper.vm.$apollo.queries.groups, 'fetchMore');
await waitForPromises(); await waitForPromises();
}); });
...@@ -147,10 +141,6 @@ describe('DevopsAdoptionApp', () => { ...@@ -147,10 +141,6 @@ describe('DevopsAdoptionApp', () => {
expect(groupsSpy).toHaveBeenCalledTimes(1); expect(groupsSpy).toHaveBeenCalledTimes(1);
}); });
it('should not fetch more data', () => {
expect(wrapper.vm.$apollo.queries.groups.fetchMore).not.toHaveBeenCalled();
});
it('displays the error message and calls Sentry', () => { it('displays the error message and calls Sentry', () => {
const alert = wrapper.find(GlAlert); const alert = wrapper.find(GlAlert);
expect(alert.exists()).toBe(true); expect(alert.exists()).toBe(true);
...@@ -176,7 +166,6 @@ describe('DevopsAdoptionApp', () => { ...@@ -176,7 +166,6 @@ describe('DevopsAdoptionApp', () => {
.mockResolvedValueOnce({ __typename: 'Groups', nodes: [nextGroupNode], nextPage: null }); .mockResolvedValueOnce({ __typename: 'Groups', nodes: [nextGroupNode], nextPage: null });
const mockApollo = createMockApolloProvider({ groupsSpy }); const mockApollo = createMockApolloProvider({ groupsSpy });
wrapper = createComponent({ mockApollo }); wrapper = createComponent({ mockApollo });
jest.spyOn(wrapper.vm.$apollo.queries.groups, 'fetchMore');
await waitForPromises(); await waitForPromises();
}); });
...@@ -193,12 +182,12 @@ describe('DevopsAdoptionApp', () => { ...@@ -193,12 +182,12 @@ describe('DevopsAdoptionApp', () => {
}); });
it('should fetch more data', () => { it('should fetch more data', () => {
expect(wrapper.vm.$apollo.queries.groups.fetchMore).toHaveBeenCalledTimes(1); expect(groupsSpy.mock.calls[0][1]).toMatchObject({
expect(wrapper.vm.$apollo.queries.groups.fetchMore).toHaveBeenCalledWith( nextPage: undefined,
expect.objectContaining({ });
variables: { nextPage: 2 }, expect(groupsSpy.mock.calls[1][1]).toMatchObject({
}), nextPage: 2,
); });
}); });
}); });
...@@ -208,7 +197,6 @@ describe('DevopsAdoptionApp', () => { ...@@ -208,7 +197,6 @@ describe('DevopsAdoptionApp', () => {
groupsSpy = jest.fn().mockResolvedValue(initialResponse); groupsSpy = jest.fn().mockResolvedValue(initialResponse);
const mockApollo = createMockApolloProvider({ groupsSpy }); const mockApollo = createMockApolloProvider({ groupsSpy });
wrapper = createComponent({ mockApollo, data: { requestCount: 2 } }); wrapper = createComponent({ mockApollo, data: { requestCount: 2 } });
jest.spyOn(wrapper.vm.$apollo.queries.groups, 'fetchMore');
await waitForPromises(); await waitForPromises();
}); });
...@@ -219,10 +207,6 @@ describe('DevopsAdoptionApp', () => { ...@@ -219,10 +207,6 @@ describe('DevopsAdoptionApp', () => {
it('should fetch data twice', () => { it('should fetch data twice', () => {
expect(groupsSpy).toHaveBeenCalledTimes(2); expect(groupsSpy).toHaveBeenCalledTimes(2);
}); });
it('should not fetch more than `requestCount`', () => {
expect(wrapper.vm.$apollo.queries.groups.fetchMore).toHaveBeenCalledTimes(1);
});
}); });
describe('when error is thrown in the fetchMore request', () => { describe('when error is thrown in the fetchMore request', () => {
...@@ -237,7 +221,6 @@ describe('DevopsAdoptionApp', () => { ...@@ -237,7 +221,6 @@ describe('DevopsAdoptionApp', () => {
.mockRejectedValue(error); .mockRejectedValue(error);
const mockApollo = createMockApolloProvider({ groupsSpy }); const mockApollo = createMockApolloProvider({ groupsSpy });
wrapper = createComponent({ mockApollo }); wrapper = createComponent({ mockApollo });
jest.spyOn(wrapper.vm.$apollo.queries.groups, 'fetchMore');
await waitForPromises(); await waitForPromises();
}); });
...@@ -254,11 +237,12 @@ describe('DevopsAdoptionApp', () => { ...@@ -254,11 +237,12 @@ describe('DevopsAdoptionApp', () => {
}); });
it('should fetch more data', () => { it('should fetch more data', () => {
expect(wrapper.vm.$apollo.queries.groups.fetchMore).toHaveBeenCalledWith( expect(groupsSpy.mock.calls[0][1]).toMatchObject({
expect.objectContaining({ nextPage: undefined,
variables: { nextPage: 2 }, });
}), expect(groupsSpy.mock.calls[1][1]).toMatchObject({
); nextPage: 2,
});
}); });
it('displays the error message and calls Sentry', () => { it('displays the error message and calls Sentry', () => {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment