diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-18 11:18:50 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-18 11:18:50 +0000 |
commit | 8c7f4e9d5f36cff46365a7f8c4b9c21578c1e781 (patch) | |
tree | a77e7fe7a93de11213032ed4ab1f33a3db51b738 /spec/frontend/pages | |
parent | 00b35af3db1abfe813a778f643dad221aad51fca (diff) | |
download | gitlab-ce-8c7f4e9d5f36cff46365a7f8c4b9c21578c1e781.tar.gz |
Add latest changes from gitlab-org/gitlab@13-1-stable-ee
Diffstat (limited to 'spec/frontend/pages')
7 files changed, 704 insertions, 9 deletions
diff --git a/spec/frontend/pages/dashboard/todos/index/todos_spec.js b/spec/frontend/pages/dashboard/todos/index/todos_spec.js new file mode 100644 index 00000000000..204fe3d0a68 --- /dev/null +++ b/spec/frontend/pages/dashboard/todos/index/todos_spec.js @@ -0,0 +1,111 @@ +import $ from 'jquery'; +import MockAdapter from 'axios-mock-adapter'; +import Todos from '~/pages/dashboard/todos/index/todos'; +import '~/lib/utils/common_utils'; +import '~/gl_dropdown'; +import axios from '~/lib/utils/axios_utils'; +import { addDelimiter } from '~/lib/utils/text_utility'; +import { visitUrl } from '~/lib/utils/url_utility'; + +jest.mock('~/lib/utils/url_utility', () => ({ + visitUrl: jest.fn().mockName('visitUrl'), +})); + +const TEST_COUNT_BIG = 2000; +const TEST_DONE_COUNT_BIG = 7300; + +describe('Todos', () => { + preloadFixtures('todos/todos.html'); + let todoItem; + let mock; + + beforeEach(() => { + loadFixtures('todos/todos.html'); + todoItem = document.querySelector('.todos-list .todo'); + mock = new MockAdapter(axios); + + return new Todos(); + }); + + afterEach(() => { + mock.restore(); + }); + + describe('goToTodoUrl', () => { + it('opens the todo url', done => { + const todoLink = todoItem.dataset.url; + + visitUrl.mockImplementation(url => { + expect(url).toEqual(todoLink); + done(); + }); + + todoItem.click(); + }); + + describe('meta click', () => { + let windowOpenSpy; + let metakeyEvent; + + beforeEach(() => { + metakeyEvent = $.Event('click', { keyCode: 91, ctrlKey: true }); + windowOpenSpy = jest.spyOn(window, 'open').mockImplementation(() => {}); + }); + + it('opens the todo url in another tab', () => { + const todoLink = todoItem.dataset.url; + + $('.todos-list .todo').trigger(metakeyEvent); + + expect(visitUrl).not.toHaveBeenCalled(); + expect(windowOpenSpy).toHaveBeenCalledWith(todoLink, '_blank'); + }); + + it('run native funcionality when avatar is clicked', () => { + $('.todos-list a').on('click', e => e.preventDefault()); + $('.todos-list img').trigger(metakeyEvent); + + expect(visitUrl).not.toHaveBeenCalled(); + expect(windowOpenSpy).not.toHaveBeenCalled(); + }); + }); + + describe('on done todo click', () => { + let onToggleSpy; + + beforeEach(done => { + const el = document.querySelector('.js-done-todo'); + const path = el.dataset.href; + + // Arrange + mock + .onDelete(path) + .replyOnce(200, { count: TEST_COUNT_BIG, done_count: TEST_DONE_COUNT_BIG }); + onToggleSpy = jest.fn(); + $(document).on('todo:toggle', onToggleSpy); + + // Act + el.click(); + + // Wait for axios and HTML to udpate + setImmediate(done); + }); + + it('dispatches todo:toggle', () => { + expect(onToggleSpy).toHaveBeenCalledWith(expect.anything(), TEST_COUNT_BIG); + }); + + it('updates pending text', () => { + expect(document.querySelector('.todos-pending .badge').innerHTML).toEqual( + addDelimiter(TEST_COUNT_BIG), + ); + }); + + it('updates done text', () => { + expect(document.querySelector('.todos-done .badge').innerHTML).toEqual( + addDelimiter(TEST_DONE_COUNT_BIG), + ); + }); + }); + }); +}); diff --git a/spec/frontend/pages/import/bitbucket_server/components/bitbucket_server_status_table_spec.js b/spec/frontend/pages/import/bitbucket_server/components/bitbucket_server_status_table_spec.js new file mode 100644 index 00000000000..0bb96ee33d4 --- /dev/null +++ b/spec/frontend/pages/import/bitbucket_server/components/bitbucket_server_status_table_spec.js @@ -0,0 +1,47 @@ +import { shallowMount } from '@vue/test-utils'; + +import { GlButton } from '@gitlab/ui'; +import BitbucketServerStatusTable from '~/pages/import/bitbucket_server/status/components/bitbucket_server_status_table.vue'; +import BitbucketStatusTable from '~/import_projects/components/bitbucket_status_table.vue'; + +const BitbucketStatusTableStub = { + name: 'BitbucketStatusTable', + template: '<div><slot name="actions"></slot></div>', +}; + +describe('BitbucketServerStatusTable', () => { + let wrapper; + + const findReconfigureButton = () => + wrapper + .findAll(GlButton) + .filter(w => w.props().variant === 'info') + .at(0); + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + wrapper = null; + } + }); + + function createComponent(bitbucketStatusTableStub = true) { + wrapper = shallowMount(BitbucketServerStatusTable, { + propsData: { providerTitle: 'Test', reconfigurePath: '/reconfigure' }, + stubs: { + BitbucketStatusTable: bitbucketStatusTableStub, + }, + }); + } + + it('renders bitbucket status table component', () => { + createComponent(); + expect(wrapper.contains(BitbucketStatusTable)).toBe(true); + }); + + it('renders Reconfigure button', async () => { + createComponent(BitbucketStatusTableStub); + expect(findReconfigureButton().attributes().href).toBe('/reconfigure'); + expect(findReconfigureButton().text()).toBe('Reconfigure'); + }); +}); diff --git a/spec/frontend/pages/projects/graphs/__snapshots__/code_coverage_spec.js.snap b/spec/frontend/pages/projects/graphs/__snapshots__/code_coverage_spec.js.snap new file mode 100644 index 00000000000..94089ea922b --- /dev/null +++ b/spec/frontend/pages/projects/graphs/__snapshots__/code_coverage_spec.js.snap @@ -0,0 +1,88 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Code Coverage when fetching data is successful matches the snapshot 1`] = ` +<div> + <div + class="gl-mt-3 gl-mb-3" + > + <!----> + + <!----> + + <gl-dropdown-stub + text="rspec" + > + <gl-dropdown-item-stub + value="rspec" + > + <div + class="gl-display-flex" + > + <gl-icon-stub + class="gl-absolute" + name="mobile-issue-close" + size="16" + /> + + <span + class="gl-display-flex align-items-center ml-4" + > + + rspec + + </span> + </div> + </gl-dropdown-item-stub> + <gl-dropdown-item-stub + value="cypress" + > + <div + class="gl-display-flex" + > + <!----> + + <span + class="gl-display-flex align-items-center ml-4" + > + + cypress + + </span> + </div> + </gl-dropdown-item-stub> + <gl-dropdown-item-stub + value="karma" + > + <div + class="gl-display-flex" + > + <!----> + + <span + class="gl-display-flex align-items-center ml-4" + > + + karma + + </span> + </div> + </gl-dropdown-item-stub> + </gl-dropdown-stub> + </div> + + <gl-area-chart-stub + annotations="" + data="[object Object]" + formattooltiptext="function () { [native code] }" + height="200" + includelegendavgmax="true" + legendaveragetext="Avg" + legendcurrenttext="Current" + legendlayout="inline" + legendmaxtext="Max" + legendmintext="Min" + option="[object Object]" + thresholds="" + /> +</div> +`; diff --git a/spec/frontend/pages/projects/graphs/code_coverage_spec.js b/spec/frontend/pages/projects/graphs/code_coverage_spec.js new file mode 100644 index 00000000000..4990985b076 --- /dev/null +++ b/spec/frontend/pages/projects/graphs/code_coverage_spec.js @@ -0,0 +1,164 @@ +import MockAdapter from 'axios-mock-adapter'; +import { shallowMount } from '@vue/test-utils'; +import { GlAlert, GlIcon, GlDropdown, GlDropdownItem } from '@gitlab/ui'; +import { GlAreaChart } from '@gitlab/ui/dist/charts'; + +import axios from '~/lib/utils/axios_utils'; +import CodeCoverage from '~/pages/projects/graphs/components/code_coverage.vue'; +import codeCoverageMockData from './mock_data'; +import waitForPromises from 'helpers/wait_for_promises'; +import httpStatusCodes from '~/lib/utils/http_status'; + +describe('Code Coverage', () => { + let wrapper; + let mockAxios; + + const graphEndpoint = '/graph'; + + const findAlert = () => wrapper.find(GlAlert); + const findAreaChart = () => wrapper.find(GlAreaChart); + const findAllDropdownItems = () => wrapper.findAll(GlDropdownItem); + const findFirstDropdownItem = () => findAllDropdownItems().at(0); + const findSecondDropdownItem = () => findAllDropdownItems().at(1); + + const createComponent = () => { + wrapper = shallowMount(CodeCoverage, { + propsData: { + graphEndpoint, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + describe('when fetching data is successful', () => { + beforeEach(() => { + mockAxios = new MockAdapter(axios); + mockAxios.onGet().replyOnce(httpStatusCodes.OK, codeCoverageMockData); + + createComponent(); + + return waitForPromises(); + }); + + afterEach(() => { + mockAxios.restore(); + }); + + it('renders the area chart', () => { + expect(findAreaChart().exists()).toBe(true); + }); + + it('matches the snapshot', () => { + expect(wrapper.element).toMatchSnapshot(); + }); + + it('shows no error messages', () => { + expect(findAlert().exists()).toBe(false); + }); + }); + + describe('when fetching data fails', () => { + beforeEach(() => { + mockAxios = new MockAdapter(axios); + mockAxios.onGet().replyOnce(httpStatusCodes.BAD_REQUEST); + + createComponent(); + + return waitForPromises(); + }); + + afterEach(() => { + mockAxios.restore(); + }); + + it('renders an error message', () => { + expect(findAlert().exists()).toBe(true); + expect(findAlert().attributes().variant).toBe('danger'); + }); + + it('still renders an empty graph', () => { + expect(findAreaChart().exists()).toBe(true); + }); + }); + + describe('when fetching data succeed but returns an empty state', () => { + beforeEach(() => { + mockAxios = new MockAdapter(axios); + mockAxios.onGet().replyOnce(httpStatusCodes.OK, []); + + createComponent(); + + return waitForPromises(); + }); + + afterEach(() => { + mockAxios.restore(); + }); + + it('renders an information message', () => { + expect(findAlert().exists()).toBe(true); + expect(findAlert().attributes().variant).toBe('info'); + }); + + it('still renders an empty graph', () => { + expect(findAreaChart().exists()).toBe(true); + }); + }); + + describe('dropdown options', () => { + beforeEach(() => { + mockAxios = new MockAdapter(axios); + mockAxios.onGet().replyOnce(httpStatusCodes.OK, codeCoverageMockData); + + createComponent(); + + return waitForPromises(); + }); + + it('renders the dropdown with all custom names as options', () => { + expect(wrapper.contains(GlDropdown)).toBeDefined(); + expect(findAllDropdownItems()).toHaveLength(codeCoverageMockData.length); + expect(findFirstDropdownItem().text()).toBe(codeCoverageMockData[0].group_name); + }); + }); + + describe('interactions', () => { + beforeEach(() => { + mockAxios = new MockAdapter(axios); + mockAxios.onGet().replyOnce(httpStatusCodes.OK, codeCoverageMockData); + + createComponent(); + + return waitForPromises(); + }); + + it('updates the selected dropdown option with an icon', async () => { + findSecondDropdownItem().vm.$emit('click'); + + await wrapper.vm.$nextTick(); + + expect( + findFirstDropdownItem() + .find(GlIcon) + .exists(), + ).toBe(false); + expect(findSecondDropdownItem().contains(GlIcon)).toBe(true); + }); + + it('updates the graph data when selecting a different option in dropdown', async () => { + const originalSelectedData = wrapper.vm.selectedDailyCoverage; + const expectedData = codeCoverageMockData[1]; + + findSecondDropdownItem().vm.$emit('click'); + + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.selectedDailyCoverage).not.toBe(originalSelectedData); + expect(wrapper.vm.selectedDailyCoverage).toBe(expectedData); + }); + }); +}); diff --git a/spec/frontend/pages/projects/graphs/mock_data.js b/spec/frontend/pages/projects/graphs/mock_data.js new file mode 100644 index 00000000000..a15f861ee7a --- /dev/null +++ b/spec/frontend/pages/projects/graphs/mock_data.js @@ -0,0 +1,60 @@ +export default [ + { + group_name: 'rspec', + data: [ + { date: '2020-04-30', coverage: 40.0 }, + { date: '2020-05-01', coverage: 80.0 }, + { date: '2020-05-02', coverage: 99.0 }, + { date: '2020-05-10', coverage: 80.0 }, + { date: '2020-05-15', coverage: 70.0 }, + { date: '2020-05-20', coverage: 69.0 }, + ], + }, + { + group_name: 'cypress', + data: [ + { date: '2022-07-30', coverage: 1.0 }, + { date: '2022-08-01', coverage: 2.4 }, + { date: '2022-08-02', coverage: 5.0 }, + { date: '2022-08-10', coverage: 15.0 }, + { date: '2022-08-15', coverage: 30.0 }, + { date: '2022-08-20', coverage: 40.0 }, + ], + }, + { + group_name: 'karma', + data: [ + { date: '2020-05-01', coverage: 94.0 }, + { date: '2020-05-02', coverage: 94.0 }, + { date: '2020-05-03', coverage: 94.0 }, + { date: '2020-05-04', coverage: 94.0 }, + { date: '2020-05-05', coverage: 92.0 }, + { date: '2020-05-06', coverage: 91.0 }, + { date: '2020-05-07', coverage: 78.0 }, + { date: '2020-05-08', coverage: 94.0 }, + { date: '2020-05-09', coverage: 94.0 }, + { date: '2020-05-10', coverage: 94.0 }, + { date: '2020-05-11', coverage: 94.0 }, + { date: '2020-05-12', coverage: 94.0 }, + { date: '2020-05-13', coverage: 92.0 }, + { date: '2020-05-14', coverage: 91.0 }, + { date: '2020-05-15', coverage: 78.0 }, + { date: '2020-05-16', coverage: 94.0 }, + { date: '2020-05-17', coverage: 94.0 }, + { date: '2020-05-18', coverage: 93.0 }, + { date: '2020-05-19', coverage: 92.0 }, + { date: '2020-05-20', coverage: 91.0 }, + { date: '2020-05-21', coverage: 90.0 }, + { date: '2020-05-22', coverage: 91.0 }, + { date: '2020-05-23', coverage: 92.0 }, + { date: '2020-05-24', coverage: 75.0 }, + { date: '2020-05-25', coverage: 74.0 }, + { date: '2020-05-26', coverage: 74.0 }, + { date: '2020-05-27', coverage: 74.0 }, + { date: '2020-05-28', coverage: 80.0 }, + { date: '2020-05-29', coverage: 85.0 }, + { date: '2020-05-30', coverage: 92.0 }, + { date: '2020-05-31', coverage: 91.0 }, + ], + }, +]; diff --git a/spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js b/spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js index 1809e92e1d9..0d9af0cb856 100644 --- a/spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js +++ b/spec/frontend/pages/sessions/new/preserve_url_fragment_spec.js @@ -2,6 +2,12 @@ import $ from 'jquery'; import preserveUrlFragment from '~/pages/sessions/new/preserve_url_fragment'; describe('preserve_url_fragment', () => { + const findFormAction = selector => { + return $(`.omniauth-container ${selector}`) + .parent('form') + .attr('action'); + }; + preloadFixtures('sessions/new.html'); beforeEach(() => { @@ -25,35 +31,36 @@ describe('preserve_url_fragment', () => { it('does not add an empty query parameter to OmniAuth login buttons', () => { preserveUrlFragment(); - expect($('#oauth-login-cas3').attr('href')).toBe('http://test.host/users/auth/cas3'); + expect(findFormAction('#oauth-login-cas3')).toBe('http://test.host/users/auth/cas3'); - expect($('.omniauth-container #oauth-login-auth0').attr('href')).toBe( - 'http://test.host/users/auth/auth0', - ); + expect(findFormAction('#oauth-login-auth0')).toBe('http://test.host/users/auth/auth0'); }); describe('adds "redirect_fragment" query parameter to OmniAuth login buttons', () => { it('when "remember_me" is not present', () => { preserveUrlFragment('#L65'); - expect($('#oauth-login-cas3').attr('href')).toBe( + expect(findFormAction('#oauth-login-cas3')).toBe( 'http://test.host/users/auth/cas3?redirect_fragment=L65', ); - expect($('.omniauth-container #oauth-login-auth0').attr('href')).toBe( + expect(findFormAction('#oauth-login-auth0')).toBe( 'http://test.host/users/auth/auth0?redirect_fragment=L65', ); }); it('when "remember-me" is present', () => { - $('a.omniauth-btn').attr('href', (i, href) => `${href}?remember_me=1`); + $('.omniauth-btn') + .parent('form') + .attr('action', (i, href) => `${href}?remember_me=1`); + preserveUrlFragment('#L65'); - expect($('#oauth-login-cas3').attr('href')).toBe( + expect(findFormAction('#oauth-login-cas3')).toBe( 'http://test.host/users/auth/cas3?remember_me=1&redirect_fragment=L65', ); - expect($('#oauth-login-auth0').attr('href')).toBe( + expect(findFormAction('#oauth-login-auth0')).toBe( 'http://test.host/users/auth/auth0?remember_me=1&redirect_fragment=L65', ); }); diff --git a/spec/frontend/pages/sessions/new/signin_tabs_memoizer_spec.js b/spec/frontend/pages/sessions/new/signin_tabs_memoizer_spec.js new file mode 100644 index 00000000000..738498edbd3 --- /dev/null +++ b/spec/frontend/pages/sessions/new/signin_tabs_memoizer_spec.js @@ -0,0 +1,218 @@ +import AccessorUtilities from '~/lib/utils/accessor'; +import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer'; +import trackData from '~/pages/sessions/new/index'; +import Tracking from '~/tracking'; +import { useLocalStorageSpy } from 'helpers/local_storage_helper'; + +useLocalStorageSpy(); + +describe('SigninTabsMemoizer', () => { + const fixtureTemplate = 'static/signin_tabs.html'; + const tabSelector = 'ul.new-session-tabs'; + const currentTabKey = 'current_signin_tab'; + let memo; + + function createMemoizer() { + memo = new SigninTabsMemoizer({ + currentTabKey, + tabSelector, + }); + return memo; + } + + preloadFixtures(fixtureTemplate); + + beforeEach(() => { + loadFixtures(fixtureTemplate); + + jest.spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').mockReturnValue(true); + }); + + it('does nothing if no tab was previously selected', () => { + createMemoizer(); + + expect(document.querySelector(`${tabSelector} > li.active a`).getAttribute('href')).toEqual( + '#ldap', + ); + }); + + it('shows last selected tab on boot', () => { + createMemoizer().saveData('#ldap'); + const fakeTab = { + click: () => {}, + }; + jest.spyOn(document, 'querySelector').mockReturnValue(fakeTab); + jest.spyOn(fakeTab, 'click').mockImplementation(() => {}); + + memo.bootstrap(); + + // verify that triggers click on the last selected tab + expect(document.querySelector).toHaveBeenCalledWith(`${tabSelector} a[href="#ldap"]`); + expect(fakeTab.click).toHaveBeenCalled(); + }); + + it('clicks the first tab if value in local storage is bad', () => { + createMemoizer().saveData('#bogus'); + const fakeTab = { + click: jest.fn().mockName('fakeTab_click'), + }; + jest + .spyOn(document, 'querySelector') + .mockImplementation(selector => + selector === `${tabSelector} a[href="#bogus"]` ? null : fakeTab, + ); + + memo.bootstrap(); + + // verify that triggers click on stored selector and fallback + expect(document.querySelector.mock.calls).toEqual([ + ['ul.new-session-tabs a[href="#bogus"]'], + ['ul.new-session-tabs a'], + ]); + + expect(fakeTab.click).toHaveBeenCalled(); + }); + + it('saves last selected tab on change', () => { + createMemoizer(); + + document.querySelector('a[href="#login-pane"]').click(); + + expect(memo.readData()).toEqual('#login-pane'); + }); + + it('overrides last selected tab with hash tag when given', () => { + window.location.hash = '#ldap'; + createMemoizer(); + + expect(memo.readData()).toEqual('#ldap'); + }); + + describe('class constructor', () => { + beforeEach(() => { + memo = createMemoizer(); + }); + + it('should set .isLocalStorageAvailable', () => { + expect(AccessorUtilities.isLocalStorageAccessSafe).toHaveBeenCalled(); + expect(memo.isLocalStorageAvailable).toBe(true); + }); + }); + + describe('trackData', () => { + beforeEach(() => { + jest.spyOn(Tracking, 'event').mockImplementation(() => {}); + }); + + describe('with tracking data', () => { + beforeEach(() => { + gon.tracking_data = { + category: 'Growth::Acquisition::Experiment::SignUpFlow', + action: 'start', + label: 'uuid', + property: 'control_group', + }; + trackData(); + }); + + it('should track data when the "click" event of the register tab is triggered', () => { + document.querySelector('a[href="#register-pane"]').click(); + + expect(Tracking.event).toHaveBeenCalledWith( + 'Growth::Acquisition::Experiment::SignUpFlow', + 'start', + { + label: 'uuid', + property: 'control_group', + }, + ); + }); + }); + + describe('without tracking data', () => { + beforeEach(() => { + gon.tracking_data = undefined; + trackData(); + }); + + it('should not track data when the "click" event of the register tab is triggered', () => { + document.querySelector('a[href="#register-pane"]').click(); + + expect(Tracking.event).not.toHaveBeenCalled(); + }); + }); + }); + + describe('saveData', () => { + beforeEach(() => { + memo = { + currentTabKey, + }; + }); + + describe('if .isLocalStorageAvailable is `false`', () => { + beforeEach(() => { + memo.isLocalStorageAvailable = false; + + SigninTabsMemoizer.prototype.saveData.call(memo); + }); + + it('should not call .setItem', () => { + expect(localStorage.setItem).not.toHaveBeenCalled(); + }); + }); + + describe('if .isLocalStorageAvailable is `true`', () => { + const value = 'value'; + + beforeEach(() => { + memo.isLocalStorageAvailable = true; + + SigninTabsMemoizer.prototype.saveData.call(memo, value); + }); + + it('should call .setItem', () => { + expect(localStorage.setItem).toHaveBeenCalledWith(currentTabKey, value); + }); + }); + }); + + describe('readData', () => { + const itemValue = 'itemValue'; + let readData; + + beforeEach(() => { + memo = { + currentTabKey, + }; + + localStorage.getItem.mockReturnValue(itemValue); + }); + + describe('if .isLocalStorageAvailable is `false`', () => { + beforeEach(() => { + memo.isLocalStorageAvailable = false; + + readData = SigninTabsMemoizer.prototype.readData.call(memo); + }); + + it('should not call .getItem and should return `null`', () => { + expect(localStorage.getItem).not.toHaveBeenCalled(); + expect(readData).toBe(null); + }); + }); + + describe('if .isLocalStorageAvailable is `true`', () => { + beforeEach(() => { + memo.isLocalStorageAvailable = true; + + readData = SigninTabsMemoizer.prototype.readData.call(memo); + }); + + it('should call .getItem and return the localStorage value', () => { + expect(window.localStorage.getItem).toHaveBeenCalledWith(currentTabKey); + expect(readData).toBe(itemValue); + }); + }); + }); +}); |