Commit bacc37c2 authored by Robert Hunt's avatar Robert Hunt

Setup compliance dashboard merge request drawer

- Created new development feature flag
- Created basic drawer component
- Added draw to dashboard
- Wrapped MR item with a link which triggers the opening/closing of the
drawer
- Added new CSS to handle the fact that the MR item needs to span the
full row but also still maintain the same column widths throughout
- Removed unnecessary heading class
- Updated tests for when the feature is enabled and disabled
parent 398ddd20
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
import { GlTabs, GlTab } from '@gitlab/ui'; import { GlTabs, GlTab } from '@gitlab/ui';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { __ } from '~/locale'; import { __ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { COMPLIANCE_TAB_COOKIE_KEY } from '../constants'; import { COMPLIANCE_TAB_COOKIE_KEY } from '../constants';
import MergeRequestDrawer from './drawer.vue';
import EmptyState from './empty_state.vue'; import EmptyState from './empty_state.vue';
import MergeRequestsGrid from './merge_requests/grid.vue'; import MergeRequestsGrid from './merge_requests/grid.vue';
import MergeCommitsExportButton from './merge_requests/merge_commits_export_button.vue'; import MergeCommitsExportButton from './merge_requests/merge_commits_export_button.vue';
...@@ -10,12 +12,14 @@ import MergeCommitsExportButton from './merge_requests/merge_commits_export_butt ...@@ -10,12 +12,14 @@ import MergeCommitsExportButton from './merge_requests/merge_commits_export_butt
export default { export default {
name: 'ComplianceDashboard', name: 'ComplianceDashboard',
components: { components: {
MergeRequestDrawer,
MergeRequestsGrid, MergeRequestsGrid,
EmptyState, EmptyState,
GlTab, GlTab,
GlTabs, GlTabs,
MergeCommitsExportButton, MergeCommitsExportButton,
}, },
mixins: [glFeatureFlagsMixin()],
props: { props: {
emptyStateSvgPath: { emptyStateSvgPath: {
type: String, type: String,
...@@ -36,6 +40,12 @@ export default { ...@@ -36,6 +40,12 @@ export default {
default: '', default: '',
}, },
}, },
data() {
return {
showDrawer: false,
drawerData: {},
};
},
computed: { computed: {
hasMergeRequests() { hasMergeRequests() {
return this.mergeRequests.length > 0; return this.mergeRequests.length > 0;
...@@ -43,11 +53,29 @@ export default { ...@@ -43,11 +53,29 @@ export default {
hasMergeCommitsCsvExportPath() { hasMergeCommitsCsvExportPath() {
return this.mergeCommitsCsvExportPath !== ''; return this.mergeCommitsCsvExportPath !== '';
}, },
drawerEnabled() {
return this.glFeatures.complianceDashboardDrawer;
},
}, },
methods: { methods: {
showTabs() { showTabs() {
return Cookies.get(COMPLIANCE_TAB_COOKIE_KEY) === 'true'; return Cookies.get(COMPLIANCE_TAB_COOKIE_KEY) === 'true';
}, },
toggleDrawer(mergeRequest) {
if (this.showDrawer && mergeRequest.id === this.drawerData.id) {
this.closeDrawer();
} else {
this.openDrawer(mergeRequest);
}
},
openDrawer(mergeRequest) {
this.showDrawer = true;
this.drawerData = mergeRequest;
},
closeDrawer() {
this.showDrawer = false;
this.drawerData = {};
},
}, },
strings: { strings: {
heading: __('Compliance Dashboard'), heading: __('Compliance Dashboard'),
...@@ -75,10 +103,27 @@ export default { ...@@ -75,10 +103,27 @@ export default {
<template #title> <template #title>
<span>{{ $options.strings.mergeRequestsTabLabel }}</span> <span>{{ $options.strings.mergeRequestsTabLabel }}</span>
</template> </template>
<merge-requests-grid :merge-requests="mergeRequests" :is-last-page="isLastPage" /> <merge-requests-grid
:merge-requests="mergeRequests"
:is-last-page="isLastPage"
@toggleDrawer="toggleDrawer"
/>
</gl-tab> </gl-tab>
</gl-tabs> </gl-tabs>
<merge-requests-grid v-else :merge-requests="mergeRequests" :is-last-page="isLastPage" /> <merge-requests-grid
v-else
:merge-requests="mergeRequests"
:is-last-page="isLastPage"
:drawer-enabled="drawerEnabled"
@toggleDrawer="toggleDrawer"
/>
<merge-request-drawer
v-if="drawerEnabled"
:show-drawer="showDrawer"
:merge-request="drawerData"
z-index="252"
@close="closeDrawer"
/>
</div> </div>
<empty-state v-else :image-path="emptyStateSvgPath" /> <empty-state v-else :image-path="emptyStateSvgPath" />
</template> </template>
<script>
import { GlDrawer } from '@gitlab/ui';
export default {
components: {
GlDrawer,
},
props: {
mergeRequest: {
type: Object,
required: true,
},
showDrawer: {
type: Boolean,
required: false,
default: false,
},
},
methods: {
getDrawerHeaderHeight() {
const wrapperEl = document.querySelector('.content-wrapper');
if (wrapperEl) {
return `${wrapperEl.offsetTop}px`;
}
return '';
},
},
// We set the drawer's z-index to 252 to clear flash messages that might be displayed in the page
// and that have a z-index of 251.
Z_INDEX: 252,
};
</script>
<template>
<gl-drawer
:open="showDrawer"
:header-height="getDrawerHeaderHeight()"
:z-index="$options.Z_INDEX"
@close="$emit('close')"
>
<template #header>
<h4 data-testid="dashboard-drawer-title">{{ mergeRequest.title }}</h4>
</template>
</gl-drawer>
</template>
...@@ -29,7 +29,7 @@ export default { ...@@ -29,7 +29,7 @@ export default {
</script> </script>
<template> <template>
<div class="gl-display-flex gl-align-items-center gl-justify-content-end"> <div class="gl-display-flex gl-align-items-center gl-justify-content-end gl-text-gray-900">
<gl-sprintf :message="this.$options.strings.branchDetails"> <gl-sprintf :message="this.$options.strings.branchDetails">
<template #sourceBranch> <template #sourceBranch>
<span class="gl-mr-2 gl-min-w-0"> <span class="gl-mr-2 gl-min-w-0">
......
...@@ -32,6 +32,11 @@ export default { ...@@ -32,6 +32,11 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
drawerEnabled: {
type: Boolean,
required: false,
default: false,
},
}, },
methods: { methods: {
key(id, value) { key(id, value) {
...@@ -65,50 +70,106 @@ export default { ...@@ -65,50 +70,106 @@ export default {
<grid-column-heading :heading="$options.strings.pipelineStatusLabel" class="gl-text-center" /> <grid-column-heading :heading="$options.strings.pipelineStatusLabel" class="gl-text-center" />
<grid-column-heading :heading="$options.strings.updatesLabel" class="gl-text-right" /> <grid-column-heading :heading="$options.strings.updatesLabel" class="gl-text-right" />
<template v-for="mergeRequest in mergeRequests"> <!-- TODO: Remove the if/else and duplicate components with https://gitlab.com/gitlab-org/gitlab/-/issues/334682 -->
<merge-request <template v-if="drawerEnabled">
:key="key(mergeRequest.id, $options.keyTypes.mergeRequest)" <a
:merge-request="mergeRequest" v-for="mergeRequest in mergeRequests"
/> :key="mergeRequest.id"
class="dashboard-merge-request dashboard-grid gl-display-grid gl-grid-tpl-rows-auto gl-hover-bg-blue-50 gl-hover-text-decoration-none gl-hover-cursor-pointer"
data-testid="merge-request-link"
@click="$emit('toggleDrawer', mergeRequest)"
>
<merge-request
:key="key(mergeRequest.id, $options.keyTypes.mergeRequest)"
:merge-request="mergeRequest"
/>
<status
:key="key(mergeRequest.id, 'approval')"
:status="{ type: 'approval', data: mergeRequest.approval_status }"
/>
<status
:key="key(mergeRequest.id, 'pipeline')"
:status="{ type: 'pipeline', data: mergeRequest.pipeline_status }"
/>
<status <div
:key="key(mergeRequest.id, 'approval')" :key="key(mergeRequest.id, $options.keyTypes.updates)"
:status="{ type: 'approval', data: mergeRequest.approval_status }" class="gl-text-right gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-p-5 gl-relative"
/> >
<approvers :approvers="mergeRequest.approved_by_users" />
<branch-details
v-if="hasBranchDetails(mergeRequest)"
:source-branch="{
name: mergeRequest.source_branch,
uri: mergeRequest.source_branch_uri,
}"
:target-branch="{
name: mergeRequest.target_branch,
uri: mergeRequest.target_branch_uri,
}"
/>
<time-ago-tooltip
:time="mergeRequest.merged_at"
tooltip-placement="bottom"
class="gl-text-gray-500"
>
<template #default="{ timeAgo }">
<gl-sprintf :message="$options.strings.mergedAtText">
<template #timeAgo>{{ timeAgo }}</template>
</gl-sprintf>
</template>
</time-ago-tooltip>
</div>
</a>
</template>
<template v-else>
<template v-for="mergeRequest in mergeRequests">
<merge-request
:key="key(mergeRequest.id, $options.keyTypes.mergeRequest)"
:merge-request="mergeRequest"
/>
<status <status
:key="key(mergeRequest.id, 'pipeline')" :key="key(mergeRequest.id, 'approval')"
:status="{ type: 'pipeline', data: mergeRequest.pipeline_status }" :status="{ type: 'approval', data: mergeRequest.approval_status }"
/> />
<div <status
:key="key(mergeRequest.id, $options.keyTypes.updates)" :key="key(mergeRequest.id, 'pipeline')"
class="gl-text-right gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-p-5 gl-relative" :status="{ type: 'pipeline', data: mergeRequest.pipeline_status }"
>
<approvers :approvers="mergeRequest.approved_by_users" />
<branch-details
v-if="hasBranchDetails(mergeRequest)"
:source-branch="{
name: mergeRequest.source_branch,
uri: mergeRequest.source_branch_uri,
}"
:target-branch="{
name: mergeRequest.target_branch,
uri: mergeRequest.target_branch_uri,
}"
/> />
<time-ago-tooltip
:time="mergeRequest.merged_at" <div
tooltip-placement="bottom" :key="key(mergeRequest.id, $options.keyTypes.updates)"
class="gl-text-gray-500" class="gl-text-right gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-p-5 gl-relative"
> >
<template #default="{ timeAgo }"> <approvers :approvers="mergeRequest.approved_by_users" />
<gl-sprintf :message="$options.strings.mergedAtText"> <branch-details
<template #timeAgo>{{ timeAgo }}</template> v-if="hasBranchDetails(mergeRequest)"
</gl-sprintf> :source-branch="{
</template> name: mergeRequest.source_branch,
</time-ago-tooltip> uri: mergeRequest.source_branch_uri,
</div> }"
:target-branch="{
name: mergeRequest.target_branch,
uri: mergeRequest.target_branch_uri,
}"
/>
<time-ago-tooltip
:time="mergeRequest.merged_at"
tooltip-placement="bottom"
class="gl-text-gray-500"
>
<template #default="{ timeAgo }">
<gl-sprintf :message="$options.strings.mergedAtText">
<template #timeAgo>{{ timeAgo }}</template>
</gl-sprintf>
</template>
</time-ago-tooltip>
</div>
</template>
</template> </template>
</div> </div>
......
...@@ -32,9 +32,11 @@ export default { ...@@ -32,9 +32,11 @@ export default {
class="gl-grid-col-start-1 gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-p-5" class="gl-grid-col-start-1 gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-p-5"
data-testid="merge-request" data-testid="merge-request"
> >
<a :href="mergeRequest.path" class="gl-display-block gl-text-gray-900 gl-font-weight-bold"> <div>
{{ mergeRequest.title }} <a :href="mergeRequest.path" class="gl-text-gray-900 gl-font-weight-bold">
</a> {{ mergeRequest.title }}
</a>
</div>
<span class="gl-text-gray-500">{{ mergeRequest.issuable_reference }}</span> <span class="gl-text-gray-500">{{ mergeRequest.issuable_reference }}</span>
<span class="issuable-authored gl-text-gray-500 gl-display-inline-flex gl-align-items-center"> <span class="issuable-authored gl-text-gray-500 gl-display-inline-flex gl-align-items-center">
- {{ $options.strings.createdBy }} - {{ $options.strings.createdBy }}
......
...@@ -10,9 +10,7 @@ export default { ...@@ -10,9 +10,7 @@ export default {
</script> </script>
<template> <template>
<p <p class="gl-text-gray-500 gl-border-b-solid gl-border-b-2 gl-border-b-gray-100 gl-mb-0 gl-p-5">
class="grid-column-heading gl-text-gray-500 gl-border-b-solid gl-border-b-2 gl-border-b-gray-100 gl-mb-0 gl-p-5"
>
{{ heading }} {{ heading }}
</p> </p>
</template> </template>
...@@ -3,6 +3,11 @@ ...@@ -3,6 +3,11 @@
min-width: 550px; min-width: 550px;
.dashboard-grid { .dashboard-grid {
grid-template-columns: 1fr auto auto 35%; // Column widths: Merge request | approval status | pipeline status | updates
grid-template-columns: 1fr 12% 8% 35%;
}
.dashboard-merge-request {
grid-column: auto / span 4;
} }
} }
...@@ -6,6 +6,9 @@ class Groups::Security::ComplianceDashboardsController < Groups::ApplicationCont ...@@ -6,6 +6,9 @@ class Groups::Security::ComplianceDashboardsController < Groups::ApplicationCont
layout 'group' layout 'group'
before_action :authorize_compliance_dashboard! before_action :authorize_compliance_dashboard!
before_action do
push_frontend_feature_flag(:compliance_dashboard_drawer, @group, default_enabled: :yaml)
end
track_unique_visits :show, target_id: 'g_compliance_dashboard' track_unique_visits :show, target_id: 'g_compliance_dashboard'
......
---
name: compliance_dashboard_drawer
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64771
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/334682
milestone: '14.1'
type: development
group: group::compliance
default_enabled: false
...@@ -47,6 +47,8 @@ exports[`ComplianceDashboard component when there are merge requests and the sho ...@@ -47,6 +47,8 @@ exports[`ComplianceDashboard component when there are merge requests and the sho
</template> </template>
</b-tab-stub> </b-tab-stub>
</gl-tabs-stub> </gl-tabs-stub>
<!---->
</div> </div>
`; `;
...@@ -77,6 +79,8 @@ exports[`ComplianceDashboard component when there are merge requests matches the ...@@ -77,6 +79,8 @@ exports[`ComplianceDashboard component when there are merge requests matches the
<merge-requests-grid-stub <merge-requests-grid-stub
mergerequests="[object Object],[object Object]" mergerequests="[object Object],[object Object]"
/> />
<!---->
</div> </div>
`; `;
......
...@@ -3,6 +3,7 @@ import { shallowMount } from '@vue/test-utils'; ...@@ -3,6 +3,7 @@ import { shallowMount } from '@vue/test-utils';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import ComplianceDashboard from 'ee/compliance_dashboard/components/dashboard.vue'; import ComplianceDashboard from 'ee/compliance_dashboard/components/dashboard.vue';
import MergeRequestDrawer from 'ee/compliance_dashboard/components/drawer.vue';
import MergeRequestGrid from 'ee/compliance_dashboard/components/merge_requests/grid.vue'; import MergeRequestGrid from 'ee/compliance_dashboard/components/merge_requests/grid.vue';
import MergeCommitsExportButton from 'ee/compliance_dashboard/components/merge_requests/merge_commits_export_button.vue'; import MergeCommitsExportButton from 'ee/compliance_dashboard/components/merge_requests/merge_commits_export_button.vue';
import { COMPLIANCE_TAB_COOKIE_KEY } from 'ee/compliance_dashboard/constants'; import { COMPLIANCE_TAB_COOKIE_KEY } from 'ee/compliance_dashboard/constants';
...@@ -15,11 +16,12 @@ describe('ComplianceDashboard component', () => { ...@@ -15,11 +16,12 @@ describe('ComplianceDashboard component', () => {
const mergeRequests = createMergeRequests({ count: 2 }); const mergeRequests = createMergeRequests({ count: 2 });
const mergeCommitsCsvExportPath = '/csv'; const mergeCommitsCsvExportPath = '/csv';
const findMergeRequestsGrid = () => wrapper.find(MergeRequestGrid); const findMergeRequestsGrid = () => wrapper.findComponent(MergeRequestGrid);
const findMergeCommitsExportButton = () => wrapper.find(MergeCommitsExportButton); const findMergeRequestsDrawer = () => wrapper.findComponent(MergeRequestDrawer);
const findDashboardTabs = () => wrapper.find(GlTabs); const findMergeCommitsExportButton = () => wrapper.findComponent(MergeCommitsExportButton);
const findDashboardTabs = () => wrapper.findComponent(GlTabs);
const createComponent = (props = {}) => { const createComponent = (props = {}, glFeatures = {}) => {
return shallowMount(ComplianceDashboard, { return shallowMount(ComplianceDashboard, {
propsData: { propsData: {
mergeRequests, mergeRequests,
...@@ -28,6 +30,9 @@ describe('ComplianceDashboard component', () => { ...@@ -28,6 +30,9 @@ describe('ComplianceDashboard component', () => {
emptyStateSvgPath: 'empty.svg', emptyStateSvgPath: 'empty.svg',
...props, ...props,
}, },
provide: {
glFeatures,
},
stubs: { stubs: {
GlTab, GlTab,
}, },
...@@ -106,4 +111,57 @@ describe('ComplianceDashboard component', () => { ...@@ -106,4 +111,57 @@ describe('ComplianceDashboard component', () => {
}); });
}); });
}); });
describe('with the merge requests drawer', () => {
describe('when the feature is enabled', () => {
beforeEach(() => {
wrapper = createComponent({}, { complianceDashboardDrawer: true });
});
it('opens the drawer', async () => {
await findMergeRequestsGrid().vm.$emit('toggleDrawer', mergeRequests[0]);
expect(findMergeRequestsGrid().props('drawerEnabled')).toBe(true);
expect(findMergeRequestsDrawer().props('showDrawer')).toBe(true);
expect(findMergeRequestsDrawer().props('mergeRequest')).toStrictEqual(mergeRequests[0]);
});
it('closes the drawer via the drawer close event', async () => {
await findMergeRequestsDrawer().vm.$emit('close');
expect(findMergeRequestsGrid().props('drawerEnabled')).toBe(true);
expect(findMergeRequestsDrawer().props('showDrawer')).toBe(false);
expect(findMergeRequestsDrawer().props('mergeRequest')).toEqual({});
});
it('closes the drawer via the grid toggle event', async () => {
await findMergeRequestsGrid().vm.$emit('toggleDrawer', mergeRequests[0]);
await findMergeRequestsGrid().vm.$emit('toggleDrawer', mergeRequests[0]);
expect(findMergeRequestsGrid().props('drawerEnabled')).toBe(true);
expect(findMergeRequestsDrawer().props('showDrawer')).toBe(false);
expect(findMergeRequestsDrawer().props('mergeRequest')).toEqual({});
});
it('swaps the drawer when a new merge request is selected', async () => {
await findMergeRequestsGrid().vm.$emit('toggleDrawer', mergeRequests[0]);
await findMergeRequestsGrid().vm.$emit('toggleDrawer', mergeRequests[1]);
expect(findMergeRequestsGrid().props('drawerEnabled')).toBe(true);
expect(findMergeRequestsDrawer().props('showDrawer')).toBe(true);
expect(findMergeRequestsDrawer().props('mergeRequest')).toStrictEqual(mergeRequests[1]);
});
});
describe('when the feature is disabled', () => {
beforeEach(() => {
wrapper = createComponent({}, { complianceDashboardDrawer: false });
});
it('does not show the drawer', () => {
expect(findMergeRequestsGrid().props('drawerEnabled')).toBe(false);
expect(findMergeRequestsDrawer().exists()).toBe(false);
});
});
});
}); });
import { GlDrawer } from '@gitlab/ui';
import MergeRequestDrawer from 'ee/compliance_dashboard/components/drawer.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { createMergeRequests } from '../mock_data';
describe('MergeRequestDrawer component', () => {
let wrapper;
const mergeRequest = createMergeRequests({ count: 1 })[0];
const findTitle = () => wrapper.findByTestId('dashboard-drawer-title');
const findDrawer = () => wrapper.findComponent(GlDrawer);
const createComponent = (props) => {
return shallowMountExtended(MergeRequestDrawer, {
propsData: {
mergeRequest,
...props,
},
});
};
afterEach(() => {
wrapper.destroy();
});
describe('when closed', () => {
beforeEach(() => {
wrapper = createComponent({ showDrawer: false });
});
it('the drawer is not shown', () => {
expect(findDrawer().props('open')).toBe(false);
});
});
describe('when open', () => {
beforeEach(() => {
wrapper = createComponent({ showDrawer: true });
});
it('the drawer is shown', () => {
expect(findDrawer().props('open')).toBe(true);
});
it('has the drawer title', () => {
expect(findTitle().text()).toEqual(mergeRequest.title);
});
});
});
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`MergeRequestsGrid component when intialized matches the snapshot 1`] = ` exports[`MergeRequestsGrid component when drawer enabled is false when initialized matches the snapshot 1`] = `
<div> <div>
<div <div
class="dashboard-grid gl-display-grid gl-grid-tpl-rows-auto" class="dashboard-grid gl-display-grid gl-grid-tpl-rows-auto"
...@@ -91,3 +91,105 @@ exports[`MergeRequestsGrid component when intialized matches the snapshot 1`] = ...@@ -91,3 +91,105 @@ exports[`MergeRequestsGrid component when intialized matches the snapshot 1`] =
/> />
</div> </div>
`; `;
exports[`MergeRequestsGrid component when drawer enabled is true when initialized matches the snapshot 1`] = `
<div>
<div
class="dashboard-grid gl-display-grid gl-grid-tpl-rows-auto"
>
<grid-column-heading-stub
heading="Merge Request"
/>
<grid-column-heading-stub
class="gl-text-center"
heading="Approval Status"
/>
<grid-column-heading-stub
class="gl-text-center"
heading="Pipeline"
/>
<grid-column-heading-stub
class="gl-text-right"
heading="Updates"
/>
<a
class="dashboard-merge-request dashboard-grid gl-display-grid gl-grid-tpl-rows-auto gl-hover-bg-blue-50 gl-hover-text-decoration-none gl-hover-cursor-pointer"
data-testid="merge-request-link"
>
<div
data-testid="merge-request"
>
Merge request 0
</div>
<status-stub
status="[object Object]"
/>
<status-stub
status="[object Object]"
/>
<div
class="gl-text-right gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-p-5 gl-relative"
>
<approvers-stub
approvers=""
/>
<!---->
<time-ago-tooltip-stub
class="gl-text-gray-500"
cssclass=""
time="2020-01-01T00:00:00.000Z"
tooltipplacement="bottom"
/>
</div>
</a>
<a
class="dashboard-merge-request dashboard-grid gl-display-grid gl-grid-tpl-rows-auto gl-hover-bg-blue-50 gl-hover-text-decoration-none gl-hover-cursor-pointer"
data-testid="merge-request-link"
>
<div
data-testid="merge-request"
>
Merge request 1
</div>
<status-stub
status="[object Object]"
/>
<status-stub
status="[object Object]"
/>
<div
class="gl-text-right gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-p-5 gl-relative"
>
<approvers-stub
approvers=""
/>
<!---->
<time-ago-tooltip-stub
class="gl-text-gray-500"
cssclass=""
time="2020-01-01T00:00:00.000Z"
tooltipplacement="bottom"
/>
</div>
</a>
</div>
<pagination-stub
class="gl-mt-5"
/>
</div>
`;
...@@ -5,14 +5,16 @@ exports[`MergeRequest component when there is a merge request matches the snapsh ...@@ -5,14 +5,16 @@ exports[`MergeRequest component when there is a merge request matches the snapsh
class="gl-grid-col-start-1 gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-p-5" class="gl-grid-col-start-1 gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-p-5"
data-testid="merge-request" data-testid="merge-request"
> >
<a <div>
class="gl-display-block gl-text-gray-900 gl-font-weight-bold" <a
href="/h5bp/html5-boilerplate/-/merge_requests/1" class="gl-text-gray-900 gl-font-weight-bold"
> href="/h5bp/html5-boilerplate/-/merge_requests/1"
>
Merge request 1
Merge request 1 </a>
</div>
</a>
<span <span
class="gl-text-gray-500" class="gl-text-gray-500"
......
import { shallowMount } from '@vue/test-utils';
import Approvers from 'ee/compliance_dashboard/components/merge_requests/approvers.vue'; import Approvers from 'ee/compliance_dashboard/components/merge_requests/approvers.vue';
import BranchDetails from 'ee/compliance_dashboard/components/merge_requests/branch_details.vue'; import BranchDetails from 'ee/compliance_dashboard/components/merge_requests/branch_details.vue';
import MergeRequestsGrid from 'ee/compliance_dashboard/components/merge_requests/grid.vue'; import MergeRequestsGrid from 'ee/compliance_dashboard/components/merge_requests/grid.vue';
import Status from 'ee/compliance_dashboard/components/merge_requests/status.vue'; import Status from 'ee/compliance_dashboard/components/merge_requests/status.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { createMergeRequests, mergedAt } from '../../mock_data'; import { createMergeRequests, mergedAt } from '../../mock_data';
describe('MergeRequestsGrid component', () => { describe('MergeRequestsGrid component', () => {
let wrapper; let wrapper;
const findMergeRequests = () => wrapper.findAll('[data-testid="merge-request"]'); const findMergeRequestLinks = () => wrapper.findAllByTestId('merge-request-link');
const findTime = () => wrapper.find(TimeAgoTooltip); const findMergeRequests = () => wrapper.findAllByTestId('merge-request');
const findStatuses = () => wrapper.findAll(Status); const findTime = () => wrapper.findComponent(TimeAgoTooltip);
const findApprovers = () => wrapper.find(Approvers); const findStatuses = () => wrapper.findAllComponents(Status);
const findBranchDetails = () => wrapper.find(BranchDetails); const findApprovers = () => wrapper.findComponent(Approvers);
const findBranchDetails = () => wrapper.findComponent(BranchDetails);
const createComponent = (mergeRequests = {}) => { const createComponent = (mergeRequests = {}, drawerEnabled = false) => {
return shallowMount(MergeRequestsGrid, { return shallowMountExtended(MergeRequestsGrid, {
propsData: { propsData: {
mergeRequests, mergeRequests,
isLastPage: false, isLastPage: false,
drawerEnabled,
}, },
stubs: { stubs: {
MergeRequest: { MergeRequest: {
...@@ -36,43 +36,61 @@ describe('MergeRequestsGrid component', () => { ...@@ -36,43 +36,61 @@ describe('MergeRequestsGrid component', () => {
wrapper.destroy(); wrapper.destroy();
}); });
describe('when intialized', () => { // TODO: Remove the each with https://gitlab.com/gitlab-org/gitlab/-/issues/334682
beforeEach(() => { describe.each([true, false])('when drawer enabled is %s', (drawerEnabled) => {
wrapper = createComponent(createMergeRequests({ count: 2, props: {} })); describe('when initialized', () => {
}); beforeEach(() => {
wrapper = createComponent(createMergeRequests({ count: 2, props: {} }), drawerEnabled);
});
it('matches the snapshot', () => { it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot(); expect(wrapper.element).toMatchSnapshot();
}); });
it('renders a list of merge requests', () => {
expect(findMergeRequests()).toHaveLength(2);
});
it('renders the approvers list', () => {
expect(findApprovers().exists()).toBe(true);
});
it('renders a list of merge requests', () => { it('renders the "merged at" time', () => {
expect(findMergeRequests()).toHaveLength(2); expect(findTime().props('time')).toEqual(mergedAt());
});
}); });
it('passes the correct props to the statuses', () => { describe('statuses', () => {
const mergeRequest = createMergeRequests({ count: 1 }); const mergeRequest = createMergeRequests({ count: 1 });
wrapper = createComponent(mergeRequest);
findStatuses().wrappers.forEach((status) => { beforeEach(() => {
const { type, data } = status.props('status'); wrapper = createComponent(mergeRequest, drawerEnabled);
});
it('passes the correct props to the statuses', () => {
findStatuses().wrappers.forEach((status) => {
const { type, data } = status.props('status');
switch (type) { switch (type) {
case 'pipeline': case 'pipeline':
expect(data).toEqual(mergeRequest[0].pipeline_status); expect(data).toEqual(mergeRequest[0].pipeline_status);
break; break;
case 'approval': case 'approval':
expect(data).toEqual(mergeRequest[0].approval_status); expect(data).toEqual(mergeRequest[0].approval_status);
break; break;
default: default:
throw new Error('Unknown status type'); throw new Error('Unknown status type');
} }
});
}); });
}); });
describe('branch details', () => { describe('branch details', () => {
it('does not render if there are no branch details', () => { it('does not render if there are no branch details', () => {
wrapper = createComponent(createMergeRequests({ count: 2, props: {} }), drawerEnabled);
expect(findBranchDetails().exists()).toBe(false); expect(findBranchDetails().exists()).toBe(false);
}); });
...@@ -82,17 +100,26 @@ describe('MergeRequestsGrid component', () => { ...@@ -82,17 +100,26 @@ describe('MergeRequestsGrid component', () => {
count: 2, count: 2,
props: { target_branch: 'main', source_branch: 'feature' }, props: { target_branch: 'main', source_branch: 'feature' },
}), }),
drawerEnabled,
); );
expect(findBranchDetails().exists()).toBe(true); expect(findBranchDetails().exists()).toBe(true);
}); });
}); });
});
describe('when the drawer is enabled', () => {
const mergeRequests = createMergeRequests({ count: 2, props: {} });
it('renders the approvers list', () => { beforeEach(() => {
expect(findApprovers().exists()).toBe(true); const mergeRequest = createMergeRequests({ count: 1 });
wrapper = createComponent(mergeRequest, true);
}); });
it('renders the "merged at" time', () => { it('toggles the drawer when a merge request link is clicked', () => {
expect(findTime().props('time')).toEqual(mergedAt()); findMergeRequestLinks().at(0).trigger('click');
expect(wrapper.emitted('toggleDrawer')[0][0]).toStrictEqual(mergeRequests[0]);
}); });
}); });
}); });
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
exports[`GridColumnHeading component behaviour matches the screenshot 1`] = ` exports[`GridColumnHeading component behaviour matches the screenshot 1`] = `
<p <p
class="grid-column-heading gl-text-gray-500 gl-border-b-solid gl-border-b-2 gl-border-b-gray-100 gl-mb-0 gl-p-5" class="gl-text-gray-500 gl-border-b-solid gl-border-b-2 gl-border-b-gray-100 gl-mb-0 gl-p-5"
> >
Test heading Test heading
......
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