diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-06 15:10:29 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-06 15:10:29 +0300 |
commit | 5564275a0b378298dc6281599cbfe71a937109ff (patch) | |
tree | a468e1e60046356410219c35c23a8a428c5e2c5e /spec/frontend | |
parent | d87918510a866a5fcbbc2f899ad65c6938ebf5f5 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
6 files changed, 503 insertions, 0 deletions
diff --git a/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap b/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap new file mode 100644 index 00000000000..dda6d68018e --- /dev/null +++ b/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Code navigation popover component renders popover 1`] = ` +<div + class="popover code-navigation-popover popover-font-size-normal gl-popover bs-popover-bottom show" + style="left: 0px; top: 0px;" +> + <div + class="arrow" + style="left: 0px;" + /> + + <div + class="border-bottom" + > + <pre + class="border-0 bg-transparent m-0 code highlight" + > + console.log + </pre> + </div> + + <div + class="popover-body" + > + <gl-button-stub + class="w-100" + href="http://test.com" + size="md" + target="_blank" + variant="default" + > + + Go to definition + + </gl-button-stub> + </div> +</div> +`; diff --git a/spec/frontend/code_navigation/components/app_spec.js b/spec/frontend/code_navigation/components/app_spec.js new file mode 100644 index 00000000000..cfdc0dcc6cc --- /dev/null +++ b/spec/frontend/code_navigation/components/app_spec.js @@ -0,0 +1,64 @@ +import Vuex from 'vuex'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import createState from '~/code_navigation/store/state'; +import App from '~/code_navigation/components/app.vue'; +import Popover from '~/code_navigation/components/popover.vue'; + +const localVue = createLocalVue(); +const fetchData = jest.fn(); +const showDefinition = jest.fn(); +let wrapper; + +localVue.use(Vuex); + +function factory(initialState = {}) { + const store = new Vuex.Store({ + state: { + ...createState(), + ...initialState, + }, + actions: { + fetchData, + showDefinition, + }, + }); + + wrapper = shallowMount(App, { store, localVue }); +} + +describe('Code navigation app component', () => { + afterEach(() => { + wrapper.destroy(); + }); + + it('fetches data on mount', () => { + factory(); + + expect(fetchData).toHaveBeenCalled(); + }); + + it('hides popover when no definition set', () => { + factory(); + + expect(wrapper.find(Popover).exists()).toBe(false); + }); + + it('renders popover when definition set', () => { + factory({ + currentDefinition: { hover: 'console' }, + currentDefinitionPosition: { x: 0 }, + }); + + expect(wrapper.find(Popover).exists()).toBe(true); + }); + + it('calls showDefinition when clicking blob viewer', () => { + setFixtures('<div class="blob-viewer"></div>'); + + factory(); + + document.querySelector('.blob-viewer').click(); + + expect(showDefinition).toHaveBeenCalled(); + }); +}); diff --git a/spec/frontend/code_navigation/components/popover_spec.js b/spec/frontend/code_navigation/components/popover_spec.js new file mode 100644 index 00000000000..ad05504a224 --- /dev/null +++ b/spec/frontend/code_navigation/components/popover_spec.js @@ -0,0 +1,58 @@ +import { shallowMount } from '@vue/test-utils'; +import Popover from '~/code_navigation/components/popover.vue'; + +const MOCK_CODE_DATA = Object.freeze({ + hover: [ + { + language: 'javascript', + value: 'console.log', + }, + ], + definition_url: 'http://test.com', +}); + +const MOCK_DOCS_DATA = Object.freeze({ + hover: [ + { + language: null, + value: 'console.log', + }, + ], + definition_url: 'http://test.com', +}); + +let wrapper; + +function factory(position, data) { + wrapper = shallowMount(Popover, { propsData: { position, data } }); +} + +describe('Code navigation popover component', () => { + afterEach(() => { + wrapper.destroy(); + }); + + it('renders popover', () => { + factory({ x: 0, y: 0, height: 0 }, MOCK_CODE_DATA); + + expect(wrapper.element).toMatchSnapshot(); + }); + + describe('code output', () => { + it('renders code output', () => { + factory({ x: 0, y: 0, height: 0 }, MOCK_CODE_DATA); + + expect(wrapper.find({ ref: 'code-output' }).exists()).toBe(true); + expect(wrapper.find({ ref: 'doc-output' }).exists()).toBe(false); + }); + }); + + describe('documentation output', () => { + it('renders code output', () => { + factory({ x: 0, y: 0, height: 0 }, MOCK_DOCS_DATA); + + expect(wrapper.find({ ref: 'code-output' }).exists()).toBe(false); + expect(wrapper.find({ ref: 'doc-output' }).exists()).toBe(true); + }); + }); +}); diff --git a/spec/frontend/code_navigation/store/actions_spec.js b/spec/frontend/code_navigation/store/actions_spec.js new file mode 100644 index 00000000000..5e29a76f804 --- /dev/null +++ b/spec/frontend/code_navigation/store/actions_spec.js @@ -0,0 +1,221 @@ +import MockAdapter from 'axios-mock-adapter'; +import testAction from 'helpers/vuex_action_helper'; +import actions from '~/code_navigation/store/actions'; +import createFlash from '~/flash'; +import axios from '~/lib/utils/axios_utils'; +import { setCurrentHoverElement, addInteractionClass } from '~/code_navigation/utils'; + +jest.mock('~/flash'); +jest.mock('~/code_navigation/utils'); + +describe('Code navigation actions', () => { + describe('setInitialData', () => { + it('commits SET_INITIAL_DATA', done => { + testAction( + actions.setInitialData, + { projectPath: 'test' }, + {}, + [{ type: 'SET_INITIAL_DATA', payload: { projectPath: 'test' } }], + [], + done, + ); + }); + }); + + describe('requestDataError', () => { + it('commits REQUEST_DATA_ERROR', () => + testAction(actions.requestDataError, null, {}, [{ type: 'REQUEST_DATA_ERROR' }], [])); + + it('creates a flash message', () => + testAction(actions.requestDataError, null, {}, [{ type: 'REQUEST_DATA_ERROR' }], []).then( + () => { + expect(createFlash).toHaveBeenCalled(); + }, + )); + }); + + describe('fetchData', () => { + let mock; + const state = { + projectPath: 'gitlab-org/gitlab', + commitId: '123', + blobPath: 'index', + }; + const apiUrl = '/api/1/projects/gitlab-org%2Fgitlab/commits/123/lsif/info'; + + beforeEach(() => { + window.gon = { api_version: '1' }; + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + }); + + describe('success', () => { + beforeEach(() => { + mock.onGet(apiUrl).replyOnce(200, [ + { + start_line: 0, + start_char: 0, + hover: { value: '123' }, + }, + { + start_line: 1, + start_char: 0, + hover: null, + }, + ]); + }); + + it('commits REQUEST_DATA_SUCCESS with normalized data', done => { + testAction( + actions.fetchData, + null, + state, + [ + { type: 'REQUEST_DATA' }, + { + type: 'REQUEST_DATA_SUCCESS', + payload: { '0:0': { start_line: 0, start_char: 0, hover: { value: '123' } } }, + }, + ], + [], + done, + ); + }); + + it('calls addInteractionClass with data', done => { + testAction( + actions.fetchData, + null, + state, + [ + { type: 'REQUEST_DATA' }, + { + type: 'REQUEST_DATA_SUCCESS', + payload: { '0:0': { start_line: 0, start_char: 0, hover: { value: '123' } } }, + }, + ], + [], + ) + .then(() => { + expect(addInteractionClass).toHaveBeenCalledWith({ + start_line: 0, + start_char: 0, + hover: { value: '123' }, + }); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('error', () => { + beforeEach(() => { + mock.onGet(apiUrl).replyOnce(500); + }); + + it('dispatches requestDataError', done => { + testAction( + actions.fetchData, + null, + state, + [{ type: 'REQUEST_DATA' }], + [{ type: 'requestDataError' }], + done, + ); + }); + }); + }); + + describe('showDefinition', () => { + let target; + + beforeEach(() => { + target = document.createElement('div'); + }); + + it('returns early when no data exists', done => { + testAction(actions.showDefinition, { target }, {}, [], [], done); + }); + + it('commits SET_CURRENT_DEFINITION when target is not code navitation element', done => { + testAction( + actions.showDefinition, + { target }, + { data: {} }, + [ + { + type: 'SET_CURRENT_DEFINITION', + payload: { definition: undefined, position: undefined }, + }, + ], + [], + done, + ); + }); + + it('commits SET_CURRENT_DEFINITION with LSIF data', done => { + target.classList.add('js-code-navigation'); + target.setAttribute('data-line-index', '0'); + target.setAttribute('data-char-index', '0'); + + testAction( + actions.showDefinition, + { target }, + { data: { '0:0': { hover: 'test' } } }, + [ + { + type: 'SET_CURRENT_DEFINITION', + payload: { definition: { hover: 'test' }, position: { height: 0, x: 0, y: 0 } }, + }, + ], + [], + done, + ); + }); + + it('adds hll class to target element', () => { + target.classList.add('js-code-navigation'); + target.setAttribute('data-line-index', '0'); + target.setAttribute('data-char-index', '0'); + + return testAction( + actions.showDefinition, + { target }, + { data: { '0:0': { hover: 'test' } } }, + [ + { + type: 'SET_CURRENT_DEFINITION', + payload: { definition: { hover: 'test' }, position: { height: 0, x: 0, y: 0 } }, + }, + ], + [], + ).then(() => { + expect(target.classList).toContain('hll'); + }); + }); + + it('caches current target element', () => { + target.classList.add('js-code-navigation'); + target.setAttribute('data-line-index', '0'); + target.setAttribute('data-char-index', '0'); + + return testAction( + actions.showDefinition, + { target }, + { data: { '0:0': { hover: 'test' } } }, + [ + { + type: 'SET_CURRENT_DEFINITION', + payload: { definition: { hover: 'test' }, position: { height: 0, x: 0, y: 0 } }, + }, + ], + [], + ).then(() => { + expect(setCurrentHoverElement).toHaveBeenCalledWith(target); + }); + }); + }); +}); diff --git a/spec/frontend/code_navigation/store/mutations_spec.js b/spec/frontend/code_navigation/store/mutations_spec.js new file mode 100644 index 00000000000..117a2ed2f14 --- /dev/null +++ b/spec/frontend/code_navigation/store/mutations_spec.js @@ -0,0 +1,63 @@ +import mutations from '~/code_navigation/store/mutations'; +import createState from '~/code_navigation/store/state'; + +let state; + +describe('Code navigation mutations', () => { + beforeEach(() => { + state = createState(); + }); + + describe('SET_INITIAL_DATA', () => { + it('sets initial data', () => { + mutations.SET_INITIAL_DATA(state, { + projectPath: 'test', + commitId: '123', + blobPath: 'index.js', + }); + + expect(state.projectPath).toBe('test'); + expect(state.commitId).toBe('123'); + expect(state.blobPath).toBe('index.js'); + }); + }); + + describe('REQUEST_DATA', () => { + it('sets loading true', () => { + mutations.REQUEST_DATA(state); + + expect(state.loading).toBe(true); + }); + }); + + describe('REQUEST_DATA_SUCCESS', () => { + it('sets loading false', () => { + mutations.REQUEST_DATA_SUCCESS(state, ['test']); + + expect(state.loading).toBe(false); + }); + + it('sets data', () => { + mutations.REQUEST_DATA_SUCCESS(state, ['test']); + + expect(state.data).toEqual(['test']); + }); + }); + + describe('REQUEST_DATA_ERROR', () => { + it('sets loading false', () => { + mutations.REQUEST_DATA_ERROR(state); + + expect(state.loading).toBe(false); + }); + }); + + describe('SET_CURRENT_DEFINITION', () => { + it('sets current definition and position', () => { + mutations.SET_CURRENT_DEFINITION(state, { definition: 'test', position: { x: 0 } }); + + expect(state.currentDefinition).toBe('test'); + expect(state.currentDefinitionPosition).toEqual({ x: 0 }); + }); + }); +}); diff --git a/spec/frontend/code_navigation/utils/index_spec.js b/spec/frontend/code_navigation/utils/index_spec.js new file mode 100644 index 00000000000..458cc536635 --- /dev/null +++ b/spec/frontend/code_navigation/utils/index_spec.js @@ -0,0 +1,58 @@ +import { + cachedData, + getCurrentHoverElement, + setCurrentHoverElement, + addInteractionClass, +} from '~/code_navigation/utils'; + +afterEach(() => { + if (cachedData.has('current')) { + cachedData.delete('current'); + } +}); + +describe('getCurrentHoverElement', () => { + it.each` + value + ${'test'} + ${undefined} + `('it returns cached current key', ({ value }) => { + if (value) { + cachedData.set('current', value); + } + + expect(getCurrentHoverElement()).toEqual(value); + }); +}); + +describe('setCurrentHoverElement', () => { + it('sets cached current key', () => { + setCurrentHoverElement('test'); + + expect(getCurrentHoverElement()).toEqual('test'); + }); +}); + +describe('addInteractionClass', () => { + beforeEach(() => { + setFixtures( + '<div id="LC1"><span>console</span><span>.</span><span>log</span></div><div id="LC2"><span>function</span></div>', + ); + }); + + it.each` + line | char | index + ${0} | ${0} | ${0} + ${0} | ${8} | ${2} + ${1} | ${0} | ${0} + `( + 'it sets code navigation attributes for line $line and character $char', + ({ line, char, index }) => { + addInteractionClass({ start_line: line, start_char: char }); + + expect(document.querySelectorAll(`#LC${line + 1} span`)[index].classList).toContain( + 'js-code-navigation', + ); + }, + ); +}); |