Commit 8f03ffa2 authored by Phil Hughes's avatar Phil Hughes

Merge branch 'cngo-delete-unused-code' into 'master'

Delete unused sidebar code

See merge request gitlab-org/gitlab!79743
parents 571fcbff 887612fd
import Store from 'ee_else_ce/sidebar/stores/sidebar_store'; import Store from '~/sidebar/stores/sidebar_store';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import toast from '~/vue_shared/plugins/global_toast'; import toast from '~/vue_shared/plugins/global_toast';
......
<script>
import { GlDatepicker } from '@gitlab/ui';
import { pikadayToString } from '~/lib/utils/datetime_utility';
export default {
name: 'DatePicker',
components: {
GlDatepicker,
},
props: {
selectedDate: {
type: Date,
required: false,
default: null,
},
minDate: {
type: Date,
required: false,
default: null,
},
maxDate: {
type: Date,
required: false,
default: null,
},
},
methods: {
selected(date) {
this.$emit('newDateSelected', pikadayToString(date));
},
toggled() {
this.$emit('hidePicker');
},
},
};
</script>
<template>
<gl-datepicker
:value="selectedDate"
:min-date="minDate"
:max-date="maxDate"
start-opened
@close="toggled"
@click="toggled"
@input="selected"
/>
</template>
<script>
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
export default {
name: 'CollapsedCalendarIcon',
directives: {
GlTooltip: GlTooltipDirective,
},
components: {
GlIcon,
},
props: {
containerClass: {
type: String,
required: false,
default: '',
},
text: {
type: String,
required: false,
default: '',
},
showIcon: {
type: Boolean,
required: false,
default: true,
},
tooltipText: {
type: String,
required: false,
default: '',
},
},
methods: {
click() {
this.$emit('click');
},
},
};
</script>
<template>
<div v-gl-tooltip.left.viewport="tooltipText" :class="containerClass" @click="click">
<gl-icon v-if="showIcon" name="calendar" />
<slot>
<span> {{ text }} </span>
</slot>
</div>
</template>
<script>
import { GlLoadingIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import { dateInWords } from '../../../lib/utils/datetime_utility';
import datePicker from '../pikaday.vue';
import collapsedCalendarIcon from './collapsed_calendar_icon.vue';
import toggleSidebar from './toggle_sidebar.vue';
export default {
name: 'SidebarDatePicker',
components: {
datePicker,
toggleSidebar,
collapsedCalendarIcon,
GlLoadingIcon,
},
props: {
blockClass: {
type: String,
required: false,
default: '',
},
collapsed: {
type: Boolean,
required: false,
default: true,
},
showToggleSidebar: {
type: Boolean,
required: false,
default: false,
},
isLoading: {
type: Boolean,
required: false,
default: false,
},
editable: {
type: Boolean,
required: false,
default: false,
},
label: {
type: String,
required: false,
default: __('Date picker'),
},
selectedDate: {
type: Date,
required: false,
default: null,
},
minDate: {
type: Date,
required: false,
default: null,
},
maxDate: {
type: Date,
required: false,
default: null,
},
},
data() {
return {
editing: false,
};
},
computed: {
selectedAndEditable() {
return this.selectedDate && this.editable;
},
selectedDateWords() {
return dateInWords(this.selectedDate, true);
},
collapsedText() {
return this.selectedDateWords ? this.selectedDateWords : __('None');
},
},
methods: {
stopEditing() {
this.editing = false;
},
toggleDatePicker() {
this.editing = !this.editing;
},
newDateSelected(date = null) {
this.date = date;
this.editing = false;
this.$emit('saveDate', date);
},
toggleSidebar() {
this.$emit('toggleCollapse');
},
},
};
</script>
<template>
<div :class="blockClass" class="block">
<div class="issuable-sidebar-header">
<toggle-sidebar :collapsed="collapsed" @toggle="toggleSidebar" />
</div>
<collapsed-calendar-icon :text="collapsedText" class="sidebar-collapsed-icon" />
<div class="title">
{{ label }}
<gl-loading-icon v-if="isLoading" size="sm" :inline="true" />
<div class="float-right">
<button
v-if="editable && !editing"
type="button"
class="btn-blank btn-link btn-primary-hover-link btn-sidebar-action"
@click="toggleDatePicker"
>
{{ __('Edit') }}
</button>
<toggle-sidebar v-if="showToggleSidebar" :collapsed="collapsed" @toggle="toggleSidebar" />
</div>
</div>
<div class="value">
<date-picker
v-if="editing"
:selected-date="selectedDate"
:min-date="minDate"
:max-date="maxDate"
:label="label"
@newDateSelected="newDateSelected"
@hidePicker="stopEditing"
/>
<span v-else class="value-content">
<template v-if="selectedDate">
<strong>{{ selectedDateWords }}</strong>
<span v-if="selectedAndEditable" class="no-value">
-
<button
type="button"
class="btn-blank btn-link btn-secondary-hover-link"
@click="newDateSelected(null)"
>
{{ __('remove') }}
</button>
</span>
</template>
<span v-else class="no-value">{{ __('None') }}</span>
</span>
</div>
</div>
</template>
<script>
import { GlDropdown, GlDropdownForm, GlDropdownDivider } from '@gitlab/ui';
export default {
components: {
GlDropdownForm,
GlDropdown,
GlDropdownDivider,
},
props: {
headerText: {
type: String,
required: true,
},
text: {
type: String,
required: true,
},
},
};
</script>
<template>
<gl-dropdown class="show" :text="text" @toggle="$emit('toggle')">
<template #header>
<p class="gl-font-weight-bold gl-text-center gl-mt-2 gl-mb-4">{{ headerText }}</p>
<gl-dropdown-divider />
<slot name="search"></slot>
</template>
<gl-dropdown-form>
<slot name="items"></slot>
</gl-dropdown-form>
<template #footer>
<slot name="footer"></slot>
</template>
</gl-dropdown>
</template>
<script>
export default {
props: {
colors: {
type: Array,
required: true,
validator(value) {
return value.length === 2;
},
},
opacity: {
type: Array,
required: true,
validator(value) {
return value.length === 2;
},
},
identifierName: {
type: String,
required: true,
},
},
};
</script>
<template>
<svg height="0" width="0">
<defs>
<linearGradient :id="identifierName">
<stop :stop-color="colors[0]" :stop-opacity="opacity[0]" offset="0%" />
<stop :stop-color="colors[1]" :stop-opacity="opacity[1]" offset="100%" />
</linearGradient>
</defs>
</svg>
</template>
...@@ -366,29 +366,6 @@ ...@@ -366,29 +366,6 @@
background-color: transparent; background-color: transparent;
border-color: transparent; border-color: transparent;
} }
&.btn-secondary-hover-link,
&.btn-default-hover-link {
color: $gl-text-color-secondary;
&:hover,
&:active,
&:focus {
color: $blue-600;
text-decoration: none;
}
}
&.btn-primary-hover-link {
color: inherit;
&:hover,
&:active,
&:focus {
color: $blue-600;
text-decoration: none;
}
}
} }
// The .btn-svg class is available for legacy icon buttons to // The .btn-svg class is available for legacy icon buttons to
......
...@@ -274,16 +274,10 @@ ...@@ -274,16 +274,10 @@
font-weight: $gl-font-weight-normal; font-weight: $gl-font-weight-normal;
} }
.no-value, .no-value {
.btn-default-hover-link,
.btn-secondary-hover-link {
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
} }
.btn-secondary-hover-link:hover {
color: $blue-600;
}
.sidebar-collapsed-icon { .sidebar-collapsed-icon {
display: none; display: none;
} }
......
<script>
import createFlash from '~/flash';
import { __ } from '~/locale';
import eventHub from '~/sidebar/event_hub';
import Mediator from '../../sidebar_mediator';
import weightComponent from './weight.vue';
export default {
components: {
weight: weightComponent,
},
data() {
return {
// Defining `mediator` here as a data prop
// makes it reactive for any internal updates
// which wouldn't happen otherwise.
mediator: new Mediator(),
};
},
created() {
eventHub.$on('updateWeight', this.onUpdateWeight);
},
beforeDestroy() {
eventHub.$off('updateWeight', this.onUpdateWeight);
},
methods: {
onUpdateWeight({ value: newWeight }) {
this.mediator.updateWeight(newWeight).catch(() => {
createFlash({
message: __('Error occurred while updating the issue weight'),
});
});
},
},
};
</script>
<template>
<weight
:fetching="mediator.store.isFetching.weight"
:loading="mediator.store.isLoading.weight"
:weight="mediator.store.weight"
:weight-none-value="mediator.store.weightNoneValue"
:editable="mediator.store.editable"
/>
</template>
<script>
import { GlLoadingIcon, GlIcon, GlTooltipDirective } from '@gitlab/ui';
import $ from 'jquery';
import { __, s__ } from '~/locale';
import eventHub from '~/sidebar/event_hub';
import Tracking from '~/tracking';
import { MAX_DISPLAY_WEIGHT } from '../../constants';
export default {
components: {
GlIcon,
GlLoadingIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [Tracking.mixin({ label: 'right_sidebar' })],
props: {
fetching: {
type: Boolean,
required: false,
default: false,
},
loading: {
type: Boolean,
required: false,
default: false,
},
weight: {
type: [String, Number],
required: false,
default: '',
},
weightNoneValue: {
type: String,
required: true,
default: __('None'),
},
editable: {
type: Boolean,
required: false,
default: false,
},
id: {
type: [String, Number],
required: false,
default: '',
},
},
data() {
return {
hasValidInput: true,
shouldShowEditField: false,
collapsedAfterUpdate: false,
};
},
computed: {
tracking() {
return {
// eslint-disable-next-line no-underscore-dangle
category: this.$options._componentTag,
};
},
isNoValue() {
return this.checkIfNoValue(this.weight);
},
collapsedWeightLabel() {
return this.checkIfNoValue(this.weight)
? this.noValueLabel
: this.weight.toString().substr(0, 5);
},
noValueLabel() {
return s__('Sidebar|None');
},
dropdownToggleLabel() {
return this.checkIfNoValue(this.weight) ? s__('Sidebar|Weight') : this.weight;
},
shouldShowWeight() {
return !this.fetching && !this.shouldShowEditField;
},
tooltipTitle() {
let tooltipTitle = s__('Sidebar|Weight');
if (!this.checkIfNoValue(this.weight)) {
tooltipTitle += ` ${this.weight}`;
}
return tooltipTitle;
},
},
methods: {
checkIfNoValue(weight) {
return weight === undefined || weight === null || weight === this.weightNoneValue;
},
onEditClick(shouldShowEditField = true) {
this.showEditField(shouldShowEditField);
this.track('click_edit_button', { property: 'weight' });
},
showEditField(bool = true) {
this.shouldShowEditField = bool;
if (this.shouldShowEditField) {
this.$nextTick(() => {
this.$refs.editableField.focus();
});
}
},
onCollapsedClick() {
if (this.editable) {
this.showEditField(true);
}
this.collapsedAfterUpdate = true;
},
onSubmit(e) {
const { value } = e.target;
const validatedValue = parseInt(value, 10);
const isNewValue = validatedValue !== this.weight;
this.hasValidInput = validatedValue >= 0 || value === '';
if (!this.loading && this.hasValidInput) {
$(this.$el).trigger('hidden.gl.dropdown');
if (isNewValue) {
eventHub.$emit('updateWeight', { id: this.id, value });
}
this.showEditField(false);
}
},
removeWeight() {
eventHub.$emit('updateWeight', { id: this.id, value: '' });
},
},
maxDisplayWeight: MAX_DISPLAY_WEIGHT,
};
</script>
<template>
<div :class="{ 'collapse-after-update': collapsedAfterUpdate }" class="block weight">
<div
v-gl-tooltip.left.viewport
:title="tooltipTitle"
class="sidebar-collapsed-icon js-weight-collapsed-block"
@click="onCollapsedClick"
>
<gl-icon :size="16" name="weight" />
<gl-loading-icon v-if="fetching" size="sm" class="js-weight-collapsed-loading-icon" />
<span v-else class="js-weight-collapsed-weight-label">
{{ collapsedWeightLabel
}}<template v-if="weight > $options.maxDisplayWeight">&hellip;</template>
</span>
</div>
<div class="hide-collapsed gl-line-height-20 gl-mb-2 gl-text-gray-900">
{{ s__('Sidebar|Weight') }}
<gl-loading-icon
v-if="fetching || loading"
size="sm"
:inline="true"
class="js-weight-loading-icon"
/>
<a
v-if="editable"
class="float-right edit-link btn gl-text-gray-900! gl-ml-auto hide-collapsed btn-default btn-sm gl-button btn-default-tertiary js-weight-edit-link"
data-qa-selector="edit_weight_link"
href="#"
@click.prevent="onEditClick(!shouldShowEditField)"
>{{ __('Edit') }}</a
>
</div>
<div v-if="shouldShowEditField" class="hide-collapsed">
<input
ref="editableField"
:value="weight"
class="form-control"
data-qa-selector="weight_input_field"
type="text"
:placeholder="__('Enter a number')"
@blur="onSubmit"
@keydown.enter="onSubmit"
/>
<span v-if="!hasValidInput" class="gl-field-error">
<gl-icon :size="24" name="merge-request-close-m" />
{{ s__('Sidebar|Only numeral characters allowed') }}
</span>
</div>
<div v-if="shouldShowWeight" class="value hide-collapsed js-weight-weight-label">
<span v-if="!isNoValue">
<strong class="js-weight-weight-label-value" data-qa-selector="weight_label_value">{{
weight
}}</strong>
<span v-if="editable">
-
<a
class="btn-default-hover-link js-weight-remove-link"
data-qa-selector="remove_weight_link"
href="#"
@click="removeWeight"
>{{ __('remove weight') }}</a
>
</span>
</span>
<span v-else class="no-value" data-qa-selector="weight_no_value_content">{{
noValueLabel
}}</span>
</div>
</div>
</template>
import Store from 'ee/sidebar/stores/sidebar_store';
import CESidebarMediator from '~/sidebar/sidebar_mediator';
export default class SidebarMediator extends CESidebarMediator {
initSingleton(options) {
super.initSingleton(options);
this.store = new Store(options);
}
processFetchedData(restData, graphQlData) {
super.processFetchedData(restData, graphQlData);
this.store.setWeightData(restData);
this.store.setEpicData(restData);
this.store.setStatusData(graphQlData);
}
updateWeight(newWeight) {
this.store.setLoadingState('weight', true);
return this.service
.update('issue', { weight: newWeight })
.then(({ data }) => {
this.store.setWeight(data.weight);
this.store.setLoadingState('weight', false);
})
.catch((err) => {
this.store.setLoadingState('weight', false);
throw err;
});
}
}
import CESidebarStore from '~/sidebar/stores/sidebar_store';
export default class SidebarStore extends CESidebarStore {
initSingleton(options) {
super.initSingleton(options);
this.isFetching.status = true;
this.isFetching.weight = true;
this.isFetching.epic = true;
this.isLoading.weight = false;
this.status = '';
this.weight = null;
this.weightOptions = options.weightOptions;
this.weightNoneValue = options.weightNoneValue;
this.epic = {};
}
setStatusData(data) {
this.isFetching.status = false;
this.status = data?.project?.issue?.healthStatus;
}
setStatus(status) {
this.status = status;
}
setWeightData({ weight }) {
this.isFetching.weight = false;
this.weight = typeof weight === 'number' ? Number(weight) : null;
}
setWeight(newWeight) {
this.weight = newWeight;
}
setEpicData(data) {
this.isFetching.epic = false;
this.epic = data.epic || {};
}
}
...@@ -9,51 +9,12 @@ ...@@ -9,51 +9,12 @@
border-color: var(--gray-100, $gray-100); border-color: var(--gray-100, $gray-100);
} }
.epic-sidebar {
.help-icon,
.date-warning-icon {
&:hover {
cursor: pointer;
}
}
.help-icon {
color: var(--gray-500, $gray-500);
}
.btn-sidebar-action.btn-link {
line-height: $gl-font-size;
}
.btn-sidebar-date-remove {
height: $gl-font-size;
line-height: $gl-btn-horz-padding;
}
.date-warning-icon {
color: var(--orange-500, $orange-500);
margin-top: -1px;
}
.is-option-selected {
> span {
color: var(--gray-500, $gray-500);
font-weight: $gl-font-weight-bold;
}
}
}
.epic-tabs-content { .epic-tabs-content {
overflow: visible; overflow: visible;
.roadmap-container { .roadmap-container {
min-height: 600px; min-height: 600px;
.roadmap-shell {
border: 1px solid var(--border-color, $border-color);
border-top: 0;
}
.empty-state { .empty-state {
margin-top: 0; margin-top: 0;
} }
......
...@@ -83,8 +83,10 @@ RSpec.describe 'Issue Sidebar' do ...@@ -83,8 +83,10 @@ RSpec.describe 'Issue Sidebar' do
visit_issue(project, issue) visit_issue(project, issue)
end end
it 'does not have a option to edit weight' do it 'does not have a option to edit weight', :js do
expect(page).not_to have_selector('.block.weight .js-weight-edit-link') within '.block.weight' do
expect(page).not_to have_button('Edit')
end
end end
end end
......
import CEMockData from 'jest/sidebar/mock_data';
const RESPONSE_MAP = { ...CEMockData.responseMap };
RESPONSE_MAP.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar'] = {
assignees: [
{
name: 'User 0',
username: 'user0',
id: 22,
state: 'active',
avatar_url:
'http: //www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon',
web_url: 'http: //localhost:3001/user0',
},
{
name: 'Marguerite Bartell',
username: 'tajuana',
id: 18,
state: 'active',
avatar_url:
'http: //www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon',
web_url: 'http: //localhost:3001/tajuana',
},
{
name: 'Laureen Ritchie',
username: 'michaele.will',
id: 16,
state: 'active',
avatar_url:
'http: //www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon',
web_url: 'http: //localhost:3001/michaele.will',
},
],
human_time_estimate: null,
human_total_time_spent: null,
participants: [
{
name: 'User 0',
username: 'user0',
id: 22,
state: 'active',
avatar_url:
'http: //www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80\u0026d=identicon',
web_url: 'http: //localhost:3001/user0',
},
{
name: 'Marguerite Bartell',
username: 'tajuana',
id: 18,
state: 'active',
avatar_url:
'http: //www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80\u0026d=identicon',
web_url: 'http: //localhost:3001/tajuana',
},
{
name: 'Laureen Ritchie',
username: 'michaele.will',
id: 16,
state: 'active',
avatar_url:
'http: //www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80\u0026d=identicon',
web_url: 'http: //localhost:3001/michaele.will',
},
],
subscribed: true,
time_estimate: 0,
total_time_spent: 0,
weight: 3,
};
export default {
...CEMockData,
mediator: {
...CEMockData.mediator,
weightOptions: ['None', 0, 1, 3],
weightNoneValue: 'None',
},
responseMap: RESPONSE_MAP,
};
import SidebarMediator from 'ee/sidebar/sidebar_mediator';
import SidebarService from '~/sidebar/services/sidebar_service';
import CESidebarMediator from '~/sidebar/sidebar_mediator';
import CESidebarStore from '~/sidebar/stores/sidebar_store';
import Mock from './ee_mock_data';
describe('EE Sidebar mediator', () => {
let mediator;
beforeEach(() => {
mediator = new SidebarMediator(Mock.mediator);
});
afterEach(() => {
SidebarService.singleton = null;
CESidebarStore.singleton = null;
CESidebarMediator.singleton = null;
});
it('processes fetched data', () => {
const mockData =
Mock.responseMap.GET['/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar'];
const mockGraphQlData = Mock.graphQlResponseData;
mediator.processFetchedData(mockData, mockGraphQlData);
expect(mediator.store.weight).toBe(mockData.weight);
expect(mediator.store.status).toBe(mockGraphQlData.project.issue.healthStatus);
});
});
import SidebarStore from 'ee/sidebar/stores/sidebar_store';
import CESidebarStore from '~/sidebar/stores/sidebar_store';
describe('EE Sidebar store', () => {
let store;
beforeEach(() => {
store = new SidebarStore({
status: '',
weight: null,
weightOptions: ['None', 0, 1, 3],
weightNoneValue: 'None',
});
});
afterEach(() => {
// Since CESidebarStore stores the actual singleton instance
// we need to clear that specific reference
CESidebarStore.singleton = null;
});
describe('setStatusData', () => {
it('sets status data', () => {
const graphQlData = {
project: {
issue: {
healthStatus: 'onTrack',
},
},
};
store.setStatusData(graphQlData);
expect(store.isFetching.status).toBe(false);
expect(store.status).toBe(graphQlData.project.issue.healthStatus);
});
});
describe('setStatus', () => {
it('sets status', () => {
expect(store.status).toBe('');
const status = 'onTrack';
store.setStatus(status);
expect(store.status).toBe(status);
});
});
describe('setWeightData', () => {
it('sets weight data', () => {
const weight = 3;
store.setWeightData({
weight,
});
expect(store.isFetching.weight).toBe(false);
expect(store.weight).toBe(weight);
});
it('supports 0 weight', () => {
store.setWeightData({
weight: 0,
});
expect(store.weight).toBe(0);
});
});
it('set weight', () => {
expect(store.weight).toBe(null);
const weight = 1;
store.setWeight(weight);
expect(store.weight).toBe(weight);
});
});
...@@ -83,19 +83,6 @@ export const mockGroupEpicsResponse = { ...@@ -83,19 +83,6 @@ export const mockGroupEpicsResponse = {
}, },
}; };
export const emptyGroupIterationsResponse = {
data: {
workspace: {
id: '1',
iterations: {
nodes: [],
},
__typename: 'IterationConnection',
},
__typename: 'Group',
},
};
export const emptyGroupEpicsResponse = { export const emptyGroupEpicsResponse = {
data: { data: {
workspace: { workspace: {
...@@ -139,16 +126,6 @@ export const mockCurrentIterationResponse2 = { ...@@ -139,16 +126,6 @@ export const mockCurrentIterationResponse2 = {
}, },
}; };
export const noCurrentIterationResponse = {
data: {
workspace: {
id: '1',
issuable: { id: mockIssueId, iteration: null, __typename: 'Issue' },
__typename: 'Project',
},
},
};
export const noCurrentEpicResponse = { export const noCurrentEpicResponse = {
data: { data: {
workspace: { workspace: {
...@@ -159,25 +136,6 @@ export const noCurrentEpicResponse = { ...@@ -159,25 +136,6 @@ export const noCurrentEpicResponse = {
}, },
}; };
export const mockIterationMutationResponse = {
data: {
issuableSetIteration: {
errors: [],
issuable: {
id: 'gid://gitlab/Issue/1',
iteration: {
id: 'gid://gitlab/Iteration/2',
title: 'Awesome Iteration',
state: 'opened',
__typename: 'Iteration',
},
__typename: 'Issue',
},
__typename: 'IssueSetIterationPayload',
},
},
};
export const mockEpicMutationResponse = { export const mockEpicMutationResponse = {
data: { data: {
issuableSetAttribute: { issuableSetAttribute: {
......
import { shallowMount } from '@vue/test-utils';
import SidebarWeight from 'ee/sidebar/components/weight/sidebar_weight.vue';
import SidebarMediator from 'ee/sidebar/sidebar_mediator';
import SidebarStore from 'ee/sidebar/stores/sidebar_store';
import eventHub from '~/sidebar/event_hub';
import SidebarService from '~/sidebar/services/sidebar_service';
import Mock from './ee_mock_data';
describe('Sidebar Weight', () => {
let sidebarMediator;
let wrapper;
const createComponent = () => {
wrapper = shallowMount(SidebarWeight);
};
beforeEach(() => {
// Set up the stores, services, etc
sidebarMediator = new SidebarMediator(Mock.mediator);
});
afterEach(() => {
if (wrapper) {
wrapper.destroy();
wrapper = null;
}
SidebarService.singleton = null;
SidebarStore.singleton = null;
SidebarMediator.singleton = null;
});
it('calls the mediator updateWeight on event', () => {
jest.spyOn(SidebarMediator.prototype, 'updateWeight').mockReturnValue(Promise.resolve());
createComponent({
mediator: sidebarMediator,
});
eventHub.$emit('updateWeight', { value: '' });
expect(SidebarMediator.prototype.updateWeight).toHaveBeenCalled();
});
});
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import Weight from 'ee/sidebar/components/weight/weight.vue';
import { mockTracking, unmockTracking, triggerEvent } from 'helpers/tracking_helper';
import { ENTER_KEY_CODE } from '~/lib/utils/keycodes';
import eventHub from '~/sidebar/event_hub';
describe('Weight', () => {
let wrapper;
const defaultProps = {
weightNoneValue: 'None',
};
const createComponent = (props = {}) => {
wrapper = shallowMount(Weight, {
propsData: { ...defaultProps, ...props },
});
};
afterEach(() => {
if (wrapper) {
wrapper.destroy();
wrapper = null;
}
});
const containsCollapsedLoadingIcon = () =>
wrapper.find('.js-weight-collapsed-loading-icon').exists();
const containsLoadingIcon = () => wrapper.find('.js-weight-loading-icon').exists();
const findCollapsedLabel = () => wrapper.find('.js-weight-collapsed-weight-label');
const findLabelValue = () => wrapper.find('.js-weight-weight-label-value');
const findLabelNoValue = () => wrapper.find('.js-weight-weight-label .no-value');
const findCollapsedBlock = () => wrapper.find('.js-weight-collapsed-block');
const findEditLink = () => wrapper.find('.js-weight-edit-link');
const findRemoveLink = () => wrapper.find('.js-weight-remove-link');
const containsEditableField = () => wrapper.findComponent({ ref: 'editableField' }).exists();
const containsInputError = () => wrapper.find('.gl-field-error').exists();
it('shows loading spinner when fetching', () => {
createComponent({
fetching: true,
});
expect(containsCollapsedLoadingIcon()).toBe(true);
expect(containsLoadingIcon()).toBe(true);
});
it('shows loading spinner when loading', () => {
createComponent({
fetching: false,
loading: true,
});
// We show the value in the collapsed view instead of the loading icon
expect(containsCollapsedLoadingIcon()).toBe(false);
expect(containsLoadingIcon()).toBe(true);
});
it('shows weight value', () => {
const expectedWeight = 3;
createComponent({
fetching: false,
weight: expectedWeight,
});
expect(findCollapsedLabel().text()).toBe(`${expectedWeight}`);
expect(findLabelValue().text()).toBe(`${expectedWeight}`);
});
it('shows weight no-value', () => {
const expectedWeight = null;
createComponent({
fetching: false,
weight: expectedWeight,
});
expect(findCollapsedLabel().text()).toBe(defaultProps.weightNoneValue);
expect(findLabelNoValue().text()).toBe(defaultProps.weightNoneValue);
});
it('adds `collapse-after-update` class when clicking the collapsed block', async () => {
createComponent();
findCollapsedBlock().trigger('click');
await nextTick;
expect(wrapper.classes()).toContain('collapse-after-update');
});
it('shows dropdown on "Edit" link click', async () => {
createComponent({
editable: true,
});
expect(containsEditableField()).toBe(false);
findEditLink().trigger('click');
await nextTick;
expect(containsEditableField()).toBe(true);
});
it('emits event on input submission', async () => {
const mockId = 123;
const expectedWeightValue = '3';
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
createComponent({
editable: true,
id: mockId,
});
findEditLink().trigger('click');
await nextTick;
const event = new CustomEvent('keydown');
event.keyCode = ENTER_KEY_CODE;
const { editableField } = wrapper.vm.$refs;
editableField.click();
editableField.value = expectedWeightValue;
editableField.dispatchEvent(event);
await nextTick;
expect(containsInputError()).toBe(false);
expect(eventHub.$emit).toHaveBeenCalledWith('updateWeight', {
id: mockId,
value: expectedWeightValue,
});
});
it('emits event on remove weight link click', async () => {
const mockId = 234;
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
createComponent({
editable: true,
weight: 3,
id: mockId,
});
findRemoveLink().trigger('click');
await nextTick;
expect(containsInputError()).toBe(false);
expect(eventHub.$emit).toHaveBeenCalledWith('updateWeight', { id: mockId, value: '' });
});
it('triggers error on invalid negative integer value', async () => {
createComponent({
editable: true,
});
findEditLink().trigger('click');
await nextTick;
const event = new CustomEvent('keydown');
event.keyCode = ENTER_KEY_CODE;
const { editableField } = wrapper.vm.$refs;
editableField.click();
editableField.value = -9001;
editableField.dispatchEvent(event);
await nextTick;
expect(containsInputError()).toBe(true);
});
describe('tracking', () => {
let trackingSpy;
beforeEach(() => {
createComponent({
editable: true,
});
trackingSpy = mockTracking('_category_', wrapper.element, (obj, what) =>
jest.spyOn(obj, what).mockImplementation(() => {}),
);
});
afterEach(() => {
unmockTracking();
});
it('calls trackEvent when "Edit" is clicked', async () => {
triggerEvent(findEditLink().element);
await nextTick;
expect(trackingSpy).toHaveBeenCalled();
});
});
});
...@@ -11382,9 +11382,6 @@ msgstr "" ...@@ -11382,9 +11382,6 @@ msgstr ""
msgid "Date merged" msgid "Date merged"
msgstr "" msgstr ""
msgid "Date picker"
msgstr ""
msgid "Date range" msgid "Date range"
msgstr "" msgstr ""
...@@ -14123,9 +14120,6 @@ msgstr "" ...@@ -14123,9 +14120,6 @@ msgstr ""
msgid "Error occurred while updating the issue status" msgid "Error occurred while updating the issue status"
msgstr "" msgstr ""
msgid "Error occurred while updating the issue weight"
msgstr ""
msgid "Error occurred. A blocked user cannot be deactivated" msgid "Error occurred. A blocked user cannot be deactivated"
msgstr "" msgstr ""
...@@ -33414,12 +33408,6 @@ msgstr "" ...@@ -33414,12 +33408,6 @@ msgstr ""
msgid "Sidebar|None" msgid "Sidebar|None"
msgstr "" msgstr ""
msgid "Sidebar|Only numeral characters allowed"
msgstr ""
msgid "Sidebar|Weight"
msgstr ""
msgid "Sidekiq job compression threshold (bytes)" msgid "Sidekiq job compression threshold (bytes)"
msgstr "" msgstr ""
......
...@@ -24,12 +24,8 @@ module QA ...@@ -24,12 +24,8 @@ module QA
element :iteration_container element :iteration_container
end end
view 'ee/app/assets/javascripts/sidebar/components/weight/weight.vue' do view 'ee/app/assets/javascripts/sidebar/components/weight/sidebar_weight_widget.vue' do
element :edit_weight_link
element :remove_weight_link
element :weight_input_field
element :weight_label_value element :weight_label_value
element :weight_no_value_content
end end
end end
end end
...@@ -47,10 +43,6 @@ module QA ...@@ -47,10 +43,6 @@ module QA
refresh refresh
end end
def click_remove_weight_link
click_element(:remove_weight_link)
end
def has_iteration?(iteration_title) def has_iteration?(iteration_title)
wait_until_iteration_container_loaded wait_until_iteration_container_loaded
...@@ -61,12 +53,6 @@ module QA ...@@ -61,12 +53,6 @@ module QA
end end
end end
def set_weight(weight)
click_element(:edit_weight_link)
fill_element(:weight_input_field, weight)
send_keys_to_element(:weight_input_field, :enter)
end
def wait_for_attachment_replication(image_url, max_wait: Runtime::Geo.max_file_replication_time) def wait_for_attachment_replication(image_url, max_wait: Runtime::Geo.max_file_replication_time)
QA::Runtime::Logger.debug(%Q[#{self.class.name} - wait_for_attachment_replication]) QA::Runtime::Logger.debug(%Q[#{self.class.name} - wait_for_attachment_replication])
wait_until_geo_max_replication_time(max_wait: max_wait) do wait_until_geo_max_replication_time(max_wait: max_wait) do
...@@ -78,10 +64,6 @@ module QA ...@@ -78,10 +64,6 @@ module QA
find_element(:weight_label_value) find_element(:weight_label_value)
end end
def weight_no_value_content
find_element(:weight_no_value_content)
end
private private
def wait_until_geo_max_replication_time(max_wait: Runtime::Geo.max_file_replication_time) def wait_until_geo_max_replication_time(max_wait: Runtime::Geo.max_file_replication_time)
......
import { GlDropdown } from '@gitlab/ui';
import { getByText } from '@testing-library/dom';
import { shallowMount } from '@vue/test-utils';
import MultiSelectDropdown from '~/vue_shared/components/sidebar/multiselect_dropdown.vue';
describe('MultiSelectDropdown Component', () => {
it('renders items slot', () => {
const wrapper = shallowMount(MultiSelectDropdown, {
propsData: {
text: '',
headerText: '',
},
slots: {
items: '<p>Test</p>',
},
});
expect(getByText(wrapper.element, 'Test')).toBeDefined();
});
it('renders search slot', () => {
const wrapper = shallowMount(MultiSelectDropdown, {
propsData: {
text: '',
headerText: '',
},
slots: {
search: '<p>Search</p>',
},
stubs: {
GlDropdown,
},
});
expect(getByText(wrapper.element, 'Search')).toBeDefined();
});
});
import { GlDatepicker } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import datePicker from '~/vue_shared/components/pikaday.vue';
describe('datePicker', () => {
let wrapper;
const buildWrapper = (propsData = {}) => {
wrapper = shallowMount(datePicker, {
propsData,
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('should emit newDateSelected when GlDatePicker emits the input event', () => {
const minDate = new Date();
const maxDate = new Date();
const selectedDate = new Date();
const theDate = selectedDate.toISOString().slice(0, 10);
buildWrapper({ minDate, maxDate, selectedDate });
expect(wrapper.find(GlDatepicker).props()).toMatchObject({
minDate,
maxDate,
value: selectedDate,
});
wrapper.find(GlDatepicker).vm.$emit('input', selectedDate);
expect(wrapper.emitted('newDateSelected')[0][0]).toBe(theDate);
});
it('should emit the hidePicker event when GlDatePicker emits the close event', () => {
buildWrapper();
wrapper.find(GlDatepicker).vm.$emit('close');
expect(wrapper.emitted('hidePicker')).toHaveLength(1);
});
});
import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
import { nextTick } from 'vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import CollapsedCalendarIcon from '~/vue_shared/components/sidebar/collapsed_calendar_icon.vue';
describe('CollapsedCalendarIcon', () => {
let wrapper;
const defaultProps = {
containerClass: 'test-class',
text: 'text',
tooltipText: 'tooltip text',
showIcon: false,
};
const createComponent = ({ props = {} } = {}) => {
wrapper = shallowMount(CollapsedCalendarIcon, {
propsData: { ...defaultProps, ...props },
directives: {
GlTooltip: createMockDirective(),
},
});
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
const findGlIcon = () => wrapper.findComponent(GlIcon);
const getTooltip = () => getBinding(wrapper.element, 'gl-tooltip');
it('adds class to container', () => {
expect(wrapper.classes()).toContain(defaultProps.containerClass);
});
it('does not render calendar icon when showIcon is false', () => {
expect(findGlIcon().exists()).toBe(false);
});
it('renders calendar icon when showIcon is true', () => {
createComponent({
props: { showIcon: true },
});
expect(findGlIcon().exists()).toBe(true);
});
it('renders text', () => {
expect(wrapper.text()).toBe(defaultProps.text);
});
it('renders tooltipText as tooltip', () => {
expect(getTooltip().value).toBe(defaultProps.tooltipText);
});
it('emits click event when container is clicked', async () => {
wrapper.trigger('click');
await nextTick();
expect(wrapper.emitted('click')[0]).toBeDefined();
});
});
import { GlLoadingIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import DatePicker from '~/vue_shared/components/pikaday.vue';
import SidebarDatePicker from '~/vue_shared/components/sidebar/date_picker.vue';
describe('SidebarDatePicker', () => {
let wrapper;
const createComponent = (propsData = {}, data = {}) => {
wrapper = mount(SidebarDatePicker, {
propsData,
data: () => data,
});
};
afterEach(() => {
wrapper.destroy();
});
const findDatePicker = () => wrapper.findComponent(DatePicker);
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findEditButton = () => wrapper.find('.title .btn-blank');
const findRemoveButton = () => wrapper.find('.value-content .btn-blank');
const findSidebarToggle = () => wrapper.find('.title .gutter-toggle');
const findValueContent = () => wrapper.find('.value-content');
it('should emit toggleCollapse when collapsed toggle sidebar is clicked', () => {
createComponent();
wrapper.find('.issuable-sidebar-header .gutter-toggle').trigger('click');
expect(wrapper.emitted('toggleCollapse')).toEqual([[]]);
});
it('should render collapsed-calendar-icon', () => {
createComponent();
expect(wrapper.find('.sidebar-collapsed-icon').exists()).toBe(true);
});
it('should render value when not editing', () => {
createComponent();
expect(findValueContent().exists()).toBe(true);
});
it('should render None if there is no selectedDate', () => {
createComponent();
expect(findValueContent().text()).toBe('None');
});
it('should render date-picker when editing', () => {
createComponent({}, { editing: true });
expect(findDatePicker().exists()).toBe(true);
});
it('should render label', () => {
const label = 'label';
createComponent({ label });
expect(wrapper.find('.title').text()).toBe(label);
});
it('should render loading-icon when isLoading', () => {
createComponent({ isLoading: true });
expect(findLoadingIcon().exists()).toBe(true);
});
describe('editable', () => {
beforeEach(() => {
createComponent({ editable: true });
});
it('should render edit button', () => {
expect(findEditButton().text()).toBe('Edit');
});
it('should enable editing when edit button is clicked', async () => {
findEditButton().trigger('click');
await nextTick();
expect(wrapper.vm.editing).toBe(true);
});
});
it('should render date if selectedDate', () => {
createComponent({ selectedDate: new Date('07/07/2017') });
expect(wrapper.find('.value-content strong').text()).toBe('Jul 7, 2017');
});
describe('selectedDate and editable', () => {
beforeEach(() => {
createComponent({ selectedDate: new Date('07/07/2017'), editable: true });
});
it('should render remove button if selectedDate and editable', () => {
expect(findRemoveButton().text()).toBe('remove');
});
it('should emit saveDate with null when remove button is clicked', () => {
findRemoveButton().trigger('click');
expect(wrapper.emitted('saveDate')).toEqual([[null]]);
});
});
describe('showToggleSidebar', () => {
beforeEach(() => {
createComponent({ showToggleSidebar: true });
});
it('should render toggle-sidebar when showToggleSidebar', () => {
expect(findSidebarToggle().exists()).toBe(true);
});
it('should emit toggleCollapse when toggle sidebar is clicked', () => {
findSidebarToggle().trigger('click');
expect(wrapper.emitted('toggleCollapse')).toEqual([[]]);
});
});
});
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