summaryrefslogtreecommitdiff
path: root/spec/frontend/sidebar
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/sidebar')
-rw-r--r--spec/frontend/sidebar/__snapshots__/todo_spec.js.snap2
-rw-r--r--spec/frontend/sidebar/components/time_tracking/time_tracker_spec.js357
-rw-r--r--spec/frontend/sidebar/issuable_assignees_spec.js18
-rw-r--r--spec/frontend/sidebar/sidebar_labels_spec.js171
-rw-r--r--spec/frontend/sidebar/subscriptions_spec.js2
5 files changed, 268 insertions, 282 deletions
diff --git a/spec/frontend/sidebar/__snapshots__/todo_spec.js.snap b/spec/frontend/sidebar/__snapshots__/todo_spec.js.snap
index 42012841f0b..6640c0844e2 100644
--- a/spec/frontend/sidebar/__snapshots__/todo_spec.js.snap
+++ b/spec/frontend/sidebar/__snapshots__/todo_spec.js.snap
@@ -27,7 +27,7 @@ exports[`SidebarTodo template renders component container element with proper da
</span>
<gl-loading-icon-stub
- color="orange"
+ color="dark"
inline="true"
label="Loading"
size="sm"
diff --git a/spec/frontend/sidebar/components/time_tracking/time_tracker_spec.js b/spec/frontend/sidebar/components/time_tracking/time_tracker_spec.js
index 5307be0bf58..bcd2c14f2fa 100644
--- a/spec/frontend/sidebar/components/time_tracking/time_tracker_spec.js
+++ b/spec/frontend/sidebar/components/time_tracking/time_tracker_spec.js
@@ -1,277 +1,226 @@
-import Vue from 'vue';
-
-import mountComponent from 'helpers/vue_mount_component_helper';
+import { createMockDirective } from 'helpers/vue_mock_directive';
+import { mount } from '@vue/test-utils';
import TimeTracker from '~/sidebar/components/time_tracking/time_tracker.vue';
describe('Issuable Time Tracker', () => {
- let initialData;
- let vm;
-
- const initTimeTrackingComponent = ({
- timeEstimate,
- timeSpent,
- timeEstimateHumanReadable,
- timeSpentHumanReadable,
- limitToHours,
- }) => {
- setFixtures(`
- <div>
- <div id="mock-container"></div>
- </div>
- `);
-
- 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');
+ let wrapper;
+
+ const findByTestId = testId => wrapper.find(`[data-testid=${testId}]`);
+ const findComparisonMeter = () => findByTestId('compareMeter').attributes('title');
+ const findCollapsedState = () => findByTestId('collapsedState');
+ const findTimeRemainingProgress = () => findByTestId('timeRemainingProgress');
+
+ const defaultProps = {
+ timeEstimate: 10_000, // 2h 46m
+ timeSpent: 5_000, // 1h 23m
+ humanTimeEstimate: '2h 46m',
+ humanTimeSpent: '1h 23m',
+ limitToHours: false,
};
+ const mountComponent = ({ props = {} } = {}) =>
+ mount(TimeTracker, {
+ propsData: { ...defaultProps, ...props },
+ directives: { GlTooltip: createMockDirective() },
+ });
+
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
});
describe('Initialization', () => {
beforeEach(() => {
- initTimeTrackingComponent({
- timeEstimate: 10000, // 2h 46m
- timeSpent: 5000, // 1h 23m
- timeEstimateHumanReadable: '2h 46m',
- timeSpentHumanReadable: '1h 23m',
- });
+ wrapper = mountComponent();
});
it('should return something defined', () => {
- expect(vm).toBeDefined();
+ expect(wrapper).toBeDefined();
});
- it('should correctly set timeEstimate', done => {
- Vue.nextTick(() => {
- expect(vm.timeEstimate).toBe(initialData.timeEstimate);
- done();
- });
+ it('should correctly render timeEstimate', () => {
+ expect(findByTestId('timeTrackingComparisonPane').html()).toContain(
+ defaultProps.humanTimeEstimate,
+ );
});
- it('should correctly set time_spent', done => {
- Vue.nextTick(() => {
- expect(vm.timeSpent).toBe(initialData.timeSpent);
- done();
- });
+ it('should correctly render time_spent', () => {
+ expect(findByTestId('timeTrackingComparisonPane').html()).toContain(
+ defaultProps.humanTimeSpent,
+ );
});
});
- describe('Content Display', () => {
- describe('Panes', () => {
- describe('Comparison pane', () => {
- beforeEach(() => {
- initTimeTrackingComponent({
- timeEstimate: 100000, // 1d 3h
- timeSpent: 5000, // 1h 23m
- timeEstimateHumanReadable: '1d 3h',
- timeSpentHumanReadable: '1h 23m',
- });
+ describe('Content panes', () => {
+ describe('Collapsed state', () => {
+ it('should render "time-tracking-collapsed-state" by default when "showCollapsed" prop is not specified', () => {
+ wrapper = mountComponent();
+
+ expect(findCollapsedState().exists()).toBe(true);
+ });
+
+ it('should not render "time-tracking-collapsed-state" when "showCollapsed" is false', () => {
+ wrapper = mountComponent({
+ props: {
+ showCollapsed: false,
+ },
});
- it('should show the "Comparison" pane when timeEstimate and time_spent are truthy', done => {
- Vue.nextTick(() => {
- expect(vm.showComparisonState).toBe(true);
- const $comparisonPane = vm.$el.querySelector('.time-tracking-comparison-pane');
+ expect(findCollapsedState().exists()).toBe(false);
+ });
+ });
- expect($comparisonPane).toBeVisible();
- done();
- });
+ describe('Comparison pane', () => {
+ beforeEach(() => {
+ wrapper = mountComponent({
+ props: {
+ timeEstimate: 100_000, // 1d 3h
+ timeSpent: 5_000, // 1h 23m
+ humanTimeEstimate: '1d 3h',
+ humanTimeSpent: '1h 23m',
+ },
});
+ });
- it('should show full times when the sidebar is collapsed', done => {
- Vue.nextTick(() => {
- const timeTrackingText = vm.$el.querySelector('.time-tracking-collapsed-summary span')
- .textContent;
+ it('should show the "Comparison" pane when timeEstimate and time_spent are truthy', () => {
+ const pane = findByTestId('timeTrackingComparisonPane');
+ expect(pane.exists()).toBe(true);
+ expect(pane.isVisible()).toBe(true);
+ });
- expect(timeTrackingText.trim()).toBe('1h 23m / 1d 3h');
- done();
- });
+ it('should show full times when the sidebar is collapsed', () => {
+ expect(findCollapsedState().text()).toBe('1h 23m / 1d 3h');
+ });
+
+ describe('Remaining meter', () => {
+ 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 width', done => {
- 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', () => {
+ expect(findTimeRemainingProgress().attributes('variant')).toBe('primary');
+ });
- it('should display the remaining meter with the correct background color when within estimate', done => {
- Vue.nextTick(() => {
- expect(
- vm.$el.querySelector('.time-tracking-comparison-pane .progress[variant="primary"]'),
- ).not.toBeNull();
- done();
- });
+ it('should display the remaining meter with the correct background color when over estimate', () => {
+ wrapper = mountComponent({
+ props: {
+ timeEstimate: 10_000, // 2h 46m
+ timeSpent: 20_000_000, // 231 days
+ },
});
- it('should display the remaining meter with the correct background color when over estimate', done => {
- 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();
- });
- });
+ expect(findTimeRemainingProgress().attributes('variant')).toBe('danger');
});
});
+ });
- describe('Comparison pane when limitToHours is true', () => {
- beforeEach(() => {
- initTimeTrackingComponent({
- timeEstimate: 100000, // 1d 3h
- timeSpent: 5000, // 1h 23m
- timeEstimateHumanReadable: '',
- timeSpentHumanReadable: '',
+ describe('Comparison pane when limitToHours is true', () => {
+ beforeEach(async () => {
+ wrapper = mountComponent({
+ props: {
+ timeEstimate: 100_000, // 1d 3h
limitToHours: true,
- });
+ },
});
+ });
- it('should show the correct tooltip text', done => {
- Vue.nextTick(() => {
- expect(vm.showComparisonState).toBe(true);
- const $title = vm.$el.querySelector('.time-tracking-content .compare-meter').title;
+ it('should show the correct tooltip text', async () => {
+ expect(findByTestId('timeTrackingComparisonPane').exists()).toBe(true);
+ await wrapper.vm.$nextTick();
- expect($title).toBe('Time remaining: 26h 23m');
- done();
- });
- });
+ expect(findComparisonMeter()).toBe('Time remaining: 26h 23m');
});
+ });
- describe('Estimate only pane', () => {
- beforeEach(() => {
- initTimeTrackingComponent({
- timeEstimate: 10000, // 2h 46m
+ describe('Estimate only pane', () => {
+ beforeEach(async () => {
+ wrapper = mountComponent({
+ props: {
+ timeEstimate: 10_000, // 2h 46m
timeSpent: 0,
timeEstimateHumanReadable: '2h 46m',
timeSpentHumanReadable: '',
- });
+ },
});
+ await wrapper.vm.$nextTick();
+ });
- it('should display the human readable version of time estimated', done => {
- Vue.nextTick(() => {
- const estimateText = vm.$el.querySelector('.time-tracking-estimate-only-pane')
- .textContent;
- const correctText = 'Estimated: 2h 46m';
-
- expect(estimateText.trim()).toBe(correctText);
- done();
- });
- });
+ it('should display the human readable version of time estimated', () => {
+ const estimateText = findByTestId('estimateOnlyPane').text();
+ expect(estimateText.trim()).toBe('Estimated: 2h 46m');
});
+ });
- describe('Spent only pane', () => {
- beforeEach(() => {
- initTimeTrackingComponent({
+ describe('Spent only pane', () => {
+ beforeEach(() => {
+ wrapper = mountComponent({
+ props: {
timeEstimate: 0,
- timeSpent: 5000, // 1h 23m
+ timeSpent: 5_000, // 1h 23m
timeEstimateHumanReadable: '2h 46m',
timeSpentHumanReadable: '1h 23m',
- });
+ },
});
+ });
- it('should display the human readable version of time spent', done => {
- Vue.nextTick(() => {
- const spentText = vm.$el.querySelector('.time-tracking-spend-only-pane').textContent;
- const correctText = 'Spent: 1h 23m';
-
- expect(spentText).toBe(correctText);
- done();
- });
- });
+ it('should display the human readable version of time spent', () => {
+ const spentText = findByTestId('spentOnlyPane').text();
+ expect(spentText.trim()).toBe('Spent: 1h 23m');
});
+ });
- describe('No time tracking pane', () => {
- beforeEach(() => {
- initTimeTrackingComponent({
+ describe('No time tracking pane', () => {
+ beforeEach(() => {
+ wrapper = mountComponent({
+ props: {
timeEstimate: 0,
timeSpent: 0,
timeEstimateHumanReadable: '',
timeSpentHumanReadable: '',
- });
+ },
});
+ });
- it('should only show the "No time tracking" pane when both timeEstimate and time_spent are falsey', done => {
- Vue.nextTick(() => {
- const $noTrackingPane = vm.$el.querySelector('.time-tracking-no-tracking-pane');
- const noTrackingText = $noTrackingPane.textContent;
- const correctText = 'No estimate or time spent';
-
- expect(vm.showNoTimeTrackingState).toBe(true);
- expect($noTrackingPane).toBeVisible();
- expect(noTrackingText.trim()).toBe(correctText);
- done();
- });
- });
+ it('should only show the "No time tracking" pane when both timeEstimate and time_spent are falsey', () => {
+ const pane = findByTestId('noTrackingPane');
+ const correctText = 'No estimate or time spent';
+ expect(pane.exists()).toBe(true);
+ expect(pane.text().trim()).toBe(correctText);
});
+ });
+
+ describe('Help pane', () => {
+ const findHelpButton = () => findByTestId('helpButton');
+ const findCloseHelpButton = () => findByTestId('closeHelpButton');
- describe('Help pane', () => {
- const helpButton = () => vm.$el.querySelector('.help-button');
- const closeHelpButton = () => vm.$el.querySelector('.close-help-button');
- const helpPane = () => vm.$el.querySelector('.time-tracking-help-state');
+ beforeEach(async () => {
+ wrapper = mountComponent({ props: { timeEstimate: 0, timeSpent: 0 } });
+ await wrapper.vm.$nextTick();
+ });
- beforeEach(() => {
- initTimeTrackingComponent({ timeEstimate: 0, timeSpent: 0 });
+ it('should not show the "Help" pane by default', () => {
+ expect(findByTestId('helpPane').exists()).toBe(false);
+ });
- return vm.$nextTick();
- });
+ it('should show the "Help" pane when help button is clicked', async () => {
+ findHelpButton().trigger('click');
- it('should not show the "Help" pane by default', () => {
- expect(vm.showHelpState).toBe(false);
- expect(helpPane()).toBeNull();
- });
+ await wrapper.vm.$nextTick();
- it('should show the "Help" pane when help button is clicked', () => {
- helpButton().click();
+ expect(findByTestId('helpPane').exists()).toBe(true);
+ });
- return vm.$nextTick().then(() => {
- expect(vm.showHelpState).toBe(true);
+ it('should not show the "Help" pane when help button is clicked and then closed', async () => {
+ findHelpButton().trigger('click');
+ await wrapper.vm.$nextTick();
- // let animations run
- jest.advanceTimersByTime(500);
+ expect(findByTestId('helpPane').classes('help-state-toggle-enter')).toBe(true);
+ expect(findByTestId('helpPane').classes('help-state-toggle-leave')).toBe(false);
- expect(helpPane()).toBeVisible();
- });
- });
+ findCloseHelpButton().trigger('click');
+ await wrapper.vm.$nextTick();
- it('should not show the "Help" pane when help button is clicked and then closed', done => {
- helpButton().click();
-
- Vue.nextTick()
- .then(() => closeHelpButton().click())
- .then(() => Vue.nextTick())
- .then(() => {
- expect(vm.showHelpState).toBe(false);
- expect(helpPane()).toBeNull();
- })
- .then(done)
- .catch(done.fail);
- });
+ expect(findByTestId('helpPane').classes('help-state-toggle-leave')).toBe(true);
+ expect(findByTestId('helpPane').classes('help-state-toggle-enter')).toBe(false);
});
});
});
diff --git a/spec/frontend/sidebar/issuable_assignees_spec.js b/spec/frontend/sidebar/issuable_assignees_spec.js
index aa930bd4198..076616de040 100644
--- a/spec/frontend/sidebar/issuable_assignees_spec.js
+++ b/spec/frontend/sidebar/issuable_assignees_spec.js
@@ -13,7 +13,6 @@ describe('IssuableAssignees', () => {
propsData: { ...props },
});
};
- const findLabel = () => wrapper.find('[data-testid="assigneeLabel"');
const findUncollapsedAssigneeList = () => wrapper.find(UncollapsedAssigneeList);
const findEmptyAssignee = () => wrapper.find('[data-testid="none"]');
@@ -30,10 +29,6 @@ describe('IssuableAssignees', () => {
it('renders "None"', () => {
expect(findEmptyAssignee().text()).toBe('None');
});
-
- it('renders "0 assignees"', () => {
- expect(findLabel().text()).toBe('0 Assignees');
- });
});
describe('when assignees are present', () => {
@@ -42,18 +37,5 @@ describe('IssuableAssignees', () => {
expect(findUncollapsedAssigneeList().exists()).toBe(true);
});
-
- it.each`
- assignees | expected
- ${[{ id: 1 }]} | ${'Assignee'}
- ${[{ id: 1 }, { id: 2 }]} | ${'2 Assignees'}
- `(
- 'when assignees have a length of $assignees.length, it renders $expected',
- ({ assignees, expected }) => {
- createComponent({ users: assignees });
-
- expect(findLabel().text()).toBe(expected);
- },
- );
});
});
diff --git a/spec/frontend/sidebar/sidebar_labels_spec.js b/spec/frontend/sidebar/sidebar_labels_spec.js
index 7a687ffa761..36d1e129b6a 100644
--- a/spec/frontend/sidebar/sidebar_labels_spec.js
+++ b/spec/frontend/sidebar/sidebar_labels_spec.js
@@ -1,16 +1,18 @@
import { shallowMount } from '@vue/test-utils';
-import AxiosMockAdapter from 'axios-mock-adapter';
import {
mockLabels,
mockRegularLabel,
} from 'jest/vue_shared/components/sidebar/labels_select_vue/mock_data';
-import axios from '~/lib/utils/axios_utils';
+import updateIssueLabelsMutation from '~/boards/queries/issue_set_labels.mutation.graphql';
+import { MutationOperationMode } from '~/graphql_shared/utils';
+import { IssuableType } from '~/issue_show/constants';
import SidebarLabels from '~/sidebar/components/labels/sidebar_labels.vue';
+import updateMergeRequestLabelsMutation from '~/sidebar/queries/update_merge_request_labels.mutation.graphql';
+import { toLabelGid } from '~/sidebar/utils';
import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_vue/constants';
import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
describe('sidebar labels', () => {
- let axiosMock;
let wrapper;
const defaultProps = {
@@ -23,29 +25,52 @@ describe('sidebar labels', () => {
issuableType: 'issue',
labelsFetchPath: '/gitlab-org/gitlab-test/-/labels.json?include_ancestor_groups=true',
labelsManagePath: '/gitlab-org/gitlab-test/-/labels',
- labelsUpdatePath: '/gitlab-org/gitlab-test/-/issues/1.json',
projectIssuesPath: '/gitlab-org/gitlab-test/-/issues',
projectPath: 'gitlab-org/gitlab-test',
};
+ const $apollo = {
+ mutate: jest.fn().mockResolvedValue(),
+ };
+
+ const userUpdatedLabels = [
+ {
+ ...mockRegularLabel,
+ set: false,
+ },
+ {
+ id: 40,
+ title: 'Security',
+ color: '#ddd',
+ text_color: '#fff',
+ set: true,
+ },
+ {
+ id: 55,
+ title: 'Tooling',
+ color: '#ddd',
+ text_color: '#fff',
+ set: false,
+ },
+ ];
+
const findLabelsSelect = () => wrapper.find(LabelsSelect);
- const mountComponent = () => {
+ const mountComponent = (props = {}) => {
wrapper = shallowMount(SidebarLabels, {
provide: {
...defaultProps,
+ ...props,
+ },
+ mocks: {
+ $apollo,
},
});
};
- beforeEach(() => {
- axiosMock = new AxiosMockAdapter(axios);
- });
-
afterEach(() => {
wrapper.destroy();
wrapper = null;
- axiosMock.restore();
});
describe('LabelsSelect props', () => {
@@ -72,64 +97,94 @@ describe('sidebar labels', () => {
});
});
- describe('when labels are updated', () => {
+ describe('when type is issue', () => {
beforeEach(() => {
- mountComponent();
+ mountComponent({ issuableType: IssuableType.Issue });
});
- it('makes an API call to update labels', async () => {
- const labels = [
- {
- ...mockRegularLabel,
- set: false,
- },
- {
- id: 40,
- title: 'Security',
- color: '#ddd',
- text_color: '#fff',
- set: true,
- },
- {
- id: 55,
- title: 'Tooling',
- color: '#ddd',
- text_color: '#fff',
- set: false,
- },
- ];
-
- findLabelsSelect().vm.$emit('updateSelectedLabels', labels);
-
- await axios.waitForAll();
-
- const expected = {
- [defaultProps.issuableType]: {
- label_ids: [27, 28, 29, 40],
- },
- };
-
- expect(axiosMock.history.put[0].data).toEqual(JSON.stringify(expected));
+ describe('when labels are updated', () => {
+ it('invokes a mutation', () => {
+ findLabelsSelect().vm.$emit('updateSelectedLabels', userUpdatedLabels);
+
+ const expected = {
+ mutation: updateIssueLabelsMutation,
+ variables: {
+ input: {
+ addLabelIds: [40],
+ iid: defaultProps.iid,
+ projectPath: defaultProps.projectPath,
+ removeLabelIds: [26, 55],
+ },
+ },
+ };
+
+ expect($apollo.mutate).toHaveBeenCalledWith(expected);
+ });
+ });
+
+ describe('when label `x` is clicked', () => {
+ it('invokes a mutation', () => {
+ findLabelsSelect().vm.$emit('onLabelRemove', 27);
+
+ const expected = {
+ mutation: updateIssueLabelsMutation,
+ variables: {
+ input: {
+ iid: defaultProps.iid,
+ projectPath: defaultProps.projectPath,
+ removeLabelIds: [27],
+ },
+ },
+ };
+
+ expect($apollo.mutate).toHaveBeenCalledWith(expected);
+ });
});
});
- describe('when label `x` is clicked', () => {
+ describe('when type is merge_request', () => {
beforeEach(() => {
- mountComponent();
+ mountComponent({ issuableType: IssuableType.MergeRequest });
});
- it('makes an API call to update labels', async () => {
- findLabelsSelect().vm.$emit('onLabelRemove', 27);
-
- await axios.waitForAll();
-
- const expected = {
- [defaultProps.issuableType]: {
- label_ids: [26, 28, 29],
- },
- };
+ describe('when labels are updated', () => {
+ it('invokes a mutation', () => {
+ findLabelsSelect().vm.$emit('updateSelectedLabels', userUpdatedLabels);
+
+ const expected = {
+ mutation: updateMergeRequestLabelsMutation,
+ variables: {
+ input: {
+ iid: defaultProps.iid,
+ labelIds: [toLabelGid(27), toLabelGid(28), toLabelGid(29), toLabelGid(40)],
+ operationMode: MutationOperationMode.Replace,
+ projectPath: defaultProps.projectPath,
+ },
+ },
+ };
+
+ expect($apollo.mutate).toHaveBeenCalledWith(expected);
+ });
+ });
- expect(axiosMock.history.put[0].data).toEqual(JSON.stringify(expected));
+ describe('when label `x` is clicked', () => {
+ it('invokes a mutation', () => {
+ findLabelsSelect().vm.$emit('onLabelRemove', 27);
+
+ const expected = {
+ mutation: updateMergeRequestLabelsMutation,
+ variables: {
+ input: {
+ iid: defaultProps.iid,
+ labelIds: [toLabelGid(27)],
+ operationMode: MutationOperationMode.Remove,
+ projectPath: defaultProps.projectPath,
+ },
+ },
+ };
+
+ expect($apollo.mutate).toHaveBeenCalledWith(expected);
+ });
});
});
});
diff --git a/spec/frontend/sidebar/subscriptions_spec.js b/spec/frontend/sidebar/subscriptions_spec.js
index dddb9c2bba9..428441656b3 100644
--- a/spec/frontend/sidebar/subscriptions_spec.js
+++ b/spec/frontend/sidebar/subscriptions_spec.js
@@ -94,7 +94,7 @@ describe('Subscriptions', () => {
it('sets the correct display text', () => {
expect(wrapper.find('.issuable-header-text').text()).toContain(subscribeDisabledDescription);
- expect(wrapper.find({ ref: 'tooltip' }).attributes('data-original-title')).toBe(
+ expect(wrapper.find({ ref: 'tooltip' }).attributes('title')).toBe(
subscribeDisabledDescription,
);
});