Commit 5e8d48d8 authored by Kushal Pandya's avatar Kushal Pandya

Update template, add scroll handler

parent 7975af2f
<script>
import { SCROLL_BAR_SIZE } from '../constants';
import bp from '~/breakpoints';
import { SCROLL_BAR_SIZE, EPIC_ITEM_HEIGHT, SHELL_MIN_WIDTH } from '../constants';
import eventHub from '../event_hub';
import epicsListSection from './epics_list_section.vue';
import roadmapTimelineSection from './roadmap_timeline_section.vue';
......@@ -26,13 +28,20 @@
data() {
return {
shellWidth: 0,
shellHeight: 0,
noScroll: false,
};
},
computed: {
tableStyles() {
// return width after deducting size of vertical scrollbar
// to hide the scrollbar while preserving ability to scroll
return `width: ${this.shellWidth - SCROLL_BAR_SIZE}px;`;
containerStyles() {
const width = bp.windowWidth() > SHELL_MIN_WIDTH ?
this.shellWidth + this.getWidthOffset() :
this.shellWidth;
return {
width: `${width}px`,
height: `${this.shellHeight}px`,
};
},
},
mounted() {
......@@ -44,28 +53,45 @@
// before setting shellWidth
// see https://vuejs.org/v2/api/#Vue-nextTick
if (this.$el.parentElement) {
this.shellWidth = this.$el.parentElement.clientWidth;
this.shellHeight = window.innerHeight - this.$el.offsetTop;
this.noScroll = this.shellHeight > (EPIC_ITEM_HEIGHT * (this.epics.length + 1));
this.shellWidth = this.$el.parentElement.clientWidth + this.getWidthOffset();
}
});
},
methods: {
getWidthOffset() {
return this.noScroll ? 0 : SCROLL_BAR_SIZE;
},
handleScroll() {
const { scrollTop, scrollLeft, clientHeight, scrollHeight } = this.$el;
if (!this.noScroll) {
eventHub.$emit('epicsListScrolled', { scrollTop, scrollLeft, clientHeight, scrollHeight });
}
},
},
};
</script>
<template>
<table
<div
class="roadmap-shell"
:style="tableStyles"
:class="{ 'prevent-vertical-scroll': noScroll }"
:style="containerStyles"
@scroll="handleScroll"
>
<roadmap-timeline-section
:epics="epics"
:timeframe="timeframe"
:shell-width="shellWidth"
:list-scrollable="!noScroll"
/>
<epics-list-section
:epics="epics"
:timeframe="timeframe"
:shell-width="shellWidth"
:current-group-id="currentGroupId"
:list-scrollable="!noScroll"
/>
</table>
</div>
</template>
import Vue from 'vue';
import roadmapShellComponent from 'ee/roadmap/components/roadmap_shell.vue';
import eventHub from 'ee/roadmap/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockEpic, mockTimeframe, mockGroupId } from '../mock_data';
import { mockEpic, mockTimeframe, mockGroupId, mockScrollBarSize } from '../mock_data';
const createComponent = ({
epics = [mockEpic],
timeframe = mockTimeframe,
currentGroupId = mockGroupId,
}) => {
}, el) => {
const Component = Vue.extend(roadmapShellComponent);
return mountComponent(Component, {
epics,
timeframe,
currentGroupId,
});
}, el);
};
describe('RoadmapShellComponent', () => {
......@@ -33,22 +34,79 @@ describe('RoadmapShellComponent', () => {
describe('data', () => {
it('returns default data props', () => {
expect(vm.shellWidth).toBe(0);
expect(vm.shellHeight).toBe(0);
expect(vm.noScroll).toBe(false);
});
});
describe('tableStyles', () => {
it('returns style string based on shellWidth and Scollbar size', () => {
// Since shellWidth is initialized on component mount
// from parentElement.clientWidth, it will always be Zero
// as parentElement is not available during tests.
// so end result is 0 - scrollbar_size = -15
expect(vm.tableStyles).toBe('width: -15px;');
describe('computed', () => {
describe('containerStyles', () => {
beforeEach(() => {
document.body.innerHTML += '<div class="roadmap-container"><div id="roadmap-shell"></div></div>';
});
afterEach(() => {
document.querySelector('.roadmap-container').remove();
});
it('returns style object based on shellWidth and current Window width with Scollbar size offset', (done) => {
const vmWithParentEl = createComponent({}, document.getElementById('roadmap-shell'));
Vue.nextTick(() => {
const stylesObj = vmWithParentEl.containerStyles;
// Ensure that value for `width` & `height`
// is a non-zero number.
expect(parseInt(stylesObj.width, 10) !== 0).toBe(true);
expect(parseInt(stylesObj.height, 10) !== 0).toBe(true);
vmWithParentEl.$destroy();
done();
});
});
});
});
describe('methods', () => {
describe('getWidthOffset', () => {
it('returns 0 when noScroll prop is true', () => {
vm.noScroll = true;
expect(vm.getWidthOffset()).toBe(0);
});
it('returns value of SCROLL_BAR_SIZE when noScroll prop is false', () => {
vm.noScroll = false;
expect(vm.getWidthOffset()).toBe(mockScrollBarSize);
});
});
describe('handleScroll', () => {
beforeEach(() => {
spyOn(eventHub, '$emit');
});
it('emits `epicsListScrolled` event via eventHub when `noScroll` prop is false', () => {
vm.noScroll = false;
vm.handleScroll();
expect(eventHub.$emit).toHaveBeenCalledWith('epicsListScrolled', jasmine.any(Object));
});
it('does not emit any event via eventHub when `noScroll` prop is true', () => {
vm.noScroll = true;
vm.handleScroll();
expect(eventHub.$emit).not.toHaveBeenCalled();
});
});
});
describe('template', () => {
it('renders component container element with class `roadmap-shell`', () => {
expect(vm.$el.classList.contains('roadmap-shell')).toBeTruthy();
expect(vm.$el.classList.contains('roadmap-shell')).toBe(true);
});
it('adds `prevent-vertical-scroll` class on component container element', (done) => {
vm.noScroll = true;
Vue.nextTick(() => {
expect(vm.$el.classList.contains('prevent-vertical-scroll')).toBe(true);
done();
});
});
});
});
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