diff options
Diffstat (limited to 'spec/frontend/error_tracking')
-rw-r--r-- | spec/frontend/error_tracking/components/error_details_spec.js | 105 | ||||
-rw-r--r-- | spec/frontend/error_tracking/components/error_tracking_list_spec.js | 15 | ||||
-rw-r--r-- | spec/frontend/error_tracking/components/stacktrace_entry_spec.js | 49 | ||||
-rw-r--r-- | spec/frontend/error_tracking/components/stacktrace_spec.js | 45 | ||||
-rw-r--r-- | spec/frontend/error_tracking/store/details/actions_spec.js | 94 | ||||
-rw-r--r-- | spec/frontend/error_tracking/store/details/getters_spec.js | 13 | ||||
-rw-r--r-- | spec/frontend/error_tracking/store/list/getters_spec.js | 33 | ||||
-rw-r--r-- | spec/frontend/error_tracking/store/list/mutation_spec.js (renamed from spec/frontend/error_tracking/store/mutation_spec.js) | 4 |
8 files changed, 351 insertions, 7 deletions
diff --git a/spec/frontend/error_tracking/components/error_details_spec.js b/spec/frontend/error_tracking/components/error_details_spec.js new file mode 100644 index 00000000000..54e8b0848a2 --- /dev/null +++ b/spec/frontend/error_tracking/components/error_details_spec.js @@ -0,0 +1,105 @@ +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import Vuex from 'vuex'; +import { GlLoadingIcon, GlLink } from '@gitlab/ui'; +import Stacktrace from '~/error_tracking/components/stacktrace.vue'; +import ErrorDetails from '~/error_tracking/components/error_details.vue'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('ErrorDetails', () => { + let store; + let wrapper; + let actions; + let getters; + + function mountComponent() { + wrapper = shallowMount(ErrorDetails, { + localVue, + store, + propsData: { + issueDetailsPath: '/123/details', + issueStackTracePath: '/stacktrace', + }, + }); + } + + beforeEach(() => { + actions = { + startPollingDetails: () => {}, + startPollingStacktrace: () => {}, + }; + + getters = { + sentryUrl: () => 'sentry.io', + stacktrace: () => [{ context: [1, 2], lineNo: 53, filename: 'index.js' }], + }; + + const state = { + error: {}, + loading: true, + stacktraceData: {}, + loadingStacktrace: true, + }; + + store = new Vuex.Store({ + modules: { + details: { + namespaced: true, + actions, + state, + getters, + }, + }, + }); + }); + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + }); + + describe('loading', () => { + beforeEach(() => { + mountComponent(); + }); + + it('should show spinner while loading', () => { + expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + expect(wrapper.find(GlLink).exists()).toBe(false); + expect(wrapper.find(Stacktrace).exists()).toBe(false); + }); + }); + + describe('Error details', () => { + it('should show Sentry error details without stacktrace', () => { + store.state.details.loading = false; + store.state.details.error.id = 1; + mountComponent(); + expect(wrapper.find(GlLink).exists()).toBe(true); + expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + expect(wrapper.find(Stacktrace).exists()).toBe(false); + }); + + describe('Stacktrace', () => { + it('should show stacktrace', () => { + store.state.details.loading = false; + store.state.details.error.id = 1; + store.state.details.loadingStacktrace = false; + mountComponent(); + expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); + expect(wrapper.find(Stacktrace).exists()).toBe(true); + }); + + it('should NOT show stacktrace if no entries', () => { + store.state.details.loading = false; + store.state.details.loadingStacktrace = false; + store.getters = { 'details/sentryUrl': () => 'sentry.io', 'details/stacktrace': () => [] }; + mountComponent(); + expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); + expect(wrapper.find(Stacktrace).exists()).toBe(false); + }); + }); + }); +}); diff --git a/spec/frontend/error_tracking/components/error_tracking_list_spec.js b/spec/frontend/error_tracking/components/error_tracking_list_spec.js index ce8b8908026..1bbf23cc602 100644 --- a/spec/frontend/error_tracking/components/error_tracking_list_spec.js +++ b/spec/frontend/error_tracking/components/error_tracking_list_spec.js @@ -34,7 +34,7 @@ describe('ErrorTrackingList', () => { beforeEach(() => { actions = { - getErrorList: () => {}, + getSentryData: () => {}, startPolling: () => {}, restartPolling: jest.fn().mockName('restartPolling'), }; @@ -45,8 +45,13 @@ describe('ErrorTrackingList', () => { }; store = new Vuex.Store({ - actions, - state, + modules: { + list: { + namespaced: true, + actions, + state, + }, + }, }); }); @@ -70,7 +75,7 @@ describe('ErrorTrackingList', () => { describe('results', () => { beforeEach(() => { - store.state.loading = false; + store.state.list.loading = false; mountComponent(); }); @@ -84,7 +89,7 @@ describe('ErrorTrackingList', () => { describe('no results', () => { beforeEach(() => { - store.state.loading = false; + store.state.list.loading = false; mountComponent(); }); diff --git a/spec/frontend/error_tracking/components/stacktrace_entry_spec.js b/spec/frontend/error_tracking/components/stacktrace_entry_spec.js new file mode 100644 index 00000000000..95958408770 --- /dev/null +++ b/spec/frontend/error_tracking/components/stacktrace_entry_spec.js @@ -0,0 +1,49 @@ +import { shallowMount } from '@vue/test-utils'; +import StackTraceEntry from '~/error_tracking/components/stacktrace_entry.vue'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import FileIcon from '~/vue_shared/components/file_icon.vue'; +import Icon from '~/vue_shared/components/icon.vue'; + +describe('Stacktrace Entry', () => { + let wrapper; + + function mountComponent(props) { + wrapper = shallowMount(StackTraceEntry, { + propsData: { + filePath: 'sidekiq/util.rb', + lines: [ + [22, ' def safe_thread(name, \u0026block)\n'], + [23, ' Thread.new do\n'], + [24, " Thread.current['sidekiq_label'] = name\n"], + [25, ' watchdog(name, \u0026block)\n'], + ], + errorLine: 24, + ...props, + }, + }); + } + + beforeEach(() => { + mountComponent(); + }); + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + }); + + it('should render stacktrace entry collapsed', () => { + expect(wrapper.find(StackTraceEntry).exists()).toBe(true); + expect(wrapper.find(ClipboardButton).exists()).toBe(true); + expect(wrapper.find(Icon).exists()).toBe(true); + expect(wrapper.find(FileIcon).exists()).toBe(true); + expect(wrapper.element.querySelectorAll('table').length).toBe(0); + }); + + it('should render stacktrace entry table expanded', () => { + mountComponent({ expanded: true }); + expect(wrapper.element.querySelectorAll('tr.line_holder').length).toBe(4); + expect(wrapper.element.querySelectorAll('.line_content.old').length).toBe(1); + }); +}); diff --git a/spec/frontend/error_tracking/components/stacktrace_spec.js b/spec/frontend/error_tracking/components/stacktrace_spec.js new file mode 100644 index 00000000000..4f4a60acba4 --- /dev/null +++ b/spec/frontend/error_tracking/components/stacktrace_spec.js @@ -0,0 +1,45 @@ +import { shallowMount } from '@vue/test-utils'; +import Stacktrace from '~/error_tracking/components/stacktrace.vue'; +import StackTraceEntry from '~/error_tracking/components/stacktrace_entry.vue'; + +describe('ErrorDetails', () => { + let wrapper; + + const stackTraceEntry = { + filename: 'sidekiq/util.rb', + context: [ + [22, ' def safe_thread(name, \u0026block)\n'], + [23, ' Thread.new do\n'], + [24, " Thread.current['sidekiq_label'] = name\n"], + [25, ' watchdog(name, \u0026block)\n'], + ], + lineNo: 24, + }; + + function mountComponent(entries) { + wrapper = shallowMount(Stacktrace, { + propsData: { + entries, + }, + }); + } + + describe('Stacktrace', () => { + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + }); + + it('should render single Stacktrace entry', () => { + mountComponent([stackTraceEntry]); + expect(wrapper.findAll(StackTraceEntry).length).toBe(1); + }); + + it('should render multiple Stacktrace entry', () => { + const entriesNum = 3; + mountComponent(new Array(entriesNum).fill(stackTraceEntry)); + expect(wrapper.findAll(StackTraceEntry).length).toBe(entriesNum); + }); + }); +}); diff --git a/spec/frontend/error_tracking/store/details/actions_spec.js b/spec/frontend/error_tracking/store/details/actions_spec.js new file mode 100644 index 00000000000..f72cd1e413b --- /dev/null +++ b/spec/frontend/error_tracking/store/details/actions_spec.js @@ -0,0 +1,94 @@ +import axios from '~/lib/utils/axios_utils'; +import MockAdapter from 'axios-mock-adapter'; +import testAction from 'helpers/vuex_action_helper'; +import createFlash from '~/flash'; +import * as actions from '~/error_tracking/store/details/actions'; +import * as types from '~/error_tracking/store/details/mutation_types'; + +jest.mock('~/flash.js'); +let mock; + +describe('Sentry error details store actions', () => { + beforeEach(() => { + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + createFlash.mockClear(); + }); + + describe('startPollingDetails', () => { + const endpoint = '123/details'; + it('should commit SET_ERROR with received response', done => { + const payload = { error: { id: 1 } }; + mock.onGet().reply(200, payload); + testAction( + actions.startPollingDetails, + { endpoint }, + {}, + [ + { type: types.SET_ERROR, payload: payload.error }, + { type: types.SET_LOADING, payload: false }, + ], + [], + () => { + done(); + }, + ); + }); + + it('should show flash on API error', done => { + mock.onGet().reply(400); + + testAction( + actions.startPollingDetails, + { endpoint }, + {}, + [{ type: types.SET_LOADING, payload: false }], + [], + () => { + expect(createFlash).toHaveBeenCalledTimes(1); + done(); + }, + ); + }); + }); + + describe('startPollingStacktrace', () => { + const endpoint = '123/stacktrace'; + it('should commit SET_ERROR with received response', done => { + const payload = { error: [1, 2, 3] }; + mock.onGet().reply(200, payload); + testAction( + actions.startPollingStacktrace, + { endpoint }, + {}, + [ + { type: types.SET_STACKTRACE_DATA, payload: payload.error }, + { type: types.SET_LOADING_STACKTRACE, payload: false }, + ], + [], + () => { + done(); + }, + ); + }); + + it('should show flash on API error', done => { + mock.onGet().reply(400); + + testAction( + actions.startPollingStacktrace, + { endpoint }, + {}, + [{ type: types.SET_LOADING_STACKTRACE, payload: false }], + [], + () => { + expect(createFlash).toHaveBeenCalledTimes(1); + done(); + }, + ); + }); + }); +}); diff --git a/spec/frontend/error_tracking/store/details/getters_spec.js b/spec/frontend/error_tracking/store/details/getters_spec.js new file mode 100644 index 00000000000..ea57de5872b --- /dev/null +++ b/spec/frontend/error_tracking/store/details/getters_spec.js @@ -0,0 +1,13 @@ +import * as getters from '~/error_tracking/store/details/getters'; + +describe('Sentry error details store getters', () => { + const state = { + stacktraceData: { stack_trace_entries: [1, 2] }, + }; + + describe('stacktrace', () => { + it('should get stacktrace', () => { + expect(getters.stacktrace(state)).toEqual([2, 1]); + }); + }); +}); diff --git a/spec/frontend/error_tracking/store/list/getters_spec.js b/spec/frontend/error_tracking/store/list/getters_spec.js new file mode 100644 index 00000000000..3cd7fa37d44 --- /dev/null +++ b/spec/frontend/error_tracking/store/list/getters_spec.js @@ -0,0 +1,33 @@ +import * as getters from '~/error_tracking/store/list/getters'; + +describe('Error Tracking getters', () => { + let state; + + const mockErrors = [ + { title: 'ActiveModel::MissingAttributeError: missing attribute: encrypted_password' }, + { title: 'Grape::Exceptions::MethodNotAllowed: Grape::Exceptions::MethodNotAllowed' }, + { title: 'NoMethodError: undefined method `sanitize_http_headers=' }, + { title: 'NoMethodError: undefined method `pry' }, + ]; + + beforeEach(() => { + state = { + errors: mockErrors, + }; + }); + + describe('search results', () => { + it('should return errors filtered by words in title matching the query', () => { + const filteredErrors = getters.filterErrorsByTitle(state)('NoMethod'); + + expect(filteredErrors).not.toContainEqual(mockErrors[0]); + expect(filteredErrors.length).toBe(2); + }); + + it('should not return results if there is no matching query', () => { + const filteredErrors = getters.filterErrorsByTitle(state)('GitLab'); + + expect(filteredErrors.length).toBe(0); + }); + }); +}); diff --git a/spec/frontend/error_tracking/store/mutation_spec.js b/spec/frontend/error_tracking/store/list/mutation_spec.js index 8117104bdbc..6e021185b4d 100644 --- a/spec/frontend/error_tracking/store/mutation_spec.js +++ b/spec/frontend/error_tracking/store/list/mutation_spec.js @@ -1,5 +1,5 @@ -import mutations from '~/error_tracking/store/mutations'; -import * as types from '~/error_tracking/store/mutation_types'; +import mutations from '~/error_tracking/store/list/mutations'; +import * as types from '~/error_tracking/store/list/mutation_types'; describe('Error tracking mutations', () => { describe('SET_ERRORS', () => { |