Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-06-26 12:10:05 +00:00
parent abc0c2c770
commit a46fed716c
9 changed files with 352 additions and 218 deletions

View File

@ -7,7 +7,9 @@ const twoFactorNode = document.querySelector('.js-two-factor-auth');
const skippable = twoFactorNode ? parseBoolean(twoFactorNode.dataset.twoFactorSkippable) : false;
if (skippable) {
const button = `<br/><a class="btn gl-button btn-sm btn-confirm gl-mt-3" data-qa-selector="configure_it_later_button" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a>`;
const button = `<div class="gl-alert-actions">
<a class="btn gl-button btn-md btn-confirm gl-alert-action" data-qa-selector="configure_it_later_button" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a>
</div>`;
const flashAlert = document.querySelector('.flash-alert');
if (flashAlert) {
// eslint-disable-next-line no-unsanitized/method
@ -17,7 +19,5 @@ if (skippable) {
mount2faRegistration();
initWebAuthnRegistration();
initRecoveryCodes();
initManageTwoFactorForm();

View File

@ -0,0 +1,180 @@
<script>
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import {
sprintfWorkItem,
WIDGET_TYPE_ASSIGNEES,
WIDGET_TYPE_HEALTH_STATUS,
WIDGET_TYPE_ITERATION,
WIDGET_TYPE_LABELS,
WIDGET_TYPE_MILESTONE,
WIDGET_TYPE_PROGRESS,
WIDGET_TYPE_START_AND_DUE_DATE,
WIDGET_TYPE_WEIGHT,
} from '../constants';
import WorkItemState from './work_item_state.vue';
import WorkItemDueDate from './work_item_due_date.vue';
import WorkItemAssignees from './work_item_assignees.vue';
import WorkItemLabels from './work_item_labels.vue';
import WorkItemMilestone from './work_item_milestone.vue';
export default {
components: {
WorkItemLabels,
WorkItemMilestone,
WorkItemAssignees,
WorkItemDueDate,
WorkItemState,
WorkItemWeight: () => import('ee_component/work_items/components/work_item_weight.vue'),
WorkItemProgress: () => import('ee_component/work_items/components/work_item_progress.vue'),
WorkItemIteration: () => import('ee_component/work_items/components/work_item_iteration.vue'),
WorkItemHealthStatus: () =>
import('ee_component/work_items/components/work_item_health_status.vue'),
},
mixins: [glFeatureFlagMixin()],
inject: ['fullPath'],
props: {
workItem: {
type: Object,
required: true,
},
workItemParentId: {
type: String,
required: false,
default: null,
},
},
computed: {
workItemType() {
return this.workItem.workItemType?.name;
},
canUpdate() {
return this.workItem?.userPermissions?.updateWorkItem;
},
canDelete() {
return this.workItem?.userPermissions?.deleteWorkItem;
},
canSetWorkItemMetadata() {
return this.workItem?.userPermissions?.setWorkItemMetadata;
},
canAssignUnassignUser() {
return this.workItemAssignees && this.canSetWorkItemMetadata;
},
confidentialTooltip() {
return sprintfWorkItem(this.$options.i18n.confidentialTooltip, this.workItemType);
},
workItemAssignees() {
return this.isWidgetPresent(WIDGET_TYPE_ASSIGNEES);
},
workItemLabels() {
return this.isWidgetPresent(WIDGET_TYPE_LABELS);
},
workItemDueDate() {
return this.isWidgetPresent(WIDGET_TYPE_START_AND_DUE_DATE);
},
workItemWeight() {
return this.isWidgetPresent(WIDGET_TYPE_WEIGHT);
},
workItemProgress() {
return this.isWidgetPresent(WIDGET_TYPE_PROGRESS);
},
workItemIteration() {
return this.isWidgetPresent(WIDGET_TYPE_ITERATION);
},
workItemHealthStatus() {
return this.isWidgetPresent(WIDGET_TYPE_HEALTH_STATUS);
},
workItemMilestone() {
return this.isWidgetPresent(WIDGET_TYPE_MILESTONE);
},
},
methods: {
isWidgetPresent(type) {
return this.workItem?.widgets?.find((widget) => widget.type === type);
},
},
};
</script>
<template>
<section>
<work-item-state
:work-item="workItem"
:work-item-parent-id="workItemParentId"
:can-update="canUpdate"
@error="$emit('error', $event)"
/>
<work-item-assignees
v-if="workItemAssignees"
:can-update="canUpdate"
:work-item-id="workItem.id"
:assignees="workItemAssignees.assignees.nodes"
:allows-multiple-assignees="workItemAssignees.allowsMultipleAssignees"
:work-item-type="workItemType"
:can-invite-members="workItemAssignees.canInviteMembers"
@error="$emit('error', $event)"
/>
<work-item-labels
v-if="workItemLabels"
:can-update="canUpdate"
:work-item-id="workItem.id"
:work-item-iid="workItem.iid"
@error="$emit('error', $event)"
/>
<work-item-due-date
v-if="workItemDueDate"
:can-update="canUpdate"
:due-date="workItemDueDate.dueDate"
:start-date="workItemDueDate.startDate"
:work-item-id="workItem.id"
:work-item-type="workItemType"
@error="$emit('error', $event)"
/>
<work-item-milestone
v-if="workItemMilestone"
:work-item-id="workItem.id"
:work-item-milestone="workItemMilestone.milestone"
:work-item-type="workItemType"
:can-update="canUpdate"
@error="$emit('error', $event)"
/>
<work-item-weight
v-if="workItemWeight"
class="gl-mb-5"
:can-update="canUpdate"
:weight="workItemWeight.weight"
:work-item-id="workItem.id"
:work-item-iid="workItem.iid"
:work-item-type="workItemType"
@error="$emit('error', $event)"
/>
<work-item-progress
v-if="workItemProgress"
class="gl-mb-5"
:can-update="canUpdate"
:progress="workItemProgress.progress"
:work-item-id="workItem.id"
:work-item-type="workItemType"
@error="$emit('error', $event)"
/>
<work-item-iteration
v-if="workItemIteration"
class="gl-mb-5"
:iteration="workItemIteration.iteration"
:can-update="canUpdate"
:work-item-id="workItem.id"
:work-item-iid="workItem.iid"
:work-item-type="workItemType"
@error="$emit('error', $event)"
/>
<work-item-health-status
v-if="workItemHealthStatus"
class="gl-mb-5"
:health-status="workItemHealthStatus.healthStatus"
:can-update="canUpdate"
:work-item-id="workItem.id"
:work-item-iid="workItem.iid"
:work-item-type="workItemType"
@error="$emit('error', $event)"
/>
</section>
</template>

View File

@ -22,18 +22,11 @@ import {
sprintfWorkItem,
i18n,
WIDGET_TYPE_ASSIGNEES,
WIDGET_TYPE_LABELS,
WIDGET_TYPE_NOTIFICATIONS,
WIDGET_TYPE_CURRENT_USER_TODOS,
WIDGET_TYPE_DESCRIPTION,
WIDGET_TYPE_AWARD_EMOJI,
WIDGET_TYPE_START_AND_DUE_DATE,
WIDGET_TYPE_WEIGHT,
WIDGET_TYPE_PROGRESS,
WIDGET_TYPE_HIERARCHY,
WIDGET_TYPE_MILESTONE,
WIDGET_TYPE_ITERATION,
WIDGET_TYPE_HEALTH_STATUS,
WORK_ITEM_TYPE_VALUE_ISSUE,
WORK_ITEM_TYPE_VALUE_OBJECTIVE,
WIDGET_TYPE_NOTES,
@ -48,17 +41,13 @@ import { findHierarchyWidgetChildren } from '../utils';
import WorkItemTree from './work_item_links/work_item_tree.vue';
import WorkItemActions from './work_item_actions.vue';
import WorkItemTodos from './work_item_todos.vue';
import WorkItemState from './work_item_state.vue';
import WorkItemTitle from './work_item_title.vue';
import WorkItemAttributesWrapper from './work_item_attributes_wrapper.vue';
import WorkItemCreatedUpdated from './work_item_created_updated.vue';
import WorkItemDescription from './work_item_description.vue';
import WorkItemAwardEmoji from './work_item_award_emoji.vue';
import WorkItemDueDate from './work_item_due_date.vue';
import WorkItemAssignees from './work_item_assignees.vue';
import WorkItemLabels from './work_item_labels.vue';
import WorkItemMilestone from './work_item_milestone.vue';
import WorkItemNotes from './work_item_notes.vue';
import WorkItemDetailModal from './work_item_detail_modal.vue';
import WorkItemAwardEmoji from './work_item_award_emoji.vue';
export default {
i18n,
@ -74,23 +63,14 @@ export default {
GlSkeletonLoader,
GlIcon,
GlEmptyState,
WorkItemAssignees,
WorkItemActions,
WorkItemTodos,
WorkItemCreatedUpdated,
WorkItemDescription,
WorkItemAwardEmoji,
WorkItemDueDate,
WorkItemLabels,
WorkItemTitle,
WorkItemState,
WorkItemWeight: () => import('ee_component/work_items/components/work_item_weight.vue'),
WorkItemProgress: () => import('ee_component/work_items/components/work_item_progress.vue'),
WorkItemAttributesWrapper,
WorkItemTypeIcon,
WorkItemIteration: () => import('ee_component/work_items/components/work_item_iteration.vue'),
WorkItemHealthStatus: () =>
import('ee_component/work_items/components/work_item_health_status.vue'),
WorkItemMilestone,
WorkItemTree,
WorkItemNotes,
WorkItemDetailModal,
@ -256,33 +236,12 @@ export default {
workItemAssignees() {
return this.isWidgetPresent(WIDGET_TYPE_ASSIGNEES);
},
workItemLabels() {
return this.isWidgetPresent(WIDGET_TYPE_LABELS);
},
workItemDueDate() {
return this.isWidgetPresent(WIDGET_TYPE_START_AND_DUE_DATE);
},
workItemWeight() {
return this.isWidgetPresent(WIDGET_TYPE_WEIGHT);
},
workItemProgress() {
return this.isWidgetPresent(WIDGET_TYPE_PROGRESS);
},
workItemAwardEmoji() {
return this.isWidgetPresent(WIDGET_TYPE_AWARD_EMOJI);
},
workItemHierarchy() {
return this.isWidgetPresent(WIDGET_TYPE_HIERARCHY);
},
workItemIteration() {
return this.isWidgetPresent(WIDGET_TYPE_ITERATION);
},
workItemHealthStatus() {
return this.isWidgetPresent(WIDGET_TYPE_HEALTH_STATUS);
},
workItemMilestone() {
return this.isWidgetPresent(WIDGET_TYPE_MILESTONE);
},
workItemNotes() {
return this.isWidgetPresent(WIDGET_TYPE_NOTES);
},
@ -511,83 +470,9 @@ export default {
@error="updateError = $event"
/>
<work-item-created-updated :work-item-iid="workItemIid" />
<work-item-state
<work-item-attributes-wrapper
:work-item="workItem"
:work-item-parent-id="workItemParentId"
:can-update="canUpdate"
@error="updateError = $event"
/>
<work-item-assignees
v-if="workItemAssignees"
:can-update="canUpdate"
:work-item-id="workItem.id"
:assignees="workItemAssignees.assignees.nodes"
:allows-multiple-assignees="workItemAssignees.allowsMultipleAssignees"
:work-item-type="workItemType"
:can-invite-members="workItemAssignees.canInviteMembers"
@error="updateError = $event"
/>
<work-item-labels
v-if="workItemLabels"
:can-update="canUpdate"
:work-item-id="workItem.id"
:work-item-iid="workItem.iid"
@error="updateError = $event"
/>
<work-item-due-date
v-if="workItemDueDate"
:can-update="canUpdate"
:due-date="workItemDueDate.dueDate"
:start-date="workItemDueDate.startDate"
:work-item-id="workItem.id"
:work-item-type="workItemType"
@error="updateError = $event"
/>
<work-item-milestone
v-if="workItemMilestone"
:work-item-id="workItem.id"
:work-item-milestone="workItemMilestone.milestone"
:work-item-type="workItemType"
:can-update="canUpdate"
@error="updateError = $event"
/>
<work-item-weight
v-if="workItemWeight"
class="gl-mb-5"
:can-update="canUpdate"
:weight="workItemWeight.weight"
:work-item-id="workItem.id"
:work-item-iid="workItem.iid"
:work-item-type="workItemType"
@error="updateError = $event"
/>
<work-item-progress
v-if="workItemProgress"
class="gl-mb-5"
:can-update="canUpdate"
:progress="workItemProgress.progress"
:work-item-id="workItem.id"
:work-item-type="workItemType"
@error="updateError = $event"
/>
<work-item-iteration
v-if="workItemIteration"
class="gl-mb-5"
:iteration="workItemIteration.iteration"
:can-update="canUpdate"
:work-item-id="workItem.id"
:work-item-iid="workItem.iid"
:work-item-type="workItemType"
@error="updateError = $event"
/>
<work-item-health-status
v-if="workItemHealthStatus"
class="gl-mb-5"
:health-status="workItemHealthStatus.healthStatus"
:can-update="canUpdate"
:work-item-id="workItem.id"
:work-item-iid="workItem.iid"
:work-item-type="workItemType"
@error="updateError = $event"
/>
<work-item-description

View File

@ -20,7 +20,7 @@
- else
= _("(removed)")
.todo-body.gl-mb-2.gl-px-2.gl-display-flex.gl-align-items-flex-start.gl-lg-align-items-center
.todo-body.gl-mb-2.gl-px-2.gl-display-flex.gl-align-items-flex-start
.todo-avatar.gl-display-none.gl-sm-display-inline-block
= author_avatar(todo, size: 24)
.todo-note
@ -47,6 +47,8 @@
%span.action-description<
= first_line_in_markdown(todo, :body, 125, is_todo: true, project: todo.project, group: todo.group)
= render_if_exists "dashboard/todos/diff_summary", local_assigns: { todo: todo }
.todo-timestamp.gl-white-space-nowrap.gl-sm-ml-3.gl-mt-2.gl-mb-2.gl-sm-my-0.gl-px-2.gl-sm-px-0
%span.todo-timestamp.gl-font-sm.gl-text-secondary
= todo_due_date(todo)

View File

@ -28,4 +28,4 @@
= clipboard_button(target: '#domain_verification', class: 'btn-default d-none d-sm-block')
%p.form-text.text-muted
- link_to_help = link_to(_('verify ownership'), help_page_path('user/project/pages/custom_domains_ssl_tls_certification/index.md', anchor: '4-verify-the-domains-ownership'))
= _("To %{link_to_help} of your domain, add the above key to a TXT record within your DNS configuration.").html_safe % { link_to_help: link_to_help }
= _("To %{link_to_help} of your domain, add the above key to a TXT record within your DNS configuration within seven days.").html_safe % { link_to_help: link_to_help }

View File

@ -7,4 +7,4 @@ feature_categories:
description: Define ownership of namespaces, projects, and users by organizations
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119421
milestone: '16.0'
gitlab_schema: gitlab_main
gitlab_schema: gitlab_main_clusterwide

View File

@ -5239,7 +5239,7 @@ msgstr ""
msgid "Analytics"
msgstr ""
msgid "Analytics|Add to Dashboard"
msgid "Analytics|A visualization with that name already exists."
msgstr ""
msgid "Analytics|Add visualizations"
@ -5311,9 +5311,15 @@ msgstr ""
msgid "Analytics|Edit"
msgstr ""
msgid "Analytics|Enter a visualization name"
msgstr ""
msgid "Analytics|Error while saving dashboard"
msgstr ""
msgid "Analytics|Error while saving visualization."
msgstr ""
msgid "Analytics|Host"
msgstr ""
@ -5323,7 +5329,7 @@ msgstr ""
msgid "Analytics|Line Chart"
msgstr ""
msgid "Analytics|New Analytics Visualization Title"
msgid "Analytics|New analytics visualization name"
msgstr ""
msgid "Analytics|New dashboard"
@ -5362,6 +5368,18 @@ msgstr ""
msgid "Analytics|Save"
msgstr ""
msgid "Analytics|Save and add to Dashboard"
msgstr ""
msgid "Analytics|Save new visualization"
msgstr ""
msgid "Analytics|Select a measurement"
msgstr ""
msgid "Analytics|Select a visualization type"
msgstr ""
msgid "Analytics|Single Statistic"
msgstr ""
@ -5374,6 +5392,9 @@ msgstr ""
msgid "Analytics|Updating dashboard %{dashboardId}"
msgstr ""
msgid "Analytics|Updating visualization %{visualizationName}"
msgstr ""
msgid "Analytics|Users"
msgstr ""
@ -5392,6 +5413,9 @@ msgstr ""
msgid "Analytics|Visualization Type"
msgstr ""
msgid "Analytics|Visualization was saved successfully"
msgstr ""
msgid "Analyze your dependencies for known vulnerabilities."
msgstr ""
@ -16424,7 +16448,7 @@ msgstr ""
msgid "DomainVerification|The following domains are configured for projects in this group. Users with email addresses that match a verified domain do not need to confirm their account."
msgstr ""
msgid "DomainVerification|To verify ownership of your domain, add the above key to a TXT record within your DNS configuration. %{link_to_help}"
msgid "DomainVerification|To verify ownership of your domain, add the above key to a TXT record within your DNS configuration within seven days. %{link_to_help}"
msgstr ""
msgid "Don't have a group?"
@ -44775,6 +44799,9 @@ msgstr ""
msgid "Summary comment (optional)"
msgstr ""
msgid "Summary generated by AI"
msgstr ""
msgid "Sun"
msgstr ""
@ -47737,7 +47764,7 @@ msgstr ""
msgid "To"
msgstr ""
msgid "To %{link_to_help} of your domain, add the above key to a TXT record within your DNS configuration."
msgid "To %{link_to_help} of your domain, add the above key to a TXT record within your DNS configuration within seven days."
msgstr ""
msgid "To Do"

View File

@ -0,0 +1,107 @@
import { shallowMount } from '@vue/test-utils';
import WorkItemAssignees from '~/work_items/components/work_item_assignees.vue';
import WorkItemDueDate from '~/work_items/components/work_item_due_date.vue';
import WorkItemState from '~/work_items/components/work_item_state.vue';
import WorkItemLabels from '~/work_items/components/work_item_labels.vue';
import WorkItemMilestone from '~/work_items/components/work_item_milestone.vue';
import WorkItemAttributesWrapper from '~/work_items/components/work_item_attributes_wrapper.vue';
import { workItemResponseFactory } from '../mock_data';
describe('WorkItemAttributesWrapper component', () => {
let wrapper;
const workItemQueryResponse = workItemResponseFactory({ canUpdate: true, canDelete: true });
const findWorkItemState = () => wrapper.findComponent(WorkItemState);
const findWorkItemDueDate = () => wrapper.findComponent(WorkItemDueDate);
const findWorkItemAssignees = () => wrapper.findComponent(WorkItemAssignees);
const findWorkItemLabels = () => wrapper.findComponent(WorkItemLabels);
const findWorkItemMilestone = () => wrapper.findComponent(WorkItemMilestone);
const createComponent = ({ workItem = workItemQueryResponse.data.workItem } = {}) => {
wrapper = shallowMount(WorkItemAttributesWrapper, {
propsData: {
workItem,
},
provide: {
hasIssueWeightsFeature: true,
hasIterationsFeature: true,
hasOkrsFeature: true,
hasIssuableHealthStatusFeature: true,
projectNamespace: 'namespace',
fullPath: 'group/project',
},
stubs: {
WorkItemWeight: true,
WorkItemIteration: true,
WorkItemHealthStatus: true,
},
});
};
describe('work item state', () => {
it('renders the work item state', () => {
createComponent();
expect(findWorkItemState().exists()).toBe(true);
});
});
describe('assignees widget', () => {
it('renders assignees component when widget is returned from the API', () => {
createComponent();
expect(findWorkItemAssignees().exists()).toBe(true);
});
it('does not render assignees component when widget is not returned from the API', () => {
createComponent({
workItem: workItemResponseFactory({ assigneesWidgetPresent: false }).data.workItem,
});
expect(findWorkItemAssignees().exists()).toBe(false);
});
});
describe('labels widget', () => {
it.each`
description | labelsWidgetPresent | exists
${'renders when widget is returned from API'} | ${true} | ${true}
${'does not render when widget is not returned from API'} | ${false} | ${false}
`('$description', ({ labelsWidgetPresent, exists }) => {
const response = workItemResponseFactory({ labelsWidgetPresent });
createComponent({ workItem: response.data.workItem });
expect(findWorkItemLabels().exists()).toBe(exists);
});
});
describe('dates widget', () => {
describe.each`
description | datesWidgetPresent | exists
${'when widget is returned from API'} | ${true} | ${true}
${'when widget is not returned from API'} | ${false} | ${false}
`('$description', ({ datesWidgetPresent, exists }) => {
it(`${datesWidgetPresent ? 'renders' : 'does not render'} due date component`, () => {
const response = workItemResponseFactory({ datesWidgetPresent });
createComponent({ workItem: response.data.workItem });
expect(findWorkItemDueDate().exists()).toBe(exists);
});
});
});
describe('milestone widget', () => {
it.each`
description | milestoneWidgetPresent | exists
${'renders when widget is returned from API'} | ${true} | ${true}
${'does not render when widget is not returned from API'} | ${false} | ${false}
`('$description', ({ milestoneWidgetPresent, exists }) => {
const response = workItemResponseFactory({ milestoneWidgetPresent });
createComponent({ workItem: response.data.workItem });
expect(findWorkItemMilestone().exists()).toBe(exists);
});
});
});

View File

@ -18,12 +18,8 @@ import WorkItemDetail from '~/work_items/components/work_item_detail.vue';
import WorkItemActions from '~/work_items/components/work_item_actions.vue';
import WorkItemDescription from '~/work_items/components/work_item_description.vue';
import WorkItemCreatedUpdated from '~/work_items/components/work_item_created_updated.vue';
import WorkItemDueDate from '~/work_items/components/work_item_due_date.vue';
import WorkItemState from '~/work_items/components/work_item_state.vue';
import WorkItemAttributesWrapper from '~/work_items/components/work_item_attributes_wrapper.vue';
import WorkItemTitle from '~/work_items/components/work_item_title.vue';
import WorkItemAssignees from '~/work_items/components/work_item_assignees.vue';
import WorkItemLabels from '~/work_items/components/work_item_labels.vue';
import WorkItemMilestone from '~/work_items/components/work_item_milestone.vue';
import WorkItemTree from '~/work_items/components/work_item_links/work_item_tree.vue';
import WorkItemNotes from '~/work_items/components/work_item_notes.vue';
import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue';
@ -69,12 +65,8 @@ describe('WorkItemDetail component', () => {
const findWorkItemActions = () => wrapper.findComponent(WorkItemActions);
const findWorkItemTitle = () => wrapper.findComponent(WorkItemTitle);
const findCreatedUpdated = () => wrapper.findComponent(WorkItemCreatedUpdated);
const findWorkItemState = () => wrapper.findComponent(WorkItemState);
const findWorkItemDescription = () => wrapper.findComponent(WorkItemDescription);
const findWorkItemDueDate = () => wrapper.findComponent(WorkItemDueDate);
const findWorkItemAssignees = () => wrapper.findComponent(WorkItemAssignees);
const findWorkItemLabels = () => wrapper.findComponent(WorkItemLabels);
const findWorkItemMilestone = () => wrapper.findComponent(WorkItemMilestone);
const findWorkItemAttributesWrapper = () => wrapper.findComponent(WorkItemAttributesWrapper);
const findParent = () => wrapper.find('[data-testid="work-item-parent"]');
const findParentButton = () => findParent().findComponent(GlButton);
const findCloseButton = () => wrapper.find('[data-testid="work-item-close"]');
@ -168,7 +160,6 @@ describe('WorkItemDetail component', () => {
it('renders skeleton loader', () => {
expect(findSkeleton().exists()).toBe(true);
expect(findWorkItemState().exists()).toBe(false);
expect(findWorkItemTitle().exists()).toBe(false);
});
});
@ -181,7 +172,6 @@ describe('WorkItemDetail component', () => {
it('does not render skeleton', () => {
expect(findSkeleton().exists()).toBe(false);
expect(findWorkItemState().exists()).toBe(true);
expect(findWorkItemTitle().exists()).toBe(true);
});
@ -480,83 +470,6 @@ describe('WorkItemDetail component', () => {
expect(findAlert().text()).toBe(updateError);
});
describe('assignees widget', () => {
it('renders assignees component when widget is returned from the API', async () => {
createComponent();
await waitForPromises();
expect(findWorkItemAssignees().exists()).toBe(true);
});
it('does not render assignees component when widget is not returned from the API', async () => {
createComponent({
handler: jest
.fn()
.mockResolvedValue(workItemByIidResponseFactory({ assigneesWidgetPresent: false })),
});
await waitForPromises();
expect(findWorkItemAssignees().exists()).toBe(false);
});
});
describe('labels widget', () => {
it.each`
description | labelsWidgetPresent | exists
${'renders when widget is returned from API'} | ${true} | ${true}
${'does not render when widget is not returned from API'} | ${false} | ${false}
`('$description', async ({ labelsWidgetPresent, exists }) => {
const response = workItemByIidResponseFactory({ labelsWidgetPresent });
const handler = jest.fn().mockResolvedValue(response);
createComponent({ handler });
await waitForPromises();
expect(findWorkItemLabels().exists()).toBe(exists);
});
});
describe('dates widget', () => {
describe.each`
description | datesWidgetPresent | exists
${'when widget is returned from API'} | ${true} | ${true}
${'when widget is not returned from API'} | ${false} | ${false}
`('$description', ({ datesWidgetPresent, exists }) => {
it(`${datesWidgetPresent ? 'renders' : 'does not render'} due date component`, async () => {
const response = workItemByIidResponseFactory({ datesWidgetPresent });
const handler = jest.fn().mockResolvedValue(response);
createComponent({ handler });
await waitForPromises();
expect(findWorkItemDueDate().exists()).toBe(exists);
});
});
it('shows an error message when it emits an `error` event', async () => {
createComponent();
await waitForPromises();
const updateError = 'Failed to update';
findWorkItemDueDate().vm.$emit('error', updateError);
await waitForPromises();
expect(findAlert().text()).toBe(updateError);
});
});
describe('milestone widget', () => {
it.each`
description | milestoneWidgetPresent | exists
${'renders when widget is returned from API'} | ${true} | ${true}
${'does not render when widget is not returned from API'} | ${false} | ${false}
`('$description', async ({ milestoneWidgetPresent, exists }) => {
const response = workItemByIidResponseFactory({ milestoneWidgetPresent });
const handler = jest.fn().mockResolvedValue(response);
createComponent({ handler });
await waitForPromises();
expect(findWorkItemMilestone().exists()).toBe(exists);
});
});
it('calls the work item query', async () => {
createComponent();
@ -713,4 +626,24 @@ describe('WorkItemDetail component', () => {
expect(findWorkItemTodos().exists()).toBe(false);
});
});
describe('work item attributes wrapper', () => {
beforeEach(async () => {
createComponent();
await waitForPromises();
});
it('renders the work item attributes wrapper', () => {
expect(findWorkItemAttributesWrapper().exists()).toBe(true);
});
it('shows an error message when it emits an `error` event', async () => {
const updateError = 'Failed to update';
findWorkItemAttributesWrapper().vm.$emit('error', updateError);
await waitForPromises();
expect(findAlert().text()).toBe(updateError);
});
});
});