Commit e96878e3 authored by Ezekiel Kigbo's avatar Ezekiel Kigbo Committed by Kushal Pandya

Refactor specs to match current guidelines

Replace done callbacks with async/await, update
to use finders instead of classes and add a
createComponent method
parent 965377e5
...@@ -96,7 +96,12 @@ export default { ...@@ -96,7 +96,12 @@ export default {
</script> </script>
<template> <template>
<div v-gl-tooltip:body.viewport.left :title="tooltipText" class="sidebar-collapsed-icon"> <div
v-gl-tooltip:body.viewport.left
:title="tooltipText"
data-testid="collapsedState"
class="sidebar-collapsed-icon"
>
<gl-icon name="timer" /> <gl-icon name="timer" />
<div class="time-tracking-collapsed-summary"> <div class="time-tracking-collapsed-summary">
<div :class="divClass"> <div :class="divClass">
......
...@@ -70,14 +70,19 @@ export default { ...@@ -70,14 +70,19 @@ export default {
</script> </script>
<template> <template>
<div class="time-tracking-comparison-pane"> <div data-testid="timeTrackingComparisonPane">
<div <div
v-gl-tooltip v-gl-tooltip
data-testid="compareMeter"
:title="timeRemainingTooltip" :title="timeRemainingTooltip"
:class="timeRemainingStatusClass" :class="timeRemainingStatusClass"
class="compare-meter" class="compare-meter"
> >
<gl-progress-bar :value="timeRemainingPercent" :variant="progressBarVariant" /> <gl-progress-bar
data-testid="timeRemainingProgress"
:value="timeRemainingPercent"
:variant="progressBarVariant"
/>
<div class="compare-display-container"> <div class="compare-display-container">
<div class="compare-display float-left"> <div class="compare-display float-left">
<span class="compare-label">{{ s__('TimeTracking|Spent') }}</span> <span class="compare-label">{{ s__('TimeTracking|Spent') }}</span>
......
...@@ -11,7 +11,8 @@ export default { ...@@ -11,7 +11,8 @@ export default {
</script> </script>
<template> <template>
<div class="time-tracking-estimate-only-pane"> <div data-testid="estimateOnlyPane">
<span class="bold"> {{ s__('TimeTracking|Estimated:') }} </span> {{ timeEstimateHumanReadable }} <span class="gl-font-weight-bold">{{ s__('TimeTracking|Estimated:') }} </span
>{{ timeEstimateHumanReadable }}
</div> </div>
</template> </template>
...@@ -34,7 +34,7 @@ export default { ...@@ -34,7 +34,7 @@ export default {
</script> </script>
<template> <template>
<div class="time-tracking-help-state"> <div data-testid="helpPane" class="time-tracking-help-state">
<div class="time-tracking-info"> <div class="time-tracking-info">
<h4>{{ __('Track time with quick actions') }}</h4> <h4>{{ __('Track time with quick actions') }}</h4>
<p>{{ __('Quick actions can be used in the issues description and comment boxes.') }}</p> <p>{{ __('Quick actions can be used in the issues description and comment boxes.') }}</p>
......
...@@ -5,7 +5,7 @@ export default { ...@@ -5,7 +5,7 @@ export default {
</script> </script>
<template> <template>
<div class="time-tracking-no-tracking-pane"> <div data-testid="noTrackingPane">
<span class="no-value"> {{ __('No estimate or time spent') }} </span> <span class="no-value">{{ __('No estimate or time spent') }}</span>
</div> </div>
</template> </template>
...@@ -15,7 +15,7 @@ export default { ...@@ -15,7 +15,7 @@ export default {
return sprintf( return sprintf(
s__('TimeTracking|%{startTag}Spent: %{endTag}%{timeSpentHumanReadable}'), s__('TimeTracking|%{startTag}Spent: %{endTag}%{timeSpentHumanReadable}'),
{ {
startTag: '<span class="bold">', startTag: '<span class="gl-font-weight-bold">',
endTag: '</span>', endTag: '</span>',
timeSpentHumanReadable: this.timeSpentHumanReadable, timeSpentHumanReadable: this.timeSpentHumanReadable,
}, },
...@@ -27,5 +27,5 @@ export default { ...@@ -27,5 +27,5 @@ export default {
</script> </script>
<template> <template>
<div class="time-tracking-spend-only-pane" v-html="timeSpent"></div> <div data-testid="spentOnlyPane" v-html="timeSpent"></div>
</template> </template>
...@@ -105,11 +105,17 @@ export default { ...@@ -105,11 +105,17 @@ export default {
/> />
<div class="title hide-collapsed"> <div class="title hide-collapsed">
{{ __('Time tracking') }} {{ __('Time tracking') }}
<div v-if="!showHelpState" class="help-button float-right" @click="toggleHelpState(true)"> <div
v-if="!showHelpState"
data-testid="helpButton"
class="help-button float-right"
@click="toggleHelpState(true)"
>
<gl-icon name="question-o" /> <gl-icon name="question-o" />
</div> </div>
<div <div
v-if="showHelpState" v-else
data-testid="closeHelpButton"
class="close-help-button float-right" class="close-help-button float-right"
@click="toggleHelpState(false)" @click="toggleHelpState(false)"
> >
......
...@@ -76,7 +76,7 @@ RSpec.describe 'Milestone' do ...@@ -76,7 +76,7 @@ RSpec.describe 'Milestone' do
wait_for_requests wait_for_requests
page.within('.time-tracking-no-tracking-pane') do page.within('[data-testid="noTrackingPane"]') do
expect(page).to have_content 'No estimate or time spent' expect(page).to have_content 'No estimate or time spent'
end end
end end
...@@ -94,7 +94,7 @@ RSpec.describe 'Milestone' do ...@@ -94,7 +94,7 @@ RSpec.describe 'Milestone' do
wait_for_requests wait_for_requests
page.within('.time-tracking-spend-only-pane') do page.within('[data-testid="spentOnlyPane"]') do
expect(page).to have_content 'Spent: 3h' expect(page).to have_content 'Spent: 3h'
end end
end end
......
import Vue from 'vue'; import { createMockDirective } from 'helpers/vue_mock_directive';
import { mount } from '@vue/test-utils';
import mountComponent from 'helpers/vue_mount_component_helper';
import TimeTracker from '~/sidebar/components/time_tracking/time_tracker.vue'; import TimeTracker from '~/sidebar/components/time_tracking/time_tracker.vue';
describe('Issuable Time Tracker', () => { describe('Issuable Time Tracker', () => {
let initialData; let wrapper;
let vm;
const findByTestId = testId => wrapper.find(`[data-testid=${testId}]`);
const initTimeTrackingComponent = ({ const findComparisonMeter = () => findByTestId('compareMeter').attributes('title');
timeEstimate, const findCollapsedState = () => findByTestId('collapsedState');
timeSpent, const findTimeRemainingProgress = () => findByTestId('timeRemainingProgress');
timeEstimateHumanReadable,
timeSpentHumanReadable, const defaultProps = {
limitToHours, timeEstimate: 10_000, // 2h 46m
}) => { timeSpent: 5_000, // 1h 23m
setFixtures(` humanTimeEstimate: '2h 46m',
<div> humanTimeSpent: '1h 23m',
<div id="mock-container"></div> limitToHours: false,
</div> rootPath: '/',
`);
initialData = {
timeEstimate,
timeSpent,
humanTimeEstimate: timeEstimateHumanReadable,
humanTimeSpent: timeSpentHumanReadable,
limitToHours: Boolean(limitToHours),
rootPath: '/',
};
const TimeTrackingComponent = Vue.extend({
...TimeTracker,
components: {
...TimeTracker.components,
transition: {
// disable animations
render(h) {
return h('div', this.$slots.default);
},
},
},
});
vm = mountComponent(TimeTrackingComponent, initialData, '#mock-container');
}; };
const mountComponent = ({ props = {} } = {}) =>
mount(TimeTracker, {
propsData: { ...defaultProps, ...props },
directives: { GlTooltip: createMockDirective() },
});
afterEach(() => { afterEach(() => {
vm.$destroy(); wrapper.destroy();
}); });
describe('Initialization', () => { describe('Initialization', () => {
beforeEach(() => { beforeEach(() => {
initTimeTrackingComponent({ wrapper = mountComponent();
timeEstimate: 10000, // 2h 46m
timeSpent: 5000, // 1h 23m
timeEstimateHumanReadable: '2h 46m',
timeSpentHumanReadable: '1h 23m',
});
}); });
it('should return something defined', () => { it('should return something defined', () => {
expect(vm).toBeDefined(); expect(wrapper).toBeDefined();
}); });
it('should correctly set timeEstimate', done => { it('should correctly render timeEstimate', () => {
Vue.nextTick(() => { expect(findByTestId('timeTrackingComparisonPane').html()).toContain(
expect(vm.timeEstimate).toBe(initialData.timeEstimate); defaultProps.humanTimeEstimate,
done(); );
});
}); });
it('should correctly set time_spent', done => { it('should correctly render time_spent', () => {
Vue.nextTick(() => { expect(findByTestId('timeTrackingComparisonPane').html()).toContain(
expect(vm.timeSpent).toBe(initialData.timeSpent); defaultProps.humanTimeSpent,
done(); );
});
}); });
}); });
describe('Content Display', () => { describe('Content panes', () => {
describe('Panes', () => { describe('Comparison pane', () => {
describe('Comparison pane', () => { beforeEach(() => {
beforeEach(() => { wrapper = mountComponent({
initTimeTrackingComponent({ props: {
timeEstimate: 100000, // 1d 3h timeEstimate: 100_000, // 1d 3h
timeSpent: 5000, // 1h 23m timeSpent: 5_000, // 1h 23m
timeEstimateHumanReadable: '1d 3h', humanTimeEstimate: '1d 3h',
timeSpentHumanReadable: '1h 23m', humanTimeSpent: '1h 23m',
}); },
}); });
});
it('should show the "Comparison" pane when timeEstimate and time_spent are truthy', done => { it('should show the "Comparison" pane when timeEstimate and time_spent are truthy', () => {
Vue.nextTick(() => { const pane = findByTestId('timeTrackingComparisonPane');
expect(vm.showComparisonState).toBe(true); expect(pane.exists()).toBe(true);
const $comparisonPane = vm.$el.querySelector('.time-tracking-comparison-pane'); expect(pane.isVisible()).toBe(true);
});
expect($comparisonPane).toBeVisible();
done();
});
});
it('should show full times when the sidebar is collapsed', done => { it('should show full times when the sidebar is collapsed', () => {
Vue.nextTick(() => { expect(findCollapsedState().text()).toBe('1h 23m / 1d 3h');
const timeTrackingText = vm.$el.querySelector('.time-tracking-collapsed-summary span') });
.textContent;
expect(timeTrackingText.trim()).toBe('1h 23m / 1d 3h'); describe('Remaining meter', () => {
done(); it('should display the remaining meter with the correct width', () => {
}); expect(findTimeRemainingProgress().attributes('value')).toBe('5');
}); });
describe('Remaining meter', () => { it('should display the remaining meter with the correct background color when within estimate', () => {
it('should display the remaining meter with the correct width', done => { expect(findTimeRemainingProgress().attributes('variant')).toBe('primary');
Vue.nextTick(() => { });
expect(
vm.$el.querySelector('.time-tracking-comparison-pane .progress[value="5"]'),
).not.toBeNull();
done();
});
});
it('should display the remaining meter with the correct background color when within estimate', done => { it('should display the remaining meter with the correct background color when over estimate', () => {
Vue.nextTick(() => { wrapper = mountComponent({
expect( props: {
vm.$el.querySelector('.time-tracking-comparison-pane .progress[variant="primary"]'), timeEstimate: 10_000, // 2h 46m
).not.toBeNull(); timeSpent: 20_000_000, // 231 days
done(); },
});
}); });
it('should display the remaining meter with the correct background color when over estimate', done => { expect(findTimeRemainingProgress().attributes('variant')).toBe('danger');
vm.timeEstimate = 10000; // 2h 46m
vm.timeSpent = 20000000; // 231 days
Vue.nextTick(() => {
expect(
vm.$el.querySelector('.time-tracking-comparison-pane .progress[variant="danger"]'),
).not.toBeNull();
done();
});
});
}); });
}); });
});
describe('Comparison pane when limitToHours is true', () => { describe('Comparison pane when limitToHours is true', () => {
beforeEach(() => { beforeEach(async () => {
initTimeTrackingComponent({ wrapper = mountComponent({
timeEstimate: 100000, // 1d 3h props: {
timeSpent: 5000, // 1h 23m timeEstimate: 100_000, // 1d 3h
timeEstimateHumanReadable: '',
timeSpentHumanReadable: '',
limitToHours: true, limitToHours: true,
}); },
}); });
});
it('should show the correct tooltip text', done => { it('should show the correct tooltip text', async () => {
Vue.nextTick(() => { expect(findByTestId('timeTrackingComparisonPane').exists()).toBe(true);
expect(vm.showComparisonState).toBe(true); await wrapper.vm.$nextTick();
const $title = vm.$el.querySelector('.time-tracking-content .compare-meter').title;
expect($title).toBe('Time remaining: 26h 23m'); expect(findComparisonMeter()).toBe('Time remaining: 26h 23m');
done();
});
});
}); });
});
describe('Estimate only pane', () => { describe('Estimate only pane', () => {
beforeEach(() => { beforeEach(async () => {
initTimeTrackingComponent({ wrapper = mountComponent({
timeEstimate: 10000, // 2h 46m props: {
timeEstimate: 10_000, // 2h 46m
timeSpent: 0, timeSpent: 0,
timeEstimateHumanReadable: '2h 46m', timeEstimateHumanReadable: '2h 46m',
timeSpentHumanReadable: '', timeSpentHumanReadable: '',
}); },
}); });
await wrapper.vm.$nextTick();
});
it('should display the human readable version of time estimated', done => { it('should display the human readable version of time estimated', () => {
Vue.nextTick(() => { const estimateText = findByTestId('estimateOnlyPane').text();
const estimateText = vm.$el.querySelector('.time-tracking-estimate-only-pane') expect(estimateText.trim()).toBe('Estimated: 2h 46m');
.textContent;
const correctText = 'Estimated: 2h 46m';
expect(estimateText.trim()).toBe(correctText);
done();
});
});
}); });
});
describe('Spent only pane', () => { describe('Spent only pane', () => {
beforeEach(() => { beforeEach(() => {
initTimeTrackingComponent({ wrapper = mountComponent({
props: {
timeEstimate: 0, timeEstimate: 0,
timeSpent: 5000, // 1h 23m timeSpent: 5_000, // 1h 23m
timeEstimateHumanReadable: '2h 46m', timeEstimateHumanReadable: '2h 46m',
timeSpentHumanReadable: '1h 23m', timeSpentHumanReadable: '1h 23m',
}); },
}); });
});
it('should display the human readable version of time spent', done => { it('should display the human readable version of time spent', () => {
Vue.nextTick(() => { const spentText = findByTestId('spentOnlyPane').text();
const spentText = vm.$el.querySelector('.time-tracking-spend-only-pane').textContent; expect(spentText.trim()).toBe('Spent: 1h 23m');
const correctText = 'Spent: 1h 23m';
expect(spentText).toBe(correctText);
done();
});
});
}); });
});
describe('No time tracking pane', () => { describe('No time tracking pane', () => {
beforeEach(() => { beforeEach(() => {
initTimeTrackingComponent({ wrapper = mountComponent({
props: {
timeEstimate: 0, timeEstimate: 0,
timeSpent: 0, timeSpent: 0,
timeEstimateHumanReadable: '', timeEstimateHumanReadable: '',
timeSpentHumanReadable: '', timeSpentHumanReadable: '',
}); },
}); });
});
it('should only show the "No time tracking" pane when both timeEstimate and time_spent are falsey', done => { it('should only show the "No time tracking" pane when both timeEstimate and time_spent are falsey', () => {
Vue.nextTick(() => { const pane = findByTestId('noTrackingPane');
const $noTrackingPane = vm.$el.querySelector('.time-tracking-no-tracking-pane'); const correctText = 'No estimate or time spent';
const noTrackingText = $noTrackingPane.textContent; expect(pane.exists()).toBe(true);
const correctText = 'No estimate or time spent'; expect(pane.text().trim()).toBe(correctText);
expect(vm.showNoTimeTrackingState).toBe(true);
expect($noTrackingPane).toBeVisible();
expect(noTrackingText.trim()).toBe(correctText);
done();
});
});
}); });
});
describe('Help pane', () => { describe('Help pane', () => {
const helpButton = () => vm.$el.querySelector('.help-button'); const findHelpButton = () => findByTestId('helpButton');
const closeHelpButton = () => vm.$el.querySelector('.close-help-button'); const findCloseHelpButton = () => findByTestId('closeHelpButton');
const helpPane = () => vm.$el.querySelector('.time-tracking-help-state');
beforeEach(() => { beforeEach(async () => {
initTimeTrackingComponent({ timeEstimate: 0, timeSpent: 0 }); wrapper = mountComponent({ props: { timeEstimate: 0, timeSpent: 0 } });
await wrapper.vm.$nextTick();
});
return vm.$nextTick(); it('should not show the "Help" pane by default', () => {
}); expect(findByTestId('helpPane').exists()).toBe(false);
});
it('should not show the "Help" pane by default', () => { it('should show the "Help" pane when help button is clicked', async () => {
expect(vm.showHelpState).toBe(false); findHelpButton().trigger('click');
expect(helpPane()).toBeNull();
});
it('should show the "Help" pane when help button is clicked', () => { await wrapper.vm.$nextTick();
helpButton().click();
return vm.$nextTick().then(() => { expect(findByTestId('helpPane').exists()).toBe(true);
expect(vm.showHelpState).toBe(true); });
// let animations run it('should not show the "Help" pane when help button is clicked and then closed', async () => {
jest.advanceTimersByTime(500); findHelpButton().trigger('click');
await wrapper.vm.$nextTick();
expect(helpPane()).toBeVisible(); expect(findByTestId('helpPane').classes('help-state-toggle-enter')).toBe(true);
}); expect(findByTestId('helpPane').classes('help-state-toggle-leave')).toBe(false);
});
it('should not show the "Help" pane when help button is clicked and then closed', done => { findCloseHelpButton().trigger('click');
helpButton().click(); await wrapper.vm.$nextTick();
Vue.nextTick() expect(findByTestId('helpPane').classes('help-state-toggle-leave')).toBe(true);
.then(() => closeHelpButton().click()) expect(findByTestId('helpPane').classes('help-state-toggle-enter')).toBe(false);
.then(() => Vue.nextTick())
.then(() => {
expect(vm.showHelpState).toBe(false);
expect(helpPane()).toBeNull();
})
.then(done)
.catch(done.fail);
});
}); });
}); });
}); });
......
...@@ -13,7 +13,7 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type| ...@@ -13,7 +13,7 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type|
end end
it 'renders the sidebar component empty state' do it 'renders the sidebar component empty state' do
page.within '.time-tracking-no-tracking-pane' do page.within '[data-testid="noTrackingPane"]' do
expect(page).to have_content 'No estimate or time spent' expect(page).to have_content 'No estimate or time spent'
end end
end end
...@@ -22,7 +22,7 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type| ...@@ -22,7 +22,7 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type|
submit_time('/estimate 3w 1d 1h') submit_time('/estimate 3w 1d 1h')
wait_for_requests wait_for_requests
page.within '.time-tracking-estimate-only-pane' do page.within '[data-testid="estimateOnlyPane"]' do
expect(page).to have_content '3w 1d 1h' expect(page).to have_content '3w 1d 1h'
end end
end end
...@@ -31,7 +31,7 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type| ...@@ -31,7 +31,7 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type|
submit_time('/spend 3w 1d 1h') submit_time('/spend 3w 1d 1h')
wait_for_requests wait_for_requests
page.within '.time-tracking-spend-only-pane' do page.within '[data-testid="spentOnlyPane"]' do
expect(page).to have_content '3w 1d 1h' expect(page).to have_content '3w 1d 1h'
end end
end end
...@@ -41,7 +41,7 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type| ...@@ -41,7 +41,7 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type|
submit_time('/spend 3w 1d 1h') submit_time('/spend 3w 1d 1h')
wait_for_requests wait_for_requests
page.within '.time-tracking-comparison-pane' do page.within '[data-testid="timeTrackingComparisonPane"]' do
expect(page).to have_content '3w 1d 1h' expect(page).to have_content '3w 1d 1h'
end end
end end
......
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