Commit 2fcb5998 authored by Paul Slaughter's avatar Paul Slaughter

Merge branch 'feature/zentao-integration-list-part' into 'master'

Feature ZenTao integration: Extracting Issue List component

See merge request gitlab-org/gitlab!69927
parents 8976bab1 572e705d
......@@ -315,7 +315,7 @@ export default {
<span
v-if="isJiraIssue"
v-safe-html="jiraLogo"
class="svg-container jira-logo-container"
class="svg-container logo-container"
data-testid="jira-logo"
></span>
{{ referencePath }}
......
<script>
import { GlEmptyState, GlButton, GlIcon, GlSprintf } from '@gitlab/ui';
import { externalIssuesListEmptyStateI18n as i18n } from 'ee/external_issues_list/constants';
import { IssuableStates } from '~/issuable_list/constants';
import { __, s__ } from '~/locale';
export default {
FilterStateEmptyMessage: {
[IssuableStates.Opened]: __('There are no open issues'),
[IssuableStates.Closed]: __('There are no closed issues'),
},
components: {
GlEmptyState,
GlButton,
GlIcon,
GlSprintf,
},
inject: ['emptyStatePath', 'issueCreateUrl'],
// The text injected is sanitized.
inject: ['emptyStatePath', 'issueCreateUrl', 'emptyStateNoIssueText', 'createNewIssueText'],
props: {
currentState: {
type: String,
......@@ -35,21 +32,25 @@ export default {
return this.issuesCount[IssuableStates.Opened] + this.issuesCount[IssuableStates.Closed] > 0;
},
emptyStateTitle() {
const { titleWhenFilters, filterStateEmptyMessage } = i18n;
if (this.hasFiltersApplied) {
return __('Sorry, your filter produced no results');
return titleWhenFilters;
} else if (this.hasIssues) {
return this.$options.FilterStateEmptyMessage[this.currentState];
return filterStateEmptyMessage[this.currentState];
}
return s__(
'Integrations|Issues created in Jira are shown here once you have created the issues in project setup in Jira.',
);
return this.emptyStateNoIssueText;
},
emptyStateDescription() {
const { descriptionWhenFilters, descriptionWhenNoIssues } = i18n;
if (this.hasFiltersApplied) {
return __('To widen your search, change or remove filters above');
return descriptionWhenFilters;
} else if (!this.hasIssues) {
return s__('Integrations|To keep this project going, create a new issue.');
return descriptionWhenNoIssues;
}
return '';
},
},
......@@ -63,7 +64,7 @@ export default {
</template>
<template v-if="!hasIssues" #actions>
<gl-button :href="issueCreateUrl" target="_blank" variant="confirm">
{{ s__('Integrations|Create new issue in Jira') }}
{{ createNewIssueText }}
<gl-icon name="external-link" />
</gl-button>
</template>
......
<script>
import jiraLogo from '@gitlab/svgs/dist/illustrations/logos/jira.svg';
import { GlButton, GlIcon, GlLink, GlSprintf, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import createFlash from '~/flash';
......@@ -10,6 +9,7 @@ import {
AvailableSortOptions,
DEFAULT_PAGE_SIZE,
} from '~/issuable_list/constants';
import { i18n } from '~/issues_list/constants';
import {
FILTERED_SEARCH_LABELS,
FILTERED_SEARCH_TERM,
......@@ -18,12 +18,10 @@ import {
} from '~/vue_shared/components/filtered_search_bar/constants';
import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue';
import { ISSUES_LIST_FETCH_ERROR } from '../constants';
import getJiraIssuesQuery from '../graphql/queries/get_jira_issues.query.graphql';
import JiraIssuesListEmptyState from './jira_issues_list_empty_state.vue';
import ExternalIssuesListEmptyState from './external_issues_list_empty_state.vue';
export default {
name: 'JiraIssuesList',
name: 'ExternalIssuesList',
IssuableListTabs,
AvailableSortOptions,
defaultPageSize: DEFAULT_PAGE_SIZE,
......@@ -33,7 +31,7 @@ export default {
GlLink,
GlSprintf,
IssuableList,
JiraIssuesListEmptyState,
ExternalIssuesListEmptyState,
},
directives: {
SafeHtml,
......@@ -45,6 +43,12 @@ export default {
'issuesFetchPath',
'projectFullPath',
'issueCreateUrl',
'getIssuesQuery',
'externalIssuesLogo',
'externalIssueTrackerName',
'searchInputPlaceholderText',
'recentSearchesStorageKey',
'createNewIssueText',
],
props: {
initialFilterParams: {
......@@ -55,7 +59,6 @@ export default {
},
data() {
return {
jiraLogo,
issues: [],
totalIssues: 0,
currentState: this.initialState,
......@@ -71,7 +74,7 @@ export default {
},
computed: {
issuesListLoading() {
return this.$apollo.queries.jiraIssues.loading;
return this.$apollo.queries.externalIssues.loading;
},
showPaginationControls() {
return Boolean(!this.issuesListLoading && this.issues.length && this.totalIssues > 1);
......@@ -90,8 +93,10 @@ export default {
},
},
apollo: {
jiraIssues: {
query: getJiraIssuesQuery,
externalIssues: {
query() {
return this.getIssuesQuery;
},
variables() {
return {
issuesFetchPath: this.issuesFetchPath,
......@@ -108,9 +113,9 @@ export default {
return;
}
const { pageInfo, nodes, errors } = data?.jiraIssues ?? {};
const { pageInfo, nodes, errors } = data?.externalIssues ?? {};
if (errors?.length > 0) {
this.onJiraIssuesQueryError(new Error(errors[0]));
this.onExternalIssuesQueryError(new Error(errors[0]));
return;
}
......@@ -120,7 +125,7 @@ export default {
this.issuesCount[this.currentState] = nodes.length;
},
error(error) {
this.onJiraIssuesQueryError(error, ISSUES_LIST_FETCH_ERROR);
this.onExternalIssuesQueryError(error, i18n.errorFetchingIssues);
},
},
},
......@@ -142,6 +147,7 @@ export default {
},
];
},
getFilteredSearchValue() {
const { labels, search } = this.filterParams || {};
const filteredSearchValue = [];
......@@ -166,7 +172,7 @@ export default {
return filteredSearchValue;
},
onJiraIssuesQueryError(error, message) {
onExternalIssuesQueryError(error, message) {
createFlash({
message: message || error.message,
captureError: true,
......@@ -223,7 +229,7 @@ export default {
:namespace="projectFullPath"
:tabs="$options.IssuableListTabs"
:current-tab="currentState"
:search-input-placeholder="s__('Integrations|Search Jira issues')"
:search-input-placeholder="searchInputPlaceholderText"
:search-tokens="getFilteredSearchTokens()"
:sort-options="$options.AvailableSortOptions"
:initial-filter-value="getFilteredSearchValue()"
......@@ -238,7 +244,7 @@ export default {
:next-page="currentPage + 1"
:url-params="urlParams"
label-filter-param="labels"
recent-searches-storage-key="jira_issues"
:recent-searches-storage-key="recentSearchesStorageKey"
@click-tab="onIssuableListClickTab"
@page-change="onIssuableListPageChange"
@sort="onIssuableListSort"
......@@ -246,16 +252,18 @@ export default {
>
<template #nav-actions>
<gl-button :href="issueCreateUrl" target="_blank" class="gl-my-5">
{{ s__('Integrations|Create new issue in Jira') }}
{{ createNewIssueText }}
<gl-icon name="external-link" />
</gl-button>
</template>
<template #reference="{ issuable }">
<span v-safe-html="jiraLogo" class="svg-container jira-logo-container"></span>
<span v-if="issuable">{{ issuable.references.relative }}</span>
<span v-safe-html="externalIssuesLogo" class="svg-container logo-container"></span>
<span v-if="issuable">
{{ issuable.references ? issuable.references.relative : issuable.id }}
</span>
</template>
<template #author="{ author }">
<gl-sprintf message="%{authorName} in Jira">
<gl-sprintf :message="`%{authorName} in ${externalIssueTrackerName}`">
<template #authorName>
<gl-link class="author-link js-user-link" target="_blank" :href="author.webUrl">
{{ author.name }}
......@@ -267,7 +275,7 @@ export default {
<template v-if="issuable"> {{ issuable.status }} </template>
</template>
<template #empty-state>
<jira-issues-list-empty-state
<external-issues-list-empty-state
:current-state="currentState"
:issues-count="issuesCount"
:has-filters-applied="hasFiltersApplied"
......
import { IssuableStates } from '~/issuable_list/constants';
import { __, s__ } from '~/locale';
export const externalIssuesListEmptyStateI18n = {
titleWhenFilters: __('Sorry, your filter produced no results'),
descriptionWhenFilters: __('To widen your search, change or remove filters above'),
descriptionWhenNoIssues: s__('Integrations|To keep this project going, create a new issue.'),
filterStateEmptyMessage: {
[IssuableStates.Opened]: __('There are no open issues'),
[IssuableStates.Closed]: __('There are no closed issues'),
},
};
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import jiraIssues from './resolvers/jira_issues';
Vue.use(VueApollo);
const resolvers = {
Query: {
jiraIssues,
},
};
export default (externalIssuesQueryResolver) => {
const resolvers = {
Query: {
externalIssues: externalIssuesQueryResolver,
},
};
const defaultClient = createDefaultClient(resolvers, { assumeImmutableResults: true });
const defaultClient = createDefaultClient(resolvers, { assumeImmutableResults: true });
export default new VueApollo({
defaultClient,
});
return new VueApollo({
defaultClient,
});
};
import Vue from 'vue';
import { IssuableStates } from '~/issuable_list/constants';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { queryToObject } from '~/lib/utils/url_utility';
import ExternalIssuesListApp from './components/external_issues_list_root.vue';
import getApolloProvider from './graphql';
export default function externalIssuesListFactory({
provides: {
getIssuesQuery,
externalIssuesLogo,
externalIssueTrackerName,
searchInputPlaceholderText,
recentSearchesStorageKey,
createNewIssueText,
emptyStateNoIssueText,
},
externalIssuesQueryResolver,
}) {
return function initExternalIssuesList({ mountPointSelector }) {
const mountPointEl = document.querySelector(mountPointSelector);
if (!mountPointEl) {
return null;
}
const {
page = 1,
initialState = IssuableStates.Opened,
initialSortBy = 'created_desc',
} = mountPointEl.dataset;
const initialFilterParams = Object.assign(
convertObjectPropsToCamelCase(
queryToObject(window.location.search.substring(1), { gatherArrays: true }),
{
dropKeys: ['scope', 'utf8', 'state', 'sort'], // These keys are unsupported/unnecessary
},
),
);
return new Vue({
el: mountPointEl,
provide: {
...mountPointEl.dataset,
page: parseInt(page, 10),
initialState,
initialSortBy,
getIssuesQuery,
externalIssuesLogo,
externalIssueTrackerName,
searchInputPlaceholderText,
recentSearchesStorageKey,
createNewIssueText,
emptyStateNoIssueText,
},
apolloProvider: getApolloProvider(externalIssuesQueryResolver),
render: (createElement) =>
createElement(ExternalIssuesListApp, {
props: {
initialFilterParams,
},
}),
});
};
}
import { __ } from '~/locale';
export const ISSUES_LIST_FETCH_ERROR = __('An error occurred while loading issues');
......@@ -9,7 +9,7 @@ query jiraIssues(
$state: String
$page: Integer
) {
jiraIssues(
externalIssues(
issuesFetchPath: $issuesFetchPath
search: $search
labels: $labels
......
import { DEFAULT_PAGE_SIZE } from '~/issuable_list/constants';
import { i18n } from '~/issues_list/constants';
import axios from '~/lib/utils/axios_utils';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { ISSUES_LIST_FETCH_ERROR } from '../../constants';
const transformJiraIssueAssignees = (jiraIssue) => {
return jiraIssue.assignees.map((assignee) => ({
......@@ -78,7 +78,7 @@ export default function jiraIssuesResolver(
.catch((error) => {
return {
__typename: 'JiraIssues',
errors: error?.response?.data?.errors || [ISSUES_LIST_FETCH_ERROR],
errors: error?.response?.data?.errors || [i18n.errorFetchingIssues],
pageInfo: transformJiraIssuePageInfo(),
nodes: [],
};
......
import Vue from 'vue';
import jiraLogo from '@gitlab/svgs/dist/illustrations/logos/jira.svg';
import externalIssuesListFactory from 'ee/external_issues_list';
import { s__ } from '~/locale';
import getIssuesQuery from './graphql/queries/get_jira_issues.query.graphql';
import jiraIssuesResolver from './graphql/resolvers/jira_issues';
import { IssuableStates } from '~/issuable_list/constants';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { queryToObject } from '~/lib/utils/url_utility';
import JiraIssuesListApp from './components/jira_issues_list_root.vue';
import apolloProvider from './graphql';
export default function initJiraIssuesList({ mountPointSelector }) {
const mountPointEl = document.querySelector(mountPointSelector);
if (!mountPointEl) {
return null;
}
const {
page = 1,
initialState = IssuableStates.Opened,
initialSortBy = 'created_desc',
} = mountPointEl.dataset;
const initialFilterParams = Object.assign(
convertObjectPropsToCamelCase(
queryToObject(window.location.search.substring(1), { gatherArrays: true }),
{
dropKeys: ['scope', 'utf8', 'state', 'sort'], // These keys are unsupported/unnecessary
},
export default externalIssuesListFactory({
externalIssuesQueryResolver: jiraIssuesResolver,
provides: {
getIssuesQuery,
externalIssuesLogo: jiraLogo,
externalIssueTrackerName: 'Jira', // eslint-disable-line @gitlab/require-i18n-strings
searchInputPlaceholderText: s__('Integrations|Search Jira issues'),
recentSearchesStorageKey: 'jira_issues',
createNewIssueText: s__('Integrations|Create new issue in Jira'),
emptyStateNoIssueText: s__(
'Integrations|Issues created in Jira are shown here once you have created the issues in project setup in Jira.',
),
);
return new Vue({
el: mountPointEl,
provide: {
...mountPointEl.dataset,
page: parseInt(page, 10),
initialState,
initialSortBy,
},
apolloProvider,
render: (createElement) =>
createElement(JiraIssuesListApp, {
props: {
initialFilterParams,
},
}),
});
}
},
});
@import '../../../../../app/assets/stylesheets/page_bundles/issues_list';
.svg-container.jira-logo-container {
.svg-container.logo-container {
svg {
vertical-align: text-bottom;
}
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`JiraIssuesListRoot when request succeeds renders issuable-list component with correct props 1`] = `
exports[`ExternalIssuesListRoot when request succeeds renders issuable-list component with correct props 1`] = `
Object {
"currentPage": 1,
"currentTab": "opened",
......
import { GlEmptyState, GlSprintf, GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import JiraIssuesListEmptyState from 'ee/integrations/jira/issues_list/components/jira_issues_list_empty_state.vue';
import ExternalIssuesListEmptyState from 'ee/external_issues_list/components/external_issues_list_empty_state.vue';
import { externalIssuesListEmptyStateI18n } from 'ee/external_issues_list/constants';
import { IssuableStates } from '~/issuable_list/constants';
import { mockProvide } from '../mock_data';
const createComponent = (props = {}) =>
shallowMount(JiraIssuesListEmptyState, {
shallowMount(ExternalIssuesListEmptyState, {
provide: mockProvide,
propsData: {
currentState: 'opened',
......@@ -22,16 +23,11 @@ const createComponent = (props = {}) =>
stubs: { GlEmptyState },
});
describe('JiraIssuesListEmptyState', () => {
const titleDefault =
'Issues created in Jira are shown here once you have created the issues in project setup in Jira.';
const titleWhenFilters = 'Sorry, your filter produced no results';
const titleWhenIssues = 'There are no open issues';
const descriptionWhenFilters = 'To widen your search, change or remove filters above';
const descriptionWhenNoIssues = 'To keep this project going, create a new issue.';
describe('ExternalIssuesListEmptyState', () => {
let wrapper;
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
beforeEach(() => {
wrapper = createComponent();
});
......@@ -61,17 +57,19 @@ describe('JiraIssuesListEmptyState', () => {
});
describe('emptyStateTitle', () => {
it(`returns string "${titleWhenFilters}" when hasFiltersApplied prop is true`, async () => {
it(`returns correct string when hasFiltersApplied prop is true`, async () => {
wrapper.setProps({
hasFiltersApplied: true,
});
await wrapper.vm.$nextTick();
expect(wrapper.vm.emptyStateTitle).toBe(titleWhenFilters);
expect(findEmptyState().props('title')).toBe(
externalIssuesListEmptyStateI18n.titleWhenFilters,
);
});
it(`returns string "${titleWhenIssues}" when hasFiltersApplied prop is false and hasIssues is true`, async () => {
it(`returns correct string when hasFiltersApplied prop is false and hasIssues is true`, async () => {
wrapper.setProps({
hasFiltersApplied: false,
issuesCount: {
......@@ -82,7 +80,9 @@ describe('JiraIssuesListEmptyState', () => {
await wrapper.vm.$nextTick();
expect(wrapper.vm.emptyStateTitle).toBe(titleWhenIssues);
expect(findEmptyState().props('title')).toBe(
externalIssuesListEmptyStateI18n.filterStateEmptyMessage[IssuableStates.Opened],
);
});
it('returns default title string when both hasFiltersApplied and hasIssues props are false', async () => {
......@@ -92,29 +92,33 @@ describe('JiraIssuesListEmptyState', () => {
await wrapper.vm.$nextTick();
expect(wrapper.vm.emptyStateTitle).toBe(titleDefault);
expect(findEmptyState().props('title')).toBe(mockProvide.emptyStateNoIssueText);
});
});
describe('emptyStateDescription', () => {
it(`returns string "${descriptionWhenFilters}" when hasFiltersApplied prop is true`, async () => {
it(`returns correct when hasFiltersApplied prop is true`, async () => {
wrapper.setProps({
hasFiltersApplied: true,
});
await wrapper.vm.$nextTick();
expect(wrapper.vm.emptyStateDescription).toBe(descriptionWhenFilters);
expect(wrapper.vm.emptyStateDescription).toBe(
externalIssuesListEmptyStateI18n.descriptionWhenFilters,
);
});
it(`returns string "${descriptionWhenNoIssues}" when both hasFiltersApplied and hasIssues props are false`, async () => {
it(`returns correct string when both hasFiltersApplied and hasIssues props are false`, async () => {
wrapper.setProps({
hasFiltersApplied: false,
});
await wrapper.vm.$nextTick();
expect(wrapper.vm.emptyStateDescription).toBe(descriptionWhenNoIssues);
expect(wrapper.vm.emptyStateDescription).toBe(
externalIssuesListEmptyStateI18n.descriptionWhenNoIssues,
);
});
it(`returns empty string when hasFiltersApplied is false and hasIssues is true`, async () => {
......@@ -143,8 +147,7 @@ describe('JiraIssuesListEmptyState', () => {
expect(emptyStateEl.props()).toMatchObject({
svgPath: mockProvide.emptyStatePath,
title:
'Issues created in Jira are shown here once you have created the issues in project setup in Jira.',
title: mockProvide.emptyStateNoIssueText,
});
wrapper.setProps({
......@@ -153,7 +156,7 @@ describe('JiraIssuesListEmptyState', () => {
await wrapper.vm.$nextTick();
expect(emptyStateEl.props('title')).toBe('Sorry, your filter produced no results');
expect(emptyStateEl.props('title')).toBe(externalIssuesListEmptyStateI18n.titleWhenFilters);
wrapper.setProps({
hasFiltersApplied: false,
......@@ -165,7 +168,9 @@ describe('JiraIssuesListEmptyState', () => {
await wrapper.vm.$nextTick();
expect(emptyStateEl.props('title')).toBe('There are no open issues');
expect(emptyStateEl.props('title')).toBe(
externalIssuesListEmptyStateI18n.filterStateEmptyMessage[IssuableStates.Opened],
);
});
it('renders empty state description', () => {
......@@ -192,12 +197,12 @@ describe('JiraIssuesListEmptyState', () => {
expect(descriptionEl.exists()).toBe(false);
});
it('renders "Create new issue in Jira" button', () => {
const buttonEl = wrapper.find(GlButton);
it('renders "create issue button', () => {
const buttonEl = wrapper.findComponent(GlButton);
expect(buttonEl.exists()).toBe(true);
expect(buttonEl.attributes('href')).toBe(mockProvide.issueCreateUrl);
expect(buttonEl.text()).toBe('Create new issue in Jira');
expect(buttonEl.text()).toBe(mockProvide.createNewIssueText);
});
});
});
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { shallowMount, createLocalVue, mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import VueApollo from 'vue-apollo';
import JiraIssuesListRoot from 'ee/integrations/jira/issues_list/components/jira_issues_list_root.vue';
import { ISSUES_LIST_FETCH_ERROR } from 'ee/integrations/jira/issues_list/constants';
import jiraIssues from 'ee/integrations/jira/issues_list/graphql/resolvers/jira_issues';
import ExternalIssuesListRoot from 'ee/external_issues_list/components/external_issues_list_root.vue';
import jiraIssuesResolver from 'ee/integrations/jira/issues_list/graphql/resolvers/jira_issues';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import IssuableList from '~/issuable_list/components/issuable_list_root.vue';
import { i18n } from '~/issues_list/constants';
import axios from '~/lib/utils/axios_utils';
import httpStatus from '~/lib/utils/http_status';
import { mockProvide, mockJiraIssues } from '../mock_data';
import {
mockProvide,
mockJiraIssues as mockExternalIssues,
mockJiraIssue4 as mockJiraIssueNoReference,
} from '../mock_data';
jest.mock('~/flash');
jest.mock('~/issuable_list/constants', () => ({
......@@ -31,16 +35,16 @@ jest.mock(
const resolvedValue = {
headers: {
'x-page': 1,
'x-total': mockJiraIssues.length,
'x-total': mockExternalIssues.length,
},
data: mockJiraIssues,
data: mockExternalIssues,
};
const localVue = createLocalVue();
const resolvers = {
Query: {
jiraIssues,
externalIssues: jiraIssuesResolver,
},
};
......@@ -49,7 +53,7 @@ function createMockApolloProvider(mockResolvers = resolvers) {
return createMockApollo([], mockResolvers);
}
describe('JiraIssuesListRoot', () => {
describe('ExternalIssuesListRoot', () => {
let wrapper;
let mock;
......@@ -65,7 +69,7 @@ describe('JiraIssuesListRoot', () => {
provide = mockProvide,
initialFilterParams = {},
} = {}) => {
wrapper = shallowMount(JiraIssuesListRoot, {
wrapper = shallowMount(ExternalIssuesListRoot, {
propsData: {
initialFilterParams,
},
......@@ -155,6 +159,45 @@ describe('JiraIssuesListRoot', () => {
expect(issuableList.props()).toMatchSnapshot();
});
describe('issuable-list reference section', () => {
it('renders issuable-list component with correct reference', async () => {
jest.spyOn(axios, 'get').mockResolvedValue(resolvedValue);
wrapper = mount(ExternalIssuesListRoot, {
propsData: {
initialFilterParams: {},
},
provide: mockProvide,
localVue,
apolloProvider: createMockApolloProvider(),
});
await waitForPromises();
expect(wrapper.find('.issuable-info').text()).toContain(
resolvedValue.data[0].references.relative,
);
});
it('renders issuable-list component with id when references is not presence', async () => {
jest.spyOn(axios, 'get').mockResolvedValue({
...resolvedValue,
data: [mockJiraIssueNoReference],
});
wrapper = mount(ExternalIssuesListRoot, {
propsData: {
initialFilterParams: {},
},
provide: mockProvide,
localVue,
apolloProvider: createMockApolloProvider(),
});
await waitForPromises();
// Since Jira transformer transforms references.relative into id, we can only test
// whether it exists.
expect(wrapper.find('.issuable-info').exists()).toBe(false);
});
});
describe('issuable-list events', () => {
it('"click-tab" event executes GET request correctly', async () => {
const issuableList = findIssuableList();
......@@ -181,7 +224,7 @@ describe('JiraIssuesListRoot', () => {
const issuableList = findIssuableList();
jest.spyOn(axios, 'get').mockResolvedValue({
...resolvedValue,
headers: { 'x-page': mockPage, 'x-total': mockJiraIssues.length },
headers: { 'x-page': mockPage, 'x-total': mockExternalIssues.length },
});
issuableList.vm.$emit('page-change', mockPage);
......@@ -261,7 +304,7 @@ describe('JiraIssuesListRoot', () => {
it.each`
APIErrors | expectedRenderedErrorMessage
${['API error']} | ${'API error'}
${undefined} | ${ISSUES_LIST_FETCH_ERROR}
${undefined} | ${i18n.errorFetchingIssues}
`(
'calls `createFlash` with "$expectedRenderedErrorMessage" when API responds with "$APIErrors"',
async ({ APIErrors, expectedRenderedErrorMessage }) => {
......@@ -287,14 +330,14 @@ describe('JiraIssuesListRoot', () => {
createComponent({
apolloProvider: createMockApolloProvider({
Query: {
jiraIssues: jest.fn().mockRejectedValue(new Error('GraphQL networkError')),
externalIssues: jest.fn().mockRejectedValue(new Error('GraphQL networkError')),
},
}),
});
await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({
message: ISSUES_LIST_FETCH_ERROR,
message: i18n.errorFetchingIssues,
captureError: true,
error: expect.any(Object),
});
......@@ -304,10 +347,10 @@ describe('JiraIssuesListRoot', () => {
describe('pagination', () => {
it.each`
scenario | issuesListLoadFailed | issues | shouldShowPaginationControls
${'fails'} | ${true} | ${[]} | ${false}
${'returns no issues'} | ${false} | ${[]} | ${false}
${`returns some issues`} | ${false} | ${mockJiraIssues} | ${true}
scenario | issuesListLoadFailed | issues | shouldShowPaginationControls
${'fails'} | ${true} | ${[]} | ${false}
${'returns no issues'} | ${false} | ${[]} | ${false}
${`returns some issues`} | ${false} | ${mockExternalIssues} | ${true}
`(
'sets `showPaginationControls` prop to $shouldShowPaginationControls when request $scenario',
async ({ issuesListLoadFailed, issues, shouldShowPaginationControls }) => {
......
import jiraLogo from '@gitlab/svgs/dist/illustrations/logos/jira.svg';
import mockGetJiraIssuesQuery from 'ee/integrations/jira/issues_list/graphql/queries/get_jira_issues.query.graphql';
export const mockProvide = {
initialState: 'opened',
initialSortBy: 'created_desc',
......@@ -6,6 +9,15 @@ export const mockProvide = {
projectFullPath: 'gitlab-org/gitlab-test',
issueCreateUrl: 'https://gitlab-jira.atlassian.net/secure/CreateIssue!default.jspa',
emptyStatePath: '/assets/illustrations/issues.svg',
getIssuesQuery: mockGetJiraIssuesQuery,
externalIssuesLogo: jiraLogo,
externalIssueTrackerName: 'Jira',
emptyStateNoIssueText:
'Issues created in Jira are shown here once you have created the issues in project setup in Jira.',
recentSearchesStorageKey: 'jira_issues',
createNewIssueText: 'Create new issue in Jira',
searchInputPlaceholderText: 'Search Jira issues',
};
export const mockJiraIssue1 = {
......@@ -87,4 +99,24 @@ export const mockJiraIssue3 = {
external_tracker: 'jira',
};
// issue without reference presence
export const mockJiraIssue4 = {
project_id: 1,
title: 'Alias ut modi est labore.',
created_at: '2020-03-19T14:31:50.012Z',
updated_at: '2020-03-19T14:31:50.012Z',
closed_at: null,
status: 'Backlog',
labels: [],
author: {
name: 'Gabe Weaver',
web_url: 'https://gitlab-jira.atlassian.net/people/5e320a31fe03e20c9d1dccde',
avatar_url: null,
},
assignees: [],
web_url: 'https://gitlab-jira.atlassian.net/browse/IG-31594',
gitlab_web_url: '',
external_tracker: 'jira',
};
export const mockJiraIssues = [mockJiraIssue1, mockJiraIssue2, mockJiraIssue3];
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