Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
9d46d31e
Commit
9d46d31e
authored
Feb 08, 2018
by
Kushal Pandya
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Roadmap EpicListSection Component
parent
6149e048
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
342 additions
and
0 deletions
+342
-0
ee/app/assets/javascripts/roadmap/components/epics_list_section.vue
...ets/javascripts/roadmap/components/epics_list_section.vue
+177
-0
spec/javascripts/roadmap/components/epics_list_section_spec.js
...javascripts/roadmap/components/epics_list_section_spec.js
+165
-0
No files found.
ee/app/assets/javascripts/roadmap/components/epics_list_section.vue
0 → 100644
View file @
9d46d31e
<
script
>
import
eventHub
from
'
../event_hub
'
;
import
{
SCROLL_BAR_SIZE
}
from
'
../constants
'
;
import
epicItem
from
'
./epic_item.vue
'
;
export
default
{
components
:
{
epicItem
,
},
props
:
{
epics
:
{
type
:
Array
,
required
:
true
,
},
timeframe
:
{
type
:
Array
,
required
:
true
,
},
currentGroupId
:
{
type
:
Number
,
required
:
true
,
},
shellWidth
:
{
type
:
Number
,
required
:
true
,
},
},
data
()
{
return
{
shellHeight
:
0
,
emptyRowHeight
:
0
,
showEmptyRow
:
false
,
};
},
computed
:
{
/**
* Return width after reducing scrollbar size
* such that Epic item cells do not consider
* scrollbar
*/
calcShellWidth
()
{
return
this
.
shellWidth
-
SCROLL_BAR_SIZE
;
},
/**
* Adjust tbody styles while pushing scrollbar further away
* from the view
*/
tbodyStyles
()
{
return
`width:
${
this
.
shellWidth
+
SCROLL_BAR_SIZE
}
px; height:
${
this
.
shellHeight
}
px;`
;
},
emptyRowCellStyles
()
{
return
`height:
${
this
.
emptyRowHeight
}
px;`
;
},
},
watch
:
{
shellWidth
:
function
shellWidth
()
{
// Scroll view to today indicator only when shellWidth is updated.
this
.
scrollToTodayIndicator
();
},
},
mounted
()
{
this
.
$nextTick
(()
=>
{
this
.
initMounted
();
});
},
methods
:
{
initMounted
()
{
// Get available shell height based on viewport height
this
.
shellHeight
=
window
.
innerHeight
-
(
this
.
$el
.
offsetTop
+
this
.
$root
.
$el
.
offsetTop
);
// In case there are epics present, initialize empty row
if
(
this
.
epics
.
length
)
{
this
.
initEmptyRow
();
}
eventHub
.
$emit
(
'
epicsListRendered
'
,
{
width
:
this
.
$el
.
clientWidth
,
height
:
this
.
shellHeight
,
});
},
/**
* In case number of epics in the list are not sufficient
* to fill in full page height, we need to show an empty row
* at the bottom with fixed absolute height such that the
* column rulers expand to full page height
*
* This method calculates absolute height for empty column in pixels
* based on height of available list items and sets it to component
* props.
*/
initEmptyRow
()
{
const
children
=
this
.
$children
;
let
approxChildrenHeight
=
children
[
0
].
$el
.
clientHeight
*
this
.
epics
.
length
;
// Check if approximate height is greater than shell height
if
(
approxChildrenHeight
<
this
.
shellHeight
)
{
// reset approximate height and recalculate actual height
approxChildrenHeight
=
0
;
children
.
forEach
((
child
)
=>
{
// accumulate children height
// compensate for bottom border
approxChildrenHeight
+=
child
.
$el
.
clientHeight
;
});
// set height and show empty row reducing horizontal scrollbar size
this
.
emptyRowHeight
=
(
this
.
shellHeight
-
approxChildrenHeight
)
-
1
;
this
.
showEmptyRow
=
true
;
}
},
/**
* We can easily use `eventHub` and dispatch this event
* to all sibling and child components but it adds an overhead/delay
* resulting to janky element positioning. Hence, we directly
* update raw element properties upon event via jQuery.
*/
handleScroll
()
{
const
scrollLeft
=
this
.
$el
.
scrollLeft
;
const
tableEl
=
this
.
$el
.
parentElement
;
if
(
tableEl
)
{
const
$theadEl
=
$
(
tableEl
).
find
(
'
thead
'
);
const
$tbodyEl
=
$
(
tableEl
).
find
(
'
tbody
'
);
$theadEl
.
css
(
'
left
'
,
-
scrollLeft
);
$theadEl
.
find
(
'
th:nth-child(1)
'
).
css
(
'
left
'
,
scrollLeft
);
$tbodyEl
.
find
(
'
td:nth-child(1)
'
).
css
(
'
left
'
,
scrollLeft
);
}
eventHub
.
$emit
(
'
epicsListScrolled
'
,
this
.
$el
.
scrollTop
,
this
.
$el
.
scrollLeft
);
},
/**
* `clientWidth` is full width of list section, and we need to
* scroll up to 60% of the view where today indicator is present.
*
* Reason for 60% is that "today" always falls in the middle of timeframe range.
*/
scrollToTodayIndicator
()
{
const
uptoTodayIndicator
=
Math
.
ceil
((
this
.
$el
.
clientWidth
*
60
)
/
100
);
this
.
$el
.
scrollTo
(
uptoTodayIndicator
,
0
);
},
},
};
</
script
>
<
template
>
<tbody
class=
"epics-list-section"
:style=
"tbodyStyles"
@
scroll=
"handleScroll"
>
<epic-item
v-for=
"(epic, index) in epics"
:key=
"index"
:epic=
"epic"
:timeframe=
"timeframe"
:current-group-id=
"currentGroupId"
:shell-width=
"calcShellWidth"
/>
<tr
v-if=
"showEmptyRow"
class=
"epics-list-item epics-list-item-empty"
>
<td
class=
"epic-details-cell"
:style=
"emptyRowCellStyles"
>
</td>
<td
class=
"epic-timeline-cell"
v-for=
"(timeframeItem, index) in timeframe"
:key=
"index"
:style=
"emptyRowCellStyles"
>
</td>
</tr>
</tbody>
</
template
>
spec/javascripts/roadmap/components/epics_list_section_spec.js
0 → 100644
View file @
9d46d31e
import
Vue
from
'
vue
'
;
import
epicsListSectionComponent
from
'
ee/roadmap/components/epics_list_section.vue
'
;
import
RoadmapStore
from
'
ee/roadmap/store/roadmap_store
'
;
import
eventHub
from
'
ee/roadmap/event_hub
'
;
import
{
rawEpics
,
mockTimeframe
,
mockGroupId
,
mockShellWidth
}
from
'
../mock_data
'
;
import
mountComponent
from
'
../../helpers/vue_mount_component_helper
'
;
const
store
=
new
RoadmapStore
(
mockGroupId
,
mockTimeframe
);
store
.
setEpics
(
rawEpics
);
const
mockEpics
=
store
.
getEpics
();
const
createComponent
=
({
epics
=
mockEpics
,
timeframe
=
mockTimeframe
,
currentGroupId
=
mockGroupId
,
shellWidth
=
mockShellWidth
,
})
=>
{
const
Component
=
Vue
.
extend
(
epicsListSectionComponent
);
return
mountComponent
(
Component
,
{
epics
,
timeframe
,
currentGroupId
,
shellWidth
,
});
};
describe
(
'
EpicsListSectionComponent
'
,
()
=>
{
let
vm
;
afterEach
(()
=>
{
vm
.
$destroy
();
});
describe
(
'
data
'
,
()
=>
{
it
(
'
returns default data props
'
,
()
=>
{
vm
=
createComponent
({});
expect
(
vm
.
shellHeight
).
toBe
(
0
);
expect
(
vm
.
emptyRowHeight
).
toBe
(
0
);
expect
(
vm
.
showEmptyRow
).
toBe
(
false
);
});
});
describe
(
'
computed
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
createComponent
({});
});
describe
(
'
calcShellWidth
'
,
()
=>
{
it
(
'
returns shellWidth after deducting predefined scrollbar size
'
,
()
=>
{
// shellWidth is 2000 (as defined above in mockShellWidth)
// SCROLLBAR_SIZE is 15 (as defined in app's constants.js)
// Hence, calcShellWidth = shellWidth - SCROLLBAR_SIZE
expect
(
vm
.
calcShellWidth
).
toBe
(
1985
);
});
});
describe
(
'
tbodyStyles
'
,
()
=>
{
it
(
'
returns computed style string based on shellWidth and shellHeight
'
,
()
=>
{
expect
(
vm
.
tbodyStyles
).
toBe
(
'
width: 2015px; height: 0px;
'
);
});
});
describe
(
'
emptyRowCellStyles
'
,
()
=>
{
it
(
'
returns computed style string based on emptyRowHeight
'
,
()
=>
{
expect
(
vm
.
emptyRowCellStyles
).
toBe
(
'
height: 0px;
'
);
});
});
});
describe
(
'
methods
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
createComponent
({});
});
describe
(
'
initMounted
'
,
()
=>
{
it
(
'
initializes shellHeight based on window.innerHeight and component element position
'
,
(
done
)
=>
{
vm
.
$nextTick
(()
=>
{
expect
(
vm
.
shellHeight
).
toBe
(
600
);
done
();
});
});
it
(
'
calls initEmptyRow() when there are Epics to render
'
,
(
done
)
=>
{
spyOn
(
vm
,
'
initEmptyRow
'
).
and
.
callThrough
();
vm
.
$nextTick
(()
=>
{
expect
(
vm
.
initEmptyRow
).
toHaveBeenCalled
();
done
();
});
});
it
(
'
emits `epicsListRendered` via eventHub
'
,
(
done
)
=>
{
spyOn
(
eventHub
,
'
$emit
'
);
vm
.
$nextTick
(()
=>
{
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
epicsListRendered
'
,
jasmine
.
any
(
Object
));
done
();
});
});
});
describe
(
'
initEmptyRow
'
,
()
=>
{
it
(
'
sets `emptyRowHeight` and `showEmptyRow` props when shellHeight is greater than approximate height of epics list
'
,
(
done
)
=>
{
vm
.
$nextTick
(()
=>
{
expect
(
vm
.
emptyRowHeight
).
toBe
(
599
);
// total size -1px
expect
(
vm
.
showEmptyRow
).
toBe
(
true
);
done
();
});
});
it
(
'
does not set `emptyRowHeight` and `showEmptyRow` props when shellHeight is less than approximate height of epics list
'
,
(
done
)
=>
{
const
initialHeight
=
window
.
innerHeight
;
window
.
innerHeight
=
0
;
const
vmMoreEpics
=
createComponent
({
epics
:
mockEpics
.
concat
(
mockEpics
).
concat
(
mockEpics
),
});
vmMoreEpics
.
$nextTick
(()
=>
{
expect
(
vmMoreEpics
.
emptyRowHeight
).
toBe
(
0
);
expect
(
vmMoreEpics
.
showEmptyRow
).
toBe
(
false
);
window
.
innerHeight
=
initialHeight
;
// reset to prevent any side effects
done
();
});
});
});
describe
(
'
handleScroll
'
,
()
=>
{
it
(
'
emits `epicsListScrolled` event via eventHub
'
,
()
=>
{
spyOn
(
eventHub
,
'
$emit
'
);
vm
.
handleScroll
();
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
epicsListScrolled
'
,
jasmine
.
any
(
Number
),
jasmine
.
any
(
Number
));
});
});
describe
(
'
scrollToTodayIndicator
'
,
()
=>
{
it
(
'
scrolls table body to put timeline today indicator in focus
'
,
()
=>
{
spyOn
(
vm
.
$el
,
'
scrollTo
'
);
vm
.
scrollToTodayIndicator
();
expect
(
vm
.
$el
.
scrollTo
).
toHaveBeenCalledWith
(
jasmine
.
any
(
Number
),
0
);
});
});
});
describe
(
'
template
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
createComponent
({});
});
it
(
'
renders component container element with class `epics-list-section`
'
,
(
done
)
=>
{
vm
.
$nextTick
(()
=>
{
expect
(
vm
.
$el
.
classList
.
contains
(
'
epics-list-section
'
)).
toBe
(
true
);
done
();
});
});
it
(
'
renders component container element with `width` and `left` properties applied via style attribute
'
,
(
done
)
=>
{
vm
.
$nextTick
(()
=>
{
expect
(
vm
.
$el
.
getAttribute
(
'
style
'
)).
toBe
(
'
width: 2015px; height: 0px;
'
);
done
();
});
});
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment