Commit fa0f0ae7 authored by Mark Florian's avatar Mark Florian Committed by Kushal Pandya

Extract PaginatedDependenciesTable component

This extracts a store-connected component that wraps the
`DependenciesTable` and `Pagination` components, and passes them the
appropriate store state.

This refactoring will help make the dual, independent and stateful
dependency lists of the [feature][1] easier to implement.

[1]: https://gitlab.com/gitlab-org/gitlab-ee/issues/10077
parent 41a8df92
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import { GlBadge, GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
import Pagination from '~/vue_shared/components/pagination_links.vue';
import DependenciesActions from './dependencies_actions.vue';
import DependenciesTable from './dependencies_table.vue';
import DependencyListIncompleteAlert from './dependency_list_incomplete_alert.vue';
import DependencyListJobFailedAlert from './dependency_list_job_failed_alert.vue';
import PaginatedDependenciesTable from './paginated_dependencies_table.vue';
import { DEPENDENCY_LIST_TYPES } from '../store/constants';
export default {
name: 'DependenciesApp',
components: {
DependenciesActions,
DependenciesTable,
GlBadge,
GlEmptyState,
GlLoadingIcon,
DependencyListIncompleteAlert,
DependencyListJobFailedAlert,
Pagination,
PaginatedDependenciesTable,
},
props: {
endpoint: {
......@@ -48,17 +46,7 @@ export default {
computed: {
...mapState(['currentList']),
...mapGetters(DEPENDENCY_LIST_TYPES.all, ['isJobNotSetUp', 'isJobFailed', 'isIncomplete']),
...mapState(DEPENDENCY_LIST_TYPES.all, [
'initialized',
'isLoading',
'errorLoading',
'dependencies',
'pageInfo',
'reportInfo',
]),
shouldShowPagination() {
return Boolean(!this.isLoading && !this.errorLoading && this.pageInfo && this.pageInfo.total);
},
...mapState(DEPENDENCY_LIST_TYPES.all, ['initialized', 'pageInfo', 'reportInfo']),
},
created() {
this.setDependenciesEndpoint(this.endpoint);
......@@ -66,9 +54,6 @@ export default {
},
methods: {
...mapActions(DEPENDENCY_LIST_TYPES.all, ['setDependenciesEndpoint', 'fetchDependencies']),
fetchPage(page) {
this.fetchDependencies({ page });
},
dismissIncompleteListAlert() {
this.isIncompleteAlertDismissed = true;
},
......@@ -114,13 +99,6 @@ export default {
<dependencies-actions :namespace="currentList" />
</div>
<dependencies-table :dependencies="dependencies" :is-loading="isLoading" />
<pagination
v-if="shouldShowPagination"
:change="fetchPage"
:page-info="pageInfo"
class="justify-content-center prepend-top-default"
/>
<paginated-dependencies-table :namespace="currentList" />
</div>
</template>
<script>
import { mapActions, mapState } from 'vuex';
import Pagination from '~/vue_shared/components/pagination_links.vue';
import DependenciesTable from './dependencies_table.vue';
import { DEPENDENCY_LIST_TYPES } from '../store/constants';
export default {
name: 'PaginatedDependenciesTable',
components: {
DependenciesTable,
Pagination,
},
props: {
namespace: {
type: String,
required: true,
validator: value => Object.values(DEPENDENCY_LIST_TYPES).includes(value),
},
},
computed: {
...mapState({
module(state) {
return state[this.namespace];
},
shouldShowPagination() {
const { isLoading, errorLoading, pageInfo } = this.module;
return Boolean(!isLoading && !errorLoading && pageInfo && pageInfo.total);
},
}),
},
methods: {
...mapActions({
fetchPage(dispatch, page) {
return dispatch(`${this.namespace}/fetchDependencies`, { page });
},
}),
},
};
</script>
<template>
<div>
<dependencies-table :dependencies="module.dependencies" :is-loading="module.isLoading" />
<pagination
v-if="shouldShowPagination"
:change="fetchPage"
:page-info="module.pageInfo"
class="justify-content-center prepend-top-default"
/>
</div>
</template>
......@@ -27,14 +27,8 @@ exports[`DependenciesApp component on creation given a dependency list which is
/>
</div>
<dependenciestable-stub
dependencies="foo,bar"
/>
<pagination-stub
change="function () { [native code] }"
class="justify-content-center prepend-top-default"
pageinfo="[object Object]"
<paginateddependenciestable-stub
namespace="allDependencies"
/>
</div>
`;
......@@ -62,11 +56,9 @@ exports[`DependenciesApp component on creation given a fetch error matches the s
/>
</div>
<dependenciestable-stub
dependencies=""
<paginateddependenciestable-stub
namespace="allDependencies"
/>
<!---->
</div>
`;
......@@ -97,14 +89,8 @@ exports[`DependenciesApp component on creation given a list of dependencies and
/>
</div>
<dependenciestable-stub
dependencies="foo,bar"
/>
<pagination-stub
change="function () { [native code] }"
class="justify-content-center prepend-top-default"
pageinfo="[object Object]"
<paginateddependenciestable-stub
namespace="allDependencies"
/>
</div>
`;
......@@ -134,11 +120,9 @@ exports[`DependenciesApp component on creation given the dependency list job fai
/>
</div>
<dependenciestable-stub
dependencies=""
<paginateddependenciestable-stub
namespace="allDependencies"
/>
<!---->
</div>
`;
......
......@@ -4,10 +4,9 @@ import createStore from 'ee/dependencies/store';
import { DEPENDENCY_LIST_TYPES } from 'ee/dependencies/store/constants';
import { REPORT_STATUS } from 'ee/dependencies/store/modules/list/constants';
import DependenciesApp from 'ee/dependencies/components/app.vue';
import DependenciesTable from 'ee/dependencies/components/dependencies_table.vue';
import DependencyListIncompleteAlert from 'ee/dependencies/components/dependency_list_incomplete_alert.vue';
import DependencyListJobFailedAlert from 'ee/dependencies/components/dependency_list_job_failed_alert.vue';
import Pagination from '~/vue_shared/components/pagination_links.vue';
import PaginatedDependenciesTable from 'ee/dependencies/components/paginated_dependencies_table.vue';
describe('DependenciesApp component', () => {
let store;
......@@ -81,17 +80,9 @@ describe('DependenciesApp component', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('passes the correct props to the dependencies table', () => {
expectComponentWithProps(DependenciesTable, {
dependencies,
isLoading: false,
});
});
it('passes the correct props to the pagination', () => {
expectComponentWithProps(Pagination, {
pageInfo: store.state[listType].pageInfo,
change: wrapper.vm.fetchPage,
it('passes the correct props to the paginated dependencies table', () => {
expectComponentWithProps(PaginatedDependenciesTable, {
namespace: listType,
});
});
});
......@@ -142,17 +133,12 @@ describe('DependenciesApp component', () => {
});
});
it('passes the correct props to the dependencies table', () => {
expectComponentWithProps(DependenciesTable, {
dependencies,
isLoading: false,
it('passes the correct props to the paginated dependencies table', () => {
expectComponentWithProps(PaginatedDependenciesTable, {
namespace: listType,
});
});
it('does not show pagination', () => {
expect(wrapper.find(Pagination).exists()).toBe(false);
});
describe('when the job failure alert emits the close event', () => {
beforeEach(() => {
const alertWrapper = wrapper.find(DependencyListJobFailedAlert);
......@@ -190,17 +176,9 @@ describe('DependenciesApp component', () => {
expect(alert.isVisible()).toBe(true);
});
it('passes the correct props to the dependencies table', () => {
expectComponentWithProps(DependenciesTable, {
dependencies,
isLoading: false,
});
});
it('passes the correct props to the pagination', () => {
expectComponentWithProps(Pagination, {
pageInfo: store.state[listType].pageInfo,
change: wrapper.vm.fetchPage,
it('passes the correct props to the paginated dependencies table', () => {
expectComponentWithProps(PaginatedDependenciesTable, {
namespace: listType,
});
});
......@@ -235,16 +213,11 @@ describe('DependenciesApp component', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('passes the correct props to the dependencies table', () => {
expectComponentWithProps(DependenciesTable, {
dependencies,
isLoading: false,
it('passes the correct props to the paginated dependencies table', () => {
expectComponentWithProps(PaginatedDependenciesTable, {
namespace: listType,
});
});
it('does not show pagination', () => {
expect(wrapper.find(Pagination).exists()).toBe(false);
});
});
});
});
import { createLocalVue, shallowMount } from '@vue/test-utils';
import createStore from 'ee/dependencies/store';
import DependenciesTable from 'ee/dependencies/components/dependencies_table.vue';
import PaginatedDependenciesTable from 'ee/dependencies/components/paginated_dependencies_table.vue';
import { DEPENDENCY_LIST_TYPES } from 'ee/dependencies/store/constants';
import Pagination from '~/vue_shared/components/pagination_links.vue';
import mockDependenciesResponse from '../store/modules/list/data/mock_dependencies';
describe('PaginatedDependenciesTable component', () => {
let store;
let wrapper;
const listType = DEPENDENCY_LIST_TYPES.all;
const factory = (props = {}) => {
const localVue = createLocalVue();
store = createStore();
wrapper = shallowMount(localVue.extend(PaginatedDependenciesTable), {
localVue,
store,
sync: false,
propsData: { ...props },
});
};
const expectComponentWithProps = (Component, props = {}) => {
const componentWrapper = wrapper.find(Component);
expect(componentWrapper.isVisible()).toBe(true);
expect(componentWrapper.props()).toEqual(expect.objectContaining(props));
};
beforeEach(() => {
factory({ namespace: listType });
const originalDispatch = store.dispatch;
jest.spyOn(store, 'dispatch').mockImplementation();
originalDispatch(`${listType}/receiveDependenciesSuccess`, {
data: mockDependenciesResponse,
headers: { 'X-Total': mockDependenciesResponse.dependencies.length },
});
return wrapper.vm.$nextTick();
});
afterEach(() => {
wrapper.destroy();
});
it('passes the correct props to the dependencies table', () => {
expectComponentWithProps(DependenciesTable, {
dependencies: mockDependenciesResponse.dependencies,
isLoading: store.state[listType].isLoading,
});
});
it('passes the correct props to the pagination', () => {
expectComponentWithProps(Pagination, {
change: wrapper.vm.fetchPage,
pageInfo: store.state[listType].pageInfo,
});
});
it('has a fetchPage method which dispatches the correct action', () => {
const page = 2;
wrapper.vm.fetchPage(page);
expect(store.dispatch).toHaveBeenCalledTimes(1);
expect(store.dispatch).toHaveBeenCalledWith(`${listType}/fetchDependencies`, { page });
});
describe.each`
context | isLoading | errorLoading | isListEmpty
${'the list is loading'} | ${true} | ${false} | ${false}
${'there was an error loading'} | ${false} | ${true} | ${false}
${'the list is empty'} | ${false} | ${false} | ${true}
`('given $context', ({ isLoading, errorLoading, isListEmpty }) => {
let module;
beforeEach(() => {
module = store.state[listType];
if (isListEmpty) {
module.dependencies = [];
module.pageInfo.total = 0;
}
module.isLoading = isLoading;
module.errorLoading = errorLoading;
return wrapper.vm.$nextTick();
});
// See https://github.com/jest-community/eslint-plugin-jest/issues/229 for
// a similar reason for disabling the rule on the next line
// eslint-disable-next-line jest/no-identical-title
it('passes the correct props to the dependencies table', () => {
expectComponentWithProps(DependenciesTable, {
dependencies: module.dependencies,
isLoading,
});
});
it('does not render pagination', () => {
expect(wrapper.find(Pagination).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