diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-12 15:10:33 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-12 15:10:33 +0000 |
commit | 8ff63012e9b7e3dc2279e636868af9a438d1fa93 (patch) | |
tree | 4dd67f247345cbc2e8629c4a5f2b935dafc988e3 /spec | |
parent | ef7cfec30c9fab7b9e757877c472ca7ca2eccc2d (diff) | |
download | gitlab-ce-8ff63012e9b7e3dc2279e636868af9a438d1fa93.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
23 files changed, 538 insertions, 288 deletions
diff --git a/spec/frontend/diffs/store/getters_versions_dropdowns_spec.js b/spec/frontend/diffs/store/getters_versions_dropdowns_spec.js index eb0f2364a50..0343ef75732 100644 --- a/spec/frontend/diffs/store/getters_versions_dropdowns_spec.js +++ b/spec/frontend/diffs/store/getters_versions_dropdowns_spec.js @@ -18,7 +18,6 @@ describe('Compare diff version dropdowns', () => { }; localState.targetBranchName = 'baseVersion'; localState.mergeRequestDiffs = diffsMockData; - gon.features = { diffCompareWithHead: true }; }); describe('selectedTargetIndex', () => { @@ -129,14 +128,6 @@ describe('Compare diff version dropdowns', () => { }); assertVersions(targetVersions); }); - - it('does not list head version if feature flag is not enabled', () => { - gon.features = { diffCompareWithHead: false }; - setupTest(); - const targetVersions = getters.diffCompareDropdownTargetVersions(localState, getters); - - expect(targetVersions.find(version => version.isHead)).toBeUndefined(); - }); }); it('diffCompareDropdownSourceVersions', () => { 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 6cb7821b341..5770778d8ee 100644 --- a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap +++ b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap @@ -16,7 +16,6 @@ exports[`Dashboard template matches the default snapshot 1`] = ` data-qa-selector="dashboards_filter_dropdown" defaultbranch="master" id="monitor-dashboards-dropdown" - selecteddashboard="[object Object]" toggle-class="dropdown-menu-toggle" /> </div> diff --git a/spec/frontend/monitoring/components/dashboards_dropdown_spec.js b/spec/frontend/monitoring/components/dashboards_dropdown_spec.js index 25b31a793f7..b29d86cbc5b 100644 --- a/spec/frontend/monitoring/components/dashboards_dropdown_spec.js +++ b/spec/frontend/monitoring/components/dashboards_dropdown_spec.js @@ -15,6 +15,7 @@ const notStarredDashboards = dashboardGitResponse.filter(({ starred }) => !starr describe('DashboardsDropdown', () => { let wrapper; let mockDashboards; + let mockSelectedDashboard; function createComponent(props, opts = {}) { const storeOpts = { @@ -23,6 +24,7 @@ describe('DashboardsDropdown', () => { }, computed: { allDashboards: () => mockDashboards, + selectedDashboard: () => mockSelectedDashboard, }, }; @@ -46,6 +48,7 @@ describe('DashboardsDropdown', () => { beforeEach(() => { mockDashboards = dashboardGitResponse; + mockSelectedDashboard = null; }); describe('when it receives dashboards data', () => { @@ -153,13 +156,12 @@ describe('DashboardsDropdown', () => { let modalDirective; beforeEach(() => { + [mockSelectedDashboard] = dashboardGitResponse; modalDirective = jest.fn(); duplicateDashboardAction = jest.fn().mockResolvedValue(); wrapper = createComponent( - { - selectedDashboard: dashboardGitResponse[0], - }, + {}, { directives: { GlModal: modalDirective, diff --git a/spec/frontend/monitoring/mock_data.js b/spec/frontend/monitoring/mock_data.js index dab560d197d..2fa88dfa87a 100644 --- a/spec/frontend/monitoring/mock_data.js +++ b/spec/frontend/monitoring/mock_data.js @@ -325,6 +325,7 @@ export const dashboardGitResponse = [ project_blob_path: null, path: 'config/prometheus/common_metrics.yml', starred: false, + user_starred_path: `${mockProjectDir}/metrics_user_starred_dashboards?dashboard_path=config/prometheus/common_metrics.yml`, }, { default: false, @@ -334,6 +335,7 @@ export const dashboardGitResponse = [ project_blob_path: `${mockProjectDir}/-/blob/master/.gitlab/dashboards/dashboard.yml`, path: '.gitlab/dashboards/dashboard.yml', starred: true, + user_starred_path: `${mockProjectDir}/metrics_user_starred_dashboards?dashboard_path=.gitlab/dashboards/dashboard.yml`, }, ...customDashboardsData, ]; diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js index 901b698b703..44626bfcc57 100644 --- a/spec/frontend/monitoring/store/actions_spec.js +++ b/spec/frontend/monitoring/store/actions_spec.js @@ -18,6 +18,7 @@ import { fetchEnvironmentsData, fetchDashboardData, fetchAnnotations, + toggleStarredValue, fetchPrometheusMetric, setInitialState, filterEnvironments, @@ -350,6 +351,49 @@ describe('Monitoring store actions', () => { }); }); + describe('Toggles starred value of current dashboard', () => { + const { state } = store; + let unstarredDashboard; + let starredDashboard; + + beforeEach(() => { + state.isUpdatingStarredValue = false; + [unstarredDashboard, starredDashboard] = dashboardGitResponse; + }); + + describe('toggleStarredValue', () => { + it('performs no changes if no dashboard is selected', () => { + return testAction(toggleStarredValue, null, state, [], []); + }); + + it('performs no changes if already changing starred value', () => { + state.selectedDashboard = unstarredDashboard; + state.isUpdatingStarredValue = true; + return testAction(toggleStarredValue, null, state, [], []); + }); + + it('stars dashboard if it is not starred', () => { + state.selectedDashboard = unstarredDashboard; + mock.onPost(unstarredDashboard.user_starred_path).reply(200); + + return testAction(toggleStarredValue, null, state, [ + { type: types.REQUEST_DASHBOARD_STARRING }, + { type: types.RECEIVE_DASHBOARD_STARRING_SUCCESS, payload: true }, + ]); + }); + + it('unstars dashboard if it is starred', () => { + state.selectedDashboard = starredDashboard; + mock.onPost(starredDashboard.user_starred_path).reply(200); + + return testAction(toggleStarredValue, null, state, [ + { type: types.REQUEST_DASHBOARD_STARRING }, + { type: types.RECEIVE_DASHBOARD_STARRING_FAILURE }, + ]); + }); + }); + }); + describe('Set initial state', () => { let mockedState; beforeEach(() => { diff --git a/spec/frontend/monitoring/store/getters_spec.js b/spec/frontend/monitoring/store/getters_spec.js index e9622071aeb..f07ae4c5a1e 100644 --- a/spec/frontend/monitoring/store/getters_spec.js +++ b/spec/frontend/monitoring/store/getters_spec.js @@ -3,7 +3,7 @@ import * as getters from '~/monitoring/stores/getters'; import mutations from '~/monitoring/stores/mutations'; import * as types from '~/monitoring/stores/mutation_types'; import { metricStates } from '~/monitoring/constants'; -import { environmentData, metricsResult } from '../mock_data'; +import { environmentData, metricsResult, dashboardGitResponse } from '../mock_data'; import { metricsDashboardPayload, metricResultStatus, @@ -350,4 +350,48 @@ describe('Monitoring store Getters', () => { expect(variablesArray).toEqual([]); }); }); + + describe('selectedDashboard', () => { + const { selectedDashboard } = getters; + + it('returns a dashboard', () => { + const state = { + allDashboards: dashboardGitResponse, + currentDashboard: dashboardGitResponse[0].path, + }; + expect(selectedDashboard(state)).toEqual(dashboardGitResponse[0]); + }); + + it('returns a non-default dashboard', () => { + const state = { + allDashboards: dashboardGitResponse, + currentDashboard: dashboardGitResponse[1].path, + }; + expect(selectedDashboard(state)).toEqual(dashboardGitResponse[1]); + }); + + it('returns a default dashboard when no dashboard is selected', () => { + const state = { + allDashboards: dashboardGitResponse, + currentDashboard: null, + }; + expect(selectedDashboard(state)).toEqual(dashboardGitResponse[0]); + }); + + it('returns a default dashboard when dashboard cannot be found', () => { + const state = { + allDashboards: dashboardGitResponse, + currentDashboard: 'wrong_path', + }; + expect(selectedDashboard(state)).toEqual(dashboardGitResponse[0]); + }); + + it('returns null when no dashboards are present', () => { + const state = { + allDashboards: [], + currentDashboard: dashboardGitResponse[0].path, + }; + expect(selectedDashboard(state)).toEqual(null); + }); + }); }); diff --git a/spec/frontend/monitoring/store/mutations_spec.js b/spec/frontend/monitoring/store/mutations_spec.js index e6564f5e329..29628c99256 100644 --- a/spec/frontend/monitoring/store/mutations_spec.js +++ b/spec/frontend/monitoring/store/mutations_spec.js @@ -72,6 +72,49 @@ describe('Monitoring mutations', () => { }); }); + describe('Dashboard starring mutations', () => { + it('REQUEST_DASHBOARD_STARRING', () => { + stateCopy = { isUpdatingStarredValue: false }; + mutations[types.REQUEST_DASHBOARD_STARRING](stateCopy); + + expect(stateCopy.isUpdatingStarredValue).toBe(true); + }); + + describe('RECEIVE_DASHBOARD_STARRING_SUCCESS', () => { + let allDashboards; + + beforeEach(() => { + allDashboards = [...dashboardGitResponse]; + stateCopy = { + allDashboards, + currentDashboard: allDashboards[1].path, + isUpdatingStarredValue: true, + }; + }); + + it('sets a dashboard as starred', () => { + mutations[types.RECEIVE_DASHBOARD_STARRING_SUCCESS](stateCopy, true); + + expect(stateCopy.isUpdatingStarredValue).toBe(false); + expect(stateCopy.allDashboards[1].starred).toBe(true); + }); + + it('sets a dashboard as unstarred', () => { + mutations[types.RECEIVE_DASHBOARD_STARRING_SUCCESS](stateCopy, false); + + expect(stateCopy.isUpdatingStarredValue).toBe(false); + expect(stateCopy.allDashboards[1].starred).toBe(false); + }); + }); + + it('RECEIVE_DASHBOARD_STARRING_FAILURE', () => { + stateCopy = { isUpdatingStarredValue: true }; + mutations[types.RECEIVE_DASHBOARD_STARRING_FAILURE](stateCopy); + + expect(stateCopy.isUpdatingStarredValue).toBe(false); + }); + }); + describe('RECEIVE_DEPLOYMENTS_DATA_SUCCESS', () => { it('stores the deployment data', () => { stateCopy.deploymentData = []; diff --git a/spec/frontend/static_site_editor/components/edit_area_spec.js b/spec/frontend/static_site_editor/components/edit_area_spec.js new file mode 100644 index 00000000000..19330039812 --- /dev/null +++ b/spec/frontend/static_site_editor/components/edit_area_spec.js @@ -0,0 +1,79 @@ +import { shallowMount } from '@vue/test-utils'; + +import RichContentEditor from '~/vue_shared/components/rich_content_editor/rich_content_editor.vue'; + +import EditArea from '~/static_site_editor/components/edit_area.vue'; +import PublishToolbar from '~/static_site_editor/components/publish_toolbar.vue'; +import EditHeader from '~/static_site_editor/components/edit_header.vue'; + +import { sourceContentTitle as title, sourceContent as content, returnUrl } from '../mock_data'; + +describe('~/static_site_editor/components/edit_area.vue', () => { + let wrapper; + const savingChanges = true; + const newContent = `new ${content}`; + + const buildWrapper = (propsData = {}) => { + wrapper = shallowMount(EditArea, { + provide: { + glFeatures: { richContentEditor: true }, + }, + propsData: { + title, + content, + returnUrl, + savingChanges, + ...propsData, + }, + }); + }; + + const findEditHeader = () => wrapper.find(EditHeader); + const findRichContentEditor = () => wrapper.find(RichContentEditor); + const findPublishToolbar = () => wrapper.find(PublishToolbar); + + beforeEach(() => { + buildWrapper(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders edit header', () => { + expect(findEditHeader().exists()).toBe(true); + expect(findEditHeader().props('title')).toBe(title); + }); + + it('renders rich content editor', () => { + expect(findRichContentEditor().exists()).toBe(true); + expect(findRichContentEditor().props('value')).toBe(content); + }); + + it('renders publish toolbar', () => { + expect(findPublishToolbar().exists()).toBe(true); + expect(findPublishToolbar().props('returnUrl')).toBe(returnUrl); + expect(findPublishToolbar().props('savingChanges')).toBe(savingChanges); + expect(findPublishToolbar().props('saveable')).toBe(false); + }); + + describe('when content changes', () => { + beforeEach(() => { + findRichContentEditor().vm.$emit('input', newContent); + + return wrapper.vm.$nextTick(); + }); + + it('sets publish toolbar as saveable when content changes', () => { + expect(findPublishToolbar().props('saveable')).toBe(true); + }); + + it('sets publish toolbar as not saveable when content changes are rollback', () => { + findRichContentEditor().vm.$emit('input', content); + + return wrapper.vm.$nextTick().then(() => { + expect(findPublishToolbar().props('saveable')).toBe(false); + }); + }); + }); +}); diff --git a/spec/frontend/static_site_editor/pages/home_spec.js b/spec/frontend/static_site_editor/pages/home_spec.js index 69646abade5..281b764a801 100644 --- a/spec/frontend/static_site_editor/pages/home_spec.js +++ b/spec/frontend/static_site_editor/pages/home_spec.js @@ -1,21 +1,20 @@ import Vuex from 'vuex'; import { shallowMount, createLocalVue } from '@vue/test-utils'; -import { GlSkeletonLoader } from '@gitlab/ui'; import createState from '~/static_site_editor/store/state'; import Home from '~/static_site_editor/pages/home.vue'; -import RichContentEditor from '~/vue_shared/components/rich_content_editor/rich_content_editor.vue'; -import EditHeader from '~/static_site_editor/components/edit_header.vue'; + +import SkeletonLoader from '~/static_site_editor/components/skeleton_loader.vue'; +import EditArea from '~/static_site_editor/components/edit_area.vue'; import InvalidContentMessage from '~/static_site_editor/components/invalid_content_message.vue'; -import PublishToolbar from '~/static_site_editor/components/publish_toolbar.vue'; import SubmitChangesError from '~/static_site_editor/components/submit_changes_error.vue'; import SavedChangesMessage from '~/static_site_editor/components/saved_changes_message.vue'; import { returnUrl, - sourceContent, - sourceContentTitle, + sourceContent as content, + sourceContentTitle as title, savedContentMeta, submitChangesError, } from '../mock_data'; @@ -27,13 +26,12 @@ localVue.use(Vuex); describe('static_site_editor/pages/home', () => { let wrapper; let store; - let loadContentActionMock; + let $apollo; let setContentActionMock; let submitChangesActionMock; let dismissSubmitChangesErrorActionMock; const buildStore = ({ initialState, getters } = {}) => { - loadContentActionMock = jest.fn(); setContentActionMock = jest.fn(); submitChangesActionMock = jest.fn(); dismissSubmitChangesErrorActionMock = jest.fn(); @@ -47,53 +45,55 @@ describe('static_site_editor/pages/home', () => { ...getters, }, actions: { - loadContent: loadContentActionMock, setContent: setContentActionMock, submitChanges: submitChangesActionMock, dismissSubmitChangesError: dismissSubmitChangesErrorActionMock, }, }); }; - const buildContentLoadedStore = ({ initialState, getters } = {}) => { - buildStore({ - initialState: { - isContentLoaded: true, - ...initialState, - }, - getters: { - ...getters, + + const buildApollo = (queries = {}) => { + $apollo = { + queries: { + sourceContent: { + loading: false, + }, + ...queries, }, - }); + }; }; - const buildWrapper = (data = { appData: { isSupportedContent: true } }) => { + const buildWrapper = (data = {}) => { wrapper = shallowMount(Home, { localVue, store, - provide: { - glFeatures: { richContentEditor: true }, + mocks: { + $apollo, }, data() { - return data; + return { + appData: { isSupportedContent: true, returnUrl }, + ...data, + }; }, }); }; - const findRichContentEditor = () => wrapper.find(RichContentEditor); - const findEditHeader = () => wrapper.find(EditHeader); + const findEditArea = () => wrapper.find(EditArea); const findInvalidContentMessage = () => wrapper.find(InvalidContentMessage); - const findPublishToolbar = () => wrapper.find(PublishToolbar); - const findSkeletonLoader = () => wrapper.find(GlSkeletonLoader); + const findSkeletonLoader = () => wrapper.find(SkeletonLoader); const findSubmitChangesError = () => wrapper.find(SubmitChangesError); const findSavedChangesMessage = () => wrapper.find(SavedChangesMessage); beforeEach(() => { + buildApollo(); buildStore(); - buildWrapper(); }); afterEach(() => { wrapper.destroy(); + wrapper = null; + $apollo = null; }); it('renders the saved changes message when changes are submitted successfully', () => { @@ -107,103 +107,69 @@ describe('static_site_editor/pages/home', () => { }); }); - describe('when content is not loaded', () => { - it('does not render rich content editor', () => { - expect(findRichContentEditor().exists()).toBe(false); - }); - - it('does not render edit header', () => { - expect(findEditHeader().exists()).toBe(false); - }); - - it('does not render toolbar', () => { - expect(findPublishToolbar().exists()).toBe(false); - }); + it('does not render the saved changes message when changes are not submitted', () => { + buildWrapper(); - it('does not render saved changes message', () => { - expect(findSavedChangesMessage().exists()).toBe(false); - }); + expect(findSavedChangesMessage().exists()).toBe(false); }); describe('when content is loaded', () => { - const content = sourceContent; - const title = sourceContentTitle; - beforeEach(() => { - buildContentLoadedStore({ initialState: { content, title } }); - buildWrapper(); + buildStore({ initialState: { isSavingChanges: true } }); + buildWrapper({ sourceContent: { title, content } }); }); - it('renders the rich content editor', () => { - expect(findRichContentEditor().exists()).toBe(true); + it('renders edit area', () => { + expect(findEditArea().exists()).toBe(true); }); - it('renders the edit header', () => { - expect(findEditHeader().exists()).toBe(true); + it('provides source content to the edit area', () => { + expect(findEditArea().props()).toMatchObject({ + title, + content, + }); }); - it('does not render skeleton loader', () => { - expect(findSkeletonLoader().exists()).toBe(false); + it('provides returnUrl to the edit area', () => { + expect(findEditArea().props('returnUrl')).toBe(returnUrl); }); - it('passes page content to the rich content editor', () => { - expect(findRichContentEditor().props('value')).toBe(content); + it('provides isSavingChanges to the edit area', () => { + expect(findEditArea().props('savingChanges')).toBe(true); }); + }); - it('passes page title to edit header', () => { - expect(findEditHeader().props('title')).toBe(title); - }); + it('does not render edit area when content is not loaded', () => { + buildWrapper({ sourceContent: null }); - it('renders toolbar', () => { - expect(findPublishToolbar().exists()).toBe(true); - }); + expect(findEditArea().exists()).toBe(false); }); - it('sets toolbar as saveable when content changes', () => { - buildContentLoadedStore({ - getters: { - contentChanged: () => true, + it('renders skeleton loader when content is not loading', () => { + buildApollo({ + sourceContent: { + loading: true, }, }); buildWrapper(); - expect(findPublishToolbar().props('saveable')).toBe(true); - }); - - it('displays skeleton loader when loading content', () => { - buildStore({ initialState: { isLoadingContent: true } }); - buildWrapper(); - expect(findSkeletonLoader().exists()).toBe(true); }); - it('does not display submit changes error when an error does not exist', () => { - buildContentLoadedStore(); - buildWrapper(); - - expect(findSubmitChangesError().exists()).toBe(false); - }); - - it('sets toolbar as saving when saving changes', () => { - buildContentLoadedStore({ - initialState: { - isSavingChanges: true, + it('does not render skeleton loader when content is not loading', () => { + buildApollo({ + sourceContent: { + loading: false, }, }); buildWrapper(); - expect(findPublishToolbar().props('savingChanges')).toBe(true); - }); - - it('displays invalid content message when content is not supported', () => { - buildWrapper({ appData: { isSupportedContent: false } }); - - expect(findInvalidContentMessage().exists()).toBe(true); + expect(findSkeletonLoader().exists()).toBe(false); }); describe('when submitting changes fail', () => { beforeEach(() => { - buildContentLoadedStore({ + buildStore({ initialState: { submitChangesError, }, @@ -228,24 +194,32 @@ describe('static_site_editor/pages/home', () => { }); }); - it('dispatches load content action', () => { - expect(loadContentActionMock).toHaveBeenCalled(); - }); - - it('dispatches setContent action when rich content editor emits input event', () => { - buildContentLoadedStore(); + it('does not display submit changes error when an error does not exist', () => { buildWrapper(); - findRichContentEditor().vm.$emit('input', sourceContent); + expect(findSubmitChangesError().exists()).toBe(false); + }); - expect(setContentActionMock).toHaveBeenCalledWith(expect.anything(), sourceContent, undefined); + it('displays invalid content message when content is not supported', () => { + buildWrapper({ appData: { isSupportedContent: false } }); + + expect(findInvalidContentMessage().exists()).toBe(true); }); - it('dispatches submitChanges action when toolbar emits submit event', () => { - buildContentLoadedStore(); - buildWrapper(); - findPublishToolbar().vm.$emit('submit'); + describe('when edit area emits submit event', () => { + const newContent = `new ${content}`; - expect(submitChangesActionMock).toHaveBeenCalled(); + beforeEach(() => { + buildWrapper({ sourceContent: { title, content } }); + findEditArea().vm.$emit('submit', { content: newContent }); + }); + + it('dispatches setContent property', () => { + expect(setContentActionMock).toHaveBeenCalledWith(expect.anything(), newContent, undefined); + }); + + it('dispatches submitChanges action', () => { + expect(submitChangesActionMock).toHaveBeenCalled(); + }); }); }); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js index 951ee3c9b3d..74c769f86a3 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js @@ -1,9 +1,10 @@ import Vuex from 'vuex'; import { shallowMount, createLocalVue } from '@vue/test-utils'; -import { GlButton, GlLoadingIcon, GlIcon, GlSearchBoxByType, GlLink } from '@gitlab/ui'; +import { GlButton, GlLoadingIcon, GlSearchBoxByType, GlLink } from '@gitlab/ui'; import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes'; import DropdownContentsLabelsView from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue'; +import LabelItem from '~/vue_shared/components/sidebar/labels_select_vue/label_item.vue'; import defaultState from '~/vue_shared/components/sidebar/labels_select_vue/store/state'; import mutations from '~/vue_shared/components/sidebar/labels_select_vue/store/mutations'; @@ -78,16 +79,6 @@ describe('DropdownContentsLabelsView', () => { }); describe('methods', () => { - describe('getDropdownLabelBoxStyle', () => { - it('returns an object containing `backgroundColor` based on provided `label` param', () => { - expect(wrapper.vm.getDropdownLabelBoxStyle(mockRegularLabel)).toEqual( - expect.objectContaining({ - backgroundColor: mockRegularLabel.color, - }), - ); - }); - }); - describe('isLabelSelected', () => { it('returns true when provided `label` param is one of the selected labels', () => { expect(wrapper.vm.isLabelSelected(mockRegularLabel)).toBe(true); @@ -234,16 +225,7 @@ describe('DropdownContentsLabelsView', () => { }); it('renders label elements for all labels', () => { - const labelsEl = wrapper.findAll('.dropdown-content li'); - const labelItemEl = labelsEl.at(0).find(GlLink); - - expect(labelsEl.length).toBe(mockLabels.length); - expect(labelItemEl.exists()).toBe(true); - expect(labelItemEl.find(GlIcon).props('name')).toBe('mobile-issue-close'); - expect(labelItemEl.find('.dropdown-label-box').attributes('style')).toBe( - 'background-color: rgb(186, 218, 85);', - ); - expect(labelItemEl.find(GlLink).text()).toContain(mockLabels[0].title); + expect(wrapper.findAll(LabelItem)).toHaveLength(mockLabels.length); }); it('renders label element with "is-focused" when value of `currentHighlightItem` is more than -1', () => { @@ -253,9 +235,9 @@ describe('DropdownContentsLabelsView', () => { return wrapper.vm.$nextTick(() => { const labelsEl = wrapper.findAll('.dropdown-content li'); - const labelItemEl = labelsEl.at(0).find(GlLink); + const labelItemEl = labelsEl.at(0).find(LabelItem); - expect(labelItemEl.attributes('class')).toContain('is-focused'); + expect(labelItemEl.props('highlight')).toBe(true); }); }); @@ -267,7 +249,7 @@ describe('DropdownContentsLabelsView', () => { return wrapper.vm.$nextTick(() => { const noMatchEl = wrapper.find('.dropdown-content li'); - expect(noMatchEl.exists()).toBe(true); + expect(noMatchEl.isVisible()).toBe(true); expect(noMatchEl.text()).toContain('No matching results'); }); }); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/label_item_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/label_item_spec.js new file mode 100644 index 00000000000..401d208da5c --- /dev/null +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/label_item_spec.js @@ -0,0 +1,111 @@ +import { shallowMount } from '@vue/test-utils'; + +import { GlIcon, GlLink } from '@gitlab/ui'; +import LabelItem from '~/vue_shared/components/sidebar/labels_select_vue/label_item.vue'; +import { mockRegularLabel } from './mock_data'; + +const createComponent = ({ label = mockRegularLabel, highlight = true } = {}) => + shallowMount(LabelItem, { + propsData: { + label, + highlight, + }, + }); + +describe('LabelItem', () => { + let wrapper; + + beforeEach(() => { + wrapper = createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('computed', () => { + describe('labelBoxStyle', () => { + it('returns an object containing `backgroundColor` based on `label` prop', () => { + expect(wrapper.vm.labelBoxStyle).toEqual( + expect.objectContaining({ + backgroundColor: mockRegularLabel.color, + }), + ); + }); + }); + }); + + describe('methods', () => { + describe('handleClick', () => { + it('sets value of `isSet` data prop to opposite of its current value', () => { + wrapper.setData({ + isSet: true, + }); + + wrapper.vm.handleClick(); + expect(wrapper.vm.isSet).toBe(false); + wrapper.vm.handleClick(); + expect(wrapper.vm.isSet).toBe(true); + }); + + it('emits event `clickLabel` on component with `label` prop as param', () => { + wrapper.vm.handleClick(); + + expect(wrapper.emitted('clickLabel')).toBeTruthy(); + expect(wrapper.emitted('clickLabel')[0]).toEqual([mockRegularLabel]); + }); + }); + }); + + describe('template', () => { + it('renders gl-link component', () => { + expect(wrapper.find(GlLink).exists()).toBe(true); + }); + + it('renders gl-link component with class `is-focused` when `highlight` prop is true', () => { + wrapper.setProps({ + highlight: true, + }); + + return wrapper.vm.$nextTick(() => { + expect(wrapper.find(GlLink).classes()).toContain('is-focused'); + }); + }); + + it('renders visible gl-icon component when `isSet` prop is true', () => { + wrapper.setData({ + isSet: true, + }); + + return wrapper.vm.$nextTick(() => { + const iconEl = wrapper.find(GlIcon); + + expect(iconEl.isVisible()).toBe(true); + expect(iconEl.props('name')).toBe('mobile-issue-close'); + }); + }); + + it('renders visible span element as placeholder instead of gl-icon when `isSet` prop is false', () => { + wrapper.setData({ + isSet: false, + }); + + return wrapper.vm.$nextTick(() => { + const placeholderEl = wrapper.find('[data-testid="no-icon"]'); + + expect(placeholderEl.isVisible()).toBe(true); + }); + }); + + it('renders label color element', () => { + const colorEl = wrapper.find('[data-testid="label-color-box"]'); + + expect(colorEl.exists()).toBe(true); + expect(colorEl.attributes('style')).toBe('background-color: rgb(186, 218, 85);'); + }); + + it('renders label title', () => { + expect(wrapper.text()).toContain(mockRegularLabel.title); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js index 3031df3362f..8081806e314 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js @@ -155,29 +155,12 @@ describe('LabelsSelect Mutations', () => { describe(`${types.UPDATE_SELECTED_LABELS}`, () => { const labels = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]; - it('updates `state.labels` to include `touched` and `set` props based on provided `labels` param when `state.allowMultiselect` is `true`', () => { - const updatedLabelIds = [2, 4]; - const state = { - labels, - allowMultiselect: true, - }; - mutations[types.UPDATE_SELECTED_LABELS](state, { labels }); - - state.labels.forEach(label => { - if (updatedLabelIds.includes(label.id)) { - expect(label.touched).toBe(true); - expect(label.set).toBe(true); - } - }); - }); - - it('updates `state.labels` to include `touched` and `set` props based on provided `labels` param when `state.allowMultiselect` is `false`', () => { + it('updates `state.labels` to include `touched` and `set` props based on provided `labels` param', () => { const updatedLabelIds = [2]; const state = { labels, - allowMultiselect: false, }; - mutations[types.UPDATE_SELECTED_LABELS](state, { labels }); + mutations[types.UPDATE_SELECTED_LABELS](state, { labels: [{ id: 2 }] }); state.labels.forEach(label => { if (updatedLabelIds.includes(label.id)) { diff --git a/spec/helpers/milestones_helper_spec.rb b/spec/helpers/milestones_helper_spec.rb index 3574066e03e..4ce7143bdf0 100644 --- a/spec/helpers/milestones_helper_spec.rb +++ b/spec/helpers/milestones_helper_spec.rb @@ -85,4 +85,19 @@ describe MilestonesHelper do end end end + + describe "#group_milestone_route" do + let(:group) { build_stubbed(:group) } + let(:subgroup) { build_stubbed(:group, parent: group, name: "Test Subgrp") } + + context "when in subgroup" do + let(:milestone) { build_stubbed(:group_milestone, group: subgroup) } + + it 'generates correct url despite assigned @group' do + assign(:group, group) + milestone_path = "/groups/#{subgroup.full_path}/-/milestones/#{milestone.iid}" + expect(helper.group_milestone_route(milestone)).to eq(milestone_path) + end + end + end end diff --git a/spec/lib/gitlab/background_migration/backfill_environment_id_deployment_merge_requests_spec.rb b/spec/lib/gitlab/background_migration/backfill_environment_id_deployment_merge_requests_spec.rb index c3bb975727b..34ac70071bb 100644 --- a/spec/lib/gitlab/background_migration/backfill_environment_id_deployment_merge_requests_spec.rb +++ b/spec/lib/gitlab/background_migration/backfill_environment_id_deployment_merge_requests_spec.rb @@ -32,7 +32,7 @@ describe Gitlab::BackgroundMigration::BackfillEnvironmentIdDeploymentMergeReques expect(deployment_merge_requests.where(environment_id: nil).count).to eq(3) - migration.perform(1, mr.id) + migration.backfill_range(1, mr.id) expect(deployment_merge_requests.where(environment_id: nil).count).to be_zero expect(deployment_merge_requests.count).to eq(2) diff --git a/spec/lib/gitlab/cycle_analytics/summary/value_spec.rb b/spec/lib/gitlab/cycle_analytics/summary/value_spec.rb new file mode 100644 index 00000000000..21ee42295cd --- /dev/null +++ b/spec/lib/gitlab/cycle_analytics/summary/value_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::CycleAnalytics::Summary::Value do + describe Gitlab::CycleAnalytics::Summary::Value::None do + it 'returns `-`' do + expect(described_class.new.to_s).to eq('-') + end + end + + describe Gitlab::CycleAnalytics::Summary::Value::Numeric do + it 'returns the string representation of the number' do + expect(described_class.new(3.2).to_s).to eq('3.2') + end + end + + describe Gitlab::CycleAnalytics::Summary::Value::PrettyNumeric do + describe '#to_s' do + it 'returns `-` when the number is 0' do + expect(described_class.new(0).to_s).to eq('-') + end + + it 'returns the string representation of the number' do + expect(described_class.new(100).to_s).to eq('100') + end + end + end +end diff --git a/spec/migrations/backfill_environment_id_on_deployment_merge_requests_spec.rb b/spec/migrations/backfill_environment_id_on_deployment_merge_requests_spec.rb deleted file mode 100644 index 296ae07cc21..00000000000 --- a/spec/migrations/backfill_environment_id_on_deployment_merge_requests_spec.rb +++ /dev/null @@ -1,81 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20200312134637_backfill_environment_id_on_deployment_merge_requests.rb') - -describe BackfillEnvironmentIdOnDeploymentMergeRequests do - let(:environments) { table(:environments) } - let(:merge_requests) { table(:merge_requests) } - let(:deployments) { table(:deployments) } - let(:deployment_merge_requests) { table(:deployment_merge_requests) } - let(:namespaces) { table(:namespaces) } - let(:projects) { table(:projects) } - - let(:migration_worker) { double('BackgroundMigrationWorker') } - - before do - stub_const('BackgroundMigrationWorker', migration_worker) - end - - it 'schedules nothing when there are no entries' do - expect(migration_worker).not_to receive(:perform_in) - - migrate! - end - - it 'batches the workload' do - stub_const("#{described_class.name}::BATCH_SIZE", 10) - - namespace = namespaces.create!(name: 'foo', path: 'foo') - project = projects.create!(namespace_id: namespace.id) - - environment = environments.create!(project_id: project.id, name: 'staging', slug: 'staging') - - # Batching is based on DeploymentMergeRequest.merge_request_id, in order to test it - # we must generate more than described_class::BATCH_SIZE merge requests, deployments, - # and deployment_merge_requests entries - entries = 13 - expect(entries).to be > described_class::BATCH_SIZE - - # merge requests and deployments bulk generation - mrs_params = [] - deployments_params = [] - entries.times do |i| - mrs_params << { source_branch: 'x', target_branch: 'master', target_project_id: project.id } - - deployments_params << { environment_id: environment.id, iid: i + 1, project_id: project.id, ref: 'master', tag: false, sha: '123abcdef', status: 1 } - end - - all_mrs = merge_requests.insert_all(mrs_params) - all_deployments = deployments.insert_all(deployments_params) - - # deployment_merge_requests bulk generation - dmr_params = [] - entries.times do |index| - mr_id = all_mrs.rows[index].first - deployment_id = all_deployments.rows[index].first - - dmr_params << { deployment_id: deployment_id, merge_request_id: mr_id } - end - - deployment_merge_requests.insert_all(dmr_params) - - first_batch_limit = dmr_params[described_class::BATCH_SIZE][:merge_request_id] - second_batch_limit = dmr_params.last[:merge_request_id] - - expect(migration_worker).to receive(:perform_in) - .with( - 0, - 'BackfillEnvironmentIdDeploymentMergeRequests', - [1, first_batch_limit] - ) - expect(migration_worker).to receive(:perform_in) - .with( - described_class::DELAY, - 'BackfillEnvironmentIdDeploymentMergeRequests', - [first_batch_limit + 1, second_batch_limit] - ) - - migrate! - end -end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 5fe0a9052cf..38b92f22faf 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -3836,40 +3836,28 @@ describe MergeRequest do end describe '#diffable_merge_ref?' do - context 'diff_compare_with_head enabled' do - context 'merge request can be merged' do - context 'merge_to_ref is not calculated' do - it 'returns true' do - expect(subject.diffable_merge_ref?).to eq(false) - end - end - - context 'merge_to_ref is calculated' do - before do - MergeRequests::MergeToRefService.new(subject.project, subject.author).execute(subject) - end - - it 'returns true' do - expect(subject.diffable_merge_ref?).to eq(true) - end + context 'merge request can be merged' do + context 'merge_to_ref is not calculated' do + it 'returns true' do + expect(subject.diffable_merge_ref?).to eq(false) end end - context 'merge request cannot be merged' do - it 'returns false' do - subject.mark_as_unchecked! + context 'merge_to_ref is calculated' do + before do + MergeRequests::MergeToRefService.new(subject.project, subject.author).execute(subject) + end - expect(subject.diffable_merge_ref?).to eq(false) + it 'returns true' do + expect(subject.diffable_merge_ref?).to eq(true) end end end - context 'diff_compare_with_head disabled' do - before do - stub_feature_flags(diff_compare_with_head: { enabled: false, thing: subject.target_project }) - end - + context 'merge request cannot be merged' do it 'returns false' do + subject.mark_as_unchecked! + expect(subject.diffable_merge_ref?).to eq(false) end end diff --git a/spec/models/sent_notification_spec.rb b/spec/models/sent_notification_spec.rb index fedaae372c4..087bc957373 100644 --- a/spec/models/sent_notification_spec.rb +++ b/spec/models/sent_notification_spec.rb @@ -326,4 +326,26 @@ describe SentNotification do end end end + + describe "#position=" do + subject { build(:sent_notification, noteable: create(:issue)) } + + it "doesn't accept non-hash JSON passed as a string" do + subject.position = "true" + + expect(subject.attributes_before_type_cast["position"]).to be(nil) + end + + it "does accept a position hash as a string" do + subject.position = '{ "base_sha": "test" }' + + expect(subject.position.base_sha).to eq("test") + end + + it "does accept a hash" do + subject.position = { "base_sha" => "test" } + + expect(subject.position.base_sha).to eq("test") + end + end end diff --git a/spec/policies/ci/build_policy_spec.rb b/spec/policies/ci/build_policy_spec.rb index 333f4e560cf..f29ed26f2aa 100644 --- a/spec/policies/ci/build_policy_spec.rb +++ b/spec/policies/ci/build_policy_spec.rb @@ -176,15 +176,21 @@ describe Ci::BuildPolicy do end context 'when developers can push to the branch' do - before do - create(:protected_branch, :developers_can_push, - name: build.ref, project: project) - end - context 'when the build was created by the developer' do let(:owner) { user } - it { expect(policy).to be_allowed :erase_build } + context 'when the build was created for a protected ref' do + before do + create(:protected_branch, :developers_can_push, + name: build.ref, project: project) + end + + it { expect(policy).to be_disallowed :erase_build } + end + + context 'when the build was created for an unprotected ref' do + it { expect(policy).to be_allowed :erase_build } + end end context 'when the build was created by the other' do diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index 5e8223ec3cc..f2dc5b1c045 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -630,7 +630,7 @@ describe API::Branches do post api(route, user), params: { branch: 'new_design3', ref: 'foo' } expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']).to eq('Invalid reference name: new_design3') + expect(json_response['message']).to eq('Invalid reference name: foo') end end diff --git a/spec/requests/api/graphql/mutations/branches/create_spec.rb b/spec/requests/api/graphql/mutations/branches/create_spec.rb index 406ab8959a2..8340f5a7db2 100644 --- a/spec/requests/api/graphql/mutations/branches/create_spec.rb +++ b/spec/requests/api/graphql/mutations/branches/create_spec.rb @@ -38,7 +38,7 @@ describe 'Creation of a new branch' do let(:ref) { 'unknown' } it_behaves_like 'a mutation that returns errors in the response', - errors: ['Invalid reference name: new_branch'] + errors: ['Invalid reference name: unknown'] end end end diff --git a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb index ce20d494542..3cb1dbbbc2c 100644 --- a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb +++ b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb @@ -159,6 +159,17 @@ describe RuboCop::Cop::InjectEnterpriseEditionModule do SOURCE end + it 'does not flag the double use of `X_if_ee` on the last line' do + expect_no_offenses(<<~SOURCE) + class Foo + end + + Foo.extend_if_ee('EE::Foo') + Foo.include_if_ee('EE::Foo') + Foo.prepend_if_ee('EE::Foo') + SOURCE + end + it 'autocorrects offenses by just disabling the Cop' do source = <<~SOURCE class Foo diff --git a/spec/services/branches/create_service_spec.rb b/spec/services/branches/create_service_spec.rb index b0629c5e25a..072a86d17fc 100644 --- a/spec/services/branches/create_service_spec.rb +++ b/spec/services/branches/create_service_spec.rb @@ -3,39 +3,45 @@ require 'spec_helper' describe Branches::CreateService do - let(:user) { create(:user) } - subject(:service) { described_class.new(project, user) } + let_it_be(:project) { create(:project_empty_repo) } + let_it_be(:user) { create(:user) } + describe '#execute' do context 'when repository is empty' do - let(:project) { create(:project_empty_repo) } - it 'creates master branch' do service.execute('my-feature', 'master') expect(project.repository.branch_exists?('master')).to be_truthy end - it 'creates my-feature branch' do - service.execute('my-feature', 'master') + it 'creates another-feature branch' do + service.execute('another-feature', 'master') - expect(project.repository.branch_exists?('my-feature')).to be_truthy + expect(project.repository.branch_exists?('another-feature')).to be_truthy end end - context 'when creating a branch fails' do - let(:project) { create(:project_empty_repo) } + context 'when branch already exists' do + it 'returns an error' do + result = service.execute('master', 'master') + + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq('Branch already exists') + end + end + context 'when incorrect reference is provided' do before do allow(project.repository).to receive(:add_branch).and_return(false) end - it 'returns an error with the branch name' do - result = service.execute('my-feature', 'master') + it 'returns an error with a reference name' do + result = service.execute('new-feature', 'unknown') expect(result[:status]).to eq(:error) - expect(result[:message]).to eq("Invalid reference name: my-feature") + expect(result[:message]).to eq('Invalid reference name: unknown') end end end |