Commit e1c57877 authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch '14615-add-compare-repo-dropdown' into 'master'

Restyle compare revision and add repo dropdown [RUN ALL RSPEC] [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!52967
parents 6d32216a 66b9aa80
<script> <script>
import { GlButton } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import csrf from '~/lib/utils/csrf'; import csrf from '~/lib/utils/csrf';
import RevisionDropdown from './revision_dropdown.vue'; import RevisionCard from './revision_card.vue';
export default { export default {
csrf, csrf,
components: { components: {
RevisionDropdown, RevisionCard,
GlButton, GlButton,
}, },
props: { props: {
...@@ -48,32 +48,42 @@ export default { ...@@ -48,32 +48,42 @@ export default {
<template> <template>
<form <form
ref="form" ref="form"
class="form-inline js-requires-input js-signature-container" class="js-requires-input js-signature-container"
method="POST" method="POST"
:action="projectCompareIndexPath" :action="projectCompareIndexPath"
> >
<input :value="$options.csrf.token" type="hidden" name="authenticity_token" /> <input :value="$options.csrf.token" type="hidden" name="authenticity_token" />
<revision-dropdown <div
class="gl-lg-flex-direction-row gl-lg-display-flex gl-align-items-center compare-revision-cards"
>
<revision-card
:refs-project-path="refsProjectPath" :refs-project-path="refsProjectPath"
revision-text="Source" revision-text="Source"
params-name="to" params-name="to"
:params-branch="paramsTo" :params-branch="paramsTo"
/> />
<div class="compare-ellipsis gl-display-inline" data-testid="ellipsis">...</div> <div
<revision-dropdown class="compare-ellipsis gl-display-flex gl-justify-content-center gl-align-items-center gl-my-4 gl-md-my-0"
data-testid="ellipsis"
>
...
</div>
<revision-card
:refs-project-path="refsProjectPath" :refs-project-path="refsProjectPath"
revision-text="Target" revision-text="Target"
params-name="from" params-name="from"
:params-branch="paramsFrom" :params-branch="paramsFrom"
/> />
<gl-button category="primary" variant="success" class="gl-ml-3" @click="onSubmit"> </div>
<div class="gl-mt-4">
<gl-button category="primary" variant="success" @click="onSubmit">
{{ s__('CompareRevisions|Compare') }} {{ s__('CompareRevisions|Compare') }}
</gl-button> </gl-button>
<gl-button <gl-button
v-if="projectMergeRequestPath" v-if="projectMergeRequestPath"
:href="projectMergeRequestPath" :href="projectMergeRequestPath"
data-testid="projectMrButton" data-testid="projectMrButton"
class="btn btn-default gl-button gl-ml-3" class="btn btn-default gl-button"
> >
{{ s__('CompareRevisions|View open merge request') }} {{ s__('CompareRevisions|View open merge request') }}
</gl-button> </gl-button>
...@@ -81,9 +91,10 @@ export default { ...@@ -81,9 +91,10 @@ export default {
v-else-if="createMrPath" v-else-if="createMrPath"
:href="createMrPath" :href="createMrPath"
data-testid="createMrButton" data-testid="createMrButton"
class="btn btn-default gl-button gl-ml-3" class="btn btn-default gl-button"
> >
{{ s__('CompareRevisions|Create merge request') }} {{ s__('CompareRevisions|Create merge request') }}
</gl-button> </gl-button>
</div>
</form> </form>
</template> </template>
<script>
import { GlButton } from '@gitlab/ui';
import csrf from '~/lib/utils/csrf';
import RevisionDropdown from './revision_dropdown_legacy.vue';
export default {
csrf,
components: {
RevisionDropdown,
GlButton,
},
props: {
projectCompareIndexPath: {
type: String,
required: true,
},
refsProjectPath: {
type: String,
required: true,
},
paramsFrom: {
type: String,
required: false,
default: null,
},
paramsTo: {
type: String,
required: false,
default: null,
},
projectMergeRequestPath: {
type: String,
required: true,
},
createMrPath: {
type: String,
required: true,
},
},
methods: {
onSubmit() {
this.$refs.form.submit();
},
},
};
</script>
<template>
<form
ref="form"
class="form-inline js-requires-input js-signature-container"
method="POST"
:action="projectCompareIndexPath"
>
<input :value="$options.csrf.token" type="hidden" name="authenticity_token" />
<revision-dropdown
:refs-project-path="refsProjectPath"
revision-text="Source"
params-name="to"
:params-branch="paramsTo"
/>
<div class="compare-ellipsis gl-display-inline" data-testid="ellipsis">...</div>
<revision-dropdown
:refs-project-path="refsProjectPath"
revision-text="Target"
params-name="from"
:params-branch="paramsFrom"
/>
<gl-button category="primary" variant="success" class="gl-ml-3" @click="onSubmit">
{{ s__('CompareRevisions|Compare') }}
</gl-button>
<gl-button
v-if="projectMergeRequestPath"
:href="projectMergeRequestPath"
data-testid="projectMrButton"
class="btn btn-default gl-button gl-ml-3"
>
{{ s__('CompareRevisions|View open merge request') }}
</gl-button>
<gl-button
v-else-if="createMrPath"
:href="createMrPath"
data-testid="createMrButton"
class="btn btn-default gl-button gl-ml-3"
>
{{ s__('CompareRevisions|Create merge request') }}
</gl-button>
</form>
</template>
<script>
import { GlDropdown, GlDropdownItem, GlSearchBoxByType } from '@gitlab/ui';
const SOURCE_PARAM_NAME = 'to';
export default {
components: {
GlDropdown,
GlDropdownItem,
GlSearchBoxByType,
},
inject: ['projectTo', 'projectsFrom'],
props: {
paramsName: {
type: String,
required: true,
},
},
data() {
return {
searchTerm: '',
selectedRepo: {},
};
},
computed: {
filteredRepos() {
const lowerCaseSearchTerm = this.searchTerm.toLowerCase();
return this?.projectsFrom.filter(({ name }) =>
name.toLowerCase().includes(lowerCaseSearchTerm),
);
},
isSourceRevision() {
return this.paramsName === SOURCE_PARAM_NAME;
},
inputName() {
return `${this.paramsName}_project_id`;
},
},
mounted() {
this.setDefaultRepo();
},
methods: {
onClick(repo) {
this.selectedRepo = repo;
this.emitTargetProject(repo.name);
},
setDefaultRepo() {
if (this.isSourceRevision) {
this.selectedRepo = this.projectTo;
return;
}
const [defaultTargetProject] = this.projectsFrom;
this.emitTargetProject(defaultTargetProject.name);
this.selectedRepo = defaultTargetProject;
},
emitTargetProject(name) {
if (!this.isSourceRevision) {
this.$emit('changeTargetProject', name);
}
},
},
};
</script>
<template>
<div>
<input type="hidden" :name="inputName" :value="selectedRepo.id" />
<gl-dropdown
:text="selectedRepo.name"
:header-text="s__(`CompareRevisions|Select target project`)"
class="gl-w-full gl-font-monospace gl-sm-pr-3"
toggle-class="gl-min-w-0"
:disabled="isSourceRevision"
>
<template #header>
<gl-search-box-by-type v-if="!isSourceRevision" v-model.trim="searchTerm" />
</template>
<template v-if="!isSourceRevision">
<gl-dropdown-item
v-for="repo in filteredRepos"
:key="repo.id"
is-check-item
:is-checked="selectedRepo.id === repo.id"
@click="onClick(repo)"
>
{{ repo.name }}
</gl-dropdown-item>
</template>
</gl-dropdown>
</div>
</template>
<script>
import { GlCard } from '@gitlab/ui';
import RepoDropdown from './repo_dropdown.vue';
import RevisionDropdown from './revision_dropdown.vue';
export default {
components: {
RepoDropdown,
RevisionDropdown,
GlCard,
},
props: {
refsProjectPath: {
type: String,
required: true,
},
revisionText: {
type: String,
required: true,
},
paramsName: {
type: String,
required: true,
},
paramsBranch: {
type: String,
required: false,
default: null,
},
},
data() {
return {
selectedRefsProjectPath: this.refsProjectPath,
};
},
methods: {
onChangeTargetProject(targetProjectName) {
if (this.paramsName === 'from') {
this.selectedRefsProjectPath = `/${targetProjectName}/refs`;
}
},
},
};
</script>
<template>
<gl-card header-class="gl-py-2 gl-px-3 gl-font-weight-bold" body-class="gl-px-3">
<template #header>
{{ s__(`CompareRevisions|${revisionText}`) }}
</template>
<div class="gl-sm-display-flex gl-align-items-center">
<repo-dropdown
class="gl-sm-w-half"
:params-name="paramsName"
@changeTargetProject="onChangeTargetProject"
/>
<revision-dropdown
class="gl-sm-w-half gl-mt-3 gl-sm-mt-0"
:refs-project-path="selectedRefsProjectPath"
:params-name="paramsName"
:params-branch="paramsBranch"
/>
</div>
</gl-card>
</template>
...@@ -4,6 +4,8 @@ import createFlash from '~/flash'; ...@@ -4,6 +4,8 @@ import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
const emptyDropdownText = s__('CompareRevisions|Select branch/tag');
export default { export default {
components: { components: {
GlDropdown, GlDropdown,
...@@ -16,10 +18,6 @@ export default { ...@@ -16,10 +18,6 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
revisionText: {
type: String,
required: true,
},
paramsName: { paramsName: {
type: String, type: String,
required: true, required: true,
...@@ -55,12 +53,24 @@ export default { ...@@ -55,12 +53,24 @@ export default {
return this.filteredTags.length; return this.filteredTags.length;
}, },
}, },
watch: {
refsProjectPath(newRefsProjectPath, oldRefsProjectPath) {
if (newRefsProjectPath !== oldRefsProjectPath) {
this.fetchBranchesAndTags(true);
}
},
},
mounted() { mounted() {
this.fetchBranchesAndTags(); this.fetchBranchesAndTags();
}, },
methods: { methods: {
fetchBranchesAndTags() { fetchBranchesAndTags(reset = false) {
const endpoint = this.refsProjectPath; const endpoint = this.refsProjectPath;
this.loading = true;
if (reset) {
this.selectedRevision = emptyDropdownText;
}
return axios return axios
.get(endpoint) .get(endpoint)
...@@ -70,9 +80,9 @@ export default { ...@@ -70,9 +80,9 @@ export default {
}) })
.catch(() => { .catch(() => {
createFlash({ createFlash({
message: `${s__( message: s__(
'CompareRevisions|There was an error while updating the branch/tag list. Please try again.', 'CompareRevisions|There was an error while loading the branch/tag list. Please try again.',
)}`, ),
}); });
}) })
.finally(() => { .finally(() => {
...@@ -80,7 +90,7 @@ export default { ...@@ -80,7 +90,7 @@ export default {
}); });
}, },
getDefaultBranch() { getDefaultBranch() {
return this.paramsBranch || s__('CompareRevisions|Select branch/tag'); return this.paramsBranch || emptyDropdownText;
}, },
onClick(revision) { onClick(revision) {
this.selectedRevision = revision; this.selectedRevision = revision;
...@@ -93,19 +103,13 @@ export default { ...@@ -93,19 +103,13 @@ export default {
</script> </script>
<template> <template>
<div class="form-group compare-form-group" :class="`js-compare-${paramsName}-dropdown`"> <div :class="`js-compare-${paramsName}-dropdown`">
<div class="input-group inline-input-group">
<span class="input-group-prepend">
<div class="input-group-text">
{{ revisionText }}
</div>
</span>
<input type="hidden" :name="paramsName" :value="selectedRevision" /> <input type="hidden" :name="paramsName" :value="selectedRevision" />
<gl-dropdown <gl-dropdown
class="gl-flex-grow-1 gl-flex-basis-0 gl-min-w-0 gl-font-monospace" class="gl-w-full gl-font-monospace"
toggle-class="form-control compare-dropdown-toggle js-compare-dropdown gl-min-w-0 gl-rounded-top-left-none! gl-rounded-bottom-left-none!" toggle-class="form-control compare-dropdown-toggle js-compare-dropdown gl-min-w-0"
:text="selectedRevision" :text="selectedRevision"
header-text="Select Git revision" :header-text="s__('CompareRevisions|Select Git revision')"
:loading="loading" :loading="loading"
> >
<template #header> <template #header>
...@@ -141,5 +145,4 @@ export default { ...@@ -141,5 +145,4 @@ export default {
</gl-dropdown-item> </gl-dropdown-item>
</gl-dropdown> </gl-dropdown>
</div> </div>
</div>
</template> </template>
<script>
import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlDropdownSectionHeader } from '@gitlab/ui';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { s__ } from '~/locale';
export default {
components: {
GlDropdown,
GlDropdownItem,
GlDropdownSectionHeader,
GlSearchBoxByType,
},
props: {
refsProjectPath: {
type: String,
required: true,
},
revisionText: {
type: String,
required: true,
},
paramsName: {
type: String,
required: true,
},
paramsBranch: {
type: String,
required: false,
default: null,
},
},
data() {
return {
branches: [],
tags: [],
loading: true,
searchTerm: '',
selectedRevision: this.getDefaultBranch(),
};
},
computed: {
filteredBranches() {
return this.branches.filter((branch) =>
branch.toLowerCase().includes(this.searchTerm.toLowerCase()),
);
},
hasFilteredBranches() {
return this.filteredBranches.length;
},
filteredTags() {
return this.tags.filter((tag) => tag.toLowerCase().includes(this.searchTerm.toLowerCase()));
},
hasFilteredTags() {
return this.filteredTags.length;
},
},
mounted() {
this.fetchBranchesAndTags();
},
methods: {
fetchBranchesAndTags() {
const endpoint = this.refsProjectPath;
return axios
.get(endpoint)
.then(({ data }) => {
this.branches = data.Branches;
this.tags = data.Tags;
})
.catch(() => {
createFlash({
message: `${s__(
'CompareRevisions|There was an error while updating the branch/tag list. Please try again.',
)}`,
});
})
.finally(() => {
this.loading = false;
});
},
getDefaultBranch() {
return this.paramsBranch || s__('CompareRevisions|Select branch/tag');
},
onClick(revision) {
this.selectedRevision = revision;
},
onSearchEnter() {
this.selectedRevision = this.searchTerm;
},
},
};
</script>
<template>
<div class="form-group compare-form-group" :class="`js-compare-${paramsName}-dropdown`">
<div class="input-group inline-input-group">
<span class="input-group-prepend">
<div class="input-group-text">
{{ revisionText }}
</div>
</span>
<input type="hidden" :name="paramsName" :value="selectedRevision" />
<gl-dropdown
class="gl-flex-grow-1 gl-flex-basis-0 gl-min-w-0 gl-font-monospace"
toggle-class="form-control compare-dropdown-toggle js-compare-dropdown gl-min-w-0 gl-rounded-top-left-none! gl-rounded-bottom-left-none!"
:text="selectedRevision"
header-text="Select Git revision"
:loading="loading"
>
<template #header>
<gl-search-box-by-type
v-model.trim="searchTerm"
:placeholder="s__('CompareRevisions|Filter by Git revision')"
@keyup.enter="onSearchEnter"
/>
</template>
<gl-dropdown-section-header v-if="hasFilteredBranches">
{{ s__('CompareRevisions|Branches') }}
</gl-dropdown-section-header>
<gl-dropdown-item
v-for="(branch, index) in filteredBranches"
:key="`branch${index}`"
is-check-item
:is-checked="selectedRevision === branch"
@click="onClick(branch)"
>
{{ branch }}
</gl-dropdown-item>
<gl-dropdown-section-header v-if="hasFilteredTags">
{{ s__('CompareRevisions|Tags') }}
</gl-dropdown-section-header>
<gl-dropdown-item
v-for="(tag, index) in filteredTags"
:key="`tag${index}`"
is-check-item
:is-checked="selectedRevision === tag"
@click="onClick(tag)"
>
{{ tag }}
</gl-dropdown-item>
</gl-dropdown>
</div>
</div>
</template>
import Vue from 'vue'; import Vue from 'vue';
import CompareApp from './components/app.vue'; import CompareApp from './components/app.vue';
import CompareAppLegacy from './components/app_legacy.vue';
export default function init() { export default function init() {
const el = document.getElementById('js-compare-selector'); const el = document.getElementById('js-compare-selector');
if (gon.features?.compareRepoDropdown) {
const { const {
refsProjectPath, refsProjectPath,
paramsFrom, paramsFrom,
...@@ -10,6 +13,8 @@ export default function init() { ...@@ -10,6 +13,8 @@ export default function init() {
projectCompareIndexPath, projectCompareIndexPath,
projectMergeRequestPath, projectMergeRequestPath,
createMrPath, createMrPath,
projectTo,
projectsFrom,
} = el.dataset; } = el.dataset;
return new Vue({ return new Vue({
...@@ -17,6 +22,10 @@ export default function init() { ...@@ -17,6 +22,10 @@ export default function init() {
components: { components: {
CompareApp, CompareApp,
}, },
provide: {
projectTo: JSON.parse(projectTo),
projectsFrom: JSON.parse(projectsFrom),
},
render(createElement) { render(createElement) {
return createElement(CompareApp, { return createElement(CompareApp, {
props: { props: {
...@@ -30,4 +39,33 @@ export default function init() { ...@@ -30,4 +39,33 @@ export default function init() {
}); });
}, },
}); });
}
const {
refsProjectPath,
paramsFrom,
paramsTo,
projectCompareIndexPath,
projectMergeRequestPath,
createMrPath,
} = el.dataset;
return new Vue({
el,
components: {
CompareAppLegacy,
},
render(createElement) {
return createElement(CompareAppLegacy, {
props: {
refsProjectPath,
paramsFrom,
paramsTo,
projectCompareIndexPath,
projectMergeRequestPath,
createMrPath,
},
});
},
});
} }
...@@ -1008,6 +1008,18 @@ pre.light-well { ...@@ -1008,6 +1008,18 @@ pre.light-well {
} }
} }
.compare-revision-cards {
@media (min-width: $breakpoint-lg) {
.gl-card {
width: calc(50% - 15px);
}
.compare-ellipsis {
width: 30px;
}
}
}
.clearable-input { .clearable-input {
position: relative; position: relative;
......
...@@ -157,3 +157,17 @@ ...@@ -157,3 +157,17 @@
margin-bottom: $gl-spacing-scale-4 !important; margin-bottom: $gl-spacing-scale-4 !important;
} }
} }
// Will be moved to @gitlab/ui in https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1168
.gl-sm-pr-3 {
@media (min-width: $breakpoint-sm) {
padding-right: $gl-spacing-scale-3;
}
}
// Will be moved to @gitlab/ui in https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1168
.gl-sm-w-half {
@media (min-width: $breakpoint-sm) {
width: 50%;
}
}
...@@ -20,6 +20,10 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -20,6 +20,10 @@ class Projects::CompareController < Projects::ApplicationController
# Validation # Validation
before_action :validate_refs! before_action :validate_refs!
before_action do
push_frontend_feature_flag(:compare_repo_dropdown)
end
feature_category :source_code_management feature_category :source_code_management
def index def index
......
...@@ -13,6 +13,15 @@ ...@@ -13,6 +13,15 @@
= html_escape(_("Changes are shown as if the %{b_open}source%{b_close} revision was being merged into the %{b_open}target%{b_close} revision.")) % { b_open: '<b>'.html_safe, b_close: '</b>'.html_safe } = html_escape(_("Changes are shown as if the %{b_open}source%{b_close} revision was being merged into the %{b_open}target%{b_close} revision.")) % { b_open: '<b>'.html_safe, b_close: '</b>'.html_safe }
.prepend-top-20 .prepend-top-20
- if Feature.enabled?(:compare_repo_dropdown)
#js-compare-selector{ data: { project_compare_index_path: project_compare_index_path(@project),
refs_project_path: refs_project_path(@project),
params_from: params[:from], params_to: params[:to],
project_merge_request_path: @merge_request.present? ? project_merge_request_path(@project, @merge_request) : '',
create_mr_path: create_mr_button? ? create_mr_path : '',
project_to: { id: @project.id, name: @project.full_path }.to_json,
projects_from: target_projects(@project).map { |project| { id:project.id, name: project.full_path } }.to_json } }
- else
#js-compare-selector{ data: { project_compare_index_path: project_compare_index_path(@project), #js-compare-selector{ data: { project_compare_index_path: project_compare_index_path(@project),
refs_project_path: refs_project_path(@project), refs_project_path: refs_project_path(@project),
params_from: params[:from], params_to: params[:to], params_from: params[:from], params_to: params[:to],
......
...@@ -7569,12 +7569,21 @@ msgstr "" ...@@ -7569,12 +7569,21 @@ msgstr ""
msgid "CompareRevisions|Filter by Git revision" msgid "CompareRevisions|Filter by Git revision"
msgstr "" msgstr ""
msgid "CompareRevisions|Select Git revision"
msgstr ""
msgid "CompareRevisions|Select branch/tag" msgid "CompareRevisions|Select branch/tag"
msgstr "" msgstr ""
msgid "CompareRevisions|Select target project"
msgstr ""
msgid "CompareRevisions|Tags" msgid "CompareRevisions|Tags"
msgstr "" msgstr ""
msgid "CompareRevisions|There was an error while loading the branch/tag list. Please try again."
msgstr ""
msgid "CompareRevisions|There was an error while updating the branch/tag list. Please try again." msgid "CompareRevisions|There was an error while updating the branch/tag list. Please try again."
msgstr "" msgstr ""
......
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import CompareApp from '~/projects/compare/components/app_legacy.vue';
import RevisionDropdown from '~/projects/compare/components/revision_dropdown_legacy.vue';
jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
const projectCompareIndexPath = 'some/path';
const refsProjectPath = 'some/refs/path';
const paramsFrom = 'master';
const paramsTo = 'master';
describe('CompareApp component', () => {
let wrapper;
const createComponent = (props = {}) => {
wrapper = shallowMount(CompareApp, {
propsData: {
projectCompareIndexPath,
refsProjectPath,
paramsFrom,
paramsTo,
projectMergeRequestPath: '',
createMrPath: '',
...props,
},
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
beforeEach(() => {
createComponent();
});
it('renders component with prop', () => {
expect(wrapper.props()).toEqual(
expect.objectContaining({
projectCompareIndexPath,
refsProjectPath,
paramsFrom,
paramsTo,
}),
);
});
it('contains the correct form attributes', () => {
expect(wrapper.attributes('action')).toBe(projectCompareIndexPath);
expect(wrapper.attributes('method')).toBe('POST');
});
it('has input with csrf token', () => {
expect(wrapper.find('input[name="authenticity_token"]').attributes('value')).toBe(
'mock-csrf-token',
);
});
it('has ellipsis', () => {
expect(wrapper.find('[data-testid="ellipsis"]').exists()).toBe(true);
});
it('render Source and Target BranchDropdown components', () => {
const branchDropdowns = wrapper.findAll(RevisionDropdown);
expect(branchDropdowns.length).toBe(2);
expect(branchDropdowns.at(0).props('revisionText')).toBe('Source');
expect(branchDropdowns.at(1).props('revisionText')).toBe('Target');
});
describe('compare button', () => {
const findCompareButton = () => wrapper.find(GlButton);
it('renders button', () => {
expect(findCompareButton().exists()).toBe(true);
});
it('submits form', () => {
findCompareButton().vm.$emit('click');
expect(wrapper.find('form').element.submit).toHaveBeenCalled();
});
it('has compare text', () => {
expect(findCompareButton().text()).toBe('Compare');
});
});
describe('merge request buttons', () => {
const findProjectMrButton = () => wrapper.find('[data-testid="projectMrButton"]');
const findCreateMrButton = () => wrapper.find('[data-testid="createMrButton"]');
it('does not have merge request buttons', () => {
createComponent();
expect(findProjectMrButton().exists()).toBe(false);
expect(findCreateMrButton().exists()).toBe(false);
});
it('has "View open merge request" button', () => {
createComponent({
projectMergeRequestPath: 'some/project/merge/request/path',
});
expect(findProjectMrButton().exists()).toBe(true);
expect(findCreateMrButton().exists()).toBe(false);
});
it('has "Create merge request" button', () => {
createComponent({
createMrPath: 'some/create/create/mr/path',
});
expect(findProjectMrButton().exists()).toBe(false);
expect(findCreateMrButton().exists()).toBe(true);
});
});
});
import { GlButton } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import CompareApp from '~/projects/compare/components/app.vue'; import CompareApp from '~/projects/compare/components/app.vue';
import RevisionDropdown from '~/projects/compare/components/revision_dropdown.vue'; import RevisionCard from '~/projects/compare/components/revision_card.vue';
jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' })); jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
...@@ -63,11 +63,11 @@ describe('CompareApp component', () => { ...@@ -63,11 +63,11 @@ describe('CompareApp component', () => {
}); });
it('render Source and Target BranchDropdown components', () => { it('render Source and Target BranchDropdown components', () => {
const branchDropdowns = wrapper.findAll(RevisionDropdown); const revisionCards = wrapper.findAll(RevisionCard);
expect(branchDropdowns.length).toBe(2); expect(revisionCards.length).toBe(2);
expect(branchDropdowns.at(0).props('revisionText')).toBe('Source'); expect(revisionCards.at(0).props('revisionText')).toBe('Source');
expect(branchDropdowns.at(1).props('revisionText')).toBe('Target'); expect(revisionCards.at(1).props('revisionText')).toBe('Target');
}); });
describe('compare button', () => { describe('compare button', () => {
......
import { GlDropdown } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import RepoDropdown from '~/projects/compare/components/repo_dropdown.vue';
const defaultProps = {
paramsName: 'to',
};
const projectToId = '1';
const projectToName = 'some-to-name';
const projectFromId = '2';
const projectFromName = 'some-from-name';
const defaultProvide = {
projectTo: { id: projectToId, name: projectToName },
projectsFrom: [
{ id: projectFromId, name: projectFromName },
{ id: 3, name: 'some-from-another-name' },
],
};
describe('RepoDropdown component', () => {
let wrapper;
const createComponent = (props = {}, provide = {}) => {
wrapper = shallowMount(RepoDropdown, {
propsData: {
...defaultProps,
...props,
},
provide: {
...defaultProvide,
...provide,
},
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
const findGlDropdown = () => wrapper.find(GlDropdown);
const findHiddenInput = () => wrapper.find('input[type="hidden"]');
describe('Source Revision', () => {
beforeEach(() => {
createComponent();
});
it('set hidden input', () => {
expect(findHiddenInput().attributes('value')).toBe(projectToId);
});
it('displays the project name in the disabled dropdown', () => {
expect(findGlDropdown().props('text')).toBe(projectToName);
expect(findGlDropdown().props('disabled')).toBe(true);
});
it('does not emit `changeTargetProject` event', async () => {
wrapper.vm.emitTargetProject('foo');
await wrapper.vm.$nextTick();
expect(wrapper.emitted('changeTargetProject')).toBeUndefined();
});
});
describe('Target Revision', () => {
beforeEach(() => {
createComponent({ paramsName: 'from' });
});
it('set hidden input of the first project', () => {
expect(findHiddenInput().attributes('value')).toBe(projectFromId);
});
it('displays the first project name initially in the dropdown', () => {
expect(findGlDropdown().props('text')).toBe(projectFromName);
});
it('updates the hiddin input value when onClick method is triggered', async () => {
const repoId = '100';
wrapper.vm.onClick({ id: repoId });
await wrapper.vm.$nextTick();
expect(findHiddenInput().attributes('value')).toBe(repoId);
});
it('emits initial `changeTargetProject` event with target project', () => {
expect(wrapper.emitted('changeTargetProject')).toEqual([[projectFromName]]);
});
it('emits `changeTargetProject` event when another target project is selected', async () => {
const newTargetProject = 'new-from-name';
wrapper.vm.$emit('changeTargetProject', newTargetProject);
await wrapper.vm.$nextTick();
expect(wrapper.emitted('changeTargetProject')[1]).toEqual([newTargetProject]);
});
});
});
import { GlCard } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import RepoDropdown from '~/projects/compare/components/repo_dropdown.vue';
import RevisionCard from '~/projects/compare/components/revision_card.vue';
import RevisionDropdown from '~/projects/compare/components/revision_dropdown.vue';
const defaultProps = {
refsProjectPath: 'some/refs/path',
revisionText: 'Source',
paramsName: 'to',
paramsBranch: 'master',
};
describe('RepoDropdown component', () => {
let wrapper;
const createComponent = (props = {}) => {
wrapper = shallowMount(RevisionCard, {
propsData: {
...defaultProps,
...props,
},
stubs: {
GlCard,
},
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
beforeEach(() => {
createComponent();
});
it('displays revision text', () => {
expect(wrapper.find(GlCard).text()).toContain(defaultProps.revisionText);
});
it('renders RepoDropdown component', () => {
expect(wrapper.findAll(RepoDropdown).exists()).toBe(true);
});
it('renders RevisionDropdown component', () => {
expect(wrapper.findAll(RevisionDropdown).exists()).toBe(true);
});
});
import { GlDropdown } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import RevisionDropdown from '~/projects/compare/components/revision_dropdown_legacy.vue';
const defaultProps = {
refsProjectPath: 'some/refs/path',
revisionText: 'Target',
paramsName: 'from',
paramsBranch: 'master',
};
jest.mock('~/flash');
describe('RevisionDropdown component', () => {
let wrapper;
let axiosMock;
const createComponent = (props = {}) => {
wrapper = shallowMount(RevisionDropdown, {
propsData: {
...defaultProps,
...props,
},
});
};
beforeEach(() => {
axiosMock = new AxiosMockAdapter(axios);
});
afterEach(() => {
wrapper.destroy();
axiosMock.restore();
});
const findGlDropdown = () => wrapper.find(GlDropdown);
it('sets hidden input', () => {
createComponent();
expect(wrapper.find('input[type="hidden"]').attributes('value')).toBe(
defaultProps.paramsBranch,
);
});
it('update the branches on success', async () => {
const Branches = ['branch-1', 'branch-2'];
const Tags = ['tag-1', 'tag-2', 'tag-3'];
axiosMock.onGet(defaultProps.refsProjectPath).replyOnce(200, {
Branches,
Tags,
});
createComponent();
await axios.waitForAll();
expect(wrapper.vm.branches).toEqual(Branches);
expect(wrapper.vm.tags).toEqual(Tags);
});
it('shows flash message on error', async () => {
axiosMock.onGet('some/invalid/path').replyOnce(404);
createComponent();
await wrapper.vm.fetchBranchesAndTags();
expect(createFlash).toHaveBeenCalled();
});
describe('GlDropdown component', () => {
it('renders props', () => {
createComponent();
expect(wrapper.props()).toEqual(expect.objectContaining(defaultProps));
});
it('display default text', () => {
createComponent({
paramsBranch: null,
});
expect(findGlDropdown().props('text')).toBe('Select branch/tag');
});
it('display params branch text', () => {
createComponent();
expect(findGlDropdown().props('text')).toBe(defaultProps.paramsBranch);
});
});
});
...@@ -7,7 +7,6 @@ import RevisionDropdown from '~/projects/compare/components/revision_dropdown.vu ...@@ -7,7 +7,6 @@ import RevisionDropdown from '~/projects/compare/components/revision_dropdown.vu
const defaultProps = { const defaultProps = {
refsProjectPath: 'some/refs/path', refsProjectPath: 'some/refs/path',
revisionText: 'Target',
paramsName: 'from', paramsName: 'from',
paramsBranch: 'master', paramsBranch: 'master',
}; };
...@@ -57,7 +56,6 @@ describe('RevisionDropdown component', () => { ...@@ -57,7 +56,6 @@ describe('RevisionDropdown component', () => {
createComponent(); createComponent();
await axios.waitForAll(); await axios.waitForAll();
expect(wrapper.vm.branches).toEqual(Branches); expect(wrapper.vm.branches).toEqual(Branches);
expect(wrapper.vm.tags).toEqual(Tags); expect(wrapper.vm.tags).toEqual(Tags);
}); });
...@@ -71,6 +69,22 @@ describe('RevisionDropdown component', () => { ...@@ -71,6 +69,22 @@ describe('RevisionDropdown component', () => {
expect(createFlash).toHaveBeenCalled(); expect(createFlash).toHaveBeenCalled();
}); });
it('makes a new request when refsProjectPath is changed', async () => {
jest.spyOn(axios, 'get');
const newRefsProjectPath = 'new-selected-project-path';
createComponent();
wrapper.setProps({
...defaultProps,
refsProjectPath: newRefsProjectPath,
});
await axios.waitForAll();
expect(axios.get).toHaveBeenLastCalledWith(newRefsProjectPath);
});
describe('GlDropdown component', () => { describe('GlDropdown component', () => {
it('renders props', () => { it('renders props', () => {
createComponent(); createComponent();
......
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