diff options
Diffstat (limited to 'spec')
21 files changed, 312 insertions, 329 deletions
diff --git a/spec/frontend/behaviors/markdown/paste_markdown_table_spec.js b/spec/frontend/behaviors/markdown/paste_markdown_table_spec.js index a8177a5ad39..a98919e2113 100644 --- a/spec/frontend/behaviors/markdown/paste_markdown_table_spec.js +++ b/spec/frontend/behaviors/markdown/paste_markdown_table_spec.js @@ -10,9 +10,9 @@ describe('PasteMarkdownTable', () => { value: { getData: jest.fn().mockImplementation(type => { if (type === 'text/html') { - return '<table><tr><td></td></tr></table>'; + return '<table><tr><td>First</td><td>Second</td></tr></table>'; } - return 'hello world'; + return 'First\tSecond'; }), }, }); @@ -24,39 +24,48 @@ describe('PasteMarkdownTable', () => { it('return false when no HTML data is provided', () => { data.types = ['text/plain']; - expect(PasteMarkdownTable.isTable(data)).toBe(false); + expect(new PasteMarkdownTable(data).isTable()).toBe(false); }); it('returns false when no text data is provided', () => { data.types = ['text/html']; - expect(PasteMarkdownTable.isTable(data)).toBe(false); + expect(new PasteMarkdownTable(data).isTable()).toBe(false); }); it('returns true when a table is provided in both text and HTML', () => { data.types = ['text/html', 'text/plain']; - expect(PasteMarkdownTable.isTable(data)).toBe(true); + expect(new PasteMarkdownTable(data).isTable()).toBe(true); }); it('returns false when no HTML table is included', () => { data.types = ['text/html', 'text/plain']; data.getData = jest.fn().mockImplementation(() => 'nothing'); - expect(PasteMarkdownTable.isTable(data)).toBe(false); + expect(new PasteMarkdownTable(data).isTable()).toBe(false); }); - }); - describe('convertToTableMarkdown', () => { - let converter; + it('returns false when the number of rows are not consistent', () => { + data.types = ['text/html', 'text/plain']; + data.getData = jest.fn().mockImplementation(mimeType => { + if (mimeType === 'text/html') { + return '<table><tr><td>def test<td></tr></table>'; + } + return "def test\n 'hello'\n"; + }); - beforeEach(() => { - converter = new PasteMarkdownTable(data); + expect(new PasteMarkdownTable(data).isTable()).toBe(false); }); + }); + describe('convertToTableMarkdown', () => { it('returns a Markdown table', () => { + data.types = ['text/html', 'text/plain']; data.getData = jest.fn().mockImplementation(type => { - if (type === 'text/plain') { + if (type === 'text/html') { + return '<table><tr><td>First</td><td>Last</td><tr><td>John</td><td>Doe</td><tr><td>Jane</td><td>Doe</td></table>'; + } else if (type === 'text/plain') { return 'First\tLast\nJohn\tDoe\nJane\tDoe'; } @@ -70,12 +79,18 @@ describe('PasteMarkdownTable', () => { '| Jane | Doe |', ].join('\n'); + const converter = new PasteMarkdownTable(data); + + expect(converter.isTable()).toBe(true); expect(converter.convertToTableMarkdown()).toBe(expected); }); it('returns a Markdown table with rows normalized', () => { + data.types = ['text/html', 'text/plain']; data.getData = jest.fn().mockImplementation(type => { - if (type === 'text/plain') { + if (type === 'text/html') { + return '<table><tr><td>First</td><td>Last</td><tr><td>John</td><td>Doe</td><tr><td>Jane</td><td>/td></table>'; + } else if (type === 'text/plain') { return 'First\tLast\nJohn\tDoe\nJane'; } @@ -89,6 +104,9 @@ describe('PasteMarkdownTable', () => { '| Jane | |', ].join('\n'); + const converter = new PasteMarkdownTable(data); + + expect(converter.isTable()).toBe(true); expect(converter.convertToTableMarkdown()).toBe(expected); }); }); diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js index 8a10857d0ff..f77e8b61050 100644 --- a/spec/frontend/monitoring/components/dashboard_spec.js +++ b/spec/frontend/monitoring/components/dashboard_spec.js @@ -10,7 +10,6 @@ import DateTimePicker from '~/monitoring/components/date_time_picker/date_time_p import GroupEmptyState from '~/monitoring/components/group_empty_state.vue'; import { createStore } from '~/monitoring/stores'; import * as types from '~/monitoring/stores/mutation_types'; -import * as monitoringUtils from '~/monitoring/utils'; import { setupComponentStore, propsData } from '../init_utils'; import { metricsGroupsAPIResponse, @@ -24,13 +23,12 @@ const localVue = createLocalVue(); const expectedPanelCount = 2; describe('Dashboard', () => { - let DashboardComponent; let store; let wrapper; let mock; const createShallowWrapper = (props = {}, options = {}) => { - wrapper = shallowMount(localVue.extend(DashboardComponent), { + wrapper = shallowMount(Dashboard, { localVue, sync: false, propsData: { ...propsData, ...props }, @@ -40,7 +38,7 @@ describe('Dashboard', () => { }; const createMountedWrapper = (props = {}, options = {}) => { - wrapper = mount(localVue.extend(DashboardComponent), { + wrapper = mount(Dashboard, { localVue, sync: false, propsData: { ...propsData, ...props }, @@ -51,7 +49,6 @@ describe('Dashboard', () => { beforeEach(() => { store = createStore(); - DashboardComponent = localVue.extend(Dashboard); mock = new MockAdapter(axios); }); @@ -137,7 +134,6 @@ describe('Dashboard', () => { }); it('fetches the metrics data with proper time window', done => { - const getTimeDiffSpy = jest.spyOn(monitoringUtils, 'getTimeDiff'); jest.spyOn(store, 'dispatch'); createMountedWrapper( @@ -154,7 +150,6 @@ describe('Dashboard', () => { .$nextTick() .then(() => { expect(store.dispatch).toHaveBeenCalled(); - expect(getTimeDiffSpy).toHaveBeenCalled(); done(); }) diff --git a/spec/frontend/monitoring/components/dashboard_time_url_spec.js b/spec/frontend/monitoring/components/dashboard_time_url_spec.js index 8dc450cf131..747f07bcd0c 100644 --- a/spec/frontend/monitoring/components/dashboard_time_url_spec.js +++ b/spec/frontend/monitoring/components/dashboard_time_url_spec.js @@ -1,10 +1,10 @@ -import { mount, createLocalVue } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; import createFlash from '~/flash'; +import MockAdapter from 'axios-mock-adapter'; import Dashboard from '~/monitoring/components/dashboard.vue'; import { createStore } from '~/monitoring/stores'; import { propsData } from '../init_utils'; - -const localVue = createLocalVue(); +import axios from '~/lib/utils/axios_utils'; jest.mock('~/flash'); @@ -15,10 +15,10 @@ jest.mock('~/lib/utils/url_utility', () => ({ describe('dashboard invalid url parameters', () => { let store; let wrapper; + let mock; const createMountedWrapper = (props = {}, options = {}) => { - wrapper = mount(localVue.extend(Dashboard), { - localVue, + wrapper = mount(Dashboard, { sync: false, propsData: { ...propsData, ...props }, store, @@ -28,12 +28,14 @@ describe('dashboard invalid url parameters', () => { beforeEach(() => { store = createStore(); + mock = new MockAdapter(axios); }); afterEach(() => { if (wrapper) { wrapper.destroy(); } + mock.restore(); }); it('shows an error message if invalid url parameters are passed', done => { @@ -46,7 +48,6 @@ describe('dashboard invalid url parameters', () => { .$nextTick() .then(() => { expect(createFlash).toHaveBeenCalled(); - done(); }) .catch(done.fail); diff --git a/spec/frontend/monitoring/components/dashboard_time_window_spec.js b/spec/frontend/monitoring/components/dashboard_time_window_spec.js index d49af6f84cb..658d3b68a76 100644 --- a/spec/frontend/monitoring/components/dashboard_time_window_spec.js +++ b/spec/frontend/monitoring/components/dashboard_time_window_spec.js @@ -1,4 +1,4 @@ -import { mount, createLocalVue } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; import { GlDropdownItem } from '@gitlab/ui'; import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; @@ -8,8 +8,6 @@ import { createStore } from '~/monitoring/stores'; import { propsData, setupComponentStore } from '../init_utils'; import { metricsGroupsAPIResponse, mockApiEndpoint } from '../mock_data'; -const localVue = createLocalVue(); - jest.mock('~/lib/utils/url_utility', () => ({ getParameterValues: jest.fn().mockImplementation(param => { if (param === 'start') return ['2019-10-01T18:27:47.000Z']; @@ -25,8 +23,7 @@ describe('dashboard time window', () => { let mock; const createComponentWrapperMounted = (props = {}, options = {}) => { - wrapper = mount(localVue.extend(Dashboard), { - localVue, + wrapper = mount(Dashboard, { sync: false, propsData: { ...propsData, ...props }, store, diff --git a/spec/frontend/monitoring/components/date_time_picker/date_time_picker_spec.js b/spec/frontend/monitoring/components/date_time_picker/date_time_picker_spec.js index 88463d781ee..ba40ced9545 100644 --- a/spec/frontend/monitoring/components/date_time_picker/date_time_picker_spec.js +++ b/spec/frontend/monitoring/components/date_time_picker/date_time_picker_spec.js @@ -3,10 +3,8 @@ import DateTimePicker from '~/monitoring/components/date_time_picker/date_time_p import { timeWindows } from '~/monitoring/constants'; const timeWindowsCount = Object.keys(timeWindows).length; -const selectedTimeWindow = { - start: '2019-10-10T07:00:00.000Z', - end: '2019-10-13T07:00:00.000Z', -}; +const start = '2019-10-10T07:00:00.000Z'; +const end = '2019-10-13T07:00:00.000Z'; const selectedTimeWindowText = `3 days`; describe('DateTimePicker', () => { @@ -28,7 +26,8 @@ describe('DateTimePicker', () => { dateTimePicker = mount(DateTimePicker, { propsData: { timeWindows, - selectedTimeWindow, + start, + end, ...props, }, sync: false, @@ -66,10 +65,8 @@ describe('DateTimePicker', () => { it('renders inputs with h/m/s truncated if its all 0s', done => { createComponent({ - selectedTimeWindow: { - start: '2019-10-10T00:00:00.000Z', - end: '2019-10-14T00:10:00.000Z', - }, + start: '2019-10-10T00:00:00.000Z', + end: '2019-10-14T00:10:00.000Z', }); dateTimePicker.vm.$nextTick(() => { expect(dateTimePicker.find('#custom-time-from').element.value).toBe('2019-10-10'); @@ -98,8 +95,10 @@ describe('DateTimePicker', () => { }); }); - it('renders a disabled apply button on load', () => { - createComponent(); + it('renders a disabled apply button on wrong input', () => { + createComponent({ + start: 'invalid-input-date', + }); expect(applyButtonElement().getAttribute('disabled')).toBe('disabled'); }); @@ -131,29 +130,29 @@ describe('DateTimePicker', () => { fillInputAndBlur('#custom-time-from', '2019-10-01') .then(() => fillInputAndBlur('#custom-time-to', '2019-10-19')) .then(() => { - dateTimePicker.vm.$nextTick(() => { - expect(applyButtonElement().getAttribute('disabled')).toBeNull(); - done(); - }); + expect(applyButtonElement().getAttribute('disabled')).toBeNull(); + done(); }) - .catch(done); + .catch(done.fail); }); - it('returns an object when apply is clicked', done => { + it('emits dates in an object when apply is clicked', done => { createComponent(); fillInputAndBlur('#custom-time-from', '2019-10-01') .then(() => fillInputAndBlur('#custom-time-to', '2019-10-19')) .then(() => { - jest.spyOn(dateTimePicker.vm, '$emit'); applyButtonElement().click(); - expect(dateTimePicker.vm.$emit).toHaveBeenCalledWith('onApply', { - end: '2019-10-19T00:00:00Z', - start: '2019-10-01T00:00:00Z', - }); + expect(dateTimePicker.emitted().apply).toHaveLength(1); + expect(dateTimePicker.emitted().apply[0]).toEqual([ + { + end: '2019-10-19T00:00:00Z', + start: '2019-10-01T00:00:00Z', + }, + ]); done(); }) - .catch(done); + .catch(done.fail); }); it('hides the popover with cancel button', done => { diff --git a/spec/frontend/monitoring/components/graph_group_spec.js b/spec/frontend/monitoring/components/graph_group_spec.js index 43ca17c3cbc..edd08cdb4c9 100644 --- a/spec/frontend/monitoring/components/graph_group_spec.js +++ b/spec/frontend/monitoring/components/graph_group_spec.js @@ -1,9 +1,7 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import GraphGroup from '~/monitoring/components/graph_group.vue'; import Icon from '~/vue_shared/components/icon.vue'; -const localVue = createLocalVue(); - describe('Graph group component', () => { let wrapper; @@ -12,10 +10,9 @@ describe('Graph group component', () => { const findCaretIcon = () => wrapper.find(Icon); const createComponent = propsData => { - wrapper = shallowMount(localVue.extend(GraphGroup), { + wrapper = shallowMount(GraphGroup, { propsData, sync: false, - localVue, }); }; diff --git a/spec/frontend/vue_shared/components/callout_spec.js b/spec/frontend/vue_shared/components/callout_spec.js index 91208dfb31a..7c9bb6b4650 100644 --- a/spec/frontend/vue_shared/components/callout_spec.js +++ b/spec/frontend/vue_shared/components/callout_spec.js @@ -1,17 +1,14 @@ -import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import Callout from '~/vue_shared/components/callout.vue'; const TEST_MESSAGE = 'This is a callout message!'; const TEST_SLOT = '<button>This is a callout slot!</button>'; -const localVue = createLocalVue(); - describe('Callout Component', () => { let wrapper; const factory = options => { - wrapper = shallowMount(localVue.extend(Callout), { - localVue, + wrapper = shallowMount(Callout, { ...options, }); }; diff --git a/spec/frontend/vue_shared/components/expand_button_spec.js b/spec/frontend/vue_shared/components/expand_button_spec.js index e0893d02843..3b1c8f6219c 100644 --- a/spec/frontend/vue_shared/components/expand_button_spec.js +++ b/spec/frontend/vue_shared/components/expand_button_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import { mount, createLocalVue } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; import ExpandButton from '~/vue_shared/components/expand_button.vue'; const text = { @@ -14,10 +14,7 @@ describe('Expand button', () => { const expanderAppendEl = () => wrapper.find('.js-text-expander-append'); const factory = (options = {}) => { - const localVue = createLocalVue(); - - wrapper = mount(localVue.extend(ExpandButton), { - localVue, + wrapper = mount(ExpandButton, { ...options, }); }; diff --git a/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js b/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js index 21d05471d51..85cd90d2f8c 100644 --- a/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js +++ b/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import { mount, createLocalVue } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; import { formatDate } from '~/lib/utils/datetime_utility'; import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue'; import { @@ -29,10 +29,7 @@ describe('RelatedIssuableItem', () => { }; beforeEach(() => { - const localVue = createLocalVue(); - - wrapper = mount(localVue.extend(RelatedIssuableItem), { - localVue, + wrapper = mount(RelatedIssuableItem, { slots, sync: false, attachToDocument: true, diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js index 71f9b5e3244..3d42c02ebb6 100644 --- a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js +++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js @@ -1,9 +1,7 @@ import { GlLoadingIcon } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import SuggestionDiffHeader from '~/vue_shared/components/markdown/suggestion_diff_header.vue'; -const localVue = createLocalVue(); - const DEFAULT_PROPS = { canApply: true, isApplied: false, @@ -14,12 +12,11 @@ describe('Suggestion Diff component', () => { let wrapper; const createComponent = props => { - wrapper = shallowMount(localVue.extend(SuggestionDiffHeader), { + wrapper = shallowMount(SuggestionDiffHeader, { propsData: { ...DEFAULT_PROPS, ...props, }, - localVue, sync: false, attachToDocument: true, }); diff --git a/spec/frontend/vue_shared/components/notes/system_note_spec.js b/spec/frontend/vue_shared/components/notes/system_note_spec.js index 603c37c6c49..080dd778e29 100644 --- a/spec/frontend/vue_shared/components/notes/system_note_spec.js +++ b/spec/frontend/vue_shared/components/notes/system_note_spec.js @@ -1,12 +1,10 @@ -import { createLocalVue, mount } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; import IssueSystemNote from '~/vue_shared/components/notes/system_note.vue'; import createStore from '~/notes/stores'; import initMRPopovers from '~/mr_popover/index'; jest.mock('~/mr_popover/index', () => jest.fn()); -const localVue = createLocalVue(); - describe('system note component', () => { let vm; let props; @@ -34,7 +32,6 @@ describe('system note component', () => { vm = mount(IssueSystemNote, { store, - localVue, propsData: props, attachToDocument: true, sync: false, diff --git a/spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js b/spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js index be6c58f0683..f73d3edec5d 100644 --- a/spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js +++ b/spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js @@ -1,14 +1,11 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; describe(`TimelineEntryItem`, () => { let wrapper; const factory = (options = {}) => { - const localVue = createLocalVue(); - wrapper = shallowMount(TimelineEntryItem, { - localVue, ...options, }); }; diff --git a/spec/frontend/vue_shared/components/pagination_links_spec.js b/spec/frontend/vue_shared/components/pagination_links_spec.js index efa5825d92f..3c53cda45f5 100644 --- a/spec/frontend/vue_shared/components/pagination_links_spec.js +++ b/spec/frontend/vue_shared/components/pagination_links_spec.js @@ -1,4 +1,4 @@ -import { mount, createLocalVue } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; import { GlPagination } from '@gitlab/ui'; import PaginationLinks from '~/vue_shared/components/pagination_links.vue'; import { @@ -10,8 +10,6 @@ import { LABEL_LAST_PAGE, } from '~/vue_shared/components/pagination/constants'; -const localVue = createLocalVue(); - describe('Pagination links component', () => { const pageInfo = { page: 3, @@ -38,7 +36,6 @@ describe('Pagination links component', () => { change: changeMock, pageInfo, }, - localVue, sync: false, }); }; diff --git a/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js b/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js index ebba0cc4ad4..49591c3ce1c 100644 --- a/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js +++ b/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js @@ -1,4 +1,4 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import { formatDate, getTimeago } from '~/lib/utils/datetime_utility'; @@ -10,7 +10,6 @@ describe('Time ago with tooltip component', () => { attachToDocument: true, sync: false, propsData, - localVue: createLocalVue(), }); }; const timestamp = '2017-05-08T14:57:39.781Z'; diff --git a/spec/frontend/vue_shared/directives/track_event_spec.js b/spec/frontend/vue_shared/directives/track_event_spec.js index c9b0520ab2c..e1009e5079a 100644 --- a/spec/frontend/vue_shared/directives/track_event_spec.js +++ b/spec/frontend/vue_shared/directives/track_event_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import Tracking from '~/tracking'; import TrackEvent from '~/vue_shared/directives/track_event'; @@ -17,15 +17,12 @@ const Component = Vue.component('dummy-element', { template: '<button id="trackable" v-track-event="trackingOptions"></button>', }); -const localVue = createLocalVue(); let wrapper; let button; describe('Error Tracking directive', () => { beforeEach(() => { - wrapper = shallowMount(localVue.extend(Component), { - localVue, - }); + wrapper = shallowMount(Component, {}); button = wrapper.find('#trackable'); }); diff --git a/spec/frontend/vue_shared/droplab_dropdown_button_spec.js b/spec/frontend/vue_shared/droplab_dropdown_button_spec.js index 22295721328..e57c730ecee 100644 --- a/spec/frontend/vue_shared/droplab_dropdown_button_spec.js +++ b/spec/frontend/vue_shared/droplab_dropdown_button_spec.js @@ -1,4 +1,4 @@ -import { mount, createLocalVue } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; import DroplabDropdownButton from '~/vue_shared/components/droplab_dropdown_button.vue'; @@ -18,11 +18,8 @@ const createComponent = ({ dropdownClass = '', actions = mockActions, defaultAction = 0, -}) => { - const localVue = createLocalVue(); - - return mount(DroplabDropdownButton, { - localVue, +}) => + mount(DroplabDropdownButton, { propsData: { size, dropdownClass, @@ -30,7 +27,6 @@ const createComponent = ({ defaultAction, }, }); -}; describe('DroplabDropdownButton', () => { let wrapper; diff --git a/spec/frontend/vue_shared/mixins/gl_feature_flags_mixin_spec.js b/spec/frontend/vue_shared/mixins/gl_feature_flags_mixin_spec.js index a3e3270a4e8..3ce12caf95a 100644 --- a/spec/frontend/vue_shared/mixins/gl_feature_flags_mixin_spec.js +++ b/spec/frontend/vue_shared/mixins/gl_feature_flags_mixin_spec.js @@ -1,8 +1,6 @@ -import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; -const localVue = createLocalVue(); - describe('GitLab Feature Flags Mixin', () => { let wrapper; @@ -20,7 +18,6 @@ describe('GitLab Feature Flags Mixin', () => { }; wrapper = shallowMount(component, { - localVue, provide: { glFeatures: { ...(gon.features || {}) }, }, diff --git a/spec/javascripts/dropzone_input_spec.js b/spec/javascripts/dropzone_input_spec.js index 44a11097815..6f6f20ccca2 100644 --- a/spec/javascripts/dropzone_input_spec.js +++ b/spec/javascripts/dropzone_input_spec.js @@ -39,17 +39,17 @@ describe('dropzone_input', () => { const event = $.Event('paste'); const origEvent = new Event('paste'); const pasteData = new DataTransfer(); - pasteData.setData('text/plain', 'hello world'); - pasteData.setData('text/html', '<table></table>'); + pasteData.setData('text/plain', 'Hello World'); + pasteData.setData('text/html', '<table><tr><td>Hello World</td></tr></table>'); origEvent.clipboardData = pasteData; event.originalEvent = origEvent; - spyOn(PasteMarkdownTable, 'isTable').and.callThrough(); + spyOn(PasteMarkdownTable.prototype, 'isTable').and.callThrough(); spyOn(PasteMarkdownTable.prototype, 'convertToTableMarkdown').and.callThrough(); $('.js-gfm-input').trigger(event); - expect(PasteMarkdownTable.isTable).toHaveBeenCalled(); + expect(PasteMarkdownTable.prototype.isTable).toHaveBeenCalled(); expect(PasteMarkdownTable.prototype.convertToTableMarkdown).toHaveBeenCalled(); }); }); diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 3a56462ec1b..cf1dacd088e 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -17,8 +17,8 @@ describe Gitlab::UsageData do create(:service, project: projects[0], type: 'SlackSlashCommandsService', active: true) create(:service, project: projects[1], type: 'SlackService', active: true) create(:service, project: projects[2], type: 'SlackService', active: true) - create(:service, project: projects[2], type: 'MattermostService', active: true) - create(:service, project: projects[2], type: 'JenkinsService', active: true) + create(:service, project: projects[2], type: 'MattermostService', active: false) + create(:service, project: projects[2], type: 'MattermostService', active: true, template: true) create(:service, project: projects[2], type: 'CustomIssueTrackerService', active: true) create(:project_error_tracking_setting, project: projects[0]) create(:project_error_tracking_setting, project: projects[1], enabled: false) @@ -168,13 +168,15 @@ describe Gitlab::UsageData do pool_repositories projects projects_imported_from_github + projects_asana_active projects_jira_active projects_jira_server_active projects_jira_cloud_active projects_slack_notifications_active projects_slack_slash_active + projects_slack_active + projects_slack_slash_commands_active projects_custom_issue_tracker_active - projects_jenkins_active projects_mattermost_active projects_prometheus_active projects_with_repositories_enabled @@ -203,15 +205,17 @@ describe Gitlab::UsageData do count_data = subject[:counts] expect(count_data[:projects]).to eq(4) + expect(count_data[:projects_asana_active]).to eq(0) expect(count_data[:projects_prometheus_active]).to eq(1) expect(count_data[:projects_jira_active]).to eq(4) expect(count_data[:projects_jira_server_active]).to eq(2) expect(count_data[:projects_jira_cloud_active]).to eq(2) expect(count_data[:projects_slack_notifications_active]).to eq(2) expect(count_data[:projects_slack_slash_active]).to eq(1) + expect(count_data[:projects_slack_active]).to eq(2) + expect(count_data[:projects_slack_slash_commands_active]).to eq(1) expect(count_data[:projects_custom_issue_tracker_active]).to eq(1) - expect(count_data[:projects_jenkins_active]).to eq(1) - expect(count_data[:projects_mattermost_active]).to eq(1) + expect(count_data[:projects_mattermost_active]).to eq(0) expect(count_data[:projects_with_repositories_enabled]).to eq(3) expect(count_data[:projects_with_error_tracking_enabled]).to eq(1) expect(count_data[:issues_created_from_gitlab_error_tracking_ui]).to eq(1) diff --git a/spec/lib/sentry/client/issue_spec.rb b/spec/lib/sentry/client/issue_spec.rb index 20665c59a8d..b5ee5063e86 100644 --- a/spec/lib/sentry/client/issue_spec.rb +++ b/spec/lib/sentry/client/issue_spec.rb @@ -8,6 +8,216 @@ describe Sentry::Client::Issue do let(:token) { 'test-token' } let(:client) { Sentry::Client.new(sentry_url, token) } + describe '#list_issues' do + shared_examples 'issues have correct return type' do |klass| + it "returns objects of type #{klass}" do + expect(subject[:issues]).to all( be_a(klass) ) + end + end + + shared_examples 'issues have correct length' do |length| + it { expect(subject[:issues].length).to eq(length) } + end + + let(:issues_sample_response) do + Gitlab::Utils.deep_indifferent_access( + JSON.parse(fixture_file('sentry/issues_sample_response.json')) + ) + end + + let(:default_httparty_options) do + { + follow_redirects: false, + headers: { "Authorization" => "Bearer test-token" } + } + end + + let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' } + let(:issue_status) { 'unresolved' } + let(:limit) { 20 } + let(:search_term) { '' } + let(:cursor) { nil } + let(:sort) { 'last_seen' } + let(:sentry_api_response) { issues_sample_response } + let(:sentry_request_url) { sentry_url + '/issues/?limit=20&query=is:unresolved' } + let!(:sentry_api_request) { stub_sentry_request(sentry_request_url, body: sentry_api_response) } + + subject { client.list_issues(issue_status: issue_status, limit: limit, search_term: search_term, sort: sort, cursor: cursor) } + + it_behaves_like 'calls sentry api' + + it_behaves_like 'issues have correct return type', Gitlab::ErrorTracking::Error + it_behaves_like 'issues have correct length', 1 + + shared_examples 'has correct external_url' do + context 'external_url' do + it 'is constructed correctly' do + expect(subject[:issues][0].external_url).to eq('https://sentrytest.gitlab.com/sentry-org/sentry-project/issues/11') + end + end + end + + context 'when response has a pagination info' do + let(:headers) do + { + link: '<https://sentrytest.gitlab.com>; rel="previous"; results="true"; cursor="1573556671000:0:1", <https://sentrytest.gitlab.com>; rel="next"; results="true"; cursor="1572959139000:0:0"' + } + end + let!(:sentry_api_request) { stub_sentry_request(sentry_request_url, body: sentry_api_response, headers: headers) } + + it 'parses the pagination' do + expect(subject[:pagination]).to eq( + 'previous' => { 'cursor' => '1573556671000:0:1' }, + 'next' => { 'cursor' => '1572959139000:0:0' } + ) + end + end + + context 'error object created from sentry response' do + using RSpec::Parameterized::TableSyntax + + where(:error_object, :sentry_response) do + :id | :id + :first_seen | :firstSeen + :last_seen | :lastSeen + :title | :title + :type | :type + :user_count | :userCount + :count | :count + :message | [:metadata, :value] + :culprit | :culprit + :short_id | :shortId + :status | :status + :frequency | [:stats, '24h'] + :project_id | [:project, :id] + :project_name | [:project, :name] + :project_slug | [:project, :slug] + end + + with_them do + it { expect(subject[:issues][0].public_send(error_object)).to eq(sentry_api_response[0].dig(*sentry_response)) } + end + + it_behaves_like 'has correct external_url' + end + + context 'redirects' do + let(:sentry_api_url) { sentry_url + '/issues/?limit=20&query=is:unresolved' } + + it_behaves_like 'no Sentry redirects' + end + + # Sentry API returns 404 if there are extra slashes in the URL! + context 'extra slashes in URL' do + let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects//sentry-org/sentry-project/' } + + let(:sentry_request_url) do + 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/' \ + 'issues/?limit=20&query=is:unresolved' + end + + it 'removes extra slashes in api url' do + expect(client.url).to eq(sentry_url) + expect(Gitlab::HTTP).to receive(:get).with( + URI('https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/issues/'), + anything + ).and_call_original + + subject + + expect(sentry_api_request).to have_been_requested + end + end + + context 'requests with sort parameter in sentry api' do + let(:sentry_request_url) do + 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/' \ + 'issues/?limit=20&query=is:unresolved&sort=freq' + end + let!(:sentry_api_request) { stub_sentry_request(sentry_request_url, body: sentry_api_response) } + + subject { client.list_issues(issue_status: issue_status, limit: limit, sort: 'frequency') } + + it 'calls the sentry api with sort params' do + expect(Gitlab::HTTP).to receive(:get).with( + URI("#{sentry_url}/issues/"), + default_httparty_options.merge(query: { limit: 20, query: "is:unresolved", sort: "freq" }) + ).and_call_original + + subject + + expect(sentry_api_request).to have_been_requested + end + end + + context 'with invalid sort params' do + subject { client.list_issues(issue_status: issue_status, limit: limit, sort: 'fish') } + + it 'throws an error' do + expect { subject }.to raise_error(Sentry::Client::BadRequestError, 'Invalid value for sort param') + end + end + + context 'Older sentry versions where keys are not present' do + let(:sentry_api_response) do + issues_sample_response[0...1].map do |issue| + issue[:project].delete(:id) + issue + end + end + + it_behaves_like 'calls sentry api' + + it_behaves_like 'issues have correct return type', Gitlab::ErrorTracking::Error + it_behaves_like 'issues have correct length', 1 + + it_behaves_like 'has correct external_url' + end + + context 'essential keys missing in API response' do + let(:sentry_api_response) do + issues_sample_response[0...1].map do |issue| + issue.except(:id) + end + end + + it 'raises exception' do + expect { subject }.to raise_error(Sentry::Client::MissingKeysError, 'Sentry API response is missing keys. key not found: "id"') + end + end + + context 'sentry api response too large' do + it 'raises exception' do + deep_size = double('Gitlab::Utils::DeepSize', valid?: false) + allow(Gitlab::Utils::DeepSize).to receive(:new).with(sentry_api_response).and_return(deep_size) + + expect { subject }.to raise_error(Sentry::Client::ResponseInvalidSizeError, 'Sentry API response is too big. Limit is 1 MB.') + end + end + + it_behaves_like 'maps Sentry exceptions' + + context 'when search term is present' do + let(:search_term) { 'NoMethodError' } + let(:sentry_request_url) { "#{sentry_url}/issues/?limit=20&query=is:unresolved NoMethodError" } + + it_behaves_like 'calls sentry api' + + it_behaves_like 'issues have correct return type', Gitlab::ErrorTracking::Error + it_behaves_like 'issues have correct length', 1 + end + + context 'when cursor is present' do + let(:cursor) { '1572959139000:0:0' } + let(:sentry_request_url) { "#{sentry_url}/issues/?limit=20&cursor=#{cursor}&query=is:unresolved" } + + it_behaves_like 'calls sentry api' + + it_behaves_like 'issues have correct return type', Gitlab::ErrorTracking::Error + it_behaves_like 'issues have correct length', 1 + end + end + describe '#issue_details' do let(:issue_sample_response) do Gitlab::Utils.deep_indifferent_access( diff --git a/spec/lib/sentry/client_spec.rb b/spec/lib/sentry/client_spec.rb index 8500f67b8e9..409e8be3198 100644 --- a/spec/lib/sentry/client_spec.rb +++ b/spec/lib/sentry/client_spec.rb @@ -3,219 +3,13 @@ require 'spec_helper' describe Sentry::Client do - include SentryClientHelpers - let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' } let(:token) { 'test-token' } - let(:default_httparty_options) do - { - follow_redirects: false, - headers: { "Authorization" => "Bearer test-token" } - } - end - - subject(:client) { described_class.new(sentry_url, token) } - - shared_examples 'issues has correct return type' do |klass| - it "returns objects of type #{klass}" do - expect(subject[:issues]).to all( be_a(klass) ) - end - end - - shared_examples 'issues has correct length' do |length| - it { expect(subject[:issues].length).to eq(length) } - end - - describe '#list_issues' do - let(:issues_sample_response) do - Gitlab::Utils.deep_indifferent_access( - JSON.parse(fixture_file('sentry/issues_sample_response.json')) - ) - end - - let(:issue_status) { 'unresolved' } - let(:limit) { 20 } - let(:search_term) { '' } - let(:cursor) { nil } - let(:sort) { 'last_seen' } - let(:sentry_api_response) { issues_sample_response } - let(:sentry_request_url) { sentry_url + '/issues/?limit=20&query=is:unresolved' } - - let!(:sentry_api_request) { stub_sentry_request(sentry_request_url, body: sentry_api_response) } - - subject { client.list_issues(issue_status: issue_status, limit: limit, search_term: search_term, sort: sort, cursor: cursor) } - - it_behaves_like 'calls sentry api' - - it_behaves_like 'issues has correct return type', Gitlab::ErrorTracking::Error - it_behaves_like 'issues has correct length', 1 - - shared_examples 'has correct external_url' do - context 'external_url' do - it 'is constructed correctly' do - expect(subject[:issues][0].external_url).to eq('https://sentrytest.gitlab.com/sentry-org/sentry-project/issues/11') - end - end - end - - context 'when response has a pagination info' do - let(:headers) do - { - link: '<https://sentrytest.gitlab.com>; rel="previous"; results="true"; cursor="1573556671000:0:1", <https://sentrytest.gitlab.com>; rel="next"; results="true"; cursor="1572959139000:0:0"' - } - end - let!(:sentry_api_request) { stub_sentry_request(sentry_request_url, body: sentry_api_response, headers: headers) } - - it 'parses the pagination' do - expect(subject[:pagination]).to eq( - 'previous' => { 'cursor' => '1573556671000:0:1' }, - 'next' => { 'cursor' => '1572959139000:0:0' } - ) - end - end - - context 'error object created from sentry response' do - using RSpec::Parameterized::TableSyntax - - where(:error_object, :sentry_response) do - :id | :id - :first_seen | :firstSeen - :last_seen | :lastSeen - :title | :title - :type | :type - :user_count | :userCount - :count | :count - :message | [:metadata, :value] - :culprit | :culprit - :short_id | :shortId - :status | :status - :frequency | [:stats, '24h'] - :project_id | [:project, :id] - :project_name | [:project, :name] - :project_slug | [:project, :slug] - end - - with_them do - it { expect(subject[:issues][0].public_send(error_object)).to eq(sentry_api_response[0].dig(*sentry_response)) } - end - - it_behaves_like 'has correct external_url' - end - - context 'redirects' do - let(:sentry_api_url) { sentry_url + '/issues/?limit=20&query=is:unresolved' } - - it_behaves_like 'no Sentry redirects' - end - - # Sentry API returns 404 if there are extra slashes in the URL! - context 'extra slashes in URL' do - let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects//sentry-org/sentry-project/' } - - let(:sentry_request_url) do - 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/' \ - 'issues/?limit=20&query=is:unresolved' - end - - it 'removes extra slashes in api url' do - expect(client.url).to eq(sentry_url) - expect(Gitlab::HTTP).to receive(:get).with( - URI('https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/issues/'), - anything - ).and_call_original - - subject - - expect(sentry_api_request).to have_been_requested - end - end - - context 'requests with sort parameter in sentry api' do - let(:sentry_request_url) do - 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/' \ - 'issues/?limit=20&query=is:unresolved&sort=freq' - end - let!(:sentry_api_request) { stub_sentry_request(sentry_request_url, body: sentry_api_response) } - - subject { client.list_issues(issue_status: issue_status, limit: limit, sort: 'frequency') } - - it 'calls the sentry api with sort params' do - expect(Gitlab::HTTP).to receive(:get).with( - URI("#{sentry_url}/issues/"), - default_httparty_options.merge(query: { limit: 20, query: "is:unresolved", sort: "freq" }) - ).and_call_original - - subject - - expect(sentry_api_request).to have_been_requested - end - end - - context 'with invalid sort params' do - subject { client.list_issues(issue_status: issue_status, limit: limit, sort: 'fish') } - - it 'throws an error' do - expect { subject }.to raise_error(Sentry::Client::BadRequestError, 'Invalid value for sort param') - end - end - - context 'Older sentry versions where keys are not present' do - let(:sentry_api_response) do - issues_sample_response[0...1].map do |issue| - issue[:project].delete(:id) - issue - end - end - - it_behaves_like 'calls sentry api' - - it_behaves_like 'issues has correct return type', Gitlab::ErrorTracking::Error - it_behaves_like 'issues has correct length', 1 - - it_behaves_like 'has correct external_url' - end - - context 'essential keys missing in API response' do - let(:sentry_api_response) do - issues_sample_response[0...1].map do |issue| - issue.except(:id) - end - end - - it 'raises exception' do - expect { subject }.to raise_error(Sentry::Client::MissingKeysError, 'Sentry API response is missing keys. key not found: "id"') - end - end - - context 'sentry api response too large' do - it 'raises exception' do - deep_size = double('Gitlab::Utils::DeepSize', valid?: false) - allow(Gitlab::Utils::DeepSize).to receive(:new).with(sentry_api_response).and_return(deep_size) - - expect { subject }.to raise_error(Sentry::Client::ResponseInvalidSizeError, 'Sentry API response is too big. Limit is 1 MB.') - end - end - - it_behaves_like 'maps Sentry exceptions' - - context 'when search term is present' do - let(:search_term) { 'NoMethodError' } - let(:sentry_request_url) { "#{sentry_url}/issues/?limit=20&query=is:unresolved NoMethodError" } - - it_behaves_like 'calls sentry api' - - it_behaves_like 'issues has correct return type', Gitlab::ErrorTracking::Error - it_behaves_like 'issues has correct length', 1 - end - - context 'when cursor is present' do - let(:cursor) { '1572959139000:0:0' } - let(:sentry_request_url) { "#{sentry_url}/issues/?limit=20&cursor=#{cursor}&query=is:unresolved" } - it_behaves_like 'calls sentry api' + subject { Sentry::Client.new(sentry_url, token) } - it_behaves_like 'issues has correct return type', Gitlab::ErrorTracking::Error - it_behaves_like 'issues has correct length', 1 - end - end + it { is_expected.to respond_to :projects } + it { is_expected.to respond_to :list_issues } + it { is_expected.to respond_to :issue_details } + it { is_expected.to respond_to :issue_latest_event } end |