diff --git a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js index ea6bca644ed..8fe822e4639 100644 --- a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js +++ b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js @@ -7,7 +7,9 @@ const twoFactorNode = document.querySelector('.js-two-factor-auth'); const skippable = twoFactorNode ? parseBoolean(twoFactorNode.dataset.twoFactorSkippable) : false; if (skippable) { - const button = `
Configure it later`; + const button = `
+ Configure it later +
`; 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(); diff --git a/app/assets/javascripts/work_items/components/work_item_attributes_wrapper.vue b/app/assets/javascripts/work_items/components/work_item_attributes_wrapper.vue new file mode 100644 index 00000000000..3e82d603c1d --- /dev/null +++ b/app/assets/javascripts/work_items/components/work_item_attributes_wrapper.vue @@ -0,0 +1,180 @@ + + + diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue index 5539226e84e..3acdedf77aa 100644 --- a/app/assets/javascripts/work_items/components/work_item_detail.vue +++ b/app/assets/javascripts/work_items/components/work_item_detail.vue @@ -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" /> - - - - - - - - - { + 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); + }); + }); +}); diff --git a/spec/frontend/work_items/components/work_item_detail_spec.js b/spec/frontend/work_items/components/work_item_detail_spec.js index 0266533a11c..14a6ada16bd 100644 --- a/spec/frontend/work_items/components/work_item_detail_spec.js +++ b/spec/frontend/work_items/components/work_item_detail_spec.js @@ -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); + }); + }); });