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
c07e0377
Commit
c07e0377
authored
Nov 16, 2016
by
Bryce Johnson
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Round out tests for time tracking component.
parent
997def72
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
267 additions
and
93 deletions
+267
-93
app/assets/javascripts/issuable_time_tracker.js.es6
app/assets/javascripts/issuable_time_tracker.js.es6
+4
-1
app/views/shared/issuable/_sidebar.html.haml
app/views/shared/issuable/_sidebar.html.haml
+1
-1
spec/javascripts/issuable_time_tracker_spec.js.es6
spec/javascripts/issuable_time_tracker_spec.js.es6
+262
-91
No files found.
app/assets/javascripts/issuable_time_tracker.js.es6
View file @
c07e0377
...
@@ -125,7 +125,8 @@
...
@@ -125,7 +125,8 @@
</div>
</div>
<div class='title hide-collapsed'>
<div class='title hide-collapsed'>
Time tracking
Time tracking
<div class='help-button pull-right' v-if='showHelp' v-on:click='toggleHelpState(true)'>
<div class='help-button pull-right' v-if='!showHelp' v-on:click='toggleHelpState(true)'>
<i class='fa fa-question-circle'></i>
</div>
</div>
</div>
</div>
<div class='time-tracking-content hide-collapsed'>
<div class='time-tracking-content hide-collapsed'>
...
@@ -159,6 +160,7 @@
...
@@ -159,6 +160,7 @@
</div>
</div>
<div class='time-tracking-help-state' v-if='showHelp'>
<div class='time-tracking-help-state' v-if='showHelp'>
<div class='close-help-button pull-right' v-on:click='toggleHelpState(false)'>
<div class='close-help-button pull-right' v-on:click='toggleHelpState(false)'>
<i class='fa fa-close'></i>
</div>
</div>
<div class='time-tracking-info'>
<div class='time-tracking-info'>
<h4>Track time with slash commands</h4>
<h4>Track time with slash commands</h4>
...
@@ -171,6 +173,7 @@
...
@@ -171,6 +173,7 @@
<code>/spend</code>
<code>/spend</code>
will update the sum of the time spent.
will update the sum of the time spent.
</p>
</p>
<a class='btn btn-default learn-more-button' href='http://example.com/time-tracking-url'> Learn more </a>
</div>
</div>
</div>
</div>
</div>
</div>
...
...
app/views/shared/issuable/_sidebar.html.haml
View file @
c07e0377
...
@@ -74,7 +74,7 @@
...
@@ -74,7 +74,7 @@
=
dropdown_tag
(
'Milestone'
,
options:
{
title:
'Assign milestone'
,
toggle_class:
'js-milestone-select js-extra-options'
,
filter:
true
,
dropdown_class:
'dropdown-menu-selectable'
,
placeholder:
'Search milestones'
,
data:
{
show_no:
true
,
field_name:
"
#{
issuable
.
to_ability_name
}
[milestone_id]"
,
project_id:
@project
.
id
,
issuable_id:
issuable
.
id
,
milestones:
namespace_project_milestones_path
(
@project
.
namespace
,
@project
,
:json
),
ability_name:
issuable
.
to_ability_name
,
issue_update:
issuable_json_path
(
issuable
),
use_id:
true
}})
=
dropdown_tag
(
'Milestone'
,
options:
{
title:
'Assign milestone'
,
toggle_class:
'js-milestone-select js-extra-options'
,
filter:
true
,
dropdown_class:
'dropdown-menu-selectable'
,
placeholder:
'Search milestones'
,
data:
{
show_no:
true
,
field_name:
"
#{
issuable
.
to_ability_name
}
[milestone_id]"
,
project_id:
@project
.
id
,
issuable_id:
issuable
.
id
,
milestones:
namespace_project_milestones_path
(
@project
.
namespace
,
@project
,
:json
),
ability_name:
issuable
.
to_ability_name
,
issue_update:
issuable_json_path
(
issuable
),
use_id:
true
}})
// TODO: Need to add check for time_estimated - if issuable.has_attribute?(:time_estimated) once hooked up to backend
// TODO: Need to add check for time_estimated - if issuable.has_attribute?(:time_estimated) once hooked up to backend
-
if
issuable
.
has_attribute?
(
:time_estimate
)
-
if
issuable
.
has_attribute?
(
:time_estimate
)
#issuable-time-tracker
.block
{
issuable:
issuable
.
to_json
()
}
#issuable-time-tracker
.block
%issuable-time-tracker
{
':time_estimate'
=>
'issuable.time_estimate'
,
':time_spent'
=>
'issuable.time_spent'
}
%issuable-time-tracker
{
':time_estimate'
=>
'issuable.time_estimate'
,
':time_spent'
=>
'issuable.time_spent'
}
-
if
issuable
.
has_attribute?
(
:due_date
)
-
if
issuable
.
has_attribute?
(
:due_date
)
.block.due_date
.block.due_date
...
...
spec/javascripts/issuable_time_tracker_spec.js.es6
View file @
c07e0377
/* eslint-disable */
/* eslint-disable */
//= require jquery
//= require jquery
//= require vue
//= require vue
//= require vue-resource
//= require directives/tooltip_title
//= require issuable_time_tracker
//= require issuable_time_tracker
//= require directives/tooltip_title
((gl) => {
function initComponent(time_estimate = 100000, time_spent = 5000 ) {
function initComponent({time_estimate = 100000, time_spent = 5000}) {
fixture.set(`
fixture.set(`<div id="mock-container"><issuable-time-tracker :time_estimate='${time_estimate}', :time_spent='${time_spent}'></issuable-time-tracker></div>`);
<div>
const elem = document.getElementById('mock-container');
<div id="mock-container"></div>
debugger;
</div>
const comp = new Vue({
`);
el: elem,
propsData: { time_estimate: time_estimate, time_spent: time_estimate },
});
return comp;
this.initialData = {
}
time_estimate,
describe('Issuable Time Tracker', function() {
time_spent
};
this.timeTracker = new gl.IssuableTimeTracker({
el: '#mock-container',
propsData: this.initialData
});
}
((gl) => {
describe('Issuable Time Tracker', function() {
describe('Initialization', function() {
describe('Initialization', function() {
beforeEach(function() {
beforeEach(function() {
this.initialData = { time_estimate: 100000, time_spent: 50000 };
initComponent.apply(this);
this.timeTracker = initComponent(this.initialData);
});
});
it('should return something defined', function() {
it('should return something defined', function() {
...
@@ -30,13 +34,10 @@
...
@@ -30,13 +34,10 @@
});
});
it ('should correctly set time_estimate', function() {
it ('should correctly set time_estimate', function() {
expect(this.timeTracker.$data.time_estimate).toBeDefined();
expect(this.timeTracker.time_estimate).toBe(this.initialData.time_estimate);
expect(this.timeTracker.$data.time_estimate).toBe(this.initialData.time_estimate);
});
});
it ('should correctly set time_spent', function() {
it ('should correctly set time_spent', function() {
expect(this.timeTracker.time_spent).toBe(this.initialData.time_spent);
expect(this.timeTracker.$data.time_spent).toBeDefined();
expect(this.timeTracker.$data.time_spent).toBe(this.initialData.time_spent);
});
});
});
});
...
@@ -44,109 +45,167 @@
...
@@ -44,109 +45,167 @@
describe('Panes', function() {
describe('Panes', function() {
describe('Comparison pane', function() {
describe('Comparison pane', function() {
beforeEach(function() {
beforeEach(function() {
this.initialData = { time_estimate: 100000, time_spent: 50000 };
initComponent.apply(this);
this.timeTracker = initComponent(this.initialData);
});
});
it('should show the "Comparison" pane when time_estimate and time_spent are truthy', function() {
it('should show the "Comparison" pane when time_estimate and time_spent are truthy', function() {
const $comparisonPane =
$
('.time-tracking-pane-compare');
const $comparisonPane =
this.timeTracker.$el.querySelector
('.time-tracking-pane-compare');
expect(this.timeTracker.showComparison).toBe(true);
expect(this.timeTracker.showComparison).toBe(true);
expect($comparisonPane).toBeVisible();
expect($comparisonPane).toBeVisible();
});
});
it('should not show panes besides the "Comparison" pane when time_estimate and time_spent are truthy', function() {
it('should display the human readable version of time estimated', function() {
const $comparisonPane = $('.time-tracking-pane-compare');
const estimateText = this.timeTracker.$el.querySelector('.time-tracking-pane-compare .estimated .compare-value').innerText;
const correctText = '0w 3d 3h 46m';
expect(estimateText).toBe(correctText);
});
it('should display the human readable version of time spent', function() {
const spentText = this.timeTracker.$el.querySelector('.time-tracking-pane-compare .compare-value.spent').innerText;
const correctText = '0w 0d 1h 23m';
expect($comparisonPane.siblings()).toBeHidden();
expect(spentText).toBe(correctText);
});
describe('Remaining meter', function() {
it('should display the remaining meter with the correct width', function() {
const meterWidth = this.timeTracker.$el.querySelector('.time-tracking-pane-compare .meter-fill').style.width;
const correctWidth = '5%';
expect(meterWidth).toBe(correctWidth);
});
it('should display the remaining meter with the correct background color when within estimate', function() {
const styledMeter = $(this.timeTracker.$el).find('.time-tracking-pane-compare .within_estimate .meter-fill');
expect(styledMeter.length).toBe(1);
});
it('should display the remaining meter with the correct background color when over estimate', function() {
this.timeTracker.time_estimate = 1;
this.timeTracker.time_spent = 2;
Vue.nextTick(() => {
const styledMeter = $(this.timeTracker.$el).find('.time-tracking-pane-compare .over_estimate .meter-fill');
expect(styledMeter.length).toBe(1);
});
});
});
});
});
});
describe("Estimate only pane", function() {
describe("Estimate only pane", function() {
beforeEach(function() {
beforeEach(function() {
const time_estimate = 100000;
initComponent.apply(this, [10000, 0]);
const time_spent = 0;
const timeTrackingComponent = Vue.extend(gl.IssuableTimeTracker);
const initialData = this.initialData = { time_estimate, time_spent };
this.timeTracker = new timeTrackingComponent({
data: initialData
}).$mount();
});
});
// Look for the value
it('should only show the "Estimate only" pane when time_estimate is truthy and time_spent is falsey', function() {
it('should only show the "Estimate only" pane when time_estimate is truthy and time_spent is falsey', function() {
const $estimateOnlyPane =
$
('.time-tracking-estimate-only');
const $estimateOnlyPane =
this.timeTracker.$el.querySelector
('.time-tracking-estimate-only');
expect(this.timeTracker.showEstimateOnly).toBe(true);
expect(this.timeTracker.showEstimateOnly).toBe(true);
expect($estimateOnlyPane).toBeVisible();
expect($estimateOnlyPane).toBeVisible();
});
});
it('should display the human readable version of time estimated', function() {
const estimateText = this.timeTracker.$el.querySelector('.time-tracking-estimate-only').innerText;
const correctText = 'Estimated: 0w 0d 2h 46m';
expect(estimateText).toBe(correctText);
});
});
});
describe('Spent only pane', function() {
describe('Spent only pane', function() {
beforeEach(function() {
beforeEach(function() {
const time_estimate = 0;
initComponent.apply(this, [0, 5000]);
const time_spent = 50000;
const timeTrackingComponent = Vue.extend(gl.IssuableTimeTracker);
const initialData = this.initialData = { time_estimate, time_spent };
this.timeTracker = new timeTrackingComponent({
data: initialData
}).$mount();
});
});
// Look for the value
// Look for the value
it('should only show the "Spent only" pane when time_estimate is falsey and time_spent is truthy', function() {
it('should only show the "Spent only" pane when time_estimate is falsey and time_spent is truthy', function() {
const $spentOnlyPane =
$
('.time-tracking-spend-only');
const $spentOnlyPane =
this.timeTracker.$el.querySelector
('.time-tracking-spend-only');
expect(this.timeTracker.showSpentOnly).toBe(true);
expect(this.timeTracker.showSpentOnly).toBe(true);
expect($spentOnlyPane).toBeVisible();
expect($spentOnlyPane).toBeVisible();
});
});
it('should display the human readable version of time spent', function() {
const spentText = this.timeTracker.$el.querySelector('.time-tracking-spend-only').innerText;
const correctText = 'Spent: 0w 0d 1h 23m';
expect(spentText).toBe(correctText);
});
});
});
describe('No time tracking pane', function() {
describe('No time tracking pane', function() {
beforeEach(function() {
beforeEach(function() {
const time_estimate = 0;
initComponent.apply(this, [0, 0]);
const time_spent = 0;
const timeTrackingComponent = Vue.extend(gl.IssuableTimeTracker);
const initialData = this.initialData = { time_estimate, time_spent };
this.timeTracker = new timeTrackingComponent({
data: initialData
}).$mount();
});
});
// Look for The text
it('should only show the "No time tracking" pane when both time_estimate and time_spent are falsey', function() {
it('should only show the "No time tracking" pane when both time_estimate and time_spent are falsey', function() {
const $noTrackingPane =
$
('.time-tracking-no-tracking');
const $noTrackingPane =
this.timeTracker.$el.querySelector
('.time-tracking-no-tracking');
expect(this.timeTracker.showNoTimeTracking).toBe(true);
expect(this.timeTracker.showNoTimeTracking).toBe(true);
expect($noTrackingPane).toBeVisible();
expect($noTrackingPane).toBeVisible();
});
});
it('should display the status text', function() {
const noTrackingText = this.timeTracker.$el.querySelector('.time-tracking-no-tracking .no-value').innerText;
const correctText = 'No estimate or time spent';
expect(noTrackingText).toBe(correctText);
});
});
});
describe("Help pane", function() {
describe("Help pane", function() {
beforeEach(function() {
beforeEach(function() {
const time_estimate = 100000;
initComponent.apply(this, [0, 0]);
const time_spent = 50000;
const timeTrackingComponent = Vue.extend(gl.IssuableTimeTracker);
const initialData = this.initialData = { time_estimate, time_spent };
this.timeTracker = new timeTrackingComponent({
data: initialData
}).$mount();
});
});
// close button
// link to help
it('should not show the "Help" pane by default', function() {
it('should only not show the "Help" pane by default', function() {
const $helpPane = this.timeTracker.$el.querySelector('.time-tracking-help-state');
const $helpPane = $('.time-tracking-help-state');
expect(this.timeTracker.showHelp).toBe(false);
expect(this.timeTracker.showHelp).toBe(false);
expect($helpPane).toBeHidden();
expect($helpPane).toBeNull();
});
it('should link to the correct documentation', function(done) {
const correctUrl = 'http://example.com/time-tracking-url';
$(this.timeTracker.$el).find('.help-button').click();
Vue.nextTick(() => {
const currentHref = $(this.timeTracker.$el).find('.learn-more-button').attr('href');
expect(currentHref).toBe(correctUrl);
done();
});
});
it('should show the "Help" pane when help button is clicked', function(done) {
$(this.timeTracker.$el).find('.help-button').click();
Vue.nextTick(() => {
const $helpPane = this.timeTracker.$el.querySelector('.time-tracking-help-state');
expect(this.timeTracker.showHelp).toBe(true);
expect($helpPane).toBeVisible();
done();
});
});
});
it('should only show the "Help" pane when toggled', function() {
it('should not show the "Help" pane when help button is clicked and then closed', function(done) {
const $helpPane = $('.time-tracking-help-state');
$(this.timeTracker.$el).find('.help-button').click();
Vue.nextTick(() => {
$(this.timeTracker.$el).find('.close-help-button').click();
expect(this.timeTracker.showHelp).toBe(true);
Vue.nextTick(() => {
expect($helpPane).toBeVisible();
const $helpPane = this.timeTracker.$el.querySelector('.time-tracking-help-state');
expect(this.timeTracker.showHelp).toBe(false);
expect($helpPane).toBeNull();
done();
});
});
});
});
});
});
});
});
...
@@ -159,28 +218,140 @@
...
@@ -159,28 +218,140 @@
});
});
describe('Methods', function() {
describe('Methods', function() {
// parseSeconds
beforeEach(function() {
initComponent.apply(this);
});
});
});
// show the correct pane
describe('parseSeconds', function() {
// parse second
it('should correctly parse a negative value', function() {
// seconds to minutes
const parser = this.timeTracker.parseSeconds;
// stringify a time value
// the percent is being calculated and displayed correctly on the compare meter
// differ works, if needed
// whether values that are important are actually display
it('should parse a time diff based on total minutes', function() {
});
it('should stringify a time value', function() {
const zeroSeconds = parser(-1000);
});
it('should abbreviate a stringified value', function() {
expect(zeroSeconds.minutes).toBe(16);
});
expect(zeroSeconds.hours).toBe(0);
expect(zeroSeconds.days).toBe(0);
expect(zeroSeconds.weeks).toBe(0);
});
it('should correctly parse a zero value', function() {
const parser = this.timeTracker.parseSeconds;
const zeroSeconds = parser(0);
expect(zeroSeconds.minutes).toBe(0);
expect(zeroSeconds.hours).toBe(0);
expect(zeroSeconds.days).toBe(0);
expect(zeroSeconds.weeks).toBe(0);
});
it('should correctly parse a small non-zero second values', function() {
const parser = this.timeTracker.parseSeconds;
const subOneMinute = parser(10);
expect(subOneMinute.minutes).toBe(0);
expect(subOneMinute.hours).toBe(0);
expect(subOneMinute.days).toBe(0);
expect(subOneMinute.weeks).toBe(0);
const aboveOneMinute = parser(100);
expect(aboveOneMinute.minutes).toBe(1);
expect(aboveOneMinute.hours).toBe(0);
expect(aboveOneMinute.days).toBe(0);
expect(aboveOneMinute.weeks).toBe(0);
const manyMinutes = parser(1000);
expect(manyMinutes.minutes).toBe(16);
expect(manyMinutes.hours).toBe(0);
expect(manyMinutes.days).toBe(0);
expect(manyMinutes.weeks).toBe(0);
});
it('should correctly parse large second values', function() {
const parser = this.timeTracker.parseSeconds;
const aboveOneHour = parser(4800);
expect(aboveOneHour.minutes).toBe(20);
expect(aboveOneHour.hours).toBe(1);
expect(aboveOneHour.days).toBe(0);
expect(aboveOneHour.weeks).toBe(0);
const aboveOneDay = parser(110000);
it('should toggle the help state', function() {
expect(aboveOneDay.minutes).toBe(33);
expect(aboveOneDay.hours).toBe(6);
expect(aboveOneDay.days).toBe(3);
expect(aboveOneDay.weeks).toBe(0);
const aboveOneWeek = parser(25000000);
expect(aboveOneWeek.minutes).toBe(26);
expect(aboveOneWeek.hours).toBe(0);
expect(aboveOneWeek.days).toBe(3);
expect(aboveOneWeek.weeks).toBe(173);
});
});
describe('stringifyTime', function() {
it('should stringify values with all non-zero units', function() {
const timeObject = {
weeks: 1,
days: 4,
hours: 7,
minutes: 20
};
const timeString = this.timeTracker.stringifyTime(timeObject);
expect(timeString).toBe('1w 4d 7h 20m');
});
it('should stringify values with some non-zero units', function() {
const timeObject = {
weeks: 0,
days: 4,
hours: 0,
minutes: 20
};
const timeString = this.timeTracker.stringifyTime(timeObject);
expect(timeString).toBe('0w 4d 0h 20m');
});
it('should stringify values with no non-zero units', function() {
const timeObject = {
weeks: 0,
days: 0,
hours: 0,
minutes: 0
};
const timeString = this.timeTracker.stringifyTime(timeObject);
expect(timeString).toBe('0w 0d 0h 0m');
});
});
describe('abbreviateTime', function() {
it('should abbreviate stringified times for weeks', function() {
const fullTimeString = '1w 3d 4h 5m';
expect(this.timeTracker.abbreviateTime(fullTimeString)).toBe('1w');
});
it('should abbreviate stringified times for non-weeks', function() {
const fullTimeString = '0w 3d 4h 5m';
expect(this.timeTracker.abbreviateTime(fullTimeString)).toBe('3d');
});
});
});
});
});
});
});
})(window.gl || (window.gl = {}));
})(window.gl || (window.gl = {}));
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