diff options
Diffstat (limited to 'spec/frontend/sidebar')
-rw-r--r-- | spec/frontend/sidebar/__snapshots__/todo_spec.js.snap | 2 | ||||
-rw-r--r-- | spec/frontend/sidebar/components/time_tracking/time_tracker_spec.js | 357 | ||||
-rw-r--r-- | spec/frontend/sidebar/issuable_assignees_spec.js | 18 | ||||
-rw-r--r-- | spec/frontend/sidebar/sidebar_labels_spec.js | 171 | ||||
-rw-r--r-- | spec/frontend/sidebar/subscriptions_spec.js | 2 |
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, ); }); |