diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-03 09:07:54 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-03 09:07:54 +0000 |
commit | 87ef501eacd66d7166183d20d84e33de022f7002 (patch) | |
tree | fa4e0f41e00a4b6aeb035530be4b5473f51b7a3d /spec/frontend | |
parent | f321e51f46bcb628c3e96a44b5ebf3bb1c4033ab (diff) | |
download | gitlab-ce-87ef501eacd66d7166183d20d84e33de022f7002.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
9 files changed, 374 insertions, 24 deletions
diff --git a/spec/frontend/boards/components/boards_selector_spec.js b/spec/frontend/boards/components/boards_selector_spec.js new file mode 100644 index 00000000000..7723af07d8c --- /dev/null +++ b/spec/frontend/boards/components/boards_selector_spec.js @@ -0,0 +1,155 @@ +import Vue from 'vue'; +import { mount } from '@vue/test-utils'; +import { GlDropdown } from '@gitlab/ui'; +import { TEST_HOST } from 'spec/test_constants'; +import BoardsSelector from '~/boards/components/boards_selector.vue'; +import boardsStore from '~/boards/stores/boards_store'; + +const throttleDuration = 1; + +function boardGenerator(n) { + return new Array(n).fill().map((board, id) => { + const name = `board${id}`; + + return { + id, + name, + }; + }); +} + +describe('BoardsSelector', () => { + let wrapper; + let allBoardsResponse; + let recentBoardsResponse; + const boards = boardGenerator(20); + const recentBoards = boardGenerator(5); + + const fillSearchBox = filterTerm => { + const searchBox = wrapper.find({ ref: 'searchBox' }); + const searchBoxInput = searchBox.find('input'); + searchBoxInput.setValue(filterTerm); + searchBoxInput.trigger('input'); + }; + + const getDropdownItems = () => wrapper.findAll('.js-dropdown-item'); + const getDropdownHeaders = () => wrapper.findAll('.dropdown-bold-header'); + + beforeEach(() => { + boardsStore.setEndpoints({ + boardsEndpoint: '', + recentBoardsEndpoint: '', + listsEndpoint: '', + bulkUpdatePath: '', + boardId: '', + }); + + allBoardsResponse = Promise.resolve({ + data: boards, + }); + recentBoardsResponse = Promise.resolve({ + data: recentBoards, + }); + + boardsStore.allBoards = jest.fn(() => allBoardsResponse); + boardsStore.recentBoards = jest.fn(() => recentBoardsResponse); + + const Component = Vue.extend(BoardsSelector); + wrapper = mount(Component, { + propsData: { + throttleDuration, + currentBoard: { + id: 1, + name: 'Development', + milestone_id: null, + weight: null, + assignee_id: null, + labels: [], + }, + milestonePath: `${TEST_HOST}/milestone/path`, + boardBaseUrl: `${TEST_HOST}/board/base/url`, + hasMissingBoards: false, + canAdminBoard: true, + multipleIssueBoardsAvailable: true, + labelsPath: `${TEST_HOST}/labels/path`, + projectId: 42, + groupId: 19, + scopedIssueBoardFeatureEnabled: true, + weights: [], + }, + attachToDocument: true, + }); + + // Emits gl-dropdown show event to simulate the dropdown is opened at initialization time + wrapper.find(GlDropdown).vm.$emit('show'); + + return Promise.all([allBoardsResponse, recentBoardsResponse]).then(() => Vue.nextTick()); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + describe('filtering', () => { + it('shows all boards without filtering', () => { + expect(getDropdownItems().length).toBe(boards.length + recentBoards.length); + }); + + it('shows only matching boards when filtering', () => { + const filterTerm = 'board1'; + const expectedCount = boards.filter(board => board.name.includes(filterTerm)).length; + + fillSearchBox(filterTerm); + + return Vue.nextTick().then(() => { + expect(getDropdownItems().length).toBe(expectedCount); + }); + }); + + it('shows message if there are no matching boards', () => { + fillSearchBox('does not exist'); + + return Vue.nextTick().then(() => { + expect(getDropdownItems().length).toBe(0); + expect(wrapper.text().includes('No matching boards found')).toBe(true); + }); + }); + }); + + describe('recent boards section', () => { + it('shows only when boards are greater than 10', () => { + const expectedCount = 2; // Recent + All + + expect(getDropdownHeaders().length).toBe(expectedCount); + }); + + it('does not show when boards are less than 10', () => { + wrapper.setData({ + boards: boards.slice(0, 5), + }); + + return Vue.nextTick().then(() => { + expect(getDropdownHeaders().length).toBe(0); + }); + }); + + it('does not show when recentBoards api returns empty array', () => { + wrapper.setData({ + recentBoards: [], + }); + + return Vue.nextTick().then(() => { + expect(getDropdownHeaders().length).toBe(0); + }); + }); + + it('does not show when search is active', () => { + fillSearchBox('Random string'); + + return Vue.nextTick().then(() => { + expect(getDropdownHeaders().length).toBe(0); + }); + }); + }); +}); diff --git a/spec/frontend/error_tracking/components/error_details_spec.js b/spec/frontend/error_tracking/components/error_details_spec.js index 94bf0189c91..e43f9569ffc 100644 --- a/spec/frontend/error_tracking/components/error_details_spec.js +++ b/spec/frontend/error_tracking/components/error_details_spec.js @@ -1,8 +1,15 @@ import { createLocalVue, shallowMount } from '@vue/test-utils'; import Vuex from 'vuex'; import { __ } from '~/locale'; -import { GlLoadingIcon, GlLink, GlBadge, GlFormInput, GlAlert, GlSprintf } from '@gitlab/ui'; -import LoadingButton from '~/vue_shared/components/loading_button.vue'; +import { + GlButton, + GlLoadingIcon, + GlLink, + GlBadge, + GlFormInput, + GlAlert, + GlSprintf, +} from '@gitlab/ui'; import Stacktrace from '~/error_tracking/components/stacktrace.vue'; import ErrorDetails from '~/error_tracking/components/error_details.vue'; import { @@ -28,7 +35,7 @@ describe('ErrorDetails', () => { function mountComponent() { wrapper = shallowMount(ErrorDetails, { - stubs: { LoadingButton, GlSprintf }, + stubs: { GlButton, GlSprintf }, localVue, store, mocks, @@ -127,7 +134,7 @@ describe('ErrorDetails', () => { expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); expect(wrapper.find(Stacktrace).exists()).toBe(false); expect(wrapper.find(GlBadge).exists()).toBe(false); - expect(wrapper.findAll('button').length).toBe(3); + expect(wrapper.findAll(GlButton).length).toBe(3); }); describe('Badges', () => { @@ -226,7 +233,7 @@ describe('ErrorDetails', () => { it('should submit the form', () => { window.HTMLFormElement.prototype.submit = () => {}; const submitSpy = jest.spyOn(wrapper.vm.$refs.sentryIssueForm, 'submit'); - wrapper.find('[data-qa-selector="create_issue_button"]').trigger('click'); + wrapper.find('[data-qa-selector="create_issue_button"]').vm.$emit('click'); expect(submitSpy).toHaveBeenCalled(); submitSpy.mockRestore(); }); @@ -255,14 +262,14 @@ describe('ErrorDetails', () => { }); it('marks error as ignored when ignore button is clicked', () => { - findUpdateIgnoreStatusButton().trigger('click'); + findUpdateIgnoreStatusButton().vm.$emit('click'); expect(actions.updateIgnoreStatus.mock.calls[0][1]).toEqual( expect.objectContaining({ status: errorStatus.IGNORED }), ); }); it('marks error as resolved when resolve button is clicked', () => { - findUpdateResolveStatusButton().trigger('click'); + findUpdateResolveStatusButton().vm.$emit('click'); expect(actions.updateResolveStatus.mock.calls[0][1]).toEqual( expect.objectContaining({ status: errorStatus.RESOLVED }), ); @@ -281,14 +288,14 @@ describe('ErrorDetails', () => { }); it('marks error as unresolved when ignore button is clicked', () => { - findUpdateIgnoreStatusButton().trigger('click'); + findUpdateIgnoreStatusButton().vm.$emit('click'); expect(actions.updateIgnoreStatus.mock.calls[0][1]).toEqual( expect.objectContaining({ status: errorStatus.UNRESOLVED }), ); }); it('marks error as resolved when resolve button is clicked', () => { - findUpdateResolveStatusButton().trigger('click'); + findUpdateResolveStatusButton().vm.$emit('click'); expect(actions.updateResolveStatus.mock.calls[0][1]).toEqual( expect.objectContaining({ status: errorStatus.RESOLVED }), ); @@ -307,14 +314,14 @@ describe('ErrorDetails', () => { }); it('marks error as ignored when ignore button is clicked', () => { - findUpdateIgnoreStatusButton().trigger('click'); + findUpdateIgnoreStatusButton().vm.$emit('click'); expect(actions.updateIgnoreStatus.mock.calls[0][1]).toEqual( expect.objectContaining({ status: errorStatus.IGNORED }), ); }); it('marks error as unresolved when unresolve button is clicked', () => { - findUpdateResolveStatusButton().trigger('click'); + findUpdateResolveStatusButton().vm.$emit('click'); expect(actions.updateResolveStatus.mock.calls[0][1]).toEqual( expect.objectContaining({ status: errorStatus.UNRESOLVED }), ); 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 b632b461eb9..f852a3091aa 100644 --- a/spec/frontend/error_tracking/components/error_tracking_list_spec.js +++ b/spec/frontend/error_tracking/components/error_tracking_list_spec.js @@ -42,9 +42,6 @@ describe('ErrorTrackingList', () => { ...stubChildren(ErrorTrackingList), ...stubs, }, - data() { - return { errorSearchQuery: 'search' }; - }, }); } @@ -164,8 +161,9 @@ describe('ErrorTrackingList', () => { }); it('it searches by query', () => { + findSearchBox().vm.$emit('input', 'search'); findSearchBox().trigger('keyup.enter'); - expect(actions.searchByQuery.mock.calls[0][1]).toEqual(wrapper.vm.errorSearchQuery); + expect(actions.searchByQuery.mock.calls[0][1]).toBe('search'); }); it('it sorts by fields', () => { diff --git a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap index c705270343b..77f7f2e0609 100644 --- a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap +++ b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap @@ -72,7 +72,7 @@ exports[`Dashboard template matches the default snapshot 1`] = ` </gl-form-group-stub> <gl-form-group-stub - class="col-sm-6 col-md-6 col-lg-4" + class="col-sm-auto col-md-auto col-lg-auto" label="Show last" label-for="monitor-time-window-dropdown" label-size="sm" @@ -83,6 +83,21 @@ exports[`Dashboard template matches the default snapshot 1`] = ` /> </gl-form-group-stub> + <gl-form-group-stub + class="col-sm-2 col-md-2 col-lg-1 refresh-dashboard-button" + > + <gl-button-stub + size="md" + title="Reload this page" + variant="default" + > + <icon-stub + name="repeat" + size="16" + /> + </gl-button-stub> + </gl-form-group-stub> + <!----> </div> </div> diff --git a/spec/frontend/monitoring/components/charts/options_spec.js b/spec/frontend/monitoring/components/charts/options_spec.js new file mode 100644 index 00000000000..d219a6627bf --- /dev/null +++ b/spec/frontend/monitoring/components/charts/options_spec.js @@ -0,0 +1,60 @@ +import { SUPPORTED_FORMATS } from '~/lib/utils/unit_format'; +import { getYAxisOptions, getTooltipFormatter } from '~/monitoring/components/charts/options'; + +describe('options spec', () => { + describe('getYAxisOptions', () => { + it('default options', () => { + const options = getYAxisOptions(); + + expect(options).toMatchObject({ + name: expect.any(String), + axisLabel: { + formatter: expect.any(Function), + }, + scale: true, + boundaryGap: [expect.any(Number), expect.any(Number)], + }); + + expect(options.name).not.toHaveLength(0); + }); + + it('name options', () => { + const yAxisName = 'My axis values'; + const options = getYAxisOptions({ + name: yAxisName, + }); + + expect(options).toMatchObject({ + name: yAxisName, + nameLocation: 'center', + nameGap: expect.any(Number), + }); + }); + + it('formatter options', () => { + const options = getYAxisOptions({ + format: SUPPORTED_FORMATS.bytes, + }); + + expect(options.axisLabel.formatter).toEqual(expect.any(Function)); + expect(options.axisLabel.formatter(1)).toBe('1.00B'); + }); + }); + + describe('getTooltipFormatter', () => { + it('default format', () => { + const formatter = getTooltipFormatter(); + + expect(formatter).toEqual(expect.any(Function)); + expect(formatter(1)).toBe('1.000'); + }); + + it('defined format', () => { + const formatter = getTooltipFormatter({ + format: SUPPORTED_FORMATS.bytes, + }); + + expect(formatter(1)).toBe('1.000B'); + }); + }); +}); diff --git a/spec/frontend/monitoring/components/charts/time_series_spec.js b/spec/frontend/monitoring/components/charts/time_series_spec.js index e9322d6b5a9..8dcb54e3fd9 100644 --- a/spec/frontend/monitoring/components/charts/time_series_spec.js +++ b/spec/frontend/monitoring/components/charts/time_series_spec.js @@ -190,7 +190,8 @@ describe('Time series component', () => { it('formats tooltip content', () => { const name = 'Total'; - const value = '5.556'; + const value = '5.556MB'; + const dataIndex = 0; const seriesLabel = timeSeriesChart.find(GlChartSeriesLabel); @@ -348,9 +349,9 @@ describe('Time series component', () => { }); }); - it('additional y axis data', () => { + it('additional y-axis data', () => { const mockCustomYAxisOption = { - name: 'Custom y axis label', + name: 'Custom y-axis label', axisLabel: { formatter: jest.fn(), }, @@ -397,8 +398,8 @@ describe('Time series component', () => { deploymentFormatter = getChartOptions().yAxis[1].axisLabel.formatter; }); - it('rounds to 3 decimal places', () => { - expect(dataFormatter(0.88888)).toBe('0.889'); + it('formats and rounds to 2 decimal places', () => { + expect(dataFormatter(0.88888)).toBe('0.89MB'); }); it('deployment formatter is set as is required to display a tooltip', () => { @@ -421,7 +422,7 @@ describe('Time series component', () => { }); describe('yAxisLabel', () => { - it('y axis is configured correctly', () => { + it('y-axis is configured correctly', () => { const { yAxis } = getChartOptions(); expect(yAxis).toHaveLength(2); diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js index 6f05207204e..bec22b28a5c 100644 --- a/spec/frontend/monitoring/components/dashboard_spec.js +++ b/spec/frontend/monitoring/components/dashboard_spec.js @@ -214,6 +214,19 @@ describe('Dashboard', () => { }); }); + it('renders the refresh dashboard button', () => { + createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] }); + + setupComponentStore(wrapper); + + return wrapper.vm.$nextTick().then(() => { + const refreshBtn = wrapper.findAll({ ref: 'refreshDashboardBtn' }); + + expect(refreshBtn).toHaveLength(1); + expect(refreshBtn.is(GlButton)).toBe(true); + }); + }); + describe('when one of the metrics is missing', () => { beforeEach(() => { createShallowWrapper({ hasMetrics: true }); diff --git a/spec/frontend/monitoring/mock_data.js b/spec/frontend/monitoring/mock_data.js index 32daf990ad3..60b1510973d 100644 --- a/spec/frontend/monitoring/mock_data.js +++ b/spec/frontend/monitoring/mock_data.js @@ -393,13 +393,16 @@ export const metricsDashboardPayload = { type: 'area-chart', y_label: 'Total Memory Used', weight: 4, + y_axis: { + format: 'megabytes', + }, metrics: [ { id: 'system_metrics_kubernetes_container_memory_total', query_range: - 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024', + 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1000/1000', label: 'Total', - unit: 'GB', + unit: 'MB', metric_id: 12, prometheus_endpoint_path: 'http://test', }, diff --git a/spec/frontend/monitoring/store/utils_spec.js b/spec/frontend/monitoring/store/utils_spec.js index 57418e90470..2bd8af9b7d5 100644 --- a/spec/frontend/monitoring/store/utils_spec.js +++ b/spec/frontend/monitoring/store/utils_spec.js @@ -1,3 +1,4 @@ +import { SUPPORTED_FORMATS } from '~/lib/utils/unit_format'; import { uniqMetricsId, parseEnvironmentsResponse, @@ -44,6 +45,11 @@ describe('mapToDashboardViewModel', () => { title: 'Title A', type: 'chart-type', y_label: 'Y Label A', + yAxis: { + name: 'Y Label A', + format: 'number', + precision: 2, + }, metrics: [], }, ], @@ -90,6 +96,98 @@ describe('mapToDashboardViewModel', () => { }); }); + describe('panel mapping', () => { + const panelTitle = 'Panel Title'; + const yAxisName = 'Y Axis Name'; + + let dashboard; + + const setupWithPanel = panel => { + dashboard = { + panel_groups: [ + { + panels: [panel], + }, + ], + }; + }; + + const getMappedPanel = () => mapToDashboardViewModel(dashboard).panelGroups[0].panels[0]; + + it('group y_axis defaults', () => { + setupWithPanel({ + title: panelTitle, + }); + + expect(getMappedPanel()).toEqual({ + title: panelTitle, + y_label: '', + yAxis: { + name: '', + format: SUPPORTED_FORMATS.number, + precision: 2, + }, + metrics: [], + }); + }); + + it('panel with y_axis.name', () => { + setupWithPanel({ + y_axis: { + name: yAxisName, + }, + }); + + expect(getMappedPanel().y_label).toBe(yAxisName); + expect(getMappedPanel().yAxis.name).toBe(yAxisName); + }); + + it('panel with y_axis.name and y_label, displays y_axis.name', () => { + setupWithPanel({ + y_label: 'Ignored Y Label', + y_axis: { + name: yAxisName, + }, + }); + + expect(getMappedPanel().y_label).toBe(yAxisName); + expect(getMappedPanel().yAxis.name).toBe(yAxisName); + }); + + it('group y_label', () => { + setupWithPanel({ + y_label: yAxisName, + }); + + expect(getMappedPanel().y_label).toBe(yAxisName); + expect(getMappedPanel().yAxis.name).toBe(yAxisName); + }); + + it('group y_axis format and precision', () => { + setupWithPanel({ + title: panelTitle, + y_axis: { + precision: 0, + format: SUPPORTED_FORMATS.bytes, + }, + }); + + expect(getMappedPanel().yAxis.format).toBe(SUPPORTED_FORMATS.bytes); + expect(getMappedPanel().yAxis.precision).toBe(0); + }); + + it('group y_axis unsupported format defaults to number', () => { + setupWithPanel({ + title: panelTitle, + y_axis: { + format: 'invalid_format', + }, + }); + + expect(getMappedPanel().yAxis.format).toBe(SUPPORTED_FORMATS.number); + }); + }); + describe('metrics mapping', () => { const defaultLabel = 'Panel Label'; const dashboardWithMetric = (metric, label = defaultLabel) => ({ |