diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-30 00:09:22 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-30 00:09:22 +0300 |
commit | 27d314277bfe7fffec215efa9b1833a23bb82940 (patch) | |
tree | 898c606409718e70579beea62174624f84e28629 /spec | |
parent | 6b9d3a4e8351e662c4586b24bb152de78ae9e3bf (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r-- | spec/features/milestones/user_views_milestone_spec.rb | 31 | ||||
-rw-r--r-- | spec/frontend/lib/utils/datetime_range_spec.js | 231 | ||||
-rw-r--r-- | spec/javascripts/pipelines/header_component_spec.js | 55 | ||||
-rw-r--r-- | spec/javascripts/vue_shared/components/header_ci_component_spec.js | 37 | ||||
-rw-r--r-- | spec/migrations/add_deploy_token_type_to_deploy_tokens_spec.rb | 24 | ||||
-rw-r--r-- | spec/models/concerns/milestoneish_spec.rb | 17 | ||||
-rw-r--r-- | spec/models/deploy_token_spec.rb | 2 |
7 files changed, 316 insertions, 81 deletions
diff --git a/spec/features/milestones/user_views_milestone_spec.rb b/spec/features/milestones/user_views_milestone_spec.rb index b1c2a87ef94..71abb195ad1 100644 --- a/spec/features/milestones/user_views_milestone_spec.rb +++ b/spec/features/milestones/user_views_milestone_spec.rb @@ -25,37 +25,6 @@ describe "User views milestone" do expect { visit_milestone }.not_to exceed_query_limit(control) end - context 'limiting milestone issues' do - before_all do - 2.times do - create(:issue, milestone: milestone, project: project) - create(:issue, milestone: milestone, project: project, assignees: [user]) - create(:issue, milestone: milestone, project: project, state: :closed) - end - end - - context 'when issues on milestone are over DISPLAY_ISSUES_LIMIT' do - it "limits issues to display and shows warning" do - stub_const('Milestoneish::DISPLAY_ISSUES_LIMIT', 3) - - visit(project_milestone_path(project, milestone)) - - expect(page).to have_selector('.issuable-row', count: 3) - expect(page).to have_selector('#milestone-issue-count-warning', text: 'Showing 3 of 6 issues. View all issues') - expect(page).to have_link('View all issues', href: project_issues_path(project)) - end - end - - context 'when issues on milestone are below DISPLAY_ISSUES_LIMIT' do - it 'does not display warning' do - visit(project_milestone_path(project, milestone)) - - expect(page).not_to have_selector('#milestone-issue-count-warning', text: 'Showing 3 of 6 issues. View all issues') - expect(page).to have_selector('.issuable-row', count: 6) - end - end - end - private def visit_milestone diff --git a/spec/frontend/lib/utils/datetime_range_spec.js b/spec/frontend/lib/utils/datetime_range_spec.js new file mode 100644 index 00000000000..13eb69e1761 --- /dev/null +++ b/spec/frontend/lib/utils/datetime_range_spec.js @@ -0,0 +1,231 @@ +import _ from 'lodash'; +import { getRangeType, convertToFixedRange } from '~/lib/utils/datetime_range'; + +const MOCK_NOW = Date.UTC(2020, 0, 23, 20); + +const MOCK_NOW_ISO_STRING = new Date(MOCK_NOW).toISOString(); + +describe('Date time range utils', () => { + describe('getRangeType', () => { + it('infers correctly the range type from the input object', () => { + const rangeTypes = { + fixed: [{ start: MOCK_NOW_ISO_STRING, end: MOCK_NOW_ISO_STRING }], + anchored: [{ anchor: MOCK_NOW_ISO_STRING, duration: { seconds: 0 } }], + rolling: [{ duration: { seconds: 0 } }], + open: [{ anchor: MOCK_NOW_ISO_STRING }], + invalid: [ + {}, + { start: MOCK_NOW_ISO_STRING }, + { end: MOCK_NOW_ISO_STRING }, + { start: 'NOT_A_DATE', end: 'NOT_A_DATE' }, + { duration: { seconds: 'NOT_A_NUMBER' } }, + { duration: { seconds: Infinity } }, + { duration: { minutes: 20 } }, + { anchor: MOCK_NOW_ISO_STRING, duration: { seconds: 'NOT_A_NUMBER' } }, + { anchor: MOCK_NOW_ISO_STRING, duration: { seconds: Infinity } }, + { junk: 'exists' }, + ], + }; + + Object.entries(rangeTypes).forEach(([type, examples]) => { + examples.forEach(example => expect(getRangeType(example)).toEqual(type)); + }); + }); + }); + + describe('convertToFixedRange', () => { + beforeEach(() => { + jest.spyOn(Date, 'now').mockImplementation(() => MOCK_NOW); + }); + + afterEach(() => { + Date.now.mockRestore(); + }); + + describe('When a fixed range is input', () => { + const defaultFixedRange = { + start: '2020-01-01T00:00:00.000Z', + end: '2020-01-31T23:59:00.000Z', + label: 'January 2020', + }; + + const mockFixedRange = params => ({ ...defaultFixedRange, ...params }); + + it('converts a fixed range to an equal fixed range', () => { + const aFixedRange = mockFixedRange(); + + expect(convertToFixedRange(aFixedRange)).toEqual({ + start: defaultFixedRange.start, + end: defaultFixedRange.end, + }); + }); + + it('throws an error when fixed range does not contain an end time', () => { + const aFixedRangeMissingEnd = _.omit(mockFixedRange(), 'end'); + + expect(() => convertToFixedRange(aFixedRangeMissingEnd)).toThrow(); + }); + + it('throws an error when fixed range does not contain a start time', () => { + const aFixedRangeMissingStart = _.omit(mockFixedRange(), 'start'); + + expect(() => convertToFixedRange(aFixedRangeMissingStart)).toThrow(); + }); + + it('throws an error when the dates cannot be parsed', () => { + const wrongStart = mockFixedRange({ start: 'I_CANNOT_BE_PARSED' }); + const wrongEnd = mockFixedRange({ end: 'I_CANNOT_BE_PARSED' }); + + expect(() => convertToFixedRange(wrongStart)).toThrow(); + expect(() => convertToFixedRange(wrongEnd)).toThrow(); + }); + }); + + describe('When an anchored range is input', () => { + const defaultAnchoredRange = { + anchor: '2020-01-01T00:00:00.000Z', + direction: 'after', + duration: { + seconds: 60 * 2, + }, + label: 'First two minutes of 2020', + }; + const mockAnchoredRange = params => ({ ...defaultAnchoredRange, ...params }); + + it('converts to a fixed range', () => { + const anAnchoredRange = mockAnchoredRange(); + + expect(convertToFixedRange(anAnchoredRange)).toEqual({ + start: '2020-01-01T00:00:00.000Z', + end: '2020-01-01T00:02:00.000Z', + }); + }); + + it('converts to a fixed range with a `before` direction', () => { + const anAnchoredRange = mockAnchoredRange({ direction: 'before' }); + + expect(convertToFixedRange(anAnchoredRange)).toEqual({ + start: '2019-12-31T23:58:00.000Z', + end: '2020-01-01T00:00:00.000Z', + }); + }); + + it('converts to a fixed range without an explicit direction, defaulting to `before`', () => { + const anAnchoredRange = _.omit(mockAnchoredRange(), 'direction'); + + expect(convertToFixedRange(anAnchoredRange)).toEqual({ + start: '2019-12-31T23:58:00.000Z', + end: '2020-01-01T00:00:00.000Z', + }); + }); + + it('throws an error when the anchor cannot be parsed', () => { + const wrongAnchor = mockAnchoredRange({ anchor: 'I_CANNOT_BE_PARSED' }); + expect(() => convertToFixedRange(wrongAnchor)).toThrow(); + }); + }); + + describe('when a rolling range is input', () => { + it('converts to a fixed range', () => { + const aRollingRange = { + direction: 'after', + duration: { + seconds: 60 * 2, + }, + label: 'Next 2 minutes', + }; + + expect(convertToFixedRange(aRollingRange)).toEqual({ + start: '2020-01-23T20:00:00.000Z', + end: '2020-01-23T20:02:00.000Z', + }); + }); + + it('converts to a fixed range with an implicit `before` direction', () => { + const aRollingRangeWithNoDirection = { + duration: { + seconds: 60 * 2, + }, + label: 'Last 2 minutes', + }; + + expect(convertToFixedRange(aRollingRangeWithNoDirection)).toEqual({ + start: '2020-01-23T19:58:00.000Z', + end: '2020-01-23T20:00:00.000Z', + }); + }); + + it('throws an error when the duration is not in the right format', () => { + const wrongDuration = { + direction: 'before', + duration: { + minutes: 20, + }, + label: 'Last 20 minutes', + }; + + expect(() => convertToFixedRange(wrongDuration)).toThrow(); + }); + + it('throws an error when the anchor is not valid', () => { + const wrongAnchor = { + anchor: 'CAN_T_PARSE_THIS', + direction: 'after', + label: '2020 so far', + }; + + expect(() => convertToFixedRange(wrongAnchor)).toThrow(); + }); + }); + + describe('when an open range is input', () => { + it('converts to a fixed range with an `after` direction', () => { + const soFar2020 = { + anchor: '2020-01-01T00:00:00.000Z', + direction: 'after', + label: '2020 so far', + }; + + expect(convertToFixedRange(soFar2020)).toEqual({ + start: '2020-01-01T00:00:00.000Z', + end: '2020-01-23T20:00:00.000Z', + }); + }); + + it('converts to a fixed range with the explicit `before` direction', () => { + const before2020 = { + anchor: '2020-01-01T00:00:00.000Z', + direction: 'before', + label: 'Before 2020', + }; + + expect(convertToFixedRange(before2020)).toEqual({ + start: '1970-01-01T00:00:00.000Z', + end: '2020-01-01T00:00:00.000Z', + }); + }); + + it('converts to a fixed range with the implicit `before` direction', () => { + const alsoBefore2020 = { + anchor: '2020-01-01T00:00:00.000Z', + label: 'Before 2020', + }; + + expect(convertToFixedRange(alsoBefore2020)).toEqual({ + start: '1970-01-01T00:00:00.000Z', + end: '2020-01-01T00:00:00.000Z', + }); + }); + + it('throws an error when the anchor cannot be parsed', () => { + const wrongAnchor = { + anchor: 'CAN_T_PARSE_THIS', + direction: 'after', + label: '2020 so far', + }; + + expect(() => convertToFixedRange(wrongAnchor)).toThrow(); + }); + }); + }); +}); diff --git a/spec/javascripts/pipelines/header_component_spec.js b/spec/javascripts/pipelines/header_component_spec.js index 8c033447ce4..9043f30397d 100644 --- a/spec/javascripts/pipelines/header_component_spec.js +++ b/spec/javascripts/pipelines/header_component_spec.js @@ -8,6 +8,7 @@ describe('Pipeline details header', () => { let props; beforeEach(() => { + spyOn(eventHub, '$emit'); HeaderComponent = Vue.extend(headerComponent); const threeWeeksAgo = new Date(); @@ -33,8 +34,9 @@ describe('Pipeline details header', () => { email: 'foo@bar.com', avatar_url: 'link', }, - retry_path: 'path', - delete_path: 'path', + retry_path: 'retry', + cancel_path: 'cancel', + delete_path: 'delete', }, isLoading: false, }; @@ -43,9 +45,14 @@ describe('Pipeline details header', () => { }); afterEach(() => { + eventHub.$off(); vm.$destroy(); }); + const findDeleteModal = () => document.getElementById(headerComponent.DELETE_MODAL_ID); + const findDeleteModalSubmit = () => + [...findDeleteModal().querySelectorAll('.btn')].find(x => x.textContent === 'Delete pipeline'); + it('should render provided pipeline info', () => { expect( vm.$el @@ -56,22 +63,46 @@ describe('Pipeline details header', () => { }); describe('action buttons', () => { - it('should call postAction when retry button action is clicked', done => { - eventHub.$on('headerPostAction', action => { - expect(action.path).toEqual('path'); - done(); - }); + it('should not trigger eventHub when nothing happens', () => { + expect(eventHub.$emit).not.toHaveBeenCalled(); + }); + it('should call postAction when retry button action is clicked', () => { vm.$el.querySelector('.js-retry-button').click(); + + expect(eventHub.$emit).toHaveBeenCalledWith('headerPostAction', 'retry'); + }); + + it('should call postAction when cancel button action is clicked', () => { + vm.$el.querySelector('.js-btn-cancel-pipeline').click(); + + expect(eventHub.$emit).toHaveBeenCalledWith('headerPostAction', 'cancel'); }); - it('should fire modal event when delete button action is clicked', done => { - vm.$root.$on('bv::modal::show', action => { - expect(action.componentId).toEqual('pipeline-delete-modal'); - done(); + it('does not show delete modal', () => { + expect(findDeleteModal()).not.toBeVisible(); + }); + + describe('when delete button action is clicked', () => { + beforeEach(done => { + vm.$el.querySelector('.js-btn-delete-pipeline').click(); + + // Modal needs two ticks to show + vm.$nextTick() + .then(() => vm.$nextTick()) + .then(done) + .catch(done.fail); }); - vm.$el.querySelector('.js-btn-delete-pipeline').click(); + it('should show delete modal', () => { + expect(findDeleteModal()).toBeVisible(); + }); + + it('should call delete when modal is submitted', () => { + findDeleteModalSubmit().click(); + + expect(eventHub.$emit).toHaveBeenCalledWith('headerDeleteAction', 'delete'); + }); }); }); }); diff --git a/spec/javascripts/vue_shared/components/header_ci_component_spec.js b/spec/javascripts/vue_shared/components/header_ci_component_spec.js index ea2eed2886a..b1abc972e1d 100644 --- a/spec/javascripts/vue_shared/components/header_ci_component_spec.js +++ b/spec/javascripts/vue_shared/components/header_ci_component_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import mountComponent, { mountComponentWithSlots } from 'spec/helpers/vue_mount_component_helper'; import headerCi from '~/vue_shared/components/header_ci_component.vue'; describe('Header CI Component', () => { @@ -27,14 +27,6 @@ describe('Header CI Component', () => { email: 'foo@bar.com', avatar_url: 'link', }, - actions: [ - { - label: 'Retry', - path: 'path', - cssClass: 'btn', - isLoading: false, - }, - ], hasSidebarButton: true, }; }); @@ -43,6 +35,8 @@ describe('Header CI Component', () => { vm.$destroy(); }); + const findActionButtons = () => vm.$el.querySelector('.header-action-buttons'); + describe('render', () => { beforeEach(() => { vm = mountComponent(HeaderCi, props); @@ -68,24 +62,23 @@ describe('Header CI Component', () => { expect(vm.$el.querySelector('.js-user-link').innerText.trim()).toContain(props.user.name); }); - it('should render provided actions', () => { - const btn = vm.$el.querySelector('.btn'); + it('should render sidebar toggle button', () => { + expect(vm.$el.querySelector('.js-sidebar-build-toggle')).not.toBeNull(); + }); - expect(btn.tagName).toEqual('BUTTON'); - expect(btn.textContent.trim()).toEqual(props.actions[0].label); + it('should not render header action buttons when empty', () => { + expect(findActionButtons()).toBeNull(); }); + }); - it('should show loading icon', done => { - vm.actions[0].isLoading = true; + describe('slot', () => { + it('should render header action buttons', () => { + vm = mountComponentWithSlots(HeaderCi, { props, slots: { default: 'Test Actions' } }); - Vue.nextTick(() => { - expect(vm.$el.querySelector('.btn .gl-spinner').getAttribute('style')).toBeFalsy(); - done(); - }); - }); + const buttons = findActionButtons(); - it('should render sidebar toggle button', () => { - expect(vm.$el.querySelector('.js-sidebar-build-toggle')).not.toBeNull(); + expect(buttons).not.toBeNull(); + expect(buttons.textContent).toEqual('Test Actions'); }); }); diff --git a/spec/migrations/add_deploy_token_type_to_deploy_tokens_spec.rb b/spec/migrations/add_deploy_token_type_to_deploy_tokens_spec.rb new file mode 100644 index 00000000000..fb8213a6bd6 --- /dev/null +++ b/spec/migrations/add_deploy_token_type_to_deploy_tokens_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20200122161638_add_deploy_token_type_to_deploy_tokens.rb') + +describe AddDeployTokenTypeToDeployTokens, :migration do + let(:deploy_tokens) { table(:deploy_tokens) } + let(:deploy_token) do + deploy_tokens.create(name: 'token_test', + username: 'gitlab+deploy-token-1', + token_encrypted: 'dr8rPXwM+Mbs2p3Bg1+gpnXqrnH/wu6vaHdcc7A3isPR67WB', + read_repository: true, + expires_at: Time.now + 1.year) + end + + it 'updates the deploy_token_type column to 2' do + expect(deploy_token).not_to respond_to(:deploy_token_type) + + migrate! + + deploy_token.reload + expect(deploy_token.deploy_token_type).to eq(2) + end +end diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb index e39cbedde68..d46c9747845 100644 --- a/spec/models/concerns/milestoneish_spec.rb +++ b/spec/models/concerns/milestoneish_spec.rb @@ -33,32 +33,17 @@ describe Milestone, 'Milestoneish' do end describe '#sorted_issues' do - before do + it 'sorts issues by label priority' do issue.labels << label_1 security_issue_1.labels << label_2 closed_issue_1.labels << label_3 - end - it 'sorts issues by label priority' do issues = milestone.sorted_issues(member) expect(issues.first).to eq(issue) expect(issues.second).to eq(security_issue_1) expect(issues.third).not_to eq(closed_issue_1) end - - it 'limits issue count' do - stub_const('Milestoneish::DISPLAY_ISSUES_LIMIT', 4) - - issues = milestone.sorted_issues(member) - - # Cannot use issues.count here because it is sorting - # by a virtual column 'highest_priority' and it will break - # the query. - total_issues_count = issues.opened.unassigned.length + issues.opened.assigned.length + issues.closed.length - expect(issues.length).to eq(4) - expect(total_issues_count).to eq(4) - end end context 'attributes visibility' do diff --git a/spec/models/deploy_token_spec.rb b/spec/models/deploy_token_spec.rb index 8d951ab6f0f..5c14d57cf18 100644 --- a/spec/models/deploy_token_spec.rb +++ b/spec/models/deploy_token_spec.rb @@ -8,6 +8,8 @@ describe DeployToken do it { is_expected.to have_many :project_deploy_tokens } it { is_expected.to have_many(:projects).through(:project_deploy_tokens) } + it_behaves_like 'having unique enum values' + describe 'validations' do let(:username_format_message) { "can contain only letters, digits, '_', '-', '+', and '.'" } |