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 { __, sprintf } from '~/locale';
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 @@
background-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
......
......@@ -274,16 +274,10 @@
font-weight: $gl-font-weight-normal;
}
.no-value,
.btn-default-hover-link,
.btn-secondary-hover-link {
.no-value {
color: $gl-text-color-secondary;
}
.btn-secondary-hover-link:hover {
color: $blue-600;
}
.sidebar-collapsed-icon {
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 @@
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 {
overflow: visible;
.roadmap-container {
min-height: 600px;
.roadmap-shell {
border: 1px solid var(--border-color, $border-color);
border-top: 0;
}
.empty-state {
margin-top: 0;
}
......
......@@ -83,8 +83,10 @@ RSpec.describe 'Issue Sidebar' do
visit_issue(project, issue)
end
it 'does not have a option to edit weight' do
expect(page).not_to have_selector('.block.weight .js-weight-edit-link')
it 'does not have a option to edit weight', :js do
within '.block.weight' do
expect(page).not_to have_button('Edit')
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 = {
},
};
export const emptyGroupIterationsResponse = {
data: {
workspace: {
id: '1',
iterations: {
nodes: [],
},
__typename: 'IterationConnection',
},
__typename: 'Group',
},
};
export const emptyGroupEpicsResponse = {
data: {
workspace: {
......@@ -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 = {
data: {
workspace: {
......@@ -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 = {
data: {
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 ""
msgid "Date merged"
msgstr ""
msgid "Date picker"
msgstr ""
msgid "Date range"
msgstr ""
......@@ -14123,9 +14120,6 @@ msgstr ""
msgid "Error occurred while updating the issue status"
msgstr ""
msgid "Error occurred while updating the issue weight"
msgstr ""
msgid "Error occurred. A blocked user cannot be deactivated"
msgstr ""
......@@ -33414,12 +33408,6 @@ msgstr ""
msgid "Sidebar|None"
msgstr ""
msgid "Sidebar|Only numeral characters allowed"
msgstr ""
msgid "Sidebar|Weight"
msgstr ""
msgid "Sidekiq job compression threshold (bytes)"
msgstr ""
......
......@@ -24,12 +24,8 @@ module QA
element :iteration_container
end
view 'ee/app/assets/javascripts/sidebar/components/weight/weight.vue' do
element :edit_weight_link
element :remove_weight_link
element :weight_input_field
view 'ee/app/assets/javascripts/sidebar/components/weight/sidebar_weight_widget.vue' do
element :weight_label_value
element :weight_no_value_content
end
end
end
......@@ -47,10 +43,6 @@ module QA
refresh
end
def click_remove_weight_link
click_element(:remove_weight_link)
end
def has_iteration?(iteration_title)
wait_until_iteration_container_loaded
......@@ -61,12 +53,6 @@ module QA
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)
QA::Runtime::Logger.debug(%Q[#{self.class.name} - wait_for_attachment_replication])
wait_until_geo_max_replication_time(max_wait: max_wait) do
......@@ -78,10 +64,6 @@ module QA
find_element(:weight_label_value)
end
def weight_no_value_content
find_element(:weight_no_value_content)
end
private
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