Commit 57af7a1e authored by Florie Guibert's avatar Florie Guibert Committed by Natalia Tepluhina

Epic Boards - Edit start and due dates in sidebar

parent c5a87715
<script>
import { GlButton } from '@gitlab/ui';
export default {
components: {
GlButton,
},
inject: ['canUpdate'],
props: {
formattedDate: {
required: true,
type: String,
},
hasDate: {
required: true,
type: Boolean,
},
resetText: {
required: true,
type: String,
},
isLoading: {
required: true,
type: Boolean,
},
canDelete: {
required: false,
type: Boolean,
default: true,
},
},
};
</script>
<template>
<div class="gl-display-flex gl-align-items-center hide-collapsed">
<span
:class="hasDate ? 'gl-text-gray-900 gl-font-weight-bold' : 'gl-text-gray-500'"
data-testid="sidebar-date-value"
>
{{ formattedDate }}
</span>
<div v-if="hasDate && canUpdate && canDelete" class="gl-display-flex">
<span class="gl-px-2">-</span>
<gl-button
variant="link"
class="gl-text-gray-500!"
data-testid="reset-button"
:disabled="isLoading"
@click="$emit('reset-date', $event)"
>
{{ resetText }}
</gl-button>
</div>
</div>
</template>
<script>
import { GlFormRadio } from '@gitlab/ui';
import { dateInWords, parsePikadayDate } from '~/lib/utils/datetime_utility';
import { __ } from '~/locale';
import { dateFields } from '../../constants';
import SidebarFormattedDate from './sidebar_formatted_date.vue';
export default {
components: {
GlFormRadio,
SidebarFormattedDate,
},
inject: ['canUpdate'],
props: {
issuable: {
required: true,
type: Object,
},
isLoading: {
required: true,
type: Boolean,
},
dateType: {
type: String,
required: true,
},
},
computed: {
dateIsFixed: {
get() {
return this.issuable?.[dateFields[this.dateType].isDateFixed] || false;
},
set(fixed) {
this.$emit('set-date', fixed);
},
},
hasFixedDate() {
return this.issuable[dateFields[this.dateType].dateFixed] !== null;
},
formattedFixedDate() {
const dateFixed = this.issuable[dateFields[this.dateType].dateFixed];
if (!dateFixed) {
return this.$options.i18n.noDate;
}
return dateInWords(parsePikadayDate(dateFixed), true);
},
formattedInheritedDate() {
const dateFromMilestones = this.issuable[dateFields[this.dateType].dateFromMilestones];
if (!dateFromMilestones) {
return this.$options.i18n.noDate;
}
return dateInWords(parsePikadayDate(dateFromMilestones), true);
},
},
i18n: {
fixed: __('Fixed:'),
inherited: __('Inherited:'),
remove: __('remove'),
noDate: __('None'),
},
};
</script>
<template>
<div class="hide-collapsed gl-mt-3">
<div class="gl-display-flex gl-align-items-baseline" data-testid="sidebar-fixed-date">
<gl-form-radio
v-model="dateIsFixed"
:value="true"
:disabled="!canUpdate || isLoading"
class="gl-pr-2"
>
<span :class="dateIsFixed ? 'gl-text-gray-900 gl-font-weight-bold' : 'gl-text-gray-500'">
{{ $options.i18n.fixed }}
</span>
</gl-form-radio>
<sidebar-formatted-date
:has-date="dateIsFixed"
:formatted-date="formattedFixedDate"
:reset-text="$options.i18n.remove"
:is-loading="isLoading"
:can-delete="dateIsFixed && hasFixedDate"
class="gl-line-height-normal"
@reset-date="$emit('reset-date', $event)"
/>
</div>
<div class="gl-display-flex gl-align-items-baseline" data-testid="sidebar-inherited-date">
<gl-form-radio
v-model="dateIsFixed"
:value="false"
:disabled="!canUpdate || isLoading"
class="gl-pr-2"
>
<span :class="!dateIsFixed ? 'gl-text-gray-900 gl-font-weight-bold' : 'gl-text-gray-500'">
{{ $options.i18n.inherited }}
</span>
</gl-form-radio>
<sidebar-formatted-date
:has-date="!dateIsFixed"
:formatted-date="formattedInheritedDate"
:reset-text="$options.i18n.remove"
:is-loading="isLoading"
:can-delete="false"
class="gl-line-height-normal"
/>
</div>
</div>
</template>
...@@ -103,6 +103,7 @@ export default { ...@@ -103,6 +103,7 @@ export default {
<div> <div>
<div class="gl-display-flex gl-align-items-center" @click.self="collapse"> <div class="gl-display-flex gl-align-items-center" @click.self="collapse">
<span class="hide-collapsed" data-testid="title" @click="collapse">{{ title }}</span> <span class="hide-collapsed" data-testid="title" @click="collapse">{{ title }}</span>
<slot name="title-extra"></slot>
<gl-loading-icon v-if="loading || initialLoading" inline class="gl-ml-2 hide-collapsed" /> <gl-loading-icon v-if="loading || initialLoading" inline class="gl-ml-2 hide-collapsed" />
<gl-loading-icon <gl-loading-icon
v-if="loading && isClassicSidebar" v-if="loading && isClassicSidebar"
......
import { IssuableType } from '~/issue_show/constants'; import { IssuableType } from '~/issue_show/constants';
import epicConfidentialQuery from '~/sidebar/queries/epic_confidential.query.graphql'; import epicConfidentialQuery from '~/sidebar/queries/epic_confidential.query.graphql';
import epicDueDateQuery from '~/sidebar/queries/epic_due_date.query.graphql';
import epicStartDateQuery from '~/sidebar/queries/epic_start_date.query.graphql';
import issuableAssigneesSubscription from '~/sidebar/queries/issuable_assignees.subscription.graphql'; import issuableAssigneesSubscription from '~/sidebar/queries/issuable_assignees.subscription.graphql';
import issueConfidentialQuery from '~/sidebar/queries/issue_confidential.query.graphql'; import issueConfidentialQuery from '~/sidebar/queries/issue_confidential.query.graphql';
import issueDueDateQuery from '~/sidebar/queries/issue_due_date.query.graphql'; import issueDueDateQuery from '~/sidebar/queries/issue_due_date.query.graphql';
import issueReferenceQuery from '~/sidebar/queries/issue_reference.query.graphql'; import issueReferenceQuery from '~/sidebar/queries/issue_reference.query.graphql';
import mergeRequestReferenceQuery from '~/sidebar/queries/merge_request_reference.query.graphql'; import mergeRequestReferenceQuery from '~/sidebar/queries/merge_request_reference.query.graphql';
import updateEpicMutation from '~/sidebar/queries/update_epic_confidential.mutation.graphql'; import updateEpicConfidentialMutation from '~/sidebar/queries/update_epic_confidential.mutation.graphql';
import updateEpicDueDateMutation from '~/sidebar/queries/update_epic_due_date.mutation.graphql';
import updateEpicStartDateMutation from '~/sidebar/queries/update_epic_start_date.mutation.graphql';
import updateIssueConfidentialMutation from '~/sidebar/queries/update_issue_confidential.mutation.graphql'; import updateIssueConfidentialMutation from '~/sidebar/queries/update_issue_confidential.mutation.graphql';
import updateIssueDueDateMutation from '~/sidebar/queries/update_issue_due_date.mutation.graphql'; import updateIssueDueDateMutation from '~/sidebar/queries/update_issue_due_date.mutation.graphql';
import getIssueParticipants from '~/vue_shared/components/sidebar/queries/get_issue_participants.query.graphql'; import getIssueParticipants from '~/vue_shared/components/sidebar/queries/get_issue_participants.query.graphql';
...@@ -34,7 +38,7 @@ export const confidentialityQueries = { ...@@ -34,7 +38,7 @@ export const confidentialityQueries = {
}, },
[IssuableType.Epic]: { [IssuableType.Epic]: {
query: epicConfidentialQuery, query: epicConfidentialQuery,
mutation: updateEpicMutation, mutation: updateEpicConfidentialMutation,
}, },
}; };
...@@ -47,9 +51,38 @@ export const referenceQueries = { ...@@ -47,9 +51,38 @@ export const referenceQueries = {
}, },
}; };
export const dateTypes = {
start: 'startDate',
due: 'dueDate',
};
export const dateFields = {
[dateTypes.start]: {
isDateFixed: 'startDateIsFixed',
dateFixed: 'startDateFixed',
dateFromMilestones: 'startDateFromMilestones',
},
[dateTypes.due]: {
isDateFixed: 'dueDateIsFixed',
dateFixed: 'dueDateFixed',
dateFromMilestones: 'dueDateFromMilestones',
},
};
export const dueDateQueries = { export const dueDateQueries = {
[IssuableType.Issue]: { [IssuableType.Issue]: {
query: issueDueDateQuery, query: issueDueDateQuery,
mutation: updateIssueDueDateMutation, mutation: updateIssueDueDateMutation,
}, },
[IssuableType.Epic]: {
query: epicDueDateQuery,
mutation: updateEpicDueDateMutation,
},
};
export const startDateQueries = {
[IssuableType.Epic]: {
query: epicStartDateQuery,
mutation: updateEpicStartDateMutation,
},
}; };
...@@ -13,7 +13,7 @@ import { __ } from '~/locale'; ...@@ -13,7 +13,7 @@ import { __ } from '~/locale';
import CollapsedAssigneeList from '~/sidebar/components/assignees/collapsed_assignee_list.vue'; import CollapsedAssigneeList from '~/sidebar/components/assignees/collapsed_assignee_list.vue';
import SidebarAssigneesWidget from '~/sidebar/components/assignees/sidebar_assignees_widget.vue'; import SidebarAssigneesWidget from '~/sidebar/components/assignees/sidebar_assignees_widget.vue';
import SidebarConfidentialityWidget from '~/sidebar/components/confidential/sidebar_confidentiality_widget.vue'; import SidebarConfidentialityWidget from '~/sidebar/components/confidential/sidebar_confidentiality_widget.vue';
import SidebarDueDateWidget from '~/sidebar/components/due_date/sidebar_due_date_widget.vue'; import SidebarDueDateWidget from '~/sidebar/components/date/sidebar_date_widget.vue';
import SidebarReferenceWidget from '~/sidebar/components/reference/sidebar_reference_widget.vue'; import SidebarReferenceWidget from '~/sidebar/components/reference/sidebar_reference_widget.vue';
import { apolloProvider } from '~/sidebar/graphql'; import { apolloProvider } from '~/sidebar/graphql';
import Translate from '../vue_shared/translate'; import Translate from '../vue_shared/translate';
...@@ -225,14 +225,14 @@ function mountDueDateComponent() { ...@@ -225,14 +225,14 @@ function mountDueDateComponent() {
SidebarDueDateWidget, SidebarDueDateWidget,
}, },
provide: { provide: {
iid: String(iid),
fullPath,
canUpdate: editable, canUpdate: editable,
}, },
render: (createElement) => render: (createElement) =>
createElement('sidebar-due-date-widget', { createElement('sidebar-due-date-widget', {
props: { props: {
iid: String(iid),
fullPath,
issuableType: IssuableType.Issue, issuableType: IssuableType.Issue,
}, },
}), }),
......
query epicDueDate($fullPath: ID!, $iid: ID) {
workspace: group(fullPath: $fullPath) {
__typename
issuable: epic(iid: $iid) {
__typename
id
dueDate
dueDateIsFixed
dueDateFixed
dueDateFromMilestones
}
}
}
query epicStartDate($fullPath: ID!, $iid: ID) {
workspace: group(fullPath: $fullPath) {
__typename
issuable: epic(iid: $iid) {
__typename
id
startDate
startDateIsFixed
startDateFixed
startDateFromMilestones
}
}
}
mutation updateEpicDueDate($input: UpdateEpicInput!) {
issuableSetDate: updateEpic(input: $input) {
issuable: epic {
id
dueDateIsFixed
dueDateFixed
dueDateFromMilestones
}
errors
}
}
mutation updateEpicStartDate($input: UpdateEpicInput!) {
issuableSetDate: updateEpic(input: $input) {
issuable: epic {
id
startDateIsFixed
startDateFixed
startDateFromMilestones
}
errors
}
}
mutation updateIssueDueDate($input: UpdateIssueInput!) { mutation updateIssueDueDate($input: UpdateIssueInput!) {
issuableSetDueDate: updateIssue(input: $input) { issuableSetDate: updateIssue(input: $input) {
issuable: issue { issuable: issue {
id id
dueDate dueDate
......
...@@ -7,6 +7,7 @@ import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.v ...@@ -7,6 +7,7 @@ import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.v
import { ISSUABLE } from '~/boards/constants'; import { ISSUABLE } from '~/boards/constants';
import { contentTop } from '~/lib/utils/common_utils'; import { contentTop } from '~/lib/utils/common_utils';
import SidebarConfidentialityWidget from '~/sidebar/components/confidential/sidebar_confidentiality_widget.vue'; import SidebarConfidentialityWidget from '~/sidebar/components/confidential/sidebar_confidentiality_widget.vue';
import SidebarDateWidget from '~/sidebar/components/date/sidebar_date_widget.vue';
export default { export default {
headerHeight: `${contentTop()}px`, headerHeight: `${contentTop()}px`,
...@@ -16,6 +17,7 @@ export default { ...@@ -16,6 +17,7 @@ export default {
BoardSidebarSubscription, BoardSidebarSubscription,
BoardSidebarTitle, BoardSidebarTitle,
SidebarConfidentialityWidget, SidebarConfidentialityWidget,
SidebarDateWidget,
}, },
computed: { computed: {
...mapGetters(['isSidebarOpen', 'activeBoardItem']), ...mapGetters(['isSidebarOpen', 'activeBoardItem']),
...@@ -46,6 +48,20 @@ export default { ...@@ -46,6 +48,20 @@ export default {
<template #header>{{ __('Epic details') }}</template> <template #header>{{ __('Epic details') }}</template>
<template #default> <template #default>
<board-sidebar-title data-testid="sidebar-title" /> <board-sidebar-title data-testid="sidebar-title" />
<sidebar-date-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
date-type="startDate"
issuable-type="epic"
:can-inherit="true"
/>
<sidebar-date-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
date-type="dueDate"
issuable-type="epic"
:can-inherit="true"
/>
<board-sidebar-labels-select class="labels" /> <board-sidebar-labels-select class="labels" />
<sidebar-confidentiality-widget <sidebar-confidentiality-widget
:iid="activeBoardItem.iid" :iid="activeBoardItem.iid"
......
...@@ -81,6 +81,50 @@ RSpec.describe 'Epic boards sidebar', :js do ...@@ -81,6 +81,50 @@ RSpec.describe 'Epic boards sidebar', :js do
end end
end end
context 'start date' do
it 'edits fixed start date' do
click_card(card)
wait_for_requests
page.within('[data-testid="start-date"]') do
edit_fixed_date
end
end
it 'removes fixed start date' do
click_card(card)
wait_for_requests
page.within('[data-testid="start-date"]') do
remove_fixed_date
end
end
end
context 'due date' do
it 'edits fixed due date' do
click_card(card)
wait_for_requests
page.within('[data-testid="due-date"]') do
edit_fixed_date
end
end
it 'removes fixed due date' do
click_card(card)
wait_for_requests
page.within('[data-testid="due-date"]') do
remove_fixed_date
end
end
end
context 'labels' do context 'labels' do
it 'adds a single label' do it 'adds a single label' do
click_card(card) click_card(card)
...@@ -173,4 +217,48 @@ RSpec.describe 'Epic boards sidebar', :js do ...@@ -173,4 +217,48 @@ RSpec.describe 'Epic boards sidebar', :js do
click_card(card) click_card(card)
end end
def pick_a_date
click_button 'Edit'
expect(page).to have_selector('.gl-datepicker')
page.within('.pika-lendar') do
click_button '25'
end
wait_for_requests
end
def edit_fixed_date
page.within('[data-testid="sidebar-inherited-date"]') do
expect(find_field('Inherited:')).to be_checked
end
pick_a_date
page.within('[data-testid="sidebar-fixed-date"]') do
expect(find('[data-testid="sidebar-date-value"]').text).to include('25')
expect(find_field('Fixed:')).to be_checked
end
end
def remove_fixed_date
expect(page).not_to have_button('remove')
page.within('[data-testid="sidebar-fixed-date"]') do
expect(find('[data-testid="sidebar-date-value"]').text).to include('None')
end
pick_a_date
page.within('[data-testid="sidebar-fixed-date"]') do
expect(find('[data-testid="sidebar-date-value"]').text).not_to include('None')
expect(page).to have_button('remove')
find_button('remove').click
wait_for_requests
expect(page).not_to have_button('remove')
expect(find('[data-testid="sidebar-date-value"]').text).to include('None')
end
end
end end
...@@ -29924,10 +29924,10 @@ msgstr "" ...@@ -29924,10 +29924,10 @@ msgstr ""
msgid "Something went wrong while resolving this discussion. Please try again." msgid "Something went wrong while resolving this discussion. Please try again."
msgstr "" msgstr ""
msgid "Something went wrong while setting %{issuableType} confidentiality." msgid "Something went wrong while setting %{issuableType} %{dateType} date."
msgstr "" msgstr ""
msgid "Something went wrong while setting %{issuableType} due date." msgid "Something went wrong while setting %{issuableType} confidentiality."
msgstr "" msgstr ""
msgid "Something went wrong while stopping this environment. Please try again." msgid "Something went wrong while stopping this environment. Please try again."
...@@ -32351,6 +32351,9 @@ msgstr "" ...@@ -32351,6 +32351,9 @@ msgstr ""
msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again." msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
msgstr "" msgstr ""
msgid "These dates affect how your epics appear in the roadmap. Set a fixed date or one inherited from the milestones assigned to issues in this epic."
msgstr ""
msgid "These examples show how to trigger this project's pipeline for a branch or tag." msgid "These examples show how to trigger this project's pipeline for a branch or tag."
msgstr "" msgstr ""
...@@ -38470,6 +38473,9 @@ msgstr "" ...@@ -38470,6 +38473,9 @@ msgstr ""
msgid "remove due date" msgid "remove due date"
msgstr "" msgstr ""
msgid "remove start date"
msgstr ""
msgid "remove weight" msgid "remove weight"
msgstr "" msgstr ""
......
...@@ -417,7 +417,7 @@ RSpec.describe "Issues > User edits issue", :js do ...@@ -417,7 +417,7 @@ RSpec.describe "Issues > User edits issue", :js do
wait_for_requests wait_for_requests
expect(find('[data-testid="sidebar-duedate-value"]').text).to have_content date.strftime('%b %-d, %Y') expect(find('[data-testid="sidebar-date-value"]').text).to have_content date.strftime('%b %-d, %Y')
end end
end end
......
...@@ -4,37 +4,48 @@ import VueApollo from 'vue-apollo'; ...@@ -4,37 +4,48 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash'; import createFlash from '~/flash';
import SidebarDueDateWidget from '~/sidebar/components/due_date/sidebar_due_date_widget.vue'; import SidebarDateWidget from '~/sidebar/components/date/sidebar_date_widget.vue';
import SidebarFormattedDate from '~/sidebar/components/date/sidebar_formatted_date.vue';
import SidebarInheritDate from '~/sidebar/components/date/sidebar_inherit_date.vue';
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue'; import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
import epicStartDateQuery from '~/sidebar/queries/epic_start_date.query.graphql';
import issueDueDateQuery from '~/sidebar/queries/issue_due_date.query.graphql'; import issueDueDateQuery from '~/sidebar/queries/issue_due_date.query.graphql';
import { issueDueDateResponse } from '../../mock_data'; import { issuableDueDateResponse, issuableStartDateResponse } from '../../mock_data';
jest.mock('~/flash'); jest.mock('~/flash');
Vue.use(VueApollo); Vue.use(VueApollo);
describe('Sidebar Due date Widget', () => { describe('Sidebar date Widget', () => {
let wrapper; let wrapper;
let fakeApollo; let fakeApollo;
const date = '2021-04-15'; const date = '2021-04-15';
const findEditableItem = () => wrapper.findComponent(SidebarEditableItem); const findEditableItem = () => wrapper.findComponent(SidebarEditableItem);
const findFormattedDueDate = () => wrapper.find("[data-testid='sidebar-duedate-value']");
const createComponent = ({ const createComponent = ({
dueDateQueryHandler = jest.fn().mockResolvedValue(issueDueDateResponse()), dueDateQueryHandler = jest.fn().mockResolvedValue(issuableDueDateResponse()),
startDateQueryHandler = jest.fn().mockResolvedValue(issuableStartDateResponse()),
canInherit = false,
dateType = undefined,
issuableType = 'issue',
} = {}) => { } = {}) => {
fakeApollo = createMockApollo([[issueDueDateQuery, dueDateQueryHandler]]); fakeApollo = createMockApollo([
[issueDueDateQuery, dueDateQueryHandler],
[epicStartDateQuery, startDateQueryHandler],
]);
wrapper = shallowMount(SidebarDueDateWidget, { wrapper = shallowMount(SidebarDateWidget, {
apolloProvider: fakeApollo, apolloProvider: fakeApollo,
provide: { provide: {
fullPath: 'group/project',
iid: '1',
canUpdate: true, canUpdate: true,
}, },
propsData: { propsData: {
issuableType: 'issue', fullPath: 'group/project',
iid: '1',
issuableType,
canInherit,
dateType,
}, },
stubs: { stubs: {
SidebarEditableItem, SidebarEditableItem,
...@@ -53,10 +64,16 @@ describe('Sidebar Due date Widget', () => { ...@@ -53,10 +64,16 @@ describe('Sidebar Due date Widget', () => {
expect(findEditableItem().props('loading')).toBe(true); expect(findEditableItem().props('loading')).toBe(true);
}); });
describe('when issue has no due date', () => { it('dateType is due date by default', () => {
createComponent();
expect(wrapper.text()).toContain('Due date');
});
describe('when issuable has no due date', () => {
beforeEach(async () => { beforeEach(async () => {
createComponent({ createComponent({
dueDateQueryHandler: jest.fn().mockResolvedValue(issueDueDateResponse(null)), dueDateQueryHandler: jest.fn().mockResolvedValue(issuableDueDateResponse(null)),
}); });
await waitForPromises(); await waitForPromises();
}); });
...@@ -65,10 +82,6 @@ describe('Sidebar Due date Widget', () => { ...@@ -65,10 +82,6 @@ describe('Sidebar Due date Widget', () => {
expect(findEditableItem().props('loading')).toBe(false); expect(findEditableItem().props('loading')).toBe(false);
}); });
it('dueDate is null by default', () => {
expect(findFormattedDueDate().text()).toBe('None');
});
it('emits `dueDateUpdated` event with a `null` payload', () => { it('emits `dueDateUpdated` event with a `null` payload', () => {
expect(wrapper.emitted('dueDateUpdated')).toEqual([[null]]); expect(wrapper.emitted('dueDateUpdated')).toEqual([[null]]);
}); });
...@@ -77,7 +90,7 @@ describe('Sidebar Due date Widget', () => { ...@@ -77,7 +90,7 @@ describe('Sidebar Due date Widget', () => {
describe('when issue has due date', () => { describe('when issue has due date', () => {
beforeEach(async () => { beforeEach(async () => {
createComponent({ createComponent({
dueDateQueryHandler: jest.fn().mockResolvedValue(issueDueDateResponse(date)), dueDateQueryHandler: jest.fn().mockResolvedValue(issuableDueDateResponse(date)),
}); });
await waitForPromises(); await waitForPromises();
}); });
...@@ -86,15 +99,26 @@ describe('Sidebar Due date Widget', () => { ...@@ -86,15 +99,26 @@ describe('Sidebar Due date Widget', () => {
expect(findEditableItem().props('loading')).toBe(false); expect(findEditableItem().props('loading')).toBe(false);
}); });
it('has dueDate', () => {
expect(findFormattedDueDate().text()).toBe('Apr 15, 2021');
});
it('emits `dueDateUpdated` event with the date payload', () => { it('emits `dueDateUpdated` event with the date payload', () => {
expect(wrapper.emitted('dueDateUpdated')).toEqual([[date]]); expect(wrapper.emitted('dueDateUpdated')).toEqual([[date]]);
}); });
}); });
it.each`
canInherit | component | componentName | expected
${true} | ${SidebarFormattedDate} | ${'SidebarFormattedDate'} | ${false}
${true} | ${SidebarInheritDate} | ${'SidebarInheritDate'} | ${true}
${false} | ${SidebarFormattedDate} | ${'SidebarFormattedDate'} | ${true}
${false} | ${SidebarInheritDate} | ${'SidebarInheritDate'} | ${false}
`(
'when canInherit is $canInherit, $componentName display is $expected',
({ canInherit, component, expected }) => {
createComponent({ canInherit });
expect(wrapper.find(component).exists()).toBe(expected);
},
);
it('displays a flash message when query is rejected', async () => { it('displays a flash message when query is rejected', async () => {
createComponent({ createComponent({
dueDateQueryHandler: jest.fn().mockRejectedValue('Houston, we have a problem'), dueDateQueryHandler: jest.fn().mockRejectedValue('Houston, we have a problem'),
...@@ -103,4 +127,23 @@ describe('Sidebar Due date Widget', () => { ...@@ -103,4 +127,23 @@ describe('Sidebar Due date Widget', () => {
expect(createFlash).toHaveBeenCalled(); expect(createFlash).toHaveBeenCalled();
}); });
it.each`
dateType | text | event | mockedResponse | issuableType | queryHandler
${'dueDate'} | ${'Due date'} | ${'dueDateUpdated'} | ${issuableDueDateResponse} | ${'issue'} | ${'dueDateQueryHandler'}
${'startDate'} | ${'Start date'} | ${'startDateUpdated'} | ${issuableStartDateResponse} | ${'epic'} | ${'startDateQueryHandler'}
`(
'when dateType is $dateType, component renders $text and emits $event',
async ({ dateType, text, event, mockedResponse, issuableType, queryHandler }) => {
createComponent({
dateType,
issuableType,
[queryHandler]: jest.fn().mockResolvedValue(mockedResponse(date)),
});
await waitForPromises();
expect(wrapper.text()).toContain(text);
expect(wrapper.emitted(event)).toEqual([[date]]);
},
);
}); });
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import SidebarFormattedDate from '~/sidebar/components/date/sidebar_formatted_date.vue';
describe('SidebarFormattedDate', () => {
let wrapper;
const findFormattedDate = () => wrapper.find("[data-testid='sidebar-date-value']");
const findRemoveButton = () => wrapper.find(GlButton);
const createComponent = ({ hasDate = true } = {}) => {
wrapper = shallowMount(SidebarFormattedDate, {
provide: {
canUpdate: true,
},
propsData: {
formattedDate: 'Apr 15, 2021',
hasDate,
issuableType: 'issue',
resetText: 'remove',
isLoading: false,
canDelete: true,
},
});
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('displays formatted date', () => {
expect(findFormattedDate().text()).toBe('Apr 15, 2021');
});
describe('when issue has due date', () => {
it('displays remove button', () => {
expect(findRemoveButton().exists()).toBe(true);
expect(findRemoveButton().children).toEqual(wrapper.props.resetText);
});
it('emits reset-date event on click on remove button', () => {
findRemoveButton().vm.$emit('click');
expect(wrapper.emitted('reset-date')).toEqual([[undefined]]);
});
});
describe('when issuable has no due date', () => {
beforeEach(() => {
createComponent({
hasDate: false,
});
});
it('does not display remove button', () => {
expect(findRemoveButton().exists()).toBe(false);
});
});
});
import { GlFormRadio } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import SidebarFormattedDate from '~/sidebar/components/date/sidebar_formatted_date.vue';
import SidebarInheritDate from '~/sidebar/components/date/sidebar_inherit_date.vue';
describe('SidebarInheritDate', () => {
let wrapper;
const findFixedFormattedDate = () => wrapper.findAll(SidebarFormattedDate).at(0);
const findInheritFormattedDate = () => wrapper.findAll(SidebarFormattedDate).at(1);
const findFixedRadio = () => wrapper.findAll(GlFormRadio).at(0);
const findInheritRadio = () => wrapper.findAll(GlFormRadio).at(1);
const createComponent = () => {
wrapper = shallowMount(SidebarInheritDate, {
provide: {
canUpdate: true,
},
propsData: {
issuable: {
dueDate: '2021-04-15',
dueDateIsFixed: true,
dueDateFixed: '2021-04-15',
dueDateFromMilestones: '2021-05-15',
},
isLoading: false,
dateType: 'dueDate',
},
});
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('displays formatted fixed and inherited dates with radio buttons', () => {
expect(wrapper.findAll(SidebarFormattedDate)).toHaveLength(2);
expect(wrapper.findAll(GlFormRadio)).toHaveLength(2);
expect(findFixedFormattedDate().props('formattedDate')).toBe('Apr 15, 2021');
expect(findInheritFormattedDate().props('formattedDate')).toBe('May 15, 2021');
expect(findFixedRadio().text()).toBe('Fixed:');
expect(findInheritRadio().text()).toBe('Inherited:');
});
it('emits set-date event on click on radio button', () => {
findFixedRadio().vm.$emit('input', true);
expect(wrapper.emitted('set-date')).toEqual([[true]]);
});
});
...@@ -233,7 +233,7 @@ export const issueConfidentialityResponse = (confidential = false) => ({ ...@@ -233,7 +233,7 @@ export const issueConfidentialityResponse = (confidential = false) => ({
}, },
}); });
export const issueDueDateResponse = (dueDate = null) => ({ export const issuableDueDateResponse = (dueDate = null) => ({
data: { data: {
workspace: { workspace: {
__typename: 'Project', __typename: 'Project',
...@@ -246,6 +246,22 @@ export const issueDueDateResponse = (dueDate = null) => ({ ...@@ -246,6 +246,22 @@ export const issueDueDateResponse = (dueDate = null) => ({
}, },
}); });
export const issuableStartDateResponse = (startDate = null) => ({
data: {
workspace: {
__typename: 'Group',
issuable: {
__typename: 'Epic',
id: 'gid://gitlab/Epic/4',
startDate,
startDateIsFixed: true,
startDateFixed: startDate,
startDateFromMilestones: null,
},
},
},
});
export const issueReferenceResponse = (reference) => ({ export const issueReferenceResponse = (reference) => ({
data: { data: {
workspace: { workspace: {
......
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