summaryrefslogtreecommitdiff
path: root/spec/frontend/vue_merge_request_widget
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/vue_merge_request_widget')
-rw-r--r--spec/frontend/vue_merge_request_widget/components/added_commit_message_spec.js13
-rw-r--r--spec/frontend/vue_merge_request_widget/components/artifacts_list_spec.js6
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js28
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js75
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_status_icon_spec.js40
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_suggest_pipeline_spec.js2
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap468
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap24
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_archived_spec.js21
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_checking_spec.js22
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_closed_spec.js65
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_commit_message_dropdown_spec.js2
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_failed_to_merge_spec.js15
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_not_allowed_spec.js20
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked_spec.js19
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_failed_spec.js17
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/terraform/mr_widget_terraform_container_spec.js3
-rw-r--r--spec/frontend/vue_merge_request_widget/components/widget/app_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/widget/widget_content_section_spec.js39
-rw-r--r--spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js174
-rw-r--r--spec/frontend/vue_merge_request_widget/deployment/deployment_actions_spec.js2
-rw-r--r--spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js10
-rw-r--r--spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/stores/artifacts_list/actions_spec.js4
25 files changed, 751 insertions, 330 deletions
diff --git a/spec/frontend/vue_merge_request_widget/components/added_commit_message_spec.js b/spec/frontend/vue_merge_request_widget/components/added_commit_message_spec.js
index cb53dc1fb61..063425454d7 100644
--- a/spec/frontend/vue_merge_request_widget/components/added_commit_message_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/added_commit_message_spec.js
@@ -1,10 +1,10 @@
-import { shallowMount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
import AddedCommentMessage from '~/vue_merge_request_widget/components/added_commit_message.vue';
let wrapper;
function factory(propsData) {
- wrapper = shallowMount(AddedCommentMessage, {
+ wrapper = mount(AddedCommentMessage, {
propsData: {
isFastForwardEnabled: false,
targetBranch: 'main',
@@ -23,4 +23,13 @@ describe('Widget added commit message', () => {
expect(wrapper.element.outerHTML).toContain('The changes were not merged');
});
+
+ it('renders merge commit as a link', () => {
+ factory({ state: 'merged', mergeCommitPath: 'https://test.host/merge-commit-link' });
+
+ expect(wrapper.find('[data-testid="merge-commit-sha"]').exists()).toBe(true);
+ expect(wrapper.find('[data-testid="merge-commit-sha"]').attributes('href')).toBe(
+ 'https://test.host/merge-commit-link',
+ );
+ });
});
diff --git a/spec/frontend/vue_merge_request_widget/components/artifacts_list_spec.js b/spec/frontend/vue_merge_request_widget/components/artifacts_list_spec.js
index 712abfe228a..d519ad2cdb0 100644
--- a/spec/frontend/vue_merge_request_widget/components/artifacts_list_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/artifacts_list_spec.js
@@ -39,10 +39,12 @@ describe('Artifacts List', () => {
});
it('renders job url', () => {
- expect(wrapper.findAll(GlLink).at(1).attributes('href')).toEqual(data.artifacts[0].job_path);
+ expect(wrapper.findAllComponents(GlLink).at(1).attributes('href')).toEqual(
+ data.artifacts[0].job_path,
+ );
});
it('renders job name', () => {
- expect(wrapper.findAll(GlLink).at(1).text()).toEqual(data.artifacts[0].job_name);
+ expect(wrapper.findAllComponents(GlLink).at(1).text()).toEqual(data.artifacts[0].job_name);
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js
index 6347e3c3be3..7f0173b7445 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_spec.js
@@ -4,9 +4,8 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { trimText } from 'helpers/text_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue';
-import PipelineStage from '~/pipelines/components/pipelines_list/pipeline_stage.vue';
-import PipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
+import MRWidgetPipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
+import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue';
import { SUCCESS } from '~/vue_merge_request_widget/constants';
import mockData from '../mock_data';
@@ -30,14 +29,13 @@ describe('MRWidgetPipeline', () => {
const findPipelineInfoContainer = () => wrapper.findByTestId('pipeline-info-container');
const findCommitLink = () => wrapper.findByTestId('commit-link');
const findPipelineFinishedAt = () => wrapper.findByTestId('finished-at');
- const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph);
- const findAllPipelineStages = () => wrapper.findAllComponents(PipelineStage);
const findPipelineCoverage = () => wrapper.findByTestId('pipeline-coverage');
const findPipelineCoverageDelta = () => wrapper.findByTestId('pipeline-coverage-delta');
const findPipelineCoverageTooltipText = () =>
wrapper.findByTestId('pipeline-coverage-tooltip').text();
const findPipelineCoverageDeltaTooltipText = () =>
wrapper.findByTestId('pipeline-coverage-delta-tooltip').text();
+ const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph);
const findMonitoringPipelineMessage = () => wrapper.findByTestId('monitoring-pipeline-message');
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
@@ -45,7 +43,7 @@ describe('MRWidgetPipeline', () => {
const createWrapper = (props = {}, mountFn = shallowMount) => {
wrapper = extendedWrapper(
- mountFn(PipelineComponent, {
+ mountFn(MRWidgetPipelineComponent, {
propsData: {
...defaultProps,
...props,
@@ -106,8 +104,10 @@ describe('MRWidgetPipeline', () => {
});
it('should render pipeline graph', () => {
+ const stagesCount = mockData.pipeline.details.stages.length;
+
expect(findPipelineMiniGraph().exists()).toBe(true);
- expect(findAllPipelineStages()).toHaveLength(mockData.pipeline.details.stages.length);
+ expect(findPipelineMiniGraph().props('stages')).toHaveLength(stagesCount);
});
describe('should render pipeline coverage information', () => {
@@ -176,15 +176,11 @@ describe('MRWidgetPipeline', () => {
expect(findPipelineInfoContainer().text()).toMatch(mockData.pipeline.details.status.label);
});
- it('should render pipeline graph with correct styles', () => {
+ it('should render pipeline graph', () => {
const stagesCount = mockData.pipeline.details.stages.length;
expect(findPipelineMiniGraph().exists()).toBe(true);
- expect(findPipelineMiniGraph().findAll('.mr-widget-pipeline-stages')).toHaveLength(
- stagesCount,
- );
-
- expect(findAllPipelineStages()).toHaveLength(stagesCount);
+ expect(findPipelineMiniGraph().props('stages')).toHaveLength(stagesCount);
});
it('should render coverage information', () => {
@@ -266,13 +262,13 @@ describe('MRWidgetPipeline', () => {
});
describe('for a detached merge request pipeline', () => {
- it('renders a pipeline widget that reads "Detached merge request pipeline <ID> <status> for <SHA>"', () => {
- pipeline.details.name = 'Detached merge request pipeline';
+ it('renders a pipeline widget that reads "Merge request pipeline <ID> <status> for <SHA>"', () => {
+ pipeline.details.name = 'Merge request pipeline';
pipeline.merge_request_event_type = 'detached';
factory();
- const expected = `Detached merge request pipeline #${pipeline.id} ${pipeline.details.status.label} for ${pipeline.commit.short_id}`;
+ const expected = `Merge request pipeline #${pipeline.id} ${pipeline.details.status.label} for ${pipeline.commit.short_id}`;
const actual = trimText(findPipelineInfoContainer().text());
expect(actual).toBe(expected);
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js
index 534c0baf35d..05c259de370 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js
@@ -110,7 +110,7 @@ describe('Merge request widget rebase component', () => {
expect(findRebaseMessageText()).toContain('Something went wrong!');
});
- describe('Rebase buttons with', () => {
+ describe('Rebase buttons', () => {
beforeEach(() => {
createWrapper(
{
@@ -148,6 +148,79 @@ describe('Merge request widget rebase component', () => {
expect(rebaseMock).toHaveBeenCalledWith({ skipCi: true });
});
});
+
+ describe('Rebase when pipelines must succeed is enabled', () => {
+ beforeEach(() => {
+ createWrapper(
+ {
+ mr: {
+ rebaseInProgress: false,
+ canPushToSourceBranch: true,
+ onlyAllowMergeIfPipelineSucceeds: true,
+ },
+ service: {
+ rebase: rebaseMock,
+ poll: pollMock,
+ },
+ },
+ mergeRequestWidgetGraphql,
+ );
+ });
+
+ it('renders only the rebase button', () => {
+ expect(findRebaseWithoutCiButton().exists()).toBe(false);
+ expect(findStandardRebaseButton().exists()).toBe(true);
+ });
+
+ it('starts the rebase when clicking', async () => {
+ findStandardRebaseButton().vm.$emit('click');
+
+ await nextTick();
+
+ expect(rebaseMock).toHaveBeenCalledWith({ skipCi: false });
+ });
+ });
+
+ describe('Rebase when pipelines must succeed and skipped pipelines are considered successful are enabled', () => {
+ beforeEach(() => {
+ createWrapper(
+ {
+ mr: {
+ rebaseInProgress: false,
+ canPushToSourceBranch: true,
+ onlyAllowMergeIfPipelineSucceeds: true,
+ allowMergeOnSkippedPipeline: true,
+ },
+ service: {
+ rebase: rebaseMock,
+ poll: pollMock,
+ },
+ },
+ mergeRequestWidgetGraphql,
+ );
+ });
+
+ it('renders both rebase buttons', () => {
+ expect(findRebaseWithoutCiButton().exists()).toBe(true);
+ expect(findStandardRebaseButton().exists()).toBe(true);
+ });
+
+ it('starts the rebase when clicking', async () => {
+ findStandardRebaseButton().vm.$emit('click');
+
+ await nextTick();
+
+ expect(rebaseMock).toHaveBeenCalledWith({ skipCi: false });
+ });
+
+ it('starts the CI-skipping rebase when clicking on "Rebase without CI"', async () => {
+ findRebaseWithoutCiButton().vm.$emit('click');
+
+ await nextTick();
+
+ expect(rebaseMock).toHaveBeenCalledWith({ skipCi: true });
+ });
+ });
});
describe('without permissions', () => {
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_status_icon_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_status_icon_spec.js
index 11373be578a..530549b7b9c 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_status_icon_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_status_icon_spec.js
@@ -1,14 +1,16 @@
-import { GlLoadingIcon } from '@gitlab/ui';
-import { shallowMount, mount } from '@vue/test-utils';
+import { GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import mrStatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
+import StatusIcon from '~/vue_merge_request_widget/components/extensions/status_icon.vue';
describe('MR widget status icon component', () => {
let wrapper;
- const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
+ const findStatusIcon = () => wrapper.findComponent(StatusIcon);
+ const findIcon = () => wrapper.findComponent(GlIcon);
- const createWrapper = (props, mountFn = shallowMount) => {
- wrapper = mountFn(mrStatusIcon, {
+ const createWrapper = (props) => {
+ wrapper = shallowMount(mrStatusIcon, {
propsData: {
...props,
},
@@ -17,27 +19,45 @@ describe('MR widget status icon component', () => {
afterEach(() => {
wrapper.destroy();
+ wrapper = null;
});
describe('while loading', () => {
it('renders loading icon', () => {
createWrapper({ status: 'loading' });
- expect(findLoadingIcon().exists()).toBe(true);
+ expect(findStatusIcon().exists()).toBe(true);
+ expect(findStatusIcon().props().isLoading).toBe(true);
});
});
describe('with status icon', () => {
it('renders success status icon', () => {
- createWrapper({ status: 'success' }, mount);
+ createWrapper({ status: 'success' });
- expect(wrapper.find('[data-testid="status_success-icon"]').exists()).toBe(true);
+ expect(findStatusIcon().exists()).toBe(true);
+ expect(findStatusIcon().props().iconName).toBe('success');
});
it('renders failed status icon', () => {
- createWrapper({ status: 'failed' }, mount);
+ createWrapper({ status: 'failed' });
- expect(wrapper.find('[data-testid="status_failed-icon"]').exists()).toBe(true);
+ expect(findStatusIcon().exists()).toBe(true);
+ expect(findStatusIcon().props().iconName).toBe('failed');
+ });
+
+ it('renders merged status icon', () => {
+ createWrapper({ status: 'merged' });
+
+ expect(findIcon().exists()).toBe(true);
+ expect(findIcon().props().name).toBe('merge');
+ });
+
+ it('renders closed status icon', () => {
+ createWrapper({ status: 'closed' });
+
+ expect(findIcon().exists()).toBe(true);
+ expect(findIcon().props().name).toBe('merge-request-close');
});
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_suggest_pipeline_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_suggest_pipeline_spec.js
index 352bc1a08ea..d6c67dab381 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_suggest_pipeline_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_suggest_pipeline_spec.js
@@ -128,7 +128,7 @@ describe('MRWidgetSuggestPipeline', () => {
it('emits dismiss upon dismissal button click', () => {
findDismissContainer().vm.$emit('dismiss');
- expect(wrapper.emitted().dismiss).toBeTruthy();
+ expect(wrapper.emitted().dismiss).toHaveLength(1);
});
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap b/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap
index de25e2a0450..635ef0f6b0d 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap
+++ b/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_auto_merge_enabled_spec.js.snap
@@ -4,117 +4,171 @@ exports[`MRWidgetAutoMergeEnabled when graphql is disabled template should have
<div
class="mr-widget-body media"
>
- <svg
- aria-hidden="true"
- class="gl-text-blue-500 gl-mr-3 gl-mt-1 gl-icon s24"
- data-testid="status_scheduled-icon"
- role="img"
- >
- <use
- href="#status_scheduled"
- />
- </svg>
-
<div
- class="media-body gl-display-flex"
+ class="gl-w-6 gl-h-6 gl-display-flex gl-align-self-start gl-mr-3"
>
-
- <h4
- class="gl-mr-3"
- data-testid="statusText"
+ <div
+ class="gl-display-flex gl-m-auto"
>
- Set by
- <a
- class="author-link inline"
+ <div
+ class="gl-mr-3 gl-p-2 gl-m-0! gl-text-blue-500 gl-w-6 gl-p-2"
>
- <img
- class="avatar avatar-inline s16"
- src="no_avatar.png"
- />
-
- <span
- class="author"
+ <div
+ class="gl-rounded-full gl-relative gl-display-flex mr-widget-extension-icon"
>
-
- </span>
- </a>
- to be merged automatically when the pipeline succeeds
- </h4>
-
+ <div
+ class="gl-absolute gl-top-half gl-left-50p gl-translate-x-n50 gl-display-flex gl-m-auto"
+ >
+ <div
+ class="gl-display-flex gl-m-auto gl-translate-y-n50"
+ >
+ <svg
+ aria-label="Scheduled "
+ class="gl-display-block gl-icon s12"
+ data-qa-selector="status_scheduled_icon"
+ data-testid="status-scheduled-icon"
+ role="img"
+ >
+ <use
+ href="#status-scheduled"
+ />
+ </svg>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div
+ class="gl-display-flex gl-w-full"
+ >
<div
- class="gl-display-flex gl-md-display-block gl-font-size-0 gl-ml-auto gl-mt-1"
+ class="media-body gl-display-flex"
>
- <div>
- <div
- class="dropdown b-dropdown gl-new-dropdown gl-display-block gl-md-display-none! btn-group"
- lazy=""
- no-caret=""
+
+ <h4
+ class="gl-mr-3"
+ data-testid="statusText"
+ >
+ Set by
+ <a
+ class="author-link inline"
>
- <!---->
+ <img
+ class="avatar avatar-inline s16"
+ src="no_avatar.png"
+ />
+
+ <span
+ class="author"
+ >
+
+ </span>
+ </a>
+ to be merged automatically when the pipeline succeeds
+ </h4>
+
+ <div
+ class="gl-display-flex gl-md-display-block gl-font-size-0 gl-ml-auto"
+ >
+ <div>
+ <div
+ class="dropdown b-dropdown gl-new-dropdown gl-display-block gl-md-display-none! btn-group"
+ lazy=""
+ no-caret=""
+ >
+ <!---->
+ <button
+ aria-expanded="false"
+ aria-haspopup="true"
+ class="btn dropdown-toggle btn-default btn-sm gl-p-2! gl-button gl-dropdown-toggle btn-default-tertiary dropdown-icon-only dropdown-toggle-no-caret"
+ type="button"
+ >
+ <!---->
+
+ <svg
+ aria-hidden="true"
+ class="dropdown-icon gl-icon s16"
+ data-testid="ellipsis_v-icon"
+ role="img"
+ >
+ <use
+ href="#ellipsis_v"
+ />
+ </svg>
+
+ <span
+ class="gl-new-dropdown-button-text gl-sr-only"
+ >
+
+ </span>
+
+ <svg
+ aria-hidden="true"
+ class="gl-button-icon dropdown-chevron gl-icon s16"
+ data-testid="chevron-down-icon"
+ role="img"
+ >
+ <use
+ href="#chevron-down"
+ />
+ </svg>
+ </button>
+ <ul
+ class="dropdown-menu dropdown-menu-right"
+ role="menu"
+ tabindex="-1"
+ >
+ <!---->
+ </ul>
+ </div>
+
<button
- aria-expanded="false"
- aria-haspopup="true"
- class="btn dropdown-toggle btn-default btn-sm gl-p-2! gl-button gl-dropdown-toggle btn-default-tertiary dropdown-icon-only dropdown-toggle-no-caret"
+ class="btn gl-display-none gl-md-display-block gl-float-left btn-confirm btn-sm gl-button btn-confirm-tertiary js-cancel-auto-merge"
+ data-qa-selector="cancel_auto_merge_button"
+ data-testid="cancelAutomaticMergeButton"
type="button"
>
<!---->
- <svg
- aria-hidden="true"
- class="dropdown-icon gl-icon s16"
- data-testid="ellipsis_v-icon"
- role="img"
- >
- <use
- href="#ellipsis_v"
- />
- </svg>
-
+ <!---->
+
<span
- class="gl-new-dropdown-button-text gl-sr-only"
+ class="gl-button-text"
>
+ Cancel auto-merge
+
</span>
-
- <svg
- aria-hidden="true"
- class="gl-button-icon dropdown-chevron gl-icon s16"
- data-testid="chevron-down-icon"
- role="img"
- >
- <use
- href="#chevron-down"
- />
- </svg>
</button>
- <ul
- class="dropdown-menu dropdown-menu-right"
- role="menu"
- tabindex="-1"
- >
- <!---->
- </ul>
</div>
+ </div>
+ </div>
+
+ <div
+ class="gl-md-display-none gl-border-l-1 gl-border-l-solid gl-border-gray-100 gl-ml-3 gl-pl-3 gl-h-6 gl-mt-1"
+ >
+ <button
+ class="btn gl-vertical-align-top btn-default btn-sm gl-button btn-default-tertiary btn-icon"
+ title="Collapse merge details"
+ type="button"
+ >
+ <!---->
- <button
- class="btn gl-display-none gl-md-display-block gl-float-left btn-confirm btn-sm gl-button btn-confirm-tertiary js-cancel-auto-merge"
- data-qa-selector="cancel_auto_merge_button"
- data-testid="cancelAutomaticMergeButton"
- type="button"
+ <svg
+ aria-hidden="true"
+ class="gl-button-icon gl-icon s16"
+ data-testid="chevron-lg-up-icon"
+ role="img"
>
- <!---->
-
- <!---->
-
- <span
- class="gl-button-text"
- >
-
- Cancel auto-merge
-
- </span>
- </button>
- </div>
+ <use
+ href="#chevron-lg-up"
+ />
+ </svg>
+
+ <!---->
+ </button>
</div>
</div>
</div>
@@ -124,117 +178,171 @@ exports[`MRWidgetAutoMergeEnabled when graphql is enabled template should have c
<div
class="mr-widget-body media"
>
- <svg
- aria-hidden="true"
- class="gl-text-blue-500 gl-mr-3 gl-mt-1 gl-icon s24"
- data-testid="status_scheduled-icon"
- role="img"
- >
- <use
- href="#status_scheduled"
- />
- </svg>
-
<div
- class="media-body gl-display-flex"
+ class="gl-w-6 gl-h-6 gl-display-flex gl-align-self-start gl-mr-3"
>
-
- <h4
- class="gl-mr-3"
- data-testid="statusText"
+ <div
+ class="gl-display-flex gl-m-auto"
>
- Set by
- <a
- class="author-link inline"
+ <div
+ class="gl-mr-3 gl-p-2 gl-m-0! gl-text-blue-500 gl-w-6 gl-p-2"
>
- <img
- class="avatar avatar-inline s16"
- src="no_avatar.png"
- />
-
- <span
- class="author"
+ <div
+ class="gl-rounded-full gl-relative gl-display-flex mr-widget-extension-icon"
>
-
- </span>
- </a>
- to be merged automatically when the pipeline succeeds
- </h4>
-
+ <div
+ class="gl-absolute gl-top-half gl-left-50p gl-translate-x-n50 gl-display-flex gl-m-auto"
+ >
+ <div
+ class="gl-display-flex gl-m-auto gl-translate-y-n50"
+ >
+ <svg
+ aria-label="Scheduled "
+ class="gl-display-block gl-icon s12"
+ data-qa-selector="status_scheduled_icon"
+ data-testid="status-scheduled-icon"
+ role="img"
+ >
+ <use
+ href="#status-scheduled"
+ />
+ </svg>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div
+ class="gl-display-flex gl-w-full"
+ >
<div
- class="gl-display-flex gl-md-display-block gl-font-size-0 gl-ml-auto gl-mt-1"
+ class="media-body gl-display-flex"
>
- <div>
- <div
- class="dropdown b-dropdown gl-new-dropdown gl-display-block gl-md-display-none! btn-group"
- lazy=""
- no-caret=""
+
+ <h4
+ class="gl-mr-3"
+ data-testid="statusText"
+ >
+ Set by
+ <a
+ class="author-link inline"
>
- <!---->
+ <img
+ class="avatar avatar-inline s16"
+ src="no_avatar.png"
+ />
+
+ <span
+ class="author"
+ >
+
+ </span>
+ </a>
+ to be merged automatically when the pipeline succeeds
+ </h4>
+
+ <div
+ class="gl-display-flex gl-md-display-block gl-font-size-0 gl-ml-auto"
+ >
+ <div>
+ <div
+ class="dropdown b-dropdown gl-new-dropdown gl-display-block gl-md-display-none! btn-group"
+ lazy=""
+ no-caret=""
+ >
+ <!---->
+ <button
+ aria-expanded="false"
+ aria-haspopup="true"
+ class="btn dropdown-toggle btn-default btn-sm gl-p-2! gl-button gl-dropdown-toggle btn-default-tertiary dropdown-icon-only dropdown-toggle-no-caret"
+ type="button"
+ >
+ <!---->
+
+ <svg
+ aria-hidden="true"
+ class="dropdown-icon gl-icon s16"
+ data-testid="ellipsis_v-icon"
+ role="img"
+ >
+ <use
+ href="#ellipsis_v"
+ />
+ </svg>
+
+ <span
+ class="gl-new-dropdown-button-text gl-sr-only"
+ >
+
+ </span>
+
+ <svg
+ aria-hidden="true"
+ class="gl-button-icon dropdown-chevron gl-icon s16"
+ data-testid="chevron-down-icon"
+ role="img"
+ >
+ <use
+ href="#chevron-down"
+ />
+ </svg>
+ </button>
+ <ul
+ class="dropdown-menu dropdown-menu-right"
+ role="menu"
+ tabindex="-1"
+ >
+ <!---->
+ </ul>
+ </div>
+
<button
- aria-expanded="false"
- aria-haspopup="true"
- class="btn dropdown-toggle btn-default btn-sm gl-p-2! gl-button gl-dropdown-toggle btn-default-tertiary dropdown-icon-only dropdown-toggle-no-caret"
+ class="btn gl-display-none gl-md-display-block gl-float-left btn-confirm btn-sm gl-button btn-confirm-tertiary js-cancel-auto-merge"
+ data-qa-selector="cancel_auto_merge_button"
+ data-testid="cancelAutomaticMergeButton"
type="button"
>
<!---->
- <svg
- aria-hidden="true"
- class="dropdown-icon gl-icon s16"
- data-testid="ellipsis_v-icon"
- role="img"
- >
- <use
- href="#ellipsis_v"
- />
- </svg>
-
+ <!---->
+
<span
- class="gl-new-dropdown-button-text gl-sr-only"
+ class="gl-button-text"
>
+ Cancel auto-merge
+
</span>
-
- <svg
- aria-hidden="true"
- class="gl-button-icon dropdown-chevron gl-icon s16"
- data-testid="chevron-down-icon"
- role="img"
- >
- <use
- href="#chevron-down"
- />
- </svg>
</button>
- <ul
- class="dropdown-menu dropdown-menu-right"
- role="menu"
- tabindex="-1"
- >
- <!---->
- </ul>
</div>
+ </div>
+ </div>
+
+ <div
+ class="gl-md-display-none gl-border-l-1 gl-border-l-solid gl-border-gray-100 gl-ml-3 gl-pl-3 gl-h-6 gl-mt-1"
+ >
+ <button
+ class="btn gl-vertical-align-top btn-default btn-sm gl-button btn-default-tertiary btn-icon"
+ title="Collapse merge details"
+ type="button"
+ >
+ <!---->
- <button
- class="btn gl-display-none gl-md-display-block gl-float-left btn-confirm btn-sm gl-button btn-confirm-tertiary js-cancel-auto-merge"
- data-qa-selector="cancel_auto_merge_button"
- data-testid="cancelAutomaticMergeButton"
- type="button"
+ <svg
+ aria-hidden="true"
+ class="gl-button-icon gl-icon s16"
+ data-testid="chevron-lg-up-icon"
+ role="img"
>
- <!---->
-
- <!---->
-
- <span
- class="gl-button-text"
- >
-
- Cancel auto-merge
-
- </span>
- </button>
- </div>
+ <use
+ href="#chevron-lg-up"
+ />
+ </svg>
+
+ <!---->
+ </button>
</div>
</div>
</div>
diff --git a/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap b/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap
deleted file mode 100644
index 7e741bf4660..00000000000
--- a/spec/frontend/vue_merge_request_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap
+++ /dev/null
@@ -1,24 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`PipelineFailed should render error message with a disabled merge button 1`] = `
-<div
- class="mr-widget-body media"
->
- <status-icon-stub
- show-disabled-button="true"
- status="warning"
- />
-
- <div
- class="media-body space-children"
- >
- <span
- class="gl-ml-0! gl-text-body! bold"
- >
- <gl-sprintf-stub
- message="Merge blocked: pipeline must succeed. Push a commit that fixes the failure, or %{linkStart}learn about other solutions.%{linkEnd}"
- />
- </span>
- </div>
-</div>
-`;
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_archived_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_archived_spec.js
index 9332b7e334a..5c07f4ce143 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_archived_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_archived_spec.js
@@ -1,25 +1,26 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
+import { shallowMount } from '@vue/test-utils';
import archivedComponent from '~/vue_merge_request_widget/components/states/mr_widget_archived.vue';
+import StateContainer from '~/vue_merge_request_widget/components/state_container.vue';
describe('MRWidgetArchived', () => {
- let vm;
+ let wrapper;
beforeEach(() => {
- const Component = Vue.extend(archivedComponent);
- vm = mountComponent(Component);
+ wrapper = shallowMount(archivedComponent, { propsData: { mr: {} } });
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
+ wrapper = null;
});
- it('renders a ci status failed icon', () => {
- expect(vm.$el.querySelector('.ci-status-icon')).not.toBeNull();
+ it('renders error icon', () => {
+ expect(wrapper.findComponent(StateContainer).exists()).toBe(true);
+ expect(wrapper.findComponent(StateContainer).props().status).toBe('failed');
});
- it('renders information', () => {
- expect(vm.$el.querySelector('.bold').textContent.trim()).toEqual(
+ it('renders information about merging', () => {
+ expect(wrapper.text()).toContain(
'Merge unavailable: merge requests are read-only on archived projects.',
);
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_checking_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_checking_spec.js
index 02de426204b..ac18ccf9e26 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_checking_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_checking_spec.js
@@ -1,27 +1,25 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
-import checkingComponent from '~/vue_merge_request_widget/components/states/mr_widget_checking.vue';
+import { shallowMount } from '@vue/test-utils';
+import CheckingComponent from '~/vue_merge_request_widget/components/states/mr_widget_checking.vue';
+import StateContainer from '~/vue_merge_request_widget/components/state_container.vue';
describe('MRWidgetChecking', () => {
- let Component;
- let vm;
+ let wrapper;
beforeEach(() => {
- Component = Vue.extend(checkingComponent);
- vm = mountComponent(Component);
+ wrapper = shallowMount(CheckingComponent, { propsData: { mr: {} } });
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
+ wrapper = null;
});
it('renders loading icon', () => {
- expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('gl-spinner');
+ expect(wrapper.findComponent(StateContainer).exists()).toBe(true);
+ expect(wrapper.findComponent(StateContainer).props().status).toBe('loading');
});
it('renders information about merging', () => {
- expect(vm.$el.querySelector('.media-body').textContent.trim()).toEqual(
- 'Checking if merge request can be merged…',
- );
+ expect(wrapper.text()).toContain('Checking if merge request can be merged…');
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_closed_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_closed_spec.js
index f7d046eb8f9..06ee017dee7 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_closed_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_closed_spec.js
@@ -1,39 +1,54 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
+import { shallowMount } from '@vue/test-utils';
import closedComponent from '~/vue_merge_request_widget/components/states/mr_widget_closed.vue';
+import MrWidgetAuthorTime from '~/vue_merge_request_widget/components/mr_widget_author_time.vue';
+import StateContainer from '~/vue_merge_request_widget/components/state_container.vue';
+
+const MOCK_DATA = {
+ metrics: {
+ mergedBy: {},
+ closedBy: {
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'http://localhost:3000/root',
+ avatarUrl: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ },
+ mergedAt: 'Jan 24, 2018 1:02pm UTC',
+ closedAt: 'Jan 24, 2018 1:02pm UTC',
+ readableMergedAt: '',
+ readableClosedAt: 'less than a minute ago',
+ },
+ targetBranchPath: '/twitter/flight/commits/so_long_jquery',
+ targetBranch: 'so_long_jquery',
+};
describe('MRWidgetClosed', () => {
- let vm;
+ let wrapper;
beforeEach(() => {
- const Component = Vue.extend(closedComponent);
- vm = mountComponent(Component, {
- mr: {
- metrics: {
- mergedBy: {},
- closedBy: {
- name: 'Administrator',
- username: 'root',
- webUrl: 'http://localhost:3000/root',
- avatarUrl:
- 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
- },
- mergedAt: 'Jan 24, 2018 1:02pm UTC',
- closedAt: 'Jan 24, 2018 1:02pm UTC',
- readableMergedAt: '',
- readableClosedAt: 'less than a minute ago',
- },
- targetBranchPath: '/twitter/flight/commits/so_long_jquery',
- targetBranch: 'so_long_jquery',
+ wrapper = shallowMount(closedComponent, {
+ propsData: {
+ mr: MOCK_DATA,
},
});
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('renders closed icon', () => {
+ expect(wrapper.findComponent(StateContainer).exists()).toBe(true);
+ expect(wrapper.findComponent(StateContainer).props().status).toBe('closed');
});
- it('renders warning icon', () => {
- expect(vm.$el.querySelector('.js-ci-status-icon-warning')).not.toBeNull();
+ it('renders mr widget author time', () => {
+ expect(wrapper.findComponent(MrWidgetAuthorTime).exists()).toBe(true);
+ expect(wrapper.findComponent(MrWidgetAuthorTime).props()).toEqual({
+ actionText: 'Closed by',
+ author: MOCK_DATA.metrics.closedBy,
+ dateTitle: MOCK_DATA.metrics.closedAt,
+ dateReadable: MOCK_DATA.metrics.readableClosedAt,
+ });
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_commit_message_dropdown_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_commit_message_dropdown_spec.js
index 663fabb761c..5d2d1fdd6f1 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_commit_message_dropdown_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_commit_message_dropdown_spec.js
@@ -40,7 +40,7 @@ describe('Commits message dropdown component', () => {
wrapper.destroy();
});
- const findDropdownElements = () => wrapper.findAll(GlDropdownItem);
+ const findDropdownElements = () => wrapper.findAllComponents(GlDropdownItem);
const findFirstDropdownElement = () => findDropdownElements().at(0);
it('should have 3 elements in dropdown list', () => {
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_failed_to_merge_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_failed_to_merge_spec.js
index 989aa76f09b..833fa27d453 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_failed_to_merge_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_failed_to_merge_spec.js
@@ -1,6 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import MrWidgetFailedToMerge from '~/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue';
+import StateContainer from '~/vue_merge_request_widget/components/state_container.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
describe('MRWidgetFailedToMerge', () => {
@@ -39,7 +40,7 @@ describe('MRWidgetFailedToMerge', () => {
expect(wrapper.vm.intervalId).toBe(dummyIntervalId);
});
- it('clears interval when destroying ', () => {
+ it('clears interval when destroying', () => {
createComponent();
wrapper.destroy();
@@ -128,7 +129,11 @@ describe('MRWidgetFailedToMerge', () => {
await nextTick();
- expect(wrapper.find('.js-refresh-label').text().trim()).toBe('Refreshing now');
+ const stateContainerWrapper = wrapper.findComponent(StateContainer);
+
+ expect(stateContainerWrapper.exists()).toBe(true);
+ expect(stateContainerWrapper.props('status')).toBe('loading');
+ expect(stateContainerWrapper.text().trim()).toBe('Refreshing now');
});
});
@@ -146,9 +151,9 @@ describe('MRWidgetFailedToMerge', () => {
});
it('renders refresh button', () => {
- expect(
- wrapper.find('[data-testid="merge-request-failed-refresh-button"]').text().trim(),
- ).toBe('Refresh now');
+ expect(wrapper.findComponent(StateContainer).props('actions')).toMatchObject([
+ { text: 'Refresh now', onClick: expect.any(Function) },
+ ]);
});
it('renders remaining time', () => {
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_not_allowed_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_not_allowed_spec.js
index 63e93074857..c6e7198c678 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_not_allowed_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_not_allowed_spec.js
@@ -1,25 +1,27 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
+import { shallowMount } from '@vue/test-utils';
import notAllowedComponent from '~/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue';
+import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
describe('MRWidgetNotAllowed', () => {
- let vm;
+ let wrapper;
+
beforeEach(() => {
- const Component = Vue.extend(notAllowedComponent);
- vm = mountComponent(Component);
+ wrapper = shallowMount(notAllowedComponent);
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
+ wrapper = null;
});
it('renders success icon', () => {
- expect(vm.$el.querySelector('.ci-status-icon-success')).not.toBe(null);
+ expect(wrapper.findComponent(StatusIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(StatusIcon).props().status).toBe('success');
});
it('renders informative text', () => {
- expect(vm.$el.innerText).toContain('Ready to be merged automatically.');
- expect(vm.$el.innerText).toContain(
+ expect(wrapper.text()).toContain('Ready to be merged automatically.');
+ expect(wrapper.text()).toContain(
'Ask someone with write access to this repository to merge this request',
);
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked_spec.js
index 9b10b078e89..4219ad70b4c 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked_spec.js
@@ -1,26 +1,25 @@
-import { shallowMount, mount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
import PipelineBlockedComponent from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue';
+import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
describe('MRWidgetPipelineBlocked', () => {
let wrapper;
- const createWrapper = (mountFn = shallowMount) => {
- wrapper = mountFn(PipelineBlockedComponent);
- };
+ beforeEach(() => {
+ wrapper = shallowMount(PipelineBlockedComponent);
+ });
afterEach(() => {
wrapper.destroy();
+ wrapper = null;
});
- it('renders warning icon', () => {
- createWrapper(mount);
-
- expect(wrapper.find('.ci-status-icon-warning').exists()).toBe(true);
+ it('renders error icon', () => {
+ expect(wrapper.findComponent(StatusIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(StatusIcon).props().status).toBe('failed');
});
it('renders information text', () => {
- createWrapper();
-
expect(wrapper.text()).toBe(
"Merge blocked: pipeline must succeed. It's waiting for a manual action to continue.",
);
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_failed_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_failed_spec.js
index 4e44ac539f2..d5619d4996d 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_failed_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_failed_spec.js
@@ -1,11 +1,17 @@
+import { GlSprintf, GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import PipelineFailed from '~/vue_merge_request_widget/components/states/pipeline_failed.vue';
+import StatusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
describe('PipelineFailed', () => {
let wrapper;
const createComponent = () => {
- wrapper = shallowMount(PipelineFailed);
+ wrapper = shallowMount(PipelineFailed, {
+ stubs: {
+ GlSprintf,
+ },
+ });
};
beforeEach(() => {
@@ -17,7 +23,14 @@ describe('PipelineFailed', () => {
wrapper = null;
});
+ it('should render error status icon', () => {
+ expect(wrapper.findComponent(StatusIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(StatusIcon).props().status).toBe('failed');
+ });
+
it('should render error message with a disabled merge button', () => {
- expect(wrapper.element).toMatchSnapshot();
+ expect(wrapper.text()).toContain('Merge blocked: pipeline must succeed.');
+ expect(wrapper.text()).toContain('Push a commit that fixes the failure');
+ expect(wrapper.findComponent(GlLink).text()).toContain('learn about other solutions');
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js
index 6e89cd41559..9a6bf66909e 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -111,7 +111,7 @@ const createComponent = (
};
const findCheckboxElement = () => wrapper.find(SquashBeforeMerge);
-const findCommitEditElements = () => wrapper.findAll(CommitEdit);
+const findCommitEditElements = () => wrapper.findAllComponents(CommitEdit);
const findCommitDropdownElement = () => wrapper.find(CommitMessageDropdown);
const findFirstCommitEditLabel = () => findCommitEditElements().at(0).props('label');
const findTipLink = () => wrapper.find(GlSprintf);
@@ -549,7 +549,7 @@ describe('ReadyToMerge', () => {
${'squashIsSelected'} | ${'selected'} | ${'value'} | ${false}
${'squashIsSelected'} | ${'unselected'} | ${'value'} | ${false}
`(
- 'is $state when squashIsReadonly returns $expectation ',
+ 'is $state when squashIsReadonly returns $expectation',
({ squashState, prop, expectation }) => {
createComponent({
mr: { commitsCount: 2, enableSquashBeforeMerge: true, [squashState]: expectation },
diff --git a/spec/frontend/vue_merge_request_widget/components/terraform/mr_widget_terraform_container_spec.js b/spec/frontend/vue_merge_request_widget/components/terraform/mr_widget_terraform_container_spec.js
index 8f20d6a8fc9..7a868eb8cc9 100644
--- a/spec/frontend/vue_merge_request_widget/components/terraform/mr_widget_terraform_container_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/terraform/mr_widget_terraform_container_spec.js
@@ -16,7 +16,8 @@ describe('MrWidgetTerraformConainer', () => {
const propsData = { endpoint: '/path/to/terraform/report.json' };
const findHeader = () => wrapper.find('[data-testid="terraform-header-text"]');
- const findPlans = () => wrapper.findAll(TerraformPlan).wrappers.map((x) => x.props('plan'));
+ const findPlans = () =>
+ wrapper.findAllComponents(TerraformPlan).wrappers.map((x) => x.props('plan'));
const mockPollingApi = (response, body, header) => {
mock.onGet(propsData.endpoint).reply(response, body, header);
diff --git a/spec/frontend/vue_merge_request_widget/components/widget/app_spec.js b/spec/frontend/vue_merge_request_widget/components/widget/app_spec.js
index 6bb718082a4..8dbee9b370c 100644
--- a/spec/frontend/vue_merge_request_widget/components/widget/app_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/widget/app_spec.js
@@ -12,8 +12,8 @@ describe('MR Widget App', () => {
});
};
- it('mounts the component', () => {
+ it('does not mount if widgets array is empty', () => {
createComponent();
- expect(wrapper.findByTestId('mr-widget-app').exists()).toBe(true);
+ expect(wrapper.findByTestId('mr-widget-app').exists()).toBe(false);
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/widget/widget_content_section_spec.js b/spec/frontend/vue_merge_request_widget/components/widget/widget_content_section_spec.js
new file mode 100644
index 00000000000..c2128d3ff33
--- /dev/null
+++ b/spec/frontend/vue_merge_request_widget/components/widget/widget_content_section_spec.js
@@ -0,0 +1,39 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import WidgetContentSection from '~/vue_merge_request_widget/components/widget/widget_content_section.vue';
+import StatusIcon from '~/vue_merge_request_widget/components/extensions/status_icon.vue';
+
+describe('~/vue_merge_request_widget/components/widget/widget_content_section.vue', () => {
+ let wrapper;
+
+ const findStatusIcon = () => wrapper.findComponent(StatusIcon);
+
+ const createComponent = ({ propsData, slots } = {}) => {
+ wrapper = shallowMountExtended(WidgetContentSection, {
+ propsData: {
+ widgetName: 'MyWidget',
+ ...propsData,
+ },
+ slots,
+ });
+ };
+
+ it('does not render the status icon when it is not provided', () => {
+ createComponent();
+ expect(findStatusIcon().exists()).toBe(false);
+ });
+
+ it('renders the status icon when provided', () => {
+ createComponent({ propsData: { statusIconName: 'failed' } });
+ expect(findStatusIcon().exists()).toBe(true);
+ });
+
+ it('renders the default slot', () => {
+ createComponent({
+ slots: {
+ default: 'Hello world',
+ },
+ });
+
+ expect(wrapper.findByText('Hello world').exists()).toBe(true);
+ });
+});
diff --git a/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js b/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js
index 3c08ffdef18..b67b5703ad5 100644
--- a/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js
@@ -3,16 +3,21 @@ import * as Sentry from '@sentry/browser';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import StatusIcon from '~/vue_merge_request_widget/components/extensions/status_icon.vue';
+import ActionButtons from '~/vue_merge_request_widget/components/action_buttons.vue';
import Widget from '~/vue_merge_request_widget/components/widget/widget.vue';
describe('MR Widget', () => {
let wrapper;
const findStatusIcon = () => wrapper.findComponent(StatusIcon);
+ const findExpandedSection = () => wrapper.findByTestId('widget-extension-collapsed-section');
+ const findActionButtons = () => wrapper.findComponent(ActionButtons);
+ const findToggleButton = () => wrapper.findByTestId('toggle-button');
const createComponent = ({ propsData, slots } = {}) => {
wrapper = shallowMountExtended(Widget, {
propsData: {
+ isCollapsible: false,
loadingText: 'Loading widget',
widgetName: 'MyWidget',
value: {
@@ -38,14 +43,15 @@ describe('MR Widget', () => {
createComponent({ propsData: { fetchCollapsedData } });
await waitForPromises();
expect(fetchCollapsedData).toHaveBeenCalled();
- expect(wrapper.vm.error).toBe(null);
+ expect(wrapper.vm.summaryError).toBe(null);
});
it('sets the error text when fetch method fails', async () => {
const fetchCollapsedData = jest.fn().mockReturnValue(() => Promise.reject());
createComponent({ propsData: { fetchCollapsedData } });
await waitForPromises();
- expect(wrapper.vm.error).toBe('Failed to load');
+ expect(wrapper.findByText('Failed to load').exists()).toBe(true);
+ expect(findStatusIcon().props()).toMatchObject({ iconName: 'failed', isLoading: false });
});
it('displays loading icon until request is made and then displays status icon when the request is complete', async () => {
@@ -111,7 +117,7 @@ describe('MR Widget', () => {
jest.spyOn(Sentry, 'captureException').mockImplementation();
createComponent({
propsData: {
- fetchCollapsedData: async () => Promise.reject(error),
+ fetchCollapsedData: () => Promise.reject(error),
},
});
await waitForPromises();
@@ -125,7 +131,7 @@ describe('MR Widget', () => {
createComponent({
propsData: {
summary: 'Hello world',
- fetchCollapsedData: async () => Promise.resolve(),
+ fetchCollapsedData: () => Promise.resolve(),
},
});
@@ -137,7 +143,7 @@ describe('MR Widget', () => {
it('displays the summary slot when provided', () => {
createComponent({
propsData: {
- fetchCollapsedData: async () => Promise.resolve(),
+ fetchCollapsedData: () => Promise.resolve(),
},
slots: {
summary: '<b>More complex summary</b>',
@@ -149,19 +155,167 @@ describe('MR Widget', () => {
);
});
- it('displays the content slot when provided', () => {
+ it('does not display action buttons if actionButtons is not provided', () => {
createComponent({
propsData: {
- fetchCollapsedData: async () => Promise.resolve(),
+ fetchCollapsedData: () => Promise.resolve(),
+ },
+ });
+
+ expect(findActionButtons().exists()).toBe(false);
+ });
+
+ it('does display action buttons if actionButtons is provided', () => {
+ const actionButtons = [{ text: 'click-me', href: '#' }];
+
+ createComponent({
+ propsData: {
+ fetchCollapsedData: () => Promise.resolve(),
+ actionButtons,
+ },
+ });
+
+ expect(findActionButtons().props('tertiaryButtons')).toEqual(actionButtons);
+ });
+ });
+
+ describe('handle collapse toggle', () => {
+ it('displays the toggle button correctly', () => {
+ createComponent({
+ propsData: {
+ isCollapsible: true,
+ fetchCollapsedData: () => Promise.resolve(),
},
slots: {
content: '<b>More complex content</b>',
},
});
- expect(wrapper.findByTestId('widget-extension-collapsed-section').text()).toBe(
- 'More complex content',
- );
+ expect(findToggleButton().attributes('title')).toBe('Show details');
+ expect(findToggleButton().attributes('aria-label')).toBe('Show details');
+ });
+
+ it('does not display the content slot until toggle is clicked', async () => {
+ createComponent({
+ propsData: {
+ isCollapsible: true,
+ fetchCollapsedData: () => Promise.resolve(),
+ },
+ slots: {
+ content: '<b>More complex content</b>',
+ },
+ });
+
+ expect(findExpandedSection().exists()).toBe(false);
+ findToggleButton().vm.$emit('click');
+ await nextTick();
+ expect(findExpandedSection().text()).toBe('More complex content');
+ });
+
+ it('does not display the toggle button if isCollapsible is false', () => {
+ createComponent({
+ propsData: {
+ isCollapsible: false,
+ fetchCollapsedData: () => Promise.resolve(),
+ },
+ });
+
+ expect(findToggleButton().exists()).toBe(false);
+ });
+
+ it('fetches expanded data when clicked for the first time', async () => {
+ const mockDataCollapsed = {
+ headers: {},
+ status: 200,
+ data: { vulnerabilities: [{ vuln: 1 }] },
+ };
+
+ const mockDataExpanded = {
+ headers: {},
+ status: 200,
+ data: { vulnerabilities: [{ vuln: 2 }] },
+ };
+
+ const fetchExpandedData = jest.fn().mockResolvedValue(mockDataExpanded);
+
+ createComponent({
+ propsData: {
+ isCollapsible: true,
+ fetchCollapsedData: () => Promise.resolve(mockDataCollapsed),
+ fetchExpandedData,
+ },
+ });
+
+ findToggleButton().vm.$emit('click');
+ await waitForPromises();
+
+ // First fetches the collapsed data
+ expect(wrapper.emitted('input')[0][0]).toEqual({
+ collapsed: mockDataCollapsed.data,
+ expanded: null,
+ });
+
+ // Then fetches the expanded data
+ expect(wrapper.emitted('input')[1][0]).toEqual({
+ collapsed: null,
+ expanded: mockDataExpanded.data,
+ });
+
+ // Triggering a click does not call the expanded data again
+ findToggleButton().vm.$emit('click');
+ await waitForPromises();
+ expect(fetchExpandedData).toHaveBeenCalledTimes(1);
+ });
+
+ it('allows refetching when fetch expanded data returns an error', async () => {
+ const fetchExpandedData = jest.fn().mockRejectedValue({ error: true });
+
+ createComponent({
+ propsData: {
+ isCollapsible: true,
+ fetchCollapsedData: () => Promise.resolve([]),
+ fetchExpandedData,
+ },
+ });
+
+ findToggleButton().vm.$emit('click');
+ await waitForPromises();
+
+ // First fetches the collapsed data
+ expect(wrapper.emitted('input')[0][0]).toEqual({
+ collapsed: undefined,
+ expanded: null,
+ });
+
+ expect(fetchExpandedData).toHaveBeenCalledTimes(1);
+ expect(wrapper.emitted('input')).toHaveLength(1); // Should not an emit an input call because request failed
+
+ findToggleButton().vm.$emit('click');
+ await waitForPromises();
+ expect(fetchExpandedData).toHaveBeenCalledTimes(2);
+ });
+
+ it('resets the error message when another request is fetched', async () => {
+ const fetchExpandedData = jest.fn().mockRejectedValue({ error: true });
+
+ createComponent({
+ propsData: {
+ isCollapsible: true,
+ fetchCollapsedData: () => Promise.resolve([]),
+ fetchExpandedData,
+ },
+ });
+
+ findToggleButton().vm.$emit('click');
+ await waitForPromises();
+
+ expect(wrapper.findByText('Failed to load').exists()).toBe(true);
+ fetchExpandedData.mockImplementation(() => new Promise(() => {}));
+
+ findToggleButton().vm.$emit('click');
+ await nextTick();
+
+ expect(wrapper.findByText('Failed to load').exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/vue_merge_request_widget/deployment/deployment_actions_spec.js b/spec/frontend/vue_merge_request_widget/deployment/deployment_actions_spec.js
index a285d26f404..a8912405fa8 100644
--- a/spec/frontend/vue_merge_request_widget/deployment/deployment_actions_spec.js
+++ b/spec/frontend/vue_merge_request_widget/deployment/deployment_actions_spec.js
@@ -189,7 +189,7 @@ describe('DeploymentAction component', () => {
});
});
- describe('it should call the executeAction method ', () => {
+ describe('it should call the executeAction method', () => {
beforeEach(async () => {
jest.spyOn(wrapper.vm, 'executeAction').mockImplementation();
diff --git a/spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js b/spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js
index 5c1d3c8e8e8..82743275739 100644
--- a/spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js
+++ b/spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js
@@ -15,6 +15,7 @@ import { failedReport } from 'jest/reports/mock_data/mock_data';
import mixedResultsTestReports from 'jest/reports/mock_data/new_and_fixed_failures_report.json';
import newErrorsTestReports from 'jest/reports/mock_data/new_errors_report.json';
import newFailedTestReports from 'jest/reports/mock_data/new_failures_report.json';
+import newFailedTestWithNullFilesReport from 'jest/reports/mock_data/new_failures_with_null_files_report.json';
import successTestReports from 'jest/reports/mock_data/no_failures_report.json';
import resolvedFailures from 'jest/reports/mock_data/resolved_failures.json';
import recentFailures from 'jest/reports/mock_data/recent_failures_report.json';
@@ -157,6 +158,15 @@ describe('Test report extension', () => {
);
});
+ it('hides copy failed tests button when endpoint returns null files', async () => {
+ mockApi(httpStatusCodes.OK, newFailedTestWithNullFilesReport);
+ createComponent();
+
+ await waitForPromises();
+
+ expect(findCopyFailedSpecsBtn().exists()).toBe(false);
+ });
+
it('copy failed tests button updates tooltip text when clicked', async () => {
mockApi(httpStatusCodes.OK, newFailedTestReports);
createComponent();
diff --git a/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js b/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js
index 819841317f9..cc894f94f80 100644
--- a/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js
+++ b/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js
@@ -845,7 +845,7 @@ describe('MrWidgetOptions', () => {
${'closed'} | ${false} | ${'hides'}
${'merged'} | ${true} | ${'shows'}
${'open'} | ${true} | ${'shows'}
- `('it $showText merge error when state is $state', ({ state, show }) => {
+ `('$showText merge error when state is $state', ({ state, show }) => {
createComponent({ ...mockData, state, merge_error: 'Error!' });
expect(wrapper.find('[data-testid="merge_error"]').exists()).toBe(show);
@@ -1133,7 +1133,7 @@ describe('MrWidgetOptions', () => {
widgetName | nonStandardEvent
${'WidgetCodeQuality'} | ${'i_testing_code_quality_widget_total'}
${'WidgetTerraform'} | ${'i_testing_terraform_widget_total'}
- ${'WidgetIssues'} | ${'i_testing_load_performance_widget_total'}
+ ${'WidgetIssues'} | ${'i_testing_issues_widget_total'}
${'WidgetTestReport'} | ${'i_testing_summary_widget_total'}
`(
"sends non-standard events for the '$widgetName' widget",
diff --git a/spec/frontend/vue_merge_request_widget/stores/artifacts_list/actions_spec.js b/spec/frontend/vue_merge_request_widget/stores/artifacts_list/actions_spec.js
index 22562bb4ddb..1a109aad911 100644
--- a/spec/frontend/vue_merge_request_widget/stores/artifacts_list/actions_spec.js
+++ b/spec/frontend/vue_merge_request_widget/stores/artifacts_list/actions_spec.js
@@ -60,7 +60,7 @@ describe('Artifacts App Store Actions', () => {
});
describe('success', () => {
- it('dispatches requestArtifacts and receiveArtifactsSuccess ', () => {
+ it('dispatches requestArtifacts and receiveArtifactsSuccess', () => {
mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, [
{
text: 'result.txt',
@@ -103,7 +103,7 @@ describe('Artifacts App Store Actions', () => {
mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500);
});
- it('dispatches requestArtifacts and receiveArtifactsError ', () => {
+ it('dispatches requestArtifacts and receiveArtifactsError', () => {
return testAction(
fetchArtifacts,
null,