Skip to content
Snippets Groups Projects
Verified Commit 1d2b685e authored by Payton Burdette's avatar Payton Burdette :three: Committed by GitLab
Browse files

Merge branch '461529-user-mapping-fe-add-filtering-by-status-to-placeholders-page' into 'master'

User mapping FE - Add filtering by status to placeholders page

See merge request !159866



Merged-by: default avatarPayton Burdette <pburdette@gitlab.com>
Approved-by: default avatarAmeya Darshan <adarshan@gitlab.com>
Approved-by: default avatarPayton Burdette <pburdette@gitlab.com>
Reviewed-by: default avatarPayton Burdette <pburdette@gitlab.com>
Reviewed-by: default avatarJustin Ho Tuan Duong <hduong@gitlab.com>
Co-authored-by: Martin Wortschack's avatarMartin Wortschack <mwortschack@gitlab.com>
Co-authored-by: default avatarOiza <oizabaiye@gmail.com>
Co-authored-by: default avatarJustin Ho Tuan Duong <hduong@gitlab.com>
parents dc60c6ff 83029eb3
No related branches found
No related tags found
2 merge requests!162233Draft: Script to update Topology Service Gem,!159866User mapping FE - Add filtering by status to placeholders page
Pipeline #1406156465 passed
......@@ -39,6 +39,17 @@ export const PLACEHOLDER_STATUS_FAILED = 'FAILED';
export const PLACEHOLDER_STATUS_KEPT_AS_PLACEHOLDER = 'KEEP_AS_PLACEHOLDER';
export const PLACEHOLDER_STATUS_COMPLETED = 'COMPLETED';
export const PLACEHOLDER_USER_STATUS = {
UNASSIGNED: [
PLACEHOLDER_STATUS_PENDING_REASSIGNMENT,
PLACEHOLDER_STATUS_AWAITING_APPROVAL,
PLACEHOLDER_STATUS_REJECTED,
PLACEHOLDER_STATUS_REASSIGNING,
PLACEHOLDER_STATUS_FAILED,
],
REASSIGNED: [PLACEHOLDER_STATUS_COMPLETED, PLACEHOLDER_STATUS_KEPT_AS_PLACEHOLDER],
};
export const placeholderUserBadges = {
[PLACEHOLDER_STATUS_PENDING_REASSIGNMENT]: {
text: __('Not started'),
......
......@@ -171,6 +171,8 @@ export const AVAILABLE_FILTERED_SEARCH_TOKENS = [
export const AVATAR_SIZE = 48;
export const DEFAULT_PAGE_SIZE = 20;
export const MEMBERS_TAB_TYPES = Object.freeze({
user: 'user',
group: 'group',
......
......@@ -2,22 +2,19 @@
// eslint-disable-next-line no-restricted-imports
import { mapState } from 'vuex';
import { GlBadge, GlTab, GlTabs, GlButton, GlModalDirective } from '@gitlab/ui';
import { createAlert } from '~/alert';
import { s__, sprintf } from '~/locale';
import { getParameterByName } from '~/lib/utils/url_utility';
import {
PLACEHOLDER_STATUS_FAILED,
QUERY_PARAM_FAILED,
PLACEHOLDER_USER_STATUS,
} from '~/import_entities/import_groups/constants';
import importSourceUsersQuery from '../graphql/queries/import_source_users.query.graphql';
import PlaceholdersTable from './placeholders_table.vue';
import CsvUploadModal from './csv_upload_modal.vue';
const UPLOAD_CSV_PLACEHOLDERS_MODAL_ID = 'upload-placeholders-csv-modal';
const DEFAULT_PAGE_SIZE = 20;
export default {
name: 'PlaceholdersTabApp',
components: {
......@@ -31,59 +28,24 @@ export default {
directives: {
GlModal: GlModalDirective,
},
inject: ['group'],
data() {
return {
selectedTabIndex: 0,
unassignedCount: null,
reassignedCount: null,
cursor: {
before: null,
after: null,
},
};
},
apollo: {
sourceUsers: {
query: importSourceUsersQuery,
variables() {
return {
fullPath: this.group.path,
...this.cursor,
[this.cursor.before ? 'last' : 'first']: DEFAULT_PAGE_SIZE,
statuses: this.queryStatuses,
};
},
update(data) {
return data.namespace?.importSourceUsers;
},
error() {
createAlert({
message: s__('UserMapping|There was a problem fetching placeholder users.'),
});
},
},
},
computed: {
...mapState('placeholder', ['pagination']),
isLoading() {
return Boolean(this.$apollo.queries.sourceUsers.loading);
},
nodes() {
return this.sourceUsers?.nodes || [];
},
pageInfo() {
return this.sourceUsers?.pageInfo || {};
},
statusParamValue() {
return getParameterByName('status');
},
queryStatuses() {
unassignedUserStatuses() {
if (getParameterByName('status') === QUERY_PARAM_FAILED) {
return [PLACEHOLDER_STATUS_FAILED];
}
return [];
return PLACEHOLDER_USER_STATUS.UNASSIGNED;
},
reassignedUserStatuses() {
return PLACEHOLDER_USER_STATUS.REASSIGNED;
},
},
......@@ -129,12 +91,9 @@ export default {
<placeholders-table
key="unassigned"
:items="nodes"
:page-info="pageInfo"
:is-loading="isLoading"
data-testid="placeholders-table-unassigned"
:query-statuses="unassignedUserStatuses"
@confirm="onConfirm"
@prev="onPrevPage"
@next="onNextPage"
/>
</gl-tab>
......@@ -146,12 +105,9 @@ export default {
<placeholders-table
key="reassigned"
data-testid="placeholders-table-reassigned"
:query-statuses="reassignedUserStatuses"
reassigned
:items="nodes"
:page-info="pageInfo"
:is-loading="isLoading"
@prev="onPrevPage"
@next="onNextPage"
/>
</gl-tab>
......
......@@ -7,13 +7,17 @@ import {
GlTable,
GlTooltipDirective,
} from '@gitlab/ui';
import { createAlert } from '~/alert';
import { s__ } from '~/locale';
import { DEFAULT_PAGE_SIZE } from '~/members/constants';
import {
PLACEHOLDER_STATUS_KEPT_AS_PLACEHOLDER,
PLACEHOLDER_STATUS_COMPLETED,
placeholderUserBadges,
} from '~/import_entities/import_groups/constants';
import importSourceUsersQuery from '../graphql/queries/import_source_users.query.graphql';
import PlaceholderActions from './placeholder_actions.vue';
export default {
......@@ -29,20 +33,11 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
inject: ['group'],
props: {
isLoading: {
type: Boolean,
required: true,
},
items: {
queryStatuses: {
type: Array,
required: false,
default: () => [],
},
pageInfo: {
type: Object,
required: false,
default: () => ({}),
required: true,
},
reassigned: {
type: Boolean,
......@@ -50,6 +45,36 @@ export default {
default: false,
},
},
data() {
return {
cursor: {
before: null,
after: null,
},
};
},
apollo: {
sourceUsers: {
query: importSourceUsersQuery,
variables() {
return {
fullPath: this.group.path,
...this.cursor,
[this.cursor.before ? 'last' : 'first']: DEFAULT_PAGE_SIZE,
statuses: this.queryStatuses,
};
},
update(data) {
return data.namespace?.importSourceUsers;
},
error() {
createAlert({
message: s__('UserMapping|There was a problem fetching placeholder users.'),
});
},
},
},
computed: {
fields() {
......@@ -75,6 +100,15 @@ export default {
},
];
},
isLoading() {
return this.$apollo.queries.sourceUsers.loading;
},
nodes() {
return this.sourceUsers?.nodes || [];
},
pageInfo() {
return this.sourceUsers?.pageInfo || {};
},
},
methods: {
......@@ -98,6 +132,19 @@ export default {
return {};
},
onPrevPage() {
this.cursor = {
before: this.sourceUsers.pageInfo.startCursor,
after: null,
};
},
onNextPage() {
this.cursor = {
after: this.sourceUsers.pageInfo.endCursor,
before: null,
};
},
onConfirm(item) {
this.$emit('confirm', item);
......@@ -108,7 +155,7 @@ export default {
<template>
<div>
<gl-table :items="items" :fields="fields" :busy="isLoading">
<gl-table :items="nodes" :fields="fields" :busy="isLoading">
<template #table-busy>
<gl-loading-icon size="lg" class="gl-my-5" />
</template>
......@@ -155,8 +202,8 @@ export default {
v-bind="pageInfo"
:prev-text="__('Prev')"
:next-text="__('Next')"
@prev="$emit('prev')"
@next="$emit('next')"
@prev="onPrevPage"
@next="onNextPage"
/>
</div>
</div>
......
......@@ -3,23 +3,20 @@ import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import VueApollo from 'vue-apollo';
import { GlTab, GlTabs, GlModal } from '@gitlab/ui';
import { createAlert } from '~/alert';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import setWindowLocation from 'helpers/set_window_location_helper';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { stubComponent, RENDER_ALL_SLOTS_TEMPLATE } from 'helpers/stub_component';
import PlaceholdersTabApp from '~/members/placeholders/components/app.vue';
import PlaceholdersTable from '~/members/placeholders/components/placeholders_table.vue';
import CsvUploadModal from '~/members/placeholders/components/csv_upload_modal.vue';
import importSourceUsersQuery from '~/members/placeholders/graphql/queries/import_source_users.query.graphql';
import { MEMBERS_TAB_TYPES } from '~/members/constants';
import setWindowLocation from 'helpers/set_window_location_helper';
import {
mockSourceUsersQueryResponse,
mockSourceUsersFailedStatusResponse,
mockSourceUsers,
pagination,
} from '../mock_data';
PLACEHOLDER_STATUS_FAILED,
QUERY_PARAM_FAILED,
PLACEHOLDER_USER_STATUS,
} from '~/import_entities/import_groups/constants';
import { mockSourceUsersQueryResponse, mockSourceUsers, pagination } from '../mock_data';
Vue.use(Vuex);
Vue.use(VueApollo);
......@@ -30,15 +27,16 @@ describe('PlaceholdersTabApp', () => {
let store;
let mockApollo;
const mockGroup = {
path: 'imported-group',
name: 'Imported group',
};
const sourceUsersQueryHandler = jest.fn().mockResolvedValue(mockSourceUsersQueryResponse());
const $toast = {
show: jest.fn(),
};
const mockGroup = {
path: 'imported-group',
name: 'Imported group',
};
const createComponent = ({
queryHandler = sourceUsersQueryHandler,
mountFn = shallowMountExtended,
......@@ -60,8 +58,8 @@ describe('PlaceholdersTabApp', () => {
apolloProvider: mockApollo,
store,
provide: {
group: mockGroup,
reassignmentCsvDownloadPath: 'foo/bar',
group: mockGroup,
},
mocks: { $toast },
stubs: {
......@@ -78,7 +76,8 @@ describe('PlaceholdersTabApp', () => {
const findTabs = () => wrapper.findComponent(GlTabs);
const findTabAt = (index) => wrapper.findAllComponents(GlTab).at(index);
const findPlaceholdersTable = () => wrapper.findComponent(PlaceholdersTable);
const findUnassignedTable = () => wrapper.findByTestId('placeholders-table-unassigned');
const findReassignedTable = () => wrapper.findByTestId('placeholders-table-reassigned');
const findReassignCsvButton = () => wrapper.findByTestId('reassign-csv-button');
const findCsvModal = () => wrapper.findComponent(CsvUploadModal);
......@@ -105,7 +104,7 @@ describe('PlaceholdersTabApp', () => {
createComponent();
await nextTick();
findPlaceholdersTable().vm.$emit('confirm', mockSourceUser);
findUnassignedTable().vm.$emit('confirm', mockSourceUser);
await nextTick();
});
......@@ -123,152 +122,40 @@ describe('PlaceholdersTabApp', () => {
});
});
describe('when sourceUsers query is loading', () => {
it('renders placeholders table as loading', () => {
describe('passes the correct queryStatuses to PlaceholdersTable', () => {
it('awaiting Reassignment - when the url includes the query param failed', () => {
setWindowLocation(`?status=${QUERY_PARAM_FAILED}`);
createComponent();
expect(findPlaceholdersTable().props('isLoading')).toBe(true);
});
});
describe('when sourceUsers query fails', () => {
beforeEach(async () => {
const sourceUsersFailedQueryHandler = jest.fn().mockRejectedValue(new Error('GraphQL error'));
createComponent({
queryHandler: sourceUsersFailedQueryHandler,
});
await waitForPromises();
});
it('creates an alert', () => {
expect(createAlert).toHaveBeenCalledWith({
message: 'There was a problem fetching placeholder users.',
const placeholdersTable = findUnassignedTable();
expect(placeholdersTable.props()).toMatchObject({
queryStatuses: [PLACEHOLDER_STATUS_FAILED],
});
});
});
describe('when sourceUsers query succeeds', () => {
beforeEach(async () => {
it('awaiting Reassignment - when the url does not include query param', () => {
createComponent();
await waitForPromises();
});
it('fetches sourceUsers', () => {
expect(sourceUsersQueryHandler).toHaveBeenCalledTimes(1);
expect(sourceUsersQueryHandler).toHaveBeenCalledWith({
after: null,
before: null,
fullPath: mockGroup.path,
first: 20,
statuses: [],
});
});
it('renders placeholders table', () => {
const sourceUsers = mockSourceUsersQueryResponse().data.namespace.importSourceUsers;
expect(findPlaceholdersTable().props()).toMatchObject({
isLoading: false,
items: sourceUsers.nodes,
pageInfo: sourceUsers.pageInfo,
const placeholdersTable = findUnassignedTable();
expect(placeholdersTable.props()).toMatchObject({
queryStatuses: PLACEHOLDER_USER_STATUS.UNASSIGNED,
});
});
});
describe('when sourceUsers query succeeds and has pagination', () => {
const sourceUsersPaginatedQueryHandler = jest.fn();
const mockPageInfo = {
endCursor: 'end834',
hasNextPage: true,
hasPreviousPage: true,
startCursor: 'start971',
};
beforeEach(async () => {
sourceUsersPaginatedQueryHandler
.mockResolvedValueOnce(mockSourceUsersQueryResponse({ pageInfo: mockPageInfo }))
.mockResolvedValueOnce(mockSourceUsersQueryResponse());
createComponent({
queryHandler: sourceUsersPaginatedQueryHandler,
});
await waitForPromises();
});
describe('when "prev" event is emitted', () => {
beforeEach(() => {
findPlaceholdersTable().vm.$emit('prev');
});
it('fetches sourceUsers with previous results', () => {
expect(sourceUsersPaginatedQueryHandler).toHaveBeenCalledTimes(2);
expect(sourceUsersPaginatedQueryHandler).toHaveBeenCalledWith(
expect.objectContaining({
after: null,
before: mockPageInfo.startCursor,
last: 20,
}),
);
});
});
describe('when "next" event is emitted', () => {
beforeEach(() => {
findPlaceholdersTable().vm.$emit('next');
});
it('fetches sourceUsers with next results', () => {
expect(sourceUsersPaginatedQueryHandler).toHaveBeenCalledTimes(2);
expect(sourceUsersPaginatedQueryHandler).toHaveBeenCalledWith(
expect.objectContaining({
after: mockPageInfo.endCursor,
before: null,
first: 20,
}),
);
});
});
});
describe('correctly filters users', () => {
const sourceUsersFailureQueryHandler = jest
.fn()
.mockResolvedValue(mockSourceUsersFailedStatusResponse);
beforeEach(async () => {
setWindowLocation('?status=failed');
createComponent({ queryHandler: sourceUsersFailureQueryHandler });
await waitForPromises();
});
it('when the url includes the query param failed', () => {
const sourceUsersWithFailedStatus =
mockSourceUsersFailedStatusResponse.data.namespace.importSourceUsers;
const tableProps = findPlaceholdersTable().props();
it('reassigned', () => {
createComponent();
expect(findPlaceholdersTable().props()).toMatchObject({
isLoading: false,
items: sourceUsersWithFailedStatus.nodes,
pageInfo: sourceUsersWithFailedStatus.pageInfo,
});
expect(tableProps.items.length).toBe(1);
expect(tableProps.items[0].status).toBe('FAILED');
expect(sourceUsersFailureQueryHandler).toHaveBeenCalledTimes(1);
expect(sourceUsersFailureQueryHandler).toHaveBeenCalledWith({
after: null,
before: null,
fullPath: mockGroup.path,
first: 20,
statuses: ['FAILED'],
const placeholdersTable = findReassignedTable();
expect(placeholdersTable.props()).toMatchObject({
reassigned: true,
queryStatuses: PLACEHOLDER_USER_STATUS.REASSIGNED,
});
});
});
describe('reassign CSV button', () => {
it('renders the button and the modal', () => {
createComponent();
createComponent({ mountFn: mountExtended });
expect(findReassignCsvButton().exists()).toBe(true);
expect(findCsvModal().exists()).toBe(true);
......@@ -276,7 +163,6 @@ describe('PlaceholdersTabApp', () => {
it('shows modal when button is clicked', async () => {
createComponent({ mountFn: mountExtended });
findReassignCsvButton().trigger('click');
await nextTick();
......
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
import VueApollo from 'vue-apollo';
import { mount, shallowMount } from '@vue/test-utils';
import { GlAvatarLabeled, GlBadge, GlKeysetPagination, GlLoadingIcon, GlTable } from '@gitlab/ui';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import createMockApollo from 'helpers/mock_apollo_helper';
import { stubComponent } from 'helpers/stub_component';
import PlaceholdersTable from '~/members/placeholders/components/placeholders_table.vue';
import PlaceholderActions from '~/members/placeholders/components/placeholder_actions.vue';
import { mockSourceUsers } from '../mock_data';
import { createAlert } from '~/alert';
import waitForPromises from 'helpers/wait_for_promises';
import setWindowLocation from 'helpers/set_window_location_helper';
import {
PLACEHOLDER_STATUS_FAILED,
QUERY_PARAM_FAILED,
PLACEHOLDER_USER_STATUS,
} from '~/import_entities/import_groups/constants';
import importSourceUsersQuery from '~/members/placeholders/graphql/queries/import_source_users.query.graphql';
import { mockSourceUsersQueryResponse, mockSourceUsers } from '../mock_data';
Vue.use(Vuex);
Vue.use(VueApollo);
jest.mock('~/alert');
describe('PlaceholdersTable', () => {
let wrapper;
let mockApollo;
const mockGroup = {
path: 'imported-group',
name: 'Imported group',
};
const defaultProps = {
isLoading: false,
items: mockSourceUsers,
queryStatuses: PLACEHOLDER_USER_STATUS.UNASSIGNED,
reassigned: false,
};
const sourceUsersQueryHandler = jest.fn().mockResolvedValue(mockSourceUsersQueryResponse());
const $toast = {
show: jest.fn(),
};
const createComponent = ({ mountFn = shallowMount, props = {} } = {}) => {
mockApollo = createMockApollo();
const createComponent = ({
mountFn = shallowMount,
queryHandler = sourceUsersQueryHandler,
props = {},
options = {},
} = {}) => {
mockApollo = createMockApollo([[importSourceUsersQuery, queryHandler]]);
wrapper = mountFn(PlaceholdersTable, {
apolloProvider: mockApollo,
......@@ -29,9 +60,14 @@ describe('PlaceholdersTable', () => {
...defaultProps,
...props,
},
provide: {
group: mockGroup,
},
mocks: { $toast },
directives: {
GlTooltip: createMockDirective('gl-tooltip'),
},
...options,
});
};
......@@ -44,110 +80,134 @@ describe('PlaceholdersTable', () => {
.props('fields')
.map((f) => f.label);
it('renders table', () => {
createComponent({ mountFn: mount });
expect(findTable().exists()).toBe(true);
expect(findTableRows().length).toBe(mockSourceUsers.length);
expect(findTableFields()).toEqual([
'Placeholder user',
'Source',
'Reassignment status',
'Reassign placeholder to',
]);
describe('when sourceUsers query is loading', () => {
it('renders table as loading', () => {
createComponent();
expect(findLoadingIcon().exists()).toBe(true);
});
});
it('renders loading icon when table is loading', () => {
createComponent({
props: { isLoading: true },
describe('when sourceUsers query fails', () => {
beforeEach(async () => {
const sourceUsersFailedQueryHandler = jest.fn().mockRejectedValue(new Error('GraphQL error'));
createComponent({
queryHandler: sourceUsersFailedQueryHandler,
});
await waitForPromises();
});
expect(findLoadingIcon().exists()).toBe(true);
it('creates an alert', () => {
expect(createAlert).toHaveBeenCalledWith({
message: 'There was a problem fetching placeholder users.',
});
});
});
it('renders avatar for placeholder user', () => {
createComponent({ mountFn: mount });
describe('when sourceUsers query succeeds', () => {
beforeEach(async () => {
createComponent({
mountFn: mount,
});
await waitForPromises();
});
const avatar = findTableRows().at(0).findComponent(GlAvatarLabeled);
const { placeholderUser } = mockSourceUsers[0];
it('fetches sourceUsers', () => {
expect(sourceUsersQueryHandler).toHaveBeenCalledTimes(1);
expect(sourceUsersQueryHandler).toHaveBeenCalledWith({
after: null,
before: null,
fullPath: mockGroup.path,
first: 20,
statuses: PLACEHOLDER_USER_STATUS.UNASSIGNED,
});
});
it('renders table', async () => {
await waitForPromises();
expect(findLoadingIcon().exists()).toBe(false);
expect(findTableRows()).toHaveLength(mockSourceUsers.length);
expect(avatar.props()).toMatchObject({
label: placeholderUser.name,
subLabel: `@${placeholderUser.username}`,
expect(findTableFields()).toEqual([
'Placeholder user',
'Source',
'Reassignment status',
'Reassign placeholder to',
]);
});
expect(avatar.attributes('src')).toBe(placeholderUser.avatarUrl);
});
it('renders source info', () => {
createComponent({ mountFn: mount });
it('renders avatar for placeholder user', async () => {
await waitForPromises();
expect(findTableRows().at(0).text()).toContain(mockSourceUsers[0].sourceHostname);
expect(findTableRows().at(0).text()).toContain(mockSourceUsers[0].sourceUsername);
});
const avatar = findTableRows().at(0).findComponent(GlAvatarLabeled);
const { placeholderUser } = mockSourceUsers[0];
it('renders status badge with tooltip', () => {
createComponent({ mountFn: mount });
expect(avatar.props()).toMatchObject({
label: placeholderUser.name,
subLabel: `@${placeholderUser.username}`,
});
expect(avatar.attributes('src')).toBe(placeholderUser.avatarUrl);
});
const firstRow = findTableRows().at(0);
const badge = firstRow.findComponent(GlBadge);
const badgeTooltip = getBinding(badge.element, 'gl-tooltip');
it('renders source info', async () => {
await waitForPromises();
expect(badge.text()).toBe('Not started');
expect(badgeTooltip.value).toBe('Reassignment has not started.');
});
expect(findTableRows().at(0).text()).toContain(mockSourceUsers[0].sourceHostname);
expect(findTableRows().at(0).text()).toContain(mockSourceUsers[0].sourceUsername);
});
it('renders actions when item is not reassigned', () => {
createComponent({ mountFn: mount });
it('renders status badge with tooltip', async () => {
await waitForPromises();
const firstRow = findTableRows().at(0);
const actions = firstRow.findComponent(PlaceholderActions);
const firstRow = findTableRows().at(0);
const badge = firstRow.findComponent(GlBadge);
const badgeTooltip = getBinding(badge.element, 'gl-tooltip');
expect(actions.props('sourceUser')).toEqual(mockSourceUsers[0]);
});
expect(badge.text()).toBe('Not started');
expect(badgeTooltip.value).toBe('Reassignment has not started.');
});
it('renders avatar for placeholderUser when item status is KEEP_AS_PLACEHOLDER', () => {
createComponent({ mountFn: mount });
it('renders avatar for placeholderUser when item status is KEEP_AS_PLACEHOLDER', async () => {
await waitForPromises();
const reassignedItemRow = findTableRows().at(5);
const actionsAvatar = reassignedItemRow.findAllComponents(GlAvatarLabeled).at(1);
const { placeholderUser } = mockSourceUsers[5];
const reassignedItemRow = findTableRows().at(5);
const actionsAvatar = reassignedItemRow.findAllComponents(GlAvatarLabeled).at(1);
const { placeholderUser } = mockSourceUsers[5];
expect(actionsAvatar.props()).toMatchObject({
label: placeholderUser.name,
subLabel: `@${placeholderUser.username}`,
expect(actionsAvatar.props()).toMatchObject({
label: placeholderUser.name,
subLabel: `@${placeholderUser.username}`,
});
});
});
it('renders avatar for reassignToUser when item status is COMPLETED', () => {
createComponent({ mountFn: mount });
it('renders actions when item is not reassigned', async () => {
await waitForPromises();
const reassignedItemRow = findTableRows().at(6);
const actionsAvatar = reassignedItemRow.findAllComponents(GlAvatarLabeled).at(1);
const { reassignToUser } = mockSourceUsers[6];
const firstRow = findTableRows().at(0);
const actions = firstRow.findComponent(PlaceholderActions);
expect(actionsAvatar.props()).toMatchObject({
label: reassignToUser.name,
subLabel: `@${reassignToUser.username}`,
expect(actions.props('sourceUser')).toEqual(mockSourceUsers[0]);
});
});
describe('when is "Re-assigned" table variant', () => {
beforeEach(() => {
createComponent({
props: {
reassigned: true,
},
it('renders avatar for reassignToUser when item status is COMPLETED', async () => {
await waitForPromises();
const reassignedItemRow = findTableRows().at(6);
const actionsAvatar = reassignedItemRow.findAllComponents(GlAvatarLabeled).at(1);
const { reassignToUser } = mockSourceUsers[6];
expect(actionsAvatar.props()).toMatchObject({
label: reassignToUser.name,
subLabel: `@${reassignToUser.username}`,
});
});
it('renders table', () => {
expect(findTable().exists()).toBe(true);
expect(findTableFields()).toEqual([
'Placeholder user',
'Source',
'Reassignment status',
'Reassigned to',
]);
it('table actions emit "confirm" event with item', () => {
const actions = findTableRows().at(2).findComponent(PlaceholderActions);
actions.vm.$emit('confirm');
expect(wrapper.emitted('confirm')[0]).toEqual([mockSourceUsers[2]]);
});
});
......@@ -161,63 +221,149 @@ describe('PlaceholdersTable', () => {
`(
'when hasNextPage=$hasNextPage and hasPreviousPage=$hasPreviousPage',
({ hasNextPage, hasPreviousPage, expectPagination }) => {
beforeEach(() => {
createComponent({
props: {
beforeEach(async () => {
const customHandler = jest.fn().mockResolvedValue(
mockSourceUsersQueryResponse({
pageInfo: {
hasNextPage,
hasPreviousPage,
},
},
}),
);
createComponent({
queryHandler: customHandler,
});
await nextTick();
});
it(`${expectPagination ? 'renders' : 'does not render'} pagination`, () => {
expect(findPagination().exists()).toBe(expectPagination);
});
},
);
it('emits "prev" event', () => {
createComponent({
props: {
pageInfo: {
hasPreviousPage: true,
},
},
describe('buttons', () => {
const mockPageInfo = {
endCursor: 'end834',
hasNextPage: true,
hasPreviousPage: true,
startCursor: 'start971',
};
const customHandler = jest.fn().mockResolvedValue(
mockSourceUsersQueryResponse({
pageInfo: mockPageInfo,
}),
);
beforeEach(async () => {
createComponent({
mountFn: mount,
queryHandler: customHandler,
});
await waitForPromises();
});
findPagination().vm.$emit('prev');
it('requests the next page on "prev" click with the correct data', async () => {
const paginated = findPagination();
await paginated.vm.$emit('prev');
expect(customHandler).toHaveBeenCalledTimes(2);
expect(customHandler).toHaveBeenCalledWith(
expect.objectContaining({
after: null,
before: mockPageInfo.startCursor,
last: 20,
}),
);
});
expect(wrapper.emitted('prev')[0]).toEqual([]);
it('requests the next page on "next" click the correct data', async () => {
const paginated = findPagination();
await paginated.vm.$emit('next');
expect(customHandler).toHaveBeenCalledTimes(2);
expect(customHandler).toHaveBeenCalledWith(
expect.objectContaining({
after: mockPageInfo.endCursor,
before: null,
first: 20,
}),
);
});
});
});
describe('correctly filters users with failed status', () => {
const sourceUsersFailureQueryHandler = jest
.fn()
.mockResolvedValue(mockSourceUsersQueryResponse({ nodes: [mockSourceUsers[4]] }));
beforeEach(async () => {
setWindowLocation(`?status=${QUERY_PARAM_FAILED}`);
await waitForPromises();
it('emits "next" event', () => {
createComponent({
props: {
pageInfo: {
hasNextPage: true,
mountFn: shallowMount,
queryHandler: sourceUsersFailureQueryHandler,
props: { queryStatuses: [PLACEHOLDER_STATUS_FAILED] },
options: {
stubs: {
GlTable: stubComponent(GlTable, {
props: ['fields', 'items', 'busy'],
}),
},
},
});
await waitForPromises();
});
findPagination().vm.$emit('next');
expect(wrapper.emitted('next')[0]).toEqual([]);
it('when the url includes the query param failed', () => {
expect(findTable().props('items')).toHaveLength(1);
expect(findTable().props('items')[0].status).toBe(PLACEHOLDER_STATUS_FAILED);
expect(sourceUsersFailureQueryHandler).toHaveBeenCalledTimes(1);
expect(sourceUsersFailureQueryHandler).toHaveBeenCalledWith({
after: null,
before: null,
fullPath: mockGroup.path,
first: 20,
statuses: [PLACEHOLDER_STATUS_FAILED],
});
});
});
describe('actions events', () => {
beforeEach(() => {
createComponent({ mountFn: mount });
});
describe('when is "Re-assigned" table variant', () => {
const reassignedQueryHandler = jest
.fn()
.mockResolvedValue(
mockSourceUsersQueryResponse({ nodes: [mockSourceUsers[5], mockSourceUsers[6]] }),
);
it('emits "confirm" event with item', () => {
const actions = findTableRows().at(2).findComponent(PlaceholderActions);
beforeEach(async () => {
createComponent({
mountFn: shallowMount,
queryHandler: reassignedQueryHandler,
props: { reassigned: true, queryStatus: PLACEHOLDER_USER_STATUS.REASSIGNED },
options: {
stubs: {
GlTable: stubComponent(GlTable, {
props: ['fields', 'items', 'busy'],
}),
},
},
});
actions.vm.$emit('confirm');
await waitForPromises();
});
expect(wrapper.emitted('confirm')[0]).toEqual([mockSourceUsers[2]]);
it('renders table', () => {
expect(findTable().exists()).toBe(true);
expect(findTableFields()).toEqual([
'Placeholder user',
'Source',
'Reassignment status',
'Reassigned to',
]);
});
it('only displays items that have been already assigned', () => {
expect(findTable().props('items')).toEqual([mockSourceUsers[5], mockSourceUsers[6]]);
});
});
});
......@@ -63,14 +63,14 @@ export const mockSourceUsers = [
}),
];
export const mockSourceUsersQueryResponse = ({ pageInfo = {} } = {}) => ({
export const mockSourceUsersQueryResponse = ({ nodes = mockSourceUsers, pageInfo = {} } = {}) => ({
data: {
namespace: {
__typename: 'Namespace',
id: 'gid://gitlab/Group/1',
importSourceUsers: {
__typename: 'ImportSourceUserConnection',
nodes: mockSourceUsers,
nodes,
pageInfo: {
__typename: 'PageInfo',
hasNextPage: false,
......@@ -84,26 +84,6 @@ export const mockSourceUsersQueryResponse = ({ pageInfo = {} } = {}) => ({
},
});
export const mockSourceUsersFailedStatusResponse = {
data: {
namespace: {
__typename: 'Namespace',
id: 'gid://gitlab/Group/1',
importSourceUsers: {
__typename: 'ImportSourceUserConnection',
nodes: [mockSourceUsers[4]],
pageInfo: {
__typename: 'PageInfo',
hasNextPage: false,
hasPreviousPage: false,
startCursor: '',
endCursor: '',
},
},
},
},
};
export const mockReassignMutationResponse = {
data: {
importSourceUserReassign: {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment