Wrap pagination events in a helper

parent aec26eeb
...@@ -112,8 +112,14 @@ export default { ...@@ -112,8 +112,14 @@ export default {
}; };
}, },
computed: { computed: {
pipelineNodes() {
return this.pipelines?.nodes ?? [];
},
hasPipelines() { hasPipelines() {
return Boolean(this.pipelines?.nodes?.length); return this.pipelineNodes.length > 0;
},
pageInfo() {
return this.pipelines?.pageInfo;
}, },
tableFields() { tableFields() {
return this.fields.map(({ key, label }) => ({ return this.fields.map(({ key, label }) => ({
...@@ -124,6 +130,13 @@ export default { ...@@ -124,6 +130,13 @@ export default {
})); }));
}, },
}, },
watch: {
hasPipelines(hasPipelines) {
if (this.hasError && hasPipelines) {
this.hasError = false;
}
},
},
methods: { methods: {
resetCursor() { resetCursor() {
this.cursor = { ...defaultCursor }; this.cursor = { ...defaultCursor };
...@@ -168,23 +181,24 @@ export default { ...@@ -168,23 +181,24 @@ export default {
{{ title }} {{ title }}
<gl-badge size="sm" class="gl-tab-counter-badge">{{ itemsCount }}</gl-badge> <gl-badge size="sm" class="gl-tab-counter-badge">{{ itemsCount }}</gl-badge>
</template> </template>
<template v-if="$apollo.queries.pipelines.loading"> <template v-if="$apollo.queries.pipelines.loading || hasPipelines">
<gl-skeleton-loader v-for="i in 20" :key="i" :width="815" :height="50">
<rect width="85" height="20" x="15" y="15" rx="4" />
<rect width="155" height="20" x="125" y="15" rx="4" />
<rect width="60" height="20" x="350" y="15" rx="4" />
<rect width="150" height="20" x="450" y="15" rx="4" />
<rect width="70" height="20" x="640" y="15" rx="4" />
<rect width="25" height="20" x="740" y="15" rx="4" />
</gl-skeleton-loader>
</template>
<template v-else-if="hasPipelines">
<gl-table <gl-table
thead-class="gl-border-b-solid gl-border-gray-100 gl-border-1" thead-class="gl-border-b-solid gl-border-gray-100 gl-border-1"
:fields="tableFields" :fields="tableFields"
:items="pipelines.nodes" :items="pipelineNodes"
:busy="$apollo.queries.pipelines.loading"
stacked="md" stacked="md"
> >
<template #table-busy>
<gl-skeleton-loader v-for="i in 20" :key="i" :width="1000" :height="45">
<rect width="85" height="20" x="0" y="5" rx="4" />
<rect width="100" height="20" x="150" y="5" rx="4" />
<rect width="150" height="20" x="300" y="5" rx="4" />
<rect width="100" height="20" x="500" y="5" rx="4" />
<rect width="150" height="20" x="655" y="5" rx="4" />
<rect width="70" height="20" x="855" y="5" rx="4" />
</gl-skeleton-loader>
</template>
<template #cell(detailedStatus)="{ item }"> <template #cell(detailedStatus)="{ item }">
<div class="gl-my-3"> <div class="gl-my-3">
<ci-badge-link :status="item.detailedStatus" /> <ci-badge-link :status="item.detailedStatus" />
...@@ -207,7 +221,7 @@ export default { ...@@ -207,7 +221,7 @@ export default {
<div class="gl-display-flex gl-justify-content-center"> <div class="gl-display-flex gl-justify-content-center">
<gl-keyset-pagination <gl-keyset-pagination
data-testid="pagination" data-testid="pagination"
v-bind="pipelines.pageInfo" v-bind="pageInfo"
:prev-text="$options.i18n.previousPage" :prev-text="$options.i18n.previousPage"
:next-text="$options.i18n.nextPage" :next-text="$options.i18n.nextPage"
@prev="prevPage" @prev="prevPage"
...@@ -215,11 +229,15 @@ export default { ...@@ -215,11 +229,15 @@ export default {
/> />
</div> </div>
</template> </template>
<template v-else-if="hasError"> <gl-alert
<gl-alert variant="danger" :dismissible="false" class="gl-my-4" data-testid="error-alert"> v-else-if="hasError"
{{ $options.i18n.errorMessage }} variant="danger"
</gl-alert> :dismissible="false"
</template> class="gl-my-4"
data-testid="error-alert"
>
{{ $options.i18n.errorMessage }}
</gl-alert>
<empty-state v-else :title="emptyStateTitle" :text="emptyStateText" no-primary-button /> <empty-state v-else :title="emptyStateTitle" :text="emptyStateText" no-primary-button />
</gl-tab> </gl-tab>
</template> </template>
import { GlTab, GlTable, GlSkeletonLoader, GlAlert } from '@gitlab/ui'; import { GlTab, GlTable, GlAlert } from '@gitlab/ui';
import { createLocalVue } from '@vue/test-utils'; import { createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import allPipelinesWithPipelinesMock from 'test_fixtures/graphql/on_demand_scans/graphql/on_demand_scans.query.graphql.with_pipelines.json'; import allPipelinesWithPipelinesMock from 'test_fixtures/graphql/on_demand_scans/graphql/on_demand_scans.query.graphql.with_pipelines.json';
...@@ -32,7 +32,6 @@ describe('BaseTab', () => { ...@@ -32,7 +32,6 @@ describe('BaseTab', () => {
const findTable = () => wrapper.findComponent(GlTable); const findTable = () => wrapper.findComponent(GlTable);
const findEmptyState = () => wrapper.findComponent(EmptyState); const findEmptyState = () => wrapper.findComponent(EmptyState);
const findPagination = () => wrapper.findByTestId('pagination'); const findPagination = () => wrapper.findByTestId('pagination');
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
const findErrorAlert = () => wrapper.findComponent(GlAlert); const findErrorAlert = () => wrapper.findComponent(GlAlert);
// Helpers // Helpers
...@@ -40,6 +39,11 @@ describe('BaseTab', () => { ...@@ -40,6 +39,11 @@ describe('BaseTab', () => {
return createMockApollo([[onDemandScansQuery, requestHandler]]); return createMockApollo([[onDemandScansQuery, requestHandler]]);
}; };
const navigateToPage = (direction) => {
findPagination().vm.$emit(direction);
return wrapper.vm.$nextTick();
};
const createComponent = (propsData) => { const createComponent = (propsData) => {
router = createRouter(); router = createRouter();
wrapper = shallowMountExtended(BaseTab, { wrapper = shallowMountExtended(BaseTab, {
...@@ -68,7 +72,7 @@ describe('BaseTab', () => { ...@@ -68,7 +72,7 @@ describe('BaseTab', () => {
`, `,
}), }),
GlTable: stubComponent(GlTable, { GlTable: stubComponent(GlTable, {
props: ['items'], props: ['items', 'busy'],
}), }),
}, },
}); });
...@@ -97,12 +101,14 @@ describe('BaseTab', () => { ...@@ -97,12 +101,14 @@ describe('BaseTab', () => {
}); });
}); });
it('shows a loader until the request resolves', async () => { it('puts the table in the busy state until the request resolves', async () => {
createComponent(); createComponent();
expect(findSkeletonLoader().exists()).toBe(true); expect(findTable().props('busy')).toBe(true);
await waitForPromises(); await waitForPromises();
expect(findSkeletonLoader().exists()).toBe(false);
expect(findTable().props('busy')).toBe(false);
}); });
it('resets the route if no pipeline matches the cursor', async () => { it('resets the route if no pipeline matches the cursor', async () => {
...@@ -138,8 +144,8 @@ describe('BaseTab', () => { ...@@ -138,8 +144,8 @@ describe('BaseTab', () => {
); );
}); });
it('when navigating to another page, scrolls back to the top', () => { it('when navigating to another page, scrolls back to the top', async () => {
findPagination().vm.$emit('next'); await navigateToPage('next');
expect(scrollToElement).toHaveBeenCalledWith(wrapper.vm.$el); expect(scrollToElement).toHaveBeenCalledWith(wrapper.vm.$el);
}); });
...@@ -148,18 +154,16 @@ describe('BaseTab', () => { ...@@ -148,18 +154,16 @@ describe('BaseTab', () => {
expect(Object.keys(router.currentRoute.query)).not.toContain('after'); expect(Object.keys(router.currentRoute.query)).not.toContain('after');
expect(requestHandler).toHaveBeenCalledTimes(1); expect(requestHandler).toHaveBeenCalledTimes(1);
findPagination().vm.$emit('next'); await navigateToPage('next');
await wrapper.vm.$nextTick();
expect(Object.keys(router.currentRoute.query)).toContain('after'); expect(Object.keys(router.currentRoute.query)).toContain('after');
expect(requestHandler).toHaveBeenCalledTimes(2); expect(requestHandler).toHaveBeenCalledTimes(2);
}); });
it('when navigating back to the previous page, the route is updated and pipelines are fetched', async () => { it('when navigating back to the previous page, the route is updated and pipelines are fetched', async () => {
findPagination().vm.$emit('next'); await navigateToPage('next');
await waitForPromises(); await waitForPromises();
findPagination().vm.$emit('prev'); await navigateToPage('prev');
await wrapper.vm.$nextTick();
expect(Object.keys(router.currentRoute.query)).not.toContain('after'); expect(Object.keys(router.currentRoute.query)).not.toContain('after');
expect(Object.keys(router.currentRoute.query)).toContain('before'); expect(Object.keys(router.currentRoute.query)).toContain('before');
...@@ -179,14 +183,32 @@ describe('BaseTab', () => { ...@@ -179,14 +183,32 @@ describe('BaseTab', () => {
}); });
describe('when the request errors out', () => { describe('when the request errors out', () => {
let respondWithError;
beforeEach(async () => { beforeEach(async () => {
requestHandler = jest.fn().mockRejectedValue(); respondWithError = true;
requestHandler = () => {
const response = respondWithError
? Promise.reject()
: Promise.resolve(allPipelinesWithPipelinesMock);
respondWithError = false;
return response;
};
createComponent(); createComponent();
await waitForPromises(); await waitForPromises();
}); });
it('show an error alert', () => { it('shows an error alert', () => {
expect(findErrorAlert().exists()).toBe(true); expect(findErrorAlert().exists()).toBe(true);
}); });
it('removes the alert if the next request succeeds', async () => {
expect(findErrorAlert().exists()).toBe(true);
wrapper.vm.$apollo.queries.pipelines.refetch();
await waitForPromises();
expect(findErrorAlert().exists()).toBe(false);
});
}); });
}); });
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