diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-17 09:12:03 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-17 09:12:03 +0300 |
commit | a17e8b168778e5a107928671fd2829ab284e612d (patch) | |
tree | 604569b4f05927a8a5d4871bd15af411149907a3 /spec | |
parent | 493d6f7512116b4cae9b37bbb6535f4492f6a4e6 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
8 files changed, 297 insertions, 127 deletions
diff --git a/spec/frontend/issuable/related_issues/components/related_issues_block_spec.js b/spec/frontend/issuable/related_issues/components/related_issues_block_spec.js index b7385eddcc9..772cc75a205 100644 --- a/spec/frontend/issuable/related_issues/components/related_issues_block_spec.js +++ b/spec/frontend/issuable/related_issues/components/related_issues_block_spec.js @@ -20,8 +20,7 @@ describe('RelatedIssuesBlock', () => { const findToggleButton = () => wrapper.findByTestId('toggle-links'); const findRelatedIssuesBody = () => wrapper.findByTestId('related-issues-body'); - const findIssueCountBadgeAddButton = () => - wrapper.find('[data-testid="related-issues-plus-button"]'); + const findIssueCountBadgeAddButton = () => wrapper.findByTestId('related-issues-plus-button'); afterEach(() => { if (wrapper) { @@ -240,6 +239,7 @@ describe('RelatedIssuesBlock', () => { wrapper = shallowMountExtended(RelatedIssuesBlock, { propsData: { pathIdSeparator: PathIdSeparator.Issue, + relatedIssues: [issuable1, issuable2, issuable3], issuableType: issuableTypesMap.ISSUE, }, }); @@ -247,6 +247,7 @@ describe('RelatedIssuesBlock', () => { it('is expanded by default', () => { expect(findToggleButton().props('icon')).toBe('chevron-lg-up'); + expect(findToggleButton().props('disabled')).toBe(false); expect(findRelatedIssuesBody().exists()).toBe(true); }); @@ -258,4 +259,16 @@ describe('RelatedIssuesBlock', () => { expect(findRelatedIssuesBody().exists()).toBe(false); }); }); + + it('toggle button is disabled when issue has no related items', () => { + wrapper = shallowMountExtended(RelatedIssuesBlock, { + propsData: { + pathIdSeparator: PathIdSeparator.Issue, + relatedIssues: [], + issuableType: 'issue', + }, + }); + + expect(findToggleButton().props('disabled')).toBe(true); + }); }); diff --git a/spec/frontend/issues/show/components/incidents/create_timeline_events_form_spec.js b/spec/frontend/issues/show/components/incidents/create_timeline_events_form_spec.js new file mode 100644 index 00000000000..3ab2bb3460b --- /dev/null +++ b/spec/frontend/issues/show/components/incidents/create_timeline_events_form_spec.js @@ -0,0 +1,189 @@ +import VueApollo from 'vue-apollo'; +import Vue from 'vue'; +import { GlDatepicker } from '@gitlab/ui'; +import { __, s__ } from '~/locale'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import CreateTimelineEvent from '~/issues/show/components/incidents/create_timeline_event.vue'; +import TimelineEventsForm from '~/issues/show/components/incidents/timeline_events_form.vue'; +import createTimelineEventMutation from '~/issues/show/components/incidents/graphql/queries/create_timeline_event.mutation.graphql'; +import getTimelineEvents from '~/issues/show/components/incidents/graphql/queries/get_timeline_events.query.graphql'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { createAlert } from '~/flash'; +import { useFakeDate } from 'helpers/fake_date'; +import { + timelineEventsCreateEventResponse, + timelineEventsCreateEventError, + mockGetTimelineData, +} from './mock_data'; + +Vue.use(VueApollo); + +jest.mock('~/flash'); + +const fakeDate = '2020-07-08T00:00:00.000Z'; + +const mockInputData = { + incidentId: 'gid://gitlab/Issue/1', + note: 'test', + occurredAt: '2020-07-08T00:00:00.000Z', +}; + +describe('Create Timeline events', () => { + useFakeDate(fakeDate); + let wrapper; + let responseSpy; + let apolloProvider; + + const findSubmitButton = () => wrapper.findByText(__('Save')); + const findSubmitAndAddButton = () => + wrapper.findByText(s__('Incident|Save and add another event')); + const findCancelButton = () => wrapper.findByText(__('Cancel')); + const findDatePicker = () => wrapper.findComponent(GlDatepicker); + const findNoteInput = () => wrapper.findByTestId('input-note'); + const setNoteInput = () => { + const textarea = findNoteInput().element; + textarea.value = mockInputData.note; + textarea.dispatchEvent(new Event('input')); + }; + const findHourInput = () => wrapper.findByTestId('input-hours'); + const findMinuteInput = () => wrapper.findByTestId('input-minutes'); + const setDatetime = () => { + const inputDate = new Date(mockInputData.occurredAt); + findDatePicker().vm.$emit('input', inputDate); + findHourInput().vm.$emit('input', inputDate.getHours()); + findMinuteInput().vm.$emit('input', inputDate.getMinutes()); + }; + const fillForm = () => { + setDatetime(); + setNoteInput(); + }; + + function createMockApolloProvider() { + const requestHandlers = [[createTimelineEventMutation, responseSpy]]; + const mockApollo = createMockApollo(requestHandlers); + + mockApollo.clients.defaultClient.cache.writeQuery({ + query: getTimelineEvents, + data: mockGetTimelineData, + variables: { + fullPath: 'group/project', + incidentId: 'gid://gitlab/Issue/1', + }, + }); + + return mockApollo; + } + + const mountComponent = () => { + wrapper = mountExtended(CreateTimelineEvent, { + propsData: { + hasTimelineEvents: true, + }, + provide: { + fullPath: 'group/project', + issuableId: '1', + }, + apolloProvider, + }); + }; + + beforeEach(() => { + responseSpy = jest.fn().mockResolvedValue(timelineEventsCreateEventResponse); + apolloProvider = createMockApolloProvider(); + }); + + afterEach(() => { + createAlert.mockReset(); + wrapper.destroy(); + }); + + describe('createIncidentTimelineEvent', () => { + const closeFormEvent = { 'hide-new-timeline-events-form': [[]] }; + + const expectedData = { + input: mockInputData, + }; + + beforeEach(() => { + mountComponent(); + fillForm(); + }); + + describe('on submit', () => { + beforeEach(async () => { + findSubmitButton().trigger('click'); + await waitForPromises(); + }); + + it('should call the mutation with the right variables', () => { + expect(responseSpy).toHaveBeenCalledWith(expectedData); + }); + + it('should close the form on successful addition', () => { + expect(wrapper.emitted()).toEqual(closeFormEvent); + }); + }); + + describe('on submit and add', () => { + beforeEach(async () => { + findSubmitAndAddButton().trigger('click'); + await waitForPromises(); + }); + + it('should keep the form open for save and add another', () => { + expect(wrapper.emitted()).toEqual({}); + }); + }); + + describe('on cancel', () => { + beforeEach(async () => { + findCancelButton().trigger('click'); + await waitForPromises(); + }); + + it('should close the form', () => { + expect(wrapper.emitted()).toEqual(closeFormEvent); + }); + }); + }); + + describe('error handling', () => { + it('should show an error when submission returns an error', async () => { + const expectedAlertArgs = { + message: `Error creating incident timeline event: ${timelineEventsCreateEventError.data.timelineEventCreate.errors[0]}`, + }; + responseSpy.mockResolvedValueOnce(timelineEventsCreateEventError); + mountComponent(); + + findSubmitButton().trigger('click'); + await waitForPromises(); + + expect(createAlert).toHaveBeenCalledWith(expectedAlertArgs); + }); + + it('should show an error when submission fails', async () => { + const expectedAlertArgs = { + captureError: true, + error: new Error(), + message: 'Something went wrong while creating the incident timeline event.', + }; + responseSpy.mockRejectedValueOnce(); + mountComponent(); + + findSubmitButton().trigger('click'); + await waitForPromises(); + + expect(createAlert).toHaveBeenCalledWith(expectedAlertArgs); + }); + + it('should keep the form open on failed addition', async () => { + responseSpy.mockResolvedValueOnce(timelineEventsCreateEventError); + mountComponent(); + + await wrapper.findComponent(TimelineEventsForm).vm.$emit('save-event', mockInputData); + await waitForPromises; + expect(wrapper.emitted()).toEqual({}); + }); + }); +}); diff --git a/spec/frontend/issues/show/components/incidents/mock_data.js b/spec/frontend/issues/show/components/incidents/mock_data.js index afc6099caf4..75c0a7350ae 100644 --- a/spec/frontend/issues/show/components/incidents/mock_data.js +++ b/spec/frontend/issues/show/components/incidents/mock_data.js @@ -72,10 +72,14 @@ export const timelineEventsQueryEmptyResponse = { }; export const timelineEventsCreateEventResponse = { - timelineEvent: { - ...mockEvents[0], + data: { + timelineEventCreate: { + timelineEvent: { + ...mockEvents[0], + }, + errors: [], + }, }, - errors: [], }; export const timelineEventsCreateEventError = { @@ -103,3 +107,21 @@ const timelineEventDeleteData = (errors = []) => { export const timelineEventsDeleteEventResponse = timelineEventDeleteData(); export const timelineEventsDeleteEventError = timelineEventDeleteData(['Item does not exist']); + +export const mockGetTimelineData = { + project: { + id: 'gid://gitlab/Project/19', + incidentManagementTimelineEvents: { + nodes: [ + { + id: 'gid://gitlab/IncidentManagement::TimelineEvent/8', + note: 'another one2', + noteHtml: '<p>another one2</p>', + action: 'comment', + occurredAt: '2022-07-01T12:47:00Z', + createdAt: '2022-07-20T12:47:40Z', + }, + ], + }, + }, +}; diff --git a/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js b/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js index 620cdfc53b0..cd2cbb63246 100644 --- a/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js +++ b/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js @@ -3,49 +3,33 @@ import Vue, { nextTick } from 'vue'; import { GlDatepicker } from '@gitlab/ui'; import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import IncidentTimelineEventForm from '~/issues/show/components/incidents/timeline_events_form.vue'; -import createTimelineEventMutation from '~/issues/show/components/incidents/graphql/queries/create_timeline_event.mutation.graphql'; -import createMockApollo from 'helpers/mock_apollo_helper'; +import TimelineEventsForm from '~/issues/show/components/incidents/timeline_events_form.vue'; import { createAlert } from '~/flash'; import { useFakeDate } from 'helpers/fake_date'; -import { timelineEventsCreateEventResponse, timelineEventsCreateEventError } from './mock_data'; Vue.use(VueApollo); jest.mock('~/flash'); -const addEventResponse = jest.fn().mockResolvedValue(timelineEventsCreateEventResponse); - -function createMockApolloProvider(response = addEventResponse) { - const requestHandlers = [[createTimelineEventMutation, response]]; - return createMockApollo(requestHandlers); -} +const fakeDate = '2020-07-08T00:00:00.000Z'; describe('Timeline events form', () => { // July 8 2020 - useFakeDate(2020, 6, 8); + useFakeDate(fakeDate); let wrapper; - const mountComponent = ({ mockApollo, mountMethod = shallowMountExtended, stubs }) => { - wrapper = mountMethod(IncidentTimelineEventForm, { + const mountComponent = ({ mountMethod = shallowMountExtended }) => { + wrapper = mountMethod(TimelineEventsForm, { propsData: { hasTimelineEvents: true, + isEventProcessed: false, }, - provide: { - fullPath: 'group/project', - issuableId: '1', - }, - apolloProvider: mockApollo, - stubs, }); }; afterEach(() => { - addEventResponse.mockReset(); createAlert.mockReset(); - if (wrapper) { - wrapper.destroy(); - } + wrapper.destroy(); }); const findSubmitButton = () => wrapper.findByText('Save'); @@ -75,24 +59,28 @@ describe('Timeline events form', () => { }; describe('form button behaviour', () => { - const closeFormEvent = { 'hide-incident-timeline-event-form': [[]] }; beforeEach(() => { - mountComponent({ mockApollo: createMockApolloProvider(), mountMethod: mountExtended }); + mountComponent({ mountMethod: mountExtended }); }); - it('should close the form on submit', async () => { + it('should save event on submit', async () => { await submitForm(); - expect(wrapper.emitted()).toEqual(closeFormEvent); + + expect(wrapper.emitted()).toEqual({ + 'save-event': [[{ note: '', occurredAt: fakeDate }, false]], + }); }); - it('should not close the form on "submit and add another"', async () => { + it('should save event on "submit and add another"', async () => { await submitFormAndAddAnother(); - expect(wrapper.emitted()).toEqual({}); + expect(wrapper.emitted()).toEqual({ + 'save-event': [[{ note: '', occurredAt: fakeDate }, true]], + }); }); - it('should close the form on cancel', async () => { + it('should emit cancel on cancel', async () => { await cancelForm(); - expect(wrapper.emitted()).toEqual(closeFormEvent); + expect(wrapper.emitted()).toEqual({ cancel: [[]] }); }); it('should clear the form', async () => { @@ -111,71 +99,4 @@ describe('Timeline events form', () => { expect(findMinuteInput().element.value).toBe('0'); }); }); - - describe('addTimelineEventQuery', () => { - const expectedData = { - input: { - incidentId: 'gid://gitlab/Issue/1', - note: '', - occurredAt: '2020-07-08T00:00:00.000Z', - }, - }; - - let mockApollo; - - beforeEach(() => { - mockApollo = createMockApolloProvider(); - mountComponent({ mockApollo, mountMethod: mountExtended }); - }); - - it('should call the mutation with the right variables', async () => { - await submitForm(); - - expect(addEventResponse).toHaveBeenCalledWith(expectedData); - }); - - it('should call the mutation with user selected variables', async () => { - const expectedUserSelectedData = { - input: { - ...expectedData.input, - occurredAt: '2021-08-12T05:45:00.000Z', - }, - }; - - setDatetime(); - - await nextTick(); - await submitForm(); - - expect(addEventResponse).toHaveBeenCalledWith(expectedUserSelectedData); - }); - }); - - describe('error handling', () => { - it('should show an error when submission returns an error', async () => { - const expectedAlertArgs = { - message: 'Error creating incident timeline event: Create error', - }; - addEventResponse.mockResolvedValueOnce(timelineEventsCreateEventError); - mountComponent({ mockApollo: createMockApolloProvider(), mountMethod: mountExtended }); - - await submitForm(); - - expect(createAlert).toHaveBeenCalledWith(expectedAlertArgs); - }); - - it('should show an error when submission fails', async () => { - const expectedAlertArgs = { - captureError: true, - error: new Error(), - message: 'Something went wrong while creating the incident timeline event.', - }; - addEventResponse.mockRejectedValueOnce(); - mountComponent({ mockApollo: createMockApolloProvider(), mountMethod: mountExtended }); - - await submitForm(); - - expect(createAlert).toHaveBeenCalledWith(expectedAlertArgs); - }); - }); }); diff --git a/spec/frontend/issues/show/components/incidents/timeline_events_tab_spec.js b/spec/frontend/issues/show/components/incidents/timeline_events_tab_spec.js index 2d87851a761..2cdb971395d 100644 --- a/spec/frontend/issues/show/components/incidents/timeline_events_tab_spec.js +++ b/spec/frontend/issues/show/components/incidents/timeline_events_tab_spec.js @@ -5,7 +5,7 @@ import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_help import waitForPromises from 'helpers/wait_for_promises'; import TimelineEventsTab from '~/issues/show/components/incidents/timeline_events_tab.vue'; import IncidentTimelineEventsList from '~/issues/show/components/incidents/timeline_events_list.vue'; -import IncidentTimelineEventForm from '~/issues/show/components/incidents/timeline_events_form.vue'; +import CreateTimelineEvent from '~/issues/show/components/incidents/create_timeline_event.vue'; import timelineEventsQuery from '~/issues/show/components/incidents/graphql/queries/get_timeline_events.query.graphql'; import createMockApollo from 'helpers/mock_apollo_helper'; import { createAlert } from '~/flash'; @@ -53,7 +53,7 @@ describe('TimelineEventsTab', () => { const findLoadingSpinner = () => wrapper.findComponent(GlLoadingIcon); const findEmptyState = () => wrapper.findComponent(GlEmptyState); const findTimelineEventsList = () => wrapper.findComponent(IncidentTimelineEventsList); - const findTimelineEventForm = () => wrapper.findComponent(IncidentTimelineEventForm); + const findCreateTimelineEvent = () => wrapper.findComponent(CreateTimelineEvent); const findAddEventButton = () => wrapper.findByText(timelineTabI18n.addEventButton); describe('Timeline events tab', () => { @@ -143,18 +143,18 @@ describe('TimelineEventsTab', () => { }); it('should not show a form by default', () => { - expect(findTimelineEventForm().isVisible()).toBe(false); + expect(findCreateTimelineEvent().isVisible()).toBe(false); }); it('should show a form when button is clicked', async () => { await findAddEventButton().trigger('click'); - expect(findTimelineEventForm().isVisible()).toBe(true); + expect(findCreateTimelineEvent().isVisible()).toBe(true); }); it('should clear the form when button is clicked', async () => { const mockClear = jest.fn(); - wrapper.vm.$refs.eventForm.clear = mockClear; + wrapper.vm.$refs.createEventForm.clearForm = mockClear; await findAddEventButton().trigger('click'); @@ -165,9 +165,9 @@ describe('TimelineEventsTab', () => { // open the form await findAddEventButton().trigger('click'); - await findTimelineEventForm().vm.$emit('hide-incident-timeline-event-form'); + await findCreateTimelineEvent().vm.$emit('hide-new-timeline-events-form'); - expect(findTimelineEventForm().isVisible()).toBe(false); + expect(findCreateTimelineEvent().isVisible()).toBe(false); }); }); }); diff --git a/spec/services/database/consistency_check_service_spec.rb b/spec/services/database/consistency_check_service_spec.rb index 2e642451432..6695e4b5e9f 100644 --- a/spec/services/database/consistency_check_service_spec.rb +++ b/spec/services/database/consistency_check_service_spec.rb @@ -24,9 +24,27 @@ RSpec.describe Database::ConsistencyCheckService do ) end - describe '#random_start_id' do - let(:batch_size) { 5 } + describe '#min_id' do + before do + create_list(:namespace, 3) + end + it 'returns the id of the first record in the database' do + expect(subject.send(:min_id)).to eq(Namespace.first.id) + end + end + + describe '#max_id' do + before do + create_list(:namespace, 3) + end + + it 'returns the id of the first record in the database' do + expect(subject.send(:max_id)).to eq(Namespace.last.id) + end + end + + describe '#random_start_id' do before do create_list(:namespace, 50) # This will also create Ci::NameSpaceMirror objects end @@ -58,12 +76,11 @@ RSpec.describe Database::ConsistencyCheckService do end context 'no cursor has been saved before' do - let(:selected_start_id) { Namespace.order(:id).limit(5).pluck(:id).last } - let(:expected_next_start_id) { selected_start_id + batch_size * max_batches } + let(:min_id) { Namespace.first.id } + let(:max_id) { Namespace.last.id } before do create_list(:namespace, 50) # This will also create Ci::NameSpaceMirror objects - expect(consistency_check_service).to receive(:random_start_id).and_return(selected_start_id) end it 'picks a random start_id' do @@ -72,17 +89,21 @@ RSpec.describe Database::ConsistencyCheckService do matches: 10, mismatches: 0, mismatches_details: [], - start_id: selected_start_id, - next_start_id: expected_next_start_id + start_id: be_between(min_id, max_id), + next_start_id: be_between(min_id, max_id) } - expect(consistency_check_service.execute).to eq(expected_result) + expect(consistency_check_service).to receive(:rand).with(min_id..max_id).and_call_original + result = consistency_check_service.execute + expect(result).to match(expected_result) end it 'calls the ConsistencyCheckService with the expected parameters' do + expect(consistency_check_service).to receive(:random_start_id).and_return(min_id) + allow_next_instance_of(Gitlab::Database::ConsistencyChecker) do |instance| - expect(instance).to receive(:execute).with(start_id: selected_start_id).and_return({ + expect(instance).to receive(:execute).with(start_id: min_id).and_return({ batches: 2, - next_start_id: expected_next_start_id, + next_start_id: min_id + batch_size, matches: 10, mismatches: 0, mismatches_details: [] @@ -98,17 +119,19 @@ RSpec.describe Database::ConsistencyCheckService do expected_result = { batches: 2, - start_id: selected_start_id, - next_start_id: expected_next_start_id, matches: 10, mismatches: 0, - mismatches_details: [] + mismatches_details: [], + start_id: be_between(min_id, max_id), + next_start_id: be_between(min_id, max_id) } - expect(consistency_check_service.execute).to eq(expected_result) + result = consistency_check_service.execute + expect(result).to match(expected_result) end it 'saves the next_start_id in Redis for he next iteration' do - expect(consistency_check_service).to receive(:save_next_start_id).with(expected_next_start_id).and_call_original + expect(consistency_check_service).to receive(:save_next_start_id) + .with(be_between(min_id, max_id)).and_call_original consistency_check_service.execute end end diff --git a/spec/services/git/branch_push_service_spec.rb b/spec/services/git/branch_push_service_spec.rb index 36a723db9e8..6280f1263c3 100644 --- a/spec/services/git/branch_push_service_spec.rb +++ b/spec/services/git/branch_push_service_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Git::BranchPushService, services: true do include RepoHelpers let_it_be(:user) { create(:user) } - let_it_be(:project, reload: true) { create(:project, :repository) } + let_it_be_with_refind(:project) { create(:project, :repository) } let(:blankrev) { Gitlab::Git::BLANK_SHA } let(:oldrev) { sample_commit.parent_id } diff --git a/spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb b/spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb index 345dfbce423..95c0a76d726 100644 --- a/spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb +++ b/spec/support/shared_examples/features/sidebar/sidebar_due_date_shared_examples.rb @@ -16,7 +16,9 @@ RSpec.shared_examples 'date sidebar widget' do page.within('[data-testid="sidebar-due-date"]') do today = Date.today.day - click_button 'Edit' + button = find_button('Edit') + scroll_to(button) + button.click click_button today.to_s |