Commit 9222c789 authored by Filipa Lacerda's avatar Filipa Lacerda

Moves more mr widget components into vue files

Adds i18n
Adds better test coverage
parent a585ae27
import statusIcon from '../mr_widget_status_icon';
export default {
name: 'MRWidgetChecking',
components: {
statusIcon,
},
template: `
<div class="mr-widget-body media">
<status-icon status="loading" :show-disabled-button="true" />
<div class="media-body space-children">
<span class="bold">
Checking ability to merge automatically
</span>
</div>
</div>
`,
};
<script>
import statusIcon from '../mr_widget_status_icon';
export default {
name: 'MRWidgetChecking',
components: {
statusIcon,
},
};
</script>
<template>
<div class="mr-widget-body media">
<status-icon
status="loading"
:show-disabled-button="true"
/>
<div class="media-body space-children">
<span class="bold">
{{ s__("mrWidget|Checking ability to merge automatically") }}
</span>
</div>
</div>
</template>
import mrWidgetAuthorTime from '../../components/mr_widget_author_time';
import statusIcon from '../mr_widget_status_icon';
export default {
name: 'MRWidgetClosed',
props: {
mr: { type: Object, required: true },
},
components: {
'mr-widget-author-and-time': mrWidgetAuthorTime,
statusIcon,
},
template: `
<div class="mr-widget-body media">
<status-icon status="warning" />
<div class="media-body">
<mr-widget-author-and-time
actionText="Closed by"
:author="mr.metrics.closedBy"
:dateTitle="mr.metrics.closedAt"
:dateReadable="mr.metrics.readableClosedAt"
/>
<section class="mr-info-list">
<p>
The changes were not merged into
<a
:href="mr.targetBranchPath"
class="label-branch">
{{mr.targetBranch}}</a>
</p>
</section>
</div>
</div>
`,
};
<script>
import mrWidgetAuthorTime from '../../components/mr_widget_author_time';
import statusIcon from '../mr_widget_status_icon';
export default {
name: 'MRWidgetClosed',
components: {
mrWidgetAuthorTime,
statusIcon,
},
props: {
/* TODO: This is providing all store and service down when it
only needs metrics and targetBranch */
mr: {
type: Object,
required: true,
default: () => ({}),
},
},
};
</script>
<template>
<div class="mr-widget-body media">
<status-icon
status="warning"
/>
<div class="media-body">
<mr-widget-author-time
:action-text="s__('mrWidget|Closed by')"
:author="mr.metrics.closedBy"
:date-title="mr.metrics.closedAt"
:date-readable="mr.metrics.readableClosedAt"
/>
<section class="mr-info-list">
<p>
{{ s__("mrWidget|The changes were not merged into") }}
<a
:href="mr.targetBranchPath"
class="label-branch"
>
{{ mr.targetBranch }}
</a>
</p>
</section>
</div>
</div>
</template>
import statusIcon from '../mr_widget_status_icon';
export default {
name: 'MRWidgetConflicts',
props: {
mr: { type: Object, required: true },
},
components: {
statusIcon,
},
template: `
<div class="mr-widget-body media">
<status-icon
status="warning"
:show-disabled-button="true" />
<div class="media-body space-children">
<span
v-if="mr.shouldBeRebased"
class="bold">
Fast-forward merge is not possible.
To merge this request, first rebase locally.
</span>
<template v-else>
<span class="bold">
There are merge conflicts<span v-if="!mr.canMerge">.</span>
<span v-if="!mr.canMerge">
Resolve these conflicts or ask someone with write access to this repository to merge it locally
</span>
</span>
<a
v-if="mr.canMerge && mr.conflictResolutionPath"
:href="mr.conflictResolutionPath"
class="js-resolve-conflicts-button btn btn-default btn-xs">
Resolve conflicts
</a>
<a
v-if="mr.canMerge"
class="js-merge-locally-button btn btn-default btn-xs"
data-toggle="modal"
href="#modal_merge_info">
Merge locally
</a>
</template>
</div>
</div>
`,
};
<script>
import statusIcon from '../mr_widget_status_icon';
export default {
name: 'MRWidgetConflicts',
components: {
statusIcon,
},
props: {
/* TODO: This is providing all store and service down when it
only needs a few props */
mr: {
type: Object,
required: true,
default: () => ({}),
},
},
};
</script>
<template>
<div class="mr-widget-body media">
<status-icon
status="warning"
:show-disabled-button="true"
/>
<div class="media-body space-children">
<span
v-if="mr.shouldBeRebased"
class="bold"
>
{{ s__(`mrWidget|Fast-forward merge is not possible.
To merge this request, first rebase locally.`) }}
</span>
<template v-else>
<span class="bold">
{{ s__("mrWidget|There are merge conflicts") }}<span v-if="!mr.canMerge">.</span>
<span v-if="!mr.canMerge">
{{ s__(`mrWidget|Resolve these conflicts or ask someone
with write access to this repository to merge it locally`) }}
</span>
</span>
<a
v-if="mr.canMerge && mr.conflictResolutionPath"
:href="mr.conflictResolutionPath"
class="js-resolve-conflicts-button btn btn-default btn-xs"
>
{{ s__("mrWidget|Resolve conflicts") }}
</a>
<button
v-if="mr.canMerge"
class="js-merge-locally-button btn btn-default btn-xs"
data-toggle="modal"
data-target="#modal_merge_info"
>
{{ s__("mrWidget|Merge locally") }}
</button>
</template>
</div>
</div>
</template>
...@@ -18,11 +18,11 @@ export { default as WidgetDeployment } from './components/mr_widget_deployment'; ...@@ -18,11 +18,11 @@ export { default as WidgetDeployment } from './components/mr_widget_deployment';
export { default as WidgetRelatedLinks } from './components/mr_widget_related_links'; export { default as WidgetRelatedLinks } from './components/mr_widget_related_links';
export { default as MergedState } from './components/states/mr_widget_merged'; export { default as MergedState } from './components/states/mr_widget_merged';
export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge'; export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge';
export { default as ClosedState } from './components/states/mr_widget_closed'; export { default as ClosedState } from './components/states/mr_widget_closed.vue';
export { default as MergingState } from './components/states/mr_widget_merging'; export { default as MergingState } from './components/states/mr_widget_merging';
export { default as WipState } from './components/states/mr_widget_wip'; export { default as WipState } from './components/states/mr_widget_wip';
export { default as ArchivedState } from './components/states/mr_widget_archived.vue'; export { default as ArchivedState } from './components/states/mr_widget_archived.vue';
export { default as ConflictsState } from './components/states/mr_widget_conflicts'; export { default as ConflictsState } from './components/states/mr_widget_conflicts.vue';
export { default as NothingToMergeState } from './components/states/mr_widget_nothing_to_merge'; export { default as NothingToMergeState } from './components/states/mr_widget_nothing_to_merge';
export { default as MissingBranchState } from './components/states/mr_widget_missing_branch'; export { default as MissingBranchState } from './components/states/mr_widget_missing_branch';
export { default as NotAllowedState } from './components/states/mr_widget_not_allowed'; export { default as NotAllowedState } from './components/states/mr_widget_not_allowed';
...@@ -34,7 +34,7 @@ export { default as PipelineFailedState } from './components/states/mr_widget_pi ...@@ -34,7 +34,7 @@ export { default as PipelineFailedState } from './components/states/mr_widget_pi
export { default as MergeWhenPipelineSucceedsState } from './components/states/mr_widget_merge_when_pipeline_succeeds'; export { default as MergeWhenPipelineSucceedsState } from './components/states/mr_widget_merge_when_pipeline_succeeds';
export { default as RebaseState } from './components/states/mr_widget_rebase.vue'; export { default as RebaseState } from './components/states/mr_widget_rebase.vue';
export { default as AutoMergeFailed } from './components/states/mr_widget_auto_merge_failed.vue'; export { default as AutoMergeFailed } from './components/states/mr_widget_auto_merge_failed.vue';
export { default as CheckingState } from './components/states/mr_widget_checking'; export { default as CheckingState } from './components/states/mr_widget_checking.vue';
export { default as MRWidgetStore } from 'ee/vue_merge_request_widget/stores/mr_widget_store'; export { default as MRWidgetStore } from 'ee/vue_merge_request_widget/stores/mr_widget_store';
export { default as MRWidgetService } from 'ee/vue_merge_request_widget/services/mr_widget_service'; export { default as MRWidgetService } from 'ee/vue_merge_request_widget/services/mr_widget_service';
export { default as eventHub } from './event_hub'; export { default as eventHub } from './event_hub';
......
---
title: Refactors mr widget components into vue files and adds i18n
merge_request:
author:
type: other
import Vue from 'vue'; import Vue from 'vue';
import checkingComponent from '~/vue_merge_request_widget/components/states/mr_widget_checking'; import checkingComponent from '~/vue_merge_request_widget/components/states/mr_widget_checking.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
describe('MRWidgetChecking', () => { describe('MRWidgetChecking', () => {
describe('template', () => { let Component;
it('should have correct elements', () => { let vm;
const Component = Vue.extend(checkingComponent);
const el = new Component({
el: document.createElement('div'),
}).$el;
expect(el.classList.contains('mr-widget-body')).toBeTruthy(); beforeEach(() => {
expect(el.querySelector('button').classList.contains('btn-success')).toBeTruthy(); Component = Vue.extend(checkingComponent);
expect(el.querySelector('button').disabled).toBeTruthy(); vm = mountComponent(Component);
expect(el.innerText).toContain('Checking ability to merge automatically'); });
expect(el.querySelector('i')).toBeDefined();
}); afterEach(() => {
vm.$destroy();
});
it('renders disabled button', () => {
expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled');
});
it('renders loading icon', () => {
expect(vm.$el.querySelector('.mr-widget-icon i').classList).toContain('fa-spinner');
});
it('renders information about merging', () => {
expect(vm.$el.querySelector('.media-body').textContent.trim()).toEqual('Checking ability to merge automatically');
}); });
}); });
import Vue from 'vue'; import Vue from 'vue';
import closedComponent from '~/vue_merge_request_widget/components/states/mr_widget_closed'; import closedComponent from '~/vue_merge_request_widget/components/states/mr_widget_closed.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
const mr = {
targetBranch: 'good-branch',
targetBranchPath: '/good-branch',
metrics: {
mergedBy: {},
mergedAt: 'mergedUpdatedAt',
closedBy: {
name: 'Fatih Acet',
username: 'fatihacet',
},
closedAt: 'closedEventUpdatedAt',
readableMergedAt: '',
readableClosedAt: '',
},
updatedAt: 'mrUpdatedAt',
closedAt: '1 day ago',
};
const createComponent = () => {
const Component = Vue.extend(closedComponent);
return new Component({
el: document.createElement('div'),
propsData: { mr },
});
};
describe('MRWidgetClosed', () => { describe('MRWidgetClosed', () => {
describe('props', () => { let vm;
it('should have props', () => {
const mrProp = closedComponent.props.mr; beforeEach(() => {
const Component = Vue.extend(closedComponent);
expect(mrProp.type instanceof Object).toBeTruthy(); vm = mountComponent(Component, { mr: {
expect(mrProp.required).toBeTruthy(); 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 GMT+0000',
closedAt: 'Jan 24, 2018 1:02pm GMT+0000',
readableMergedAt: '',
readableClosedAt: 'less than a minute ago',
},
targetBranchPath: '/twitter/flight/commits/so_long_jquery',
targetBranch: 'so_long_jquery',
} });
}); });
describe('components', () => { afterEach(() => {
it('should have components added', () => { vm.$destroy();
expect(closedComponent.components['mr-widget-author-and-time']).toBeDefined();
});
}); });
describe('template', () => { it('renders warning icon', () => {
let vm; expect(vm.$el.querySelector('.js-ci-status-icon-warning')).not.toBeNull();
let el; });
beforeEach(() => { it('renders closed by information with author and time', () => {
vm = createComponent(); expect(
el = vm.$el; vm.$el.querySelector('.js-mr-widget-author').textContent.trim().replace(/\s\s+/g, ' '),
}); ).toContain(
'Closed by Administrator less than a minute ago',
);
});
afterEach(() => { it('links to the user that closed the MR', () => {
vm.$destroy(); expect(vm.$el.querySelector('.author-link').getAttribute('href')).toEqual('http://localhost:3000/root');
}); });
it('should have correct elements', () => { it('renders information about the changes not being merged', () => {
expect(el.querySelector('h4').textContent).toContain('Closed by'); expect(
expect(el.querySelector('h4').textContent).toContain(mr.metrics.closedBy.name); vm.$el.querySelector('.mr-info-list').textContent.trim().replace(/\s\s+/g, ' '),
expect(el.textContent).toContain('The changes were not merged into'); ).toContain('The changes were not merged into so_long_jquery');
expect(el.querySelector('.label-branch').getAttribute('href')).toEqual(mr.targetBranchPath); });
expect(el.querySelector('.label-branch').textContent).toContain(mr.targetBranch);
});
it('should use closedEvent updatedAt as tooltip title', () => { it('renders link for target branch', () => {
expect( expect(vm.$el.querySelector('.label-branch').getAttribute('href')).toEqual('/twitter/flight/commits/so_long_jquery');
el.querySelector('time').getAttribute('title'),
).toBe('closedEventUpdatedAt');
});
}); });
}); });
import Vue from 'vue'; import Vue from 'vue';
import conflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts'; import conflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper'; import mountComponent from '../../../helpers/vue_mount_component_helper';
const ConflictsComponent = Vue.extend(conflictsComponent);
const path = '/conflicts';
describe('MRWidgetConflicts', () => { describe('MRWidgetConflicts', () => {
describe('props', () => { let Component;
it('should have props', () => { let vm;
const { mr } = conflictsComponent.props; const path = '/conflicts';
expect(mr.type instanceof Object).toBeTruthy(); beforeEach(() => {
expect(mr.required).toBeTruthy(); Component = Vue.extend(conflictsComponent);
});
}); });
describe('template', () => { afterEach(() => {
describe('when allowed to merge', () => { vm.$destroy();
let vm; });
beforeEach(() => {
vm = mountComponent(ConflictsComponent, {
mr: {
canMerge: true,
conflictResolutionPath: path,
},
});
});
afterEach(() => {
vm.$destroy();
});
it('should tell you about conflicts without bothering other people', () => {
expect(vm.$el.textContent).toContain('There are merge conflicts');
expect(vm.$el.textContent).not.toContain('ask someone with write access');
});
it('should allow you to resolve the conflicts', () => {
const resolveButton = vm.$el.querySelector('.js-resolve-conflicts-button');
expect(resolveButton.textContent).toContain('Resolve conflicts'); describe('when allowed to merge', () => {
expect(resolveButton.getAttribute('href')).toEqual(path); beforeEach(() => {
vm = mountComponent(Component, {
mr: {
canMerge: true,
conflictResolutionPath: path,
},
}); });
});
it('should have merge buttons', () => { it('should tell you about conflicts without bothering other people', () => {
const mergeButton = vm.$el.querySelector('.js-disabled-merge-button'); expect(vm.$el.textContent).toContain('There are merge conflicts');
const mergeLocallyButton = vm.$el.querySelector('.js-merge-locally-button'); expect(vm.$el.textContent).not.toContain('ask someone with write access');
expect(mergeButton.textContent).toContain('Merge');
expect(mergeButton.disabled).toBeTruthy();
expect(mergeButton.classList.contains('btn-success')).toEqual(true);
expect(mergeLocallyButton.textContent).toContain('Merge locally');
});
}); });
describe('when user does not have permission to merge', () => { it('should allow you to resolve the conflicts', () => {
let vm; const resolveButton = vm.$el.querySelector('.js-resolve-conflicts-button');
beforeEach(() => { expect(resolveButton.textContent).toContain('Resolve conflicts');
vm = mountComponent(ConflictsComponent, { expect(resolveButton.getAttribute('href')).toEqual(path);
mr: { });
canMerge: false,
},
});
});
afterEach(() => { it('should have merge buttons', () => {
vm.$destroy(); const mergeButton = vm.$el.querySelector('.js-disabled-merge-button');
}); const mergeLocallyButton = vm.$el.querySelector('.js-merge-locally-button');
it('should show proper message', () => { expect(mergeButton.textContent).toContain('Merge');
expect(vm.$el.textContent).toContain('ask someone with write access'); expect(mergeButton.disabled).toBeTruthy();
}); expect(mergeButton.classList.contains('btn-success')).toEqual(true);
expect(mergeLocallyButton.textContent).toContain('Merge locally');
});
});
it('should not have action buttons', () => { describe('when user does not have permission to merge', () => {
expect(vm.$el.querySelector('.js-disabled-merge-button')).toBeDefined(); beforeEach(() => {
expect(vm.$el.querySelector('.js-resolve-conflicts-button')).toBeNull(); vm = mountComponent(Component, {
expect(vm.$el.querySelector('.js-merge-locally-button')).toBeNull(); mr: {
canMerge: false,
},
}); });
}); });
describe('when fast-forward or semi-linear merge enabled', () => { it('should show proper message', () => {
let vm; expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toContain('ask someone with write access');
});
beforeEach(() => { it('should not have action buttons', () => {
vm = mountComponent(ConflictsComponent, { expect(vm.$el.querySelector('.js-disabled-merge-button')).toBeDefined();
mr: { expect(vm.$el.querySelector('.js-resolve-conflicts-button')).toBeNull();
shouldBeRebased: true, expect(vm.$el.querySelector('.js-merge-locally-button')).toBeNull();
}, });
}); });
});
afterEach(() => { describe('when fast-forward or semi-linear merge enabled', () => {
vm.$destroy(); beforeEach(() => {
vm = mountComponent(Component, {
mr: {
shouldBeRebased: true,
},
}); });
});
it('should tell you to rebase locally', () => { it('should tell you to rebase locally', () => {
expect(vm.$el.textContent).toContain('Fast-forward merge is not possible.'); expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toContain('Fast-forward merge is not possible.');
expect(vm.$el.textContent).toContain('To merge this request, first rebase locally'); expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toContain('To merge this request, first rebase locally');
});
}); });
}); });
}); });
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