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> <script>
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import { GlBadge, GlEmptyState, GlLoadingIcon } from '@gitlab/ui'; import { GlBadge, GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
import Pagination from '~/vue_shared/components/pagination_links.vue';
import DependenciesActions from './dependencies_actions.vue'; import DependenciesActions from './dependencies_actions.vue';
import DependenciesTable from './dependencies_table.vue';
import DependencyListIncompleteAlert from './dependency_list_incomplete_alert.vue'; import DependencyListIncompleteAlert from './dependency_list_incomplete_alert.vue';
import DependencyListJobFailedAlert from './dependency_list_job_failed_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'; import { DEPENDENCY_LIST_TYPES } from '../store/constants';
export default { export default {
name: 'DependenciesApp', name: 'DependenciesApp',
components: { components: {
DependenciesActions, DependenciesActions,
DependenciesTable,
GlBadge, GlBadge,
GlEmptyState, GlEmptyState,
GlLoadingIcon, GlLoadingIcon,
DependencyListIncompleteAlert, DependencyListIncompleteAlert,
DependencyListJobFailedAlert, DependencyListJobFailedAlert,
Pagination, PaginatedDependenciesTable,
}, },
props: { props: {
endpoint: { endpoint: {
...@@ -48,17 +46,7 @@ export default { ...@@ -48,17 +46,7 @@ export default {
computed: { computed: {
...mapState(['currentList']), ...mapState(['currentList']),
...mapGetters(DEPENDENCY_LIST_TYPES.all, ['isJobNotSetUp', 'isJobFailed', 'isIncomplete']), ...mapGetters(DEPENDENCY_LIST_TYPES.all, ['isJobNotSetUp', 'isJobFailed', 'isIncomplete']),
...mapState(DEPENDENCY_LIST_TYPES.all, [ ...mapState(DEPENDENCY_LIST_TYPES.all, ['initialized', 'pageInfo', 'reportInfo']),
'initialized',
'isLoading',
'errorLoading',
'dependencies',
'pageInfo',
'reportInfo',
]),
shouldShowPagination() {
return Boolean(!this.isLoading && !this.errorLoading && this.pageInfo && this.pageInfo.total);
},
}, },
created() { created() {
this.setDependenciesEndpoint(this.endpoint); this.setDependenciesEndpoint(this.endpoint);
...@@ -66,9 +54,6 @@ export default { ...@@ -66,9 +54,6 @@ export default {
}, },
methods: { methods: {
...mapActions(DEPENDENCY_LIST_TYPES.all, ['setDependenciesEndpoint', 'fetchDependencies']), ...mapActions(DEPENDENCY_LIST_TYPES.all, ['setDependenciesEndpoint', 'fetchDependencies']),
fetchPage(page) {
this.fetchDependencies({ page });
},
dismissIncompleteListAlert() { dismissIncompleteListAlert() {
this.isIncompleteAlertDismissed = true; this.isIncompleteAlertDismissed = true;
}, },
...@@ -114,13 +99,6 @@ export default { ...@@ -114,13 +99,6 @@ export default {
<dependencies-actions :namespace="currentList" /> <dependencies-actions :namespace="currentList" />
</div> </div>
<dependencies-table :dependencies="dependencies" :is-loading="isLoading" /> <paginated-dependencies-table :namespace="currentList" />
<pagination
v-if="shouldShowPagination"
:change="fetchPage"
:page-info="pageInfo"
class="justify-content-center prepend-top-default"
/>
</div> </div>
</template> </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 ...@@ -27,14 +27,8 @@ exports[`DependenciesApp component on creation given a dependency list which is
/> />
</div> </div>
<dependenciestable-stub <paginateddependenciestable-stub
dependencies="foo,bar" namespace="allDependencies"
/>
<pagination-stub
change="function () { [native code] }"
class="justify-content-center prepend-top-default"
pageinfo="[object Object]"
/> />
</div> </div>
`; `;
...@@ -62,11 +56,9 @@ exports[`DependenciesApp component on creation given a fetch error matches the s ...@@ -62,11 +56,9 @@ exports[`DependenciesApp component on creation given a fetch error matches the s
/> />
</div> </div>
<dependenciestable-stub <paginateddependenciestable-stub
dependencies="" namespace="allDependencies"
/> />
<!---->
</div> </div>
`; `;
...@@ -97,14 +89,8 @@ exports[`DependenciesApp component on creation given a list of dependencies and ...@@ -97,14 +89,8 @@ exports[`DependenciesApp component on creation given a list of dependencies and
/> />
</div> </div>
<dependenciestable-stub <paginateddependenciestable-stub
dependencies="foo,bar" namespace="allDependencies"
/>
<pagination-stub
change="function () { [native code] }"
class="justify-content-center prepend-top-default"
pageinfo="[object Object]"
/> />
</div> </div>
`; `;
...@@ -134,11 +120,9 @@ exports[`DependenciesApp component on creation given the dependency list job fai ...@@ -134,11 +120,9 @@ exports[`DependenciesApp component on creation given the dependency list job fai
/> />
</div> </div>
<dependenciestable-stub <paginateddependenciestable-stub
dependencies="" namespace="allDependencies"
/> />
<!---->
</div> </div>
`; `;
......
...@@ -4,10 +4,9 @@ import createStore from 'ee/dependencies/store'; ...@@ -4,10 +4,9 @@ import createStore from 'ee/dependencies/store';
import { DEPENDENCY_LIST_TYPES } from 'ee/dependencies/store/constants'; import { DEPENDENCY_LIST_TYPES } from 'ee/dependencies/store/constants';
import { REPORT_STATUS } from 'ee/dependencies/store/modules/list/constants'; import { REPORT_STATUS } from 'ee/dependencies/store/modules/list/constants';
import DependenciesApp from 'ee/dependencies/components/app.vue'; 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 DependencyListIncompleteAlert from 'ee/dependencies/components/dependency_list_incomplete_alert.vue';
import DependencyListJobFailedAlert from 'ee/dependencies/components/dependency_list_job_failed_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', () => { describe('DependenciesApp component', () => {
let store; let store;
...@@ -81,17 +80,9 @@ describe('DependenciesApp component', () => { ...@@ -81,17 +80,9 @@ describe('DependenciesApp component', () => {
expect(wrapper.element).toMatchSnapshot(); expect(wrapper.element).toMatchSnapshot();
}); });
it('passes the correct props to the dependencies table', () => { it('passes the correct props to the paginated dependencies table', () => {
expectComponentWithProps(DependenciesTable, { expectComponentWithProps(PaginatedDependenciesTable, {
dependencies, namespace: listType,
isLoading: false,
});
});
it('passes the correct props to the pagination', () => {
expectComponentWithProps(Pagination, {
pageInfo: store.state[listType].pageInfo,
change: wrapper.vm.fetchPage,
}); });
}); });
}); });
...@@ -142,17 +133,12 @@ describe('DependenciesApp component', () => { ...@@ -142,17 +133,12 @@ describe('DependenciesApp component', () => {
}); });
}); });
it('passes the correct props to the dependencies table', () => { it('passes the correct props to the paginated dependencies table', () => {
expectComponentWithProps(DependenciesTable, { expectComponentWithProps(PaginatedDependenciesTable, {
dependencies, namespace: listType,
isLoading: false,
}); });
}); });
it('does not show pagination', () => {
expect(wrapper.find(Pagination).exists()).toBe(false);
});
describe('when the job failure alert emits the close event', () => { describe('when the job failure alert emits the close event', () => {
beforeEach(() => { beforeEach(() => {
const alertWrapper = wrapper.find(DependencyListJobFailedAlert); const alertWrapper = wrapper.find(DependencyListJobFailedAlert);
...@@ -190,17 +176,9 @@ describe('DependenciesApp component', () => { ...@@ -190,17 +176,9 @@ describe('DependenciesApp component', () => {
expect(alert.isVisible()).toBe(true); expect(alert.isVisible()).toBe(true);
}); });
it('passes the correct props to the dependencies table', () => { it('passes the correct props to the paginated dependencies table', () => {
expectComponentWithProps(DependenciesTable, { expectComponentWithProps(PaginatedDependenciesTable, {
dependencies, namespace: listType,
isLoading: false,
});
});
it('passes the correct props to the pagination', () => {
expectComponentWithProps(Pagination, {
pageInfo: store.state[listType].pageInfo,
change: wrapper.vm.fetchPage,
}); });
}); });
...@@ -235,16 +213,11 @@ describe('DependenciesApp component', () => { ...@@ -235,16 +213,11 @@ describe('DependenciesApp component', () => {
expect(wrapper.element).toMatchSnapshot(); expect(wrapper.element).toMatchSnapshot();
}); });
it('passes the correct props to the dependencies table', () => { it('passes the correct props to the paginated dependencies table', () => {
expectComponentWithProps(DependenciesTable, { expectComponentWithProps(PaginatedDependenciesTable, {
dependencies, namespace: listType,
isLoading: false,
}); });
}); });
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