Commit a70874fe authored by Simon Knox's avatar Simon Knox

Merge branch 'ss/update-copy-for-no-iterations' into 'master'

Rearchitect component and update copy for iteration dropdown

See merge request gitlab-org/gitlab!71010
parents e8ed8549 e0d3a44f
...@@ -14,9 +14,10 @@ import createFlash from '~/flash'; ...@@ -14,9 +14,10 @@ import createFlash from '~/flash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { IssuableType } from '~/issue_show/constants'; import { IssuableType } from '~/issue_show/constants';
import { timeFor } from '~/lib/utils/datetime_utility'; import { timeFor } from '~/lib/utils/datetime_utility';
import { __, s__, sprintf } from '~/locale'; import { __ } from '~/locale';
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue'; import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
import { import {
dropdowni18nText,
Tracking, Tracking,
IssuableAttributeState, IssuableAttributeState,
IssuableAttributeType, IssuableAttributeType,
...@@ -24,14 +25,11 @@ import { ...@@ -24,14 +25,11 @@ import {
noAttributeId, noAttributeId,
defaultEpicSort, defaultEpicSort,
epicIidPattern, epicIidPattern,
} from '~/sidebar/constants'; } from 'ee_else_ce/sidebar/constants';
export default { export default {
noAttributeId, noAttributeId,
IssuableAttributeState,
issuableAttributesQueries,
i18n: { i18n: {
[IssuableAttributeType.Milestone]: __('Milestone'),
expired: __('(expired)'), expired: __('(expired)'),
none: __('None'), none: __('None'),
}, },
...@@ -53,14 +51,24 @@ export default { ...@@ -53,14 +51,24 @@ export default {
isClassicSidebar: { isClassicSidebar: {
default: false, default: false,
}, },
issuableAttributesQueries: {
default: issuableAttributesQueries,
},
issuableAttributesState: {
default: IssuableAttributeState,
},
widgetTitleText: {
default: {
[IssuableAttributeType.Milestone]: __('Milestone'),
expired: __('(expired)'),
none: __('None'),
},
},
}, },
props: { props: {
issuableAttribute: { issuableAttribute: {
type: String, type: String,
required: true, required: true,
validator(value) {
return [IssuableAttributeType.Milestone].includes(value);
},
}, },
workspacePath: { workspacePath: {
required: true, required: true,
...@@ -132,13 +140,13 @@ export default { ...@@ -132,13 +140,13 @@ export default {
return { return {
fullPath: this.attrWorkspacePath, fullPath: this.attrWorkspacePath,
title: this.searchTerm, title: this.searchTerm,
state: this.$options.IssuableAttributeState[this.issuableAttribute], state: this.issuableAttributesState[this.issuableAttribute],
}; };
} }
const variables = { const variables = {
fullPath: this.attrWorkspacePath, fullPath: this.attrWorkspacePath,
state: this.$options.IssuableAttributeState[this.issuableAttribute], state: this.issuableAttributesState[this.issuableAttribute],
sort: defaultEpicSort, sort: defaultEpicSort,
}; };
...@@ -180,7 +188,7 @@ export default { ...@@ -180,7 +188,7 @@ export default {
}, },
computed: { computed: {
issuableAttributeQuery() { issuableAttributeQuery() {
return this.$options.issuableAttributesQueries[this.issuableAttribute]; return this.issuableAttributesQueries[this.issuableAttribute];
}, },
attributeTitle() { attributeTitle() {
return this.currentAttribute?.title || this.i18n.noAttribute; return this.currentAttribute?.title || this.i18n.noAttribute;
...@@ -189,9 +197,7 @@ export default { ...@@ -189,9 +197,7 @@ export default {
return this.currentAttribute?.webUrl; return this.currentAttribute?.webUrl;
}, },
dropdownText() { dropdownText() {
return this.currentAttribute return this.currentAttribute ? this.currentAttribute?.title : this.attributeTypeTitle;
? this.currentAttribute?.title
: this.$options.i18n[this.issuableAttribute];
}, },
loading() { loading() {
return this.$apollo.queries.currentAttribute.loading; return this.$apollo.queries.currentAttribute.loading;
...@@ -200,7 +206,7 @@ export default { ...@@ -200,7 +206,7 @@ export default {
return this.attributesList.length === 0; return this.attributesList.length === 0;
}, },
attributeTypeTitle() { attributeTypeTitle() {
return this.$options.i18n[this.issuableAttribute]; return this.widgetTitleText[this.issuableAttribute];
}, },
attributeTypeIcon() { attributeTypeIcon() {
return this.icon || this.issuableAttribute; return this.icon || this.issuableAttribute;
...@@ -209,37 +215,10 @@ export default { ...@@ -209,37 +215,10 @@ export default {
return timeFor(this.currentAttribute?.dueDate); return timeFor(this.currentAttribute?.dueDate);
}, },
i18n() { i18n() {
return { return dropdowni18nText(this.issuableAttribute, this.issuableType);
noAttribute: sprintf(s__('DropdownWidget|No %{issuableAttribute}'), {
issuableAttribute: this.issuableAttribute,
}),
assignAttribute: sprintf(s__('DropdownWidget|Assign %{issuableAttribute}'), {
issuableAttribute: this.issuableAttribute,
}),
noAttributesFound: sprintf(s__('DropdownWidget|No %{issuableAttribute} found'), {
issuableAttribute: this.issuableAttribute,
}),
updateError: sprintf(
s__(
'DropdownWidget|Failed to set %{issuableAttribute} on this %{issuableType}. Please try again.',
),
{ issuableAttribute: this.issuableAttribute, issuableType: this.issuableType },
),
listFetchError: sprintf(
s__(
'DropdownWidget|Failed to fetch the %{issuableAttribute} for this %{issuableType}. Please try again.',
),
{ issuableAttribute: this.issuableAttribute, issuableType: this.issuableType },
),
currentFetchError: sprintf(
s__(
'DropdownWidget|An error occurred while fetching the assigned %{issuableAttribute} of the selected %{issuableType}.',
),
{ issuableAttribute: this.issuableAttribute, issuableType: this.issuableType },
),
};
}, },
isEpic() { isEpic() {
// MV to EE https://gitlab.com/gitlab-org/gitlab/-/issues/345311
return this.issuableAttribute === IssuableType.Epic; return this.issuableAttribute === IssuableType.Epic;
}, },
}, },
...@@ -252,7 +231,7 @@ export default { ...@@ -252,7 +231,7 @@ export default {
const selectedAttribute = const selectedAttribute =
Boolean(attributeId) && this.attributesList.find((p) => p.id === attributeId); Boolean(attributeId) && this.attributesList.find((p) => p.id === attributeId);
this.selectedTitle = selectedAttribute ? selectedAttribute.title : this.$options.i18n.none; this.selectedTitle = selectedAttribute ? selectedAttribute.title : this.widgetTitleText.none;
const { current } = this.issuableAttributeQuery; const { current } = this.issuableAttributeQuery;
const { mutation } = current[this.issuableType]; const { mutation } = current[this.issuableType];
......
import { s__, sprintf } from '~/locale';
import updateIssueLabelsMutation from '~/boards/graphql/issue_set_labels.mutation.graphql'; import updateIssueLabelsMutation from '~/boards/graphql/issue_set_labels.mutation.graphql';
import { IssuableType, WorkspaceType } from '~/issue_show/constants'; import { IssuableType, WorkspaceType } from '~/issue_show/constants';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
...@@ -272,3 +273,35 @@ export const todoMutations = { ...@@ -272,3 +273,35 @@ export const todoMutations = {
[TodoMutationTypes.Create]: todoCreateMutation, [TodoMutationTypes.Create]: todoCreateMutation,
[TodoMutationTypes.MarkDone]: todoMarkDoneMutation, [TodoMutationTypes.MarkDone]: todoMarkDoneMutation,
}; };
export function dropdowni18nText(issuableAttribute, issuableType) {
return {
noAttribute: sprintf(s__('DropdownWidget|No %{issuableAttribute}'), {
issuableAttribute,
}),
assignAttribute: sprintf(s__('DropdownWidget|Assign %{issuableAttribute}'), {
issuableAttribute,
}),
noAttributesFound: sprintf(s__('DropdownWidget|No %{issuableAttribute} found'), {
issuableAttribute,
}),
updateError: sprintf(
s__(
'DropdownWidget|Failed to set %{issuableAttribute} on this %{issuableType}. Please try again.',
),
{ issuableAttribute, issuableType },
),
listFetchError: sprintf(
s__(
'DropdownWidget|Failed to fetch the %{issuableAttribute} for this %{issuableType}. Please try again.',
),
{ issuableAttribute, issuableType },
),
currentFetchError: sprintf(
s__(
'DropdownWidget|An error occurred while fetching the assigned %{issuableAttribute} of the selected %{issuableType}.',
),
{ issuableAttribute, issuableType },
),
};
}
<script> <script>
// This is a false violation of @gitlab/no-runtime-template-compiler, since it
// extends a valid Vue single file component.
/* eslint-disable @gitlab/no-runtime-template-compiler */
import { __ } from '~/locale'; import { __ } from '~/locale';
import SidebarDropdownWidgetFoss from '~/sidebar/components/sidebar_dropdown_widget.vue'; import SidebarDropdownWidget from '~/sidebar/components/sidebar_dropdown_widget.vue';
import { IssuableType } from '~/issue_show/constants';
import { import {
IssuableAttributeState,
IssuableAttributeType, IssuableAttributeType,
IssuableAttributeState,
issuableAttributesQueries, issuableAttributesQueries,
} from '../constants'; } from '../constants';
const widgetTitleText = {
[IssuableAttributeType.Milestone]: __('Milestone'),
[IssuableAttributeType.Iteration]: __('Iteration'),
[IssuableAttributeType.Epic]: __('Epic'),
none: __('None'),
expired: __('(expired)'),
};
export default { export default {
extends: SidebarDropdownWidgetFoss, components: { SidebarDropdownWidget },
IssuableAttributeState, provide: {
issuableAttributesQueries, issuableAttributesQueries,
i18n: { widgetTitleText,
[IssuableAttributeType.Milestone]: __('Milestone'), issuableAttributesState: IssuableAttributeState,
[IssuableAttributeType.Iteration]: __('Iteration'),
[IssuableAttributeType.Epic]: __('Epic'),
none: __('None'),
expired: __('(expired)'),
}, },
inheritAttrs: false,
props: { props: {
issuableAttribute: { issuableAttribute: {
type: String, type: String,
...@@ -33,6 +36,46 @@ export default { ...@@ -33,6 +36,46 @@ export default {
].includes(value); ].includes(value);
}, },
}, },
workspacePath: {
required: true,
type: String,
},
iid: {
required: true,
type: String,
},
attrWorkspacePath: {
required: true,
type: String,
},
issuableType: {
type: String,
required: true,
validator(value) {
return [IssuableType.Issue, IssuableType.MergeRequest].includes(value);
},
},
icon: {
type: String,
required: false,
default: undefined,
},
}, },
}; };
</script> </script>
<template>
<sidebar-dropdown-widget
:icon="icon"
:issuable-type="issuableType"
:attr-workspace-path="attrWorkspacePath"
:issuable-attribute="issuableAttribute"
:iid="iid"
:workspace-path="workspacePath"
v-bind="$attrs"
v-on="$listeners"
>
<template v-for="(_, name) in $scopedSlots" #[name]="slotData">
<slot :name="name" v-bind="slotData"></slot>
</template>
</sidebar-dropdown-widget>
</template>
import { IssuableType } from '~/issue_show/constants'; import { IssuableType } from '~/issue_show/constants';
import { s__, __ } from '~/locale'; import { __, s__, sprintf } from '~/locale';
import { import {
IssuableAttributeType as IssuableAttributeTypeFoss, IssuableAttributeType as IssuableAttributeTypeFoss,
IssuableAttributeState as IssuableAttributeStateFoss, IssuableAttributeState as IssuableAttributeStateFoss,
issuableAttributesQueries as issuableAttributesQueriesFoss, issuableAttributesQueries as issuableAttributesQueriesFoss,
dropdowni18nText as dropdowni18nTextFoss,
Tracking,
defaultEpicSort,
epicIidPattern,
} from '~/sidebar/constants'; } from '~/sidebar/constants';
import updateStatusMutation from '~/sidebar/queries/updateStatus.mutation.graphql'; import updateStatusMutation from '~/sidebar/queries/updateStatus.mutation.graphql';
import epicAncestorsQuery from './queries/epic_ancestors.query.graphql'; import epicAncestorsQuery from './queries/epic_ancestors.query.graphql';
...@@ -17,6 +21,8 @@ import projectIssueIterationMutation from './queries/project_issue_iteration.mut ...@@ -17,6 +21,8 @@ import projectIssueIterationMutation from './queries/project_issue_iteration.mut
import projectIssueIterationQuery from './queries/project_issue_iteration.query.graphql'; import projectIssueIterationQuery from './queries/project_issue_iteration.query.graphql';
import updateIssueWeightMutation from './queries/update_issue_weight.mutation.graphql'; import updateIssueWeightMutation from './queries/update_issue_weight.mutation.graphql';
export { Tracking, defaultEpicSort, epicIidPattern };
export const healthStatus = { export const healthStatus = {
ON_TRACK: 'onTrack', ON_TRACK: 'onTrack',
NEEDS_ATTENTION: 'needsAttention', NEEDS_ATTENTION: 'needsAttention',
...@@ -150,3 +156,18 @@ export const healthStatusQueries = { ...@@ -150,3 +156,18 @@ export const healthStatusQueries = {
query: issueHealthStatusQuery, query: issueHealthStatusQuery,
}, },
}; };
export function dropdowni18nText(issuableAttribute, issuableType) {
let noAttributesFound = s__('DropdownWidget|No %{issuableAttribute} found');
if (issuableAttribute === IssuableAttributeType.Iteration) {
noAttributesFound = s__('DropdownWidget|No open %{issuableAttribute} found');
}
return {
...dropdowni18nTextFoss(issuableAttribute, issuableType),
noAttributesFound: sprintf(noAttributesFound, {
issuableAttribute,
}),
};
}
...@@ -2,17 +2,15 @@ import { ...@@ -2,17 +2,15 @@ import {
GlDropdown, GlDropdown,
GlDropdownItem, GlDropdownItem,
GlDropdownText, GlDropdownText,
GlLink,
GlSearchBoxByType, GlSearchBoxByType,
GlFormInput, GlFormInput,
GlLoadingIcon,
} from '@gitlab/ui'; } from '@gitlab/ui';
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import { createLocalVue, shallowMount, mount } from '@vue/test-utils'; import { createLocalVue, mount } from '@vue/test-utils';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import SidebarDropdownWidget from 'ee/sidebar/components/sidebar_dropdown_widget.vue'; import SidebarDropdownWidget from 'ee/sidebar/components/sidebar_dropdown_widget.vue';
import { IssuableAttributeType } from 'ee/sidebar/constants'; import { IssuableAttributeType, issuableAttributesQueries } from 'ee/sidebar/constants';
import groupEpicsQuery from 'ee/sidebar/queries/group_epics.query.graphql'; import groupEpicsQuery from 'ee/sidebar/queries/group_epics.query.graphql';
import projectIssueEpicMutation from 'ee/sidebar/queries/project_issue_epic.mutation.graphql'; import projectIssueEpicMutation from 'ee/sidebar/queries/project_issue_epic.mutation.graphql';
import projectIssueEpicQuery from 'ee/sidebar/queries/project_issue_epic.query.graphql'; import projectIssueEpicQuery from 'ee/sidebar/queries/project_issue_epic.query.graphql';
...@@ -41,18 +39,9 @@ describe('SidebarDropdownWidget', () => { ...@@ -41,18 +39,9 @@ describe('SidebarDropdownWidget', () => {
let mockApollo; let mockApollo;
const promiseData = { issuableSetAttribute: { issue: { attribute: { id: '123' } } } }; const promiseData = { issuableSetAttribute: { issue: { attribute: { id: '123' } } } };
const firstErrorMsg = 'first error';
const promiseWithErrors = {
...promiseData,
issuableSetAttribute: { ...promiseData.issuableSetAttribute, errors: [firstErrorMsg] },
};
const mutationSuccess = () => jest.fn().mockResolvedValue({ data: promiseData }); const mutationSuccess = () => jest.fn().mockResolvedValue({ data: promiseData });
const mutationError = () =>
jest.fn().mockRejectedValue('Failed to set epic on this issue. Please try again.');
const mutationSuccessWithErrors = () => jest.fn().mockResolvedValue({ data: promiseWithErrors });
const findGlLink = () => wrapper.findComponent(GlLink);
const findDropdown = () => wrapper.findComponent(GlDropdown); const findDropdown = () => wrapper.findComponent(GlDropdown);
const findDropdownText = () => wrapper.findComponent(GlDropdownText); const findDropdownText = () => wrapper.findComponent(GlDropdownText);
const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType); const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
...@@ -62,11 +51,7 @@ describe('SidebarDropdownWidget', () => { ...@@ -62,11 +51,7 @@ describe('SidebarDropdownWidget', () => {
const findSidebarEditableItem = () => wrapper.findComponent(SidebarEditableItem); const findSidebarEditableItem = () => wrapper.findComponent(SidebarEditableItem);
const findEditButton = () => findSidebarEditableItem().find('[data-testid="edit-button"]'); const findEditButton = () => findSidebarEditableItem().find('[data-testid="edit-button"]');
const findEditableLoadingIcon = () => findSidebarEditableItem().find(GlLoadingIcon);
const findAttributeItems = () => wrapper.findByTestId('epic-items');
const findSelectedAttribute = () => wrapper.findByTestId('select-epic'); const findSelectedAttribute = () => wrapper.findByTestId('select-epic');
const findNoAttributeItem = () => wrapper.findByTestId('no-epic-item');
const findLoadingIconDropdown = () => wrapper.findByTestId('loading-icon-dropdown');
const waitForDropdown = async () => { const waitForDropdown = async () => {
// BDropdown first changes its `visible` property // BDropdown first changes its `visible` property
...@@ -94,7 +79,7 @@ describe('SidebarDropdownWidget', () => { ...@@ -94,7 +79,7 @@ describe('SidebarDropdownWidget', () => {
// Used with createComponent which shallow mounts components // Used with createComponent which shallow mounts components
const toggleDropdown = async () => { const toggleDropdown = async () => {
wrapper.vm.$refs.editable.expand(); wrapper.find(SidebarEditableItem).vm.$emit('open');
await waitForDropdown(); await waitForDropdown();
}; };
...@@ -114,7 +99,7 @@ describe('SidebarDropdownWidget', () => { ...@@ -114,7 +99,7 @@ describe('SidebarDropdownWidget', () => {
wrapper = extendedWrapper( wrapper = extendedWrapper(
mount(SidebarDropdownWidget, { mount(SidebarDropdownWidget, {
localVue, localVue,
provide: { canUpdate: true }, provide: { canUpdate: true, issuableAttributesQueries },
apolloProvider: mockApollo, apolloProvider: mockApollo,
propsData: { propsData: {
workspacePath: mockIssue.projectPath, workspacePath: mockIssue.projectPath,
...@@ -130,9 +115,14 @@ describe('SidebarDropdownWidget', () => { ...@@ -130,9 +115,14 @@ describe('SidebarDropdownWidget', () => {
await waitForApollo(); await waitForApollo();
}; };
const createComponent = ({ data = {}, mutationPromise = mutationSuccess, queries = {} } = {}) => { const createComponent = ({
data = {},
issuableAttribute = IssuableAttributeType.Epic,
mutationPromise = mutationSuccess,
queries = {},
} = {}) => {
wrapper = extendedWrapper( wrapper = extendedWrapper(
shallowMount(SidebarDropdownWidget, { mount(SidebarDropdownWidget, {
provide: { canUpdate: true }, provide: { canUpdate: true },
data() { data() {
return data; return data;
...@@ -142,7 +132,7 @@ describe('SidebarDropdownWidget', () => { ...@@ -142,7 +132,7 @@ describe('SidebarDropdownWidget', () => {
attrWorkspacePath: '', attrWorkspacePath: '',
iid: '', iid: '',
issuableType: IssuableType.Issue, issuableType: IssuableType.Issue,
issuableAttribute: IssuableAttributeType.Epic, issuableAttribute,
}, },
mocks: { mocks: {
$apollo: { $apollo: {
...@@ -164,7 +154,6 @@ describe('SidebarDropdownWidget', () => { ...@@ -164,7 +154,6 @@ describe('SidebarDropdownWidget', () => {
// We need to mock out `showDropdown` which // We need to mock out `showDropdown` which
// invokes `show` method of BDropdown used inside GlDropdown. // invokes `show` method of BDropdown used inside GlDropdown.
jest.spyOn(wrapper.vm, 'showDropdown').mockImplementation();
}; };
afterEach(() => { afterEach(() => {
...@@ -172,204 +161,18 @@ describe('SidebarDropdownWidget', () => { ...@@ -172,204 +161,18 @@ describe('SidebarDropdownWidget', () => {
wrapper = null; wrapper = null;
}); });
describe('when not editing', () => { describe('when a user is searching', () => {
beforeEach(() => { describe('when search result is not found', () => {
createComponent({ it('renders "No open iteration found"', async () => {
data: { createComponent({ issuableAttribute: IssuableAttributeType.Iteration });
currentAttribute: { id: 'id', title: 'title', webUrl: 'webUrl' },
},
stubs: {
GlDropdown,
SidebarEditableItem,
},
});
});
it('shows the current attribute', () => {
expect(findSelectedAttribute().text()).toBe('title');
});
it('links to the current attribute', () => {
expect(findGlLink().attributes().href).toBe('webUrl');
});
it('does not show a loading spinner next to the heading', () => {
expect(findEditableLoadingIcon().exists()).toBe(false);
});
it('shows a loading spinner while fetching the current attribute', () => {
createComponent({
queries: {
currentAttribute: { loading: true },
},
});
expect(findEditableLoadingIcon().exists()).toBe(true);
});
it('shows the loading spinner and the title of the selected attribute while updating', () => {
createComponent({
data: {
updating: true,
selectedTitle: 'Some epic title',
},
queries: {
currentAttribute: { loading: false },
},
});
expect(findEditableLoadingIcon().exists()).toBe(true);
expect(findSelectedAttribute().text()).toBe('Some epic title');
});
describe('when current attribute does not exist', () => {
it('renders "None" as the selected attribute title', () => {
createComponent();
expect(findSelectedAttribute().text()).toBe('None');
});
});
});
describe('when a user can edit', () => {
describe('when user is editing', () => {
describe('when rendering the dropdown', () => {
it('shows a loading spinner while fetching a list of attributes', async () => {
createComponent({
queries: {
attributesList: { loading: true },
},
});
await toggleDropdown();
expect(findLoadingIconDropdown().exists()).toBe(true); await toggleDropdown();
});
describe('GlDropdownItem with the right title and id', () => {
const id = 'id';
const title = 'title';
beforeEach(async () => {
createComponent({
data: { attributesList: [{ id, title }], currentAttribute: { id, title } },
});
await toggleDropdown();
});
it('does not show a loading spinner', () => {
expect(findLoadingIconDropdown().exists()).toBe(false);
});
it('renders title $title', () => {
expect(findDropdownItemWithText(title).exists()).toBe(true);
});
it('checks the correct dropdown item', () => {
expect(
findAllDropdownItems()
.filter((w) => w.props('isChecked') === true)
.at(0)
.text(),
).toBe(title);
});
});
describe('when no data is assigned', () => {
beforeEach(async () => {
createComponent();
await toggleDropdown();
});
it('finds GlDropdownItem with "No epic"', () => {
expect(findNoAttributeItem().text()).toBe('No epic');
});
it('"No epic" is checked', () => {
expect(findNoAttributeItem().props('isChecked')).toBe(true);
});
it('does not render any dropdown item', () => { findSearchBox().vm.$emit('input', 'non existing epics');
expect(findAttributeItems().exists()).toBe(false);
});
});
describe('when clicking on dropdown item', () => {
describe('when currentAttribute is equal to attribute id', () => {
it('does not call setIssueAttribute mutation', async () => {
createComponent({
data: {
attributesList: [{ id: 'id', title: 'title' }],
currentAttribute: { id: 'id', title: 'title' },
},
});
await toggleDropdown();
findDropdownItemWithText('title').vm.$emit('click');
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(0);
});
});
describe('when currentAttribute is not equal to attribute id', () => {
describe('when error', () => {
const bootstrapComponent = (mutationResp) => {
createComponent({
data: {
attributesList: [
{ id: '123', title: '123' },
{ id: 'id', title: 'title' },
],
currentAttribute: '123',
},
mutationPromise: mutationResp,
});
};
describe.each`
description | mutationResp | expectedMsg
${'top-level error'} | ${mutationError} | ${'Failed to set epic on this issue. Please try again.'}
${'user-recoverable error'} | ${mutationSuccessWithErrors} | ${firstErrorMsg}
`(`$description`, ({ mutationResp, expectedMsg }) => {
beforeEach(async () => {
bootstrapComponent(mutationResp);
await toggleDropdown();
findDropdownItemWithText('title').vm.$emit('click');
});
it(`calls createFlash with "${expectedMsg}"`, async () => {
await wrapper.vm.$nextTick();
expect(createFlash).toHaveBeenCalledWith({
message: expectedMsg,
captureError: true,
error: expectedMsg,
});
});
});
});
});
});
});
describe('when a user is searching', () => { await wrapper.vm.$nextTick();
describe('when search result is not found', () => {
it('renders "No epic found"', async () => {
createComponent();
await toggleDropdown(); expect(findDropdownText().text()).toBe('No open iteration found');
findSearchBox().vm.$emit('input', 'non existing epics');
await wrapper.vm.$nextTick();
expect(findDropdownText().text()).toBe('No epic found');
});
});
}); });
}); });
}); });
...@@ -434,7 +237,7 @@ describe('SidebarDropdownWidget', () => { ...@@ -434,7 +237,7 @@ describe('SidebarDropdownWidget', () => {
await clickEdit(); await clickEdit();
expect(createFlash).toHaveBeenCalledWith({ expect(createFlash).toHaveBeenCalledWith({
message: wrapper.vm.i18n.listFetchError, message: 'Failed to fetch the epic for this issue. Please try again.',
captureError: true, captureError: true,
error: expect.any(Error), error: expect.any(Error),
}); });
...@@ -528,7 +331,7 @@ describe('SidebarDropdownWidget', () => { ...@@ -528,7 +331,7 @@ describe('SidebarDropdownWidget', () => {
}); });
expect(createFlash).toHaveBeenCalledWith({ expect(createFlash).toHaveBeenCalledWith({
message: wrapper.vm.i18n.currentFetchError, message: 'An error occurred while fetching the assigned epic of the selected issue.',
captureError: true, captureError: true,
error: expect.any(Error), error: expect.any(Error),
}); });
......
...@@ -12482,6 +12482,9 @@ msgstr "" ...@@ -12482,6 +12482,9 @@ msgstr ""
msgid "DropdownWidget|No %{issuableAttribute} found" msgid "DropdownWidget|No %{issuableAttribute} found"
msgstr "" msgstr ""
msgid "DropdownWidget|No open %{issuableAttribute} found"
msgstr ""
msgid "Due Date" msgid "Due Date"
msgstr "" msgstr ""
......
...@@ -369,16 +369,18 @@ describe('SidebarDropdownWidget', () => { ...@@ -369,16 +369,18 @@ describe('SidebarDropdownWidget', () => {
describe('when a user is searching', () => { describe('when a user is searching', () => {
describe('when search result is not found', () => { describe('when search result is not found', () => {
it('renders "No milestone found"', async () => { describe('when milestone', () => {
createComponent(); it('renders "No milestone found"', async () => {
createComponent();
await toggleDropdown(); await toggleDropdown();
findSearchBox().vm.$emit('input', 'non existing milestones'); findSearchBox().vm.$emit('input', 'non existing milestones');
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(findDropdownText().text()).toBe('No milestone found'); expect(findDropdownText().text()).toBe('No milestone found');
});
}); });
}); });
}); });
......
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