diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-12-19 15:10:37 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-12-19 15:10:37 +0300 |
commit | a4db97517ad095914c0652a07486ac607d99dab4 (patch) | |
tree | 58f57b42c52b1b4231cab44ef3934cbe55991d25 /spec/frontend | |
parent | 17295c75a1a28df78f719e0098dd31fe45ce0446 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
4 files changed, 245 insertions, 7 deletions
diff --git a/spec/frontend/organizations/settings/general/components/organization_settings_spec.js b/spec/frontend/organizations/settings/general/components/organization_settings_spec.js index d1c637331a8..eca6d9fdc4a 100644 --- a/spec/frontend/organizations/settings/general/components/organization_settings_spec.js +++ b/spec/frontend/organizations/settings/general/components/organization_settings_spec.js @@ -5,7 +5,11 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import OrganizationSettings from '~/organizations/settings/general/components/organization_settings.vue'; import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue'; import NewEditForm from '~/organizations/shared/components/new_edit_form.vue'; -import { FORM_FIELD_NAME, FORM_FIELD_ID } from '~/organizations/shared/constants'; +import { + FORM_FIELD_NAME, + FORM_FIELD_ID, + FORM_FIELD_AVATAR, +} from '~/organizations/shared/constants'; import organizationUpdateMutation from '~/organizations/settings/general/graphql/mutations/organization_update.mutation.graphql'; import { organizationUpdateResponse, @@ -38,22 +42,27 @@ describe('OrganizationSettings', () => { }, }; + const file = new File(['foo'], 'foo.jpg', { + type: 'text/plain', + }); + const successfulResponseHandler = jest.fn().mockResolvedValue(organizationUpdateResponse); const createComponent = ({ handlers = [[organizationUpdateMutation, successfulResponseHandler]], + provide = {}, } = {}) => { mockApollo = createMockApollo(handlers); wrapper = shallowMountExtended(OrganizationSettings, { - provide: defaultProvide, + provide: { ...defaultProvide, ...provide }, apolloProvider: mockApollo, }); }; const findForm = () => wrapper.findComponent(NewEditForm); - const submitForm = async () => { - findForm().vm.$emit('submit', { name: 'Foo bar', path: 'foo-bar' }); + const submitForm = async (data = {}) => { + findForm().vm.$emit('submit', { name: 'Foo bar', path: 'foo-bar', avatar: file, ...data }); await nextTick(); }; @@ -75,7 +84,7 @@ describe('OrganizationSettings', () => { expect(findForm().props()).toMatchObject({ loading: false, initialFormValues: defaultProvide.organization, - fieldsToRender: [FORM_FIELD_NAME, FORM_FIELD_ID], + fieldsToRender: [FORM_FIELD_NAME, FORM_FIELD_ID, FORM_FIELD_AVATAR], }); }); @@ -108,6 +117,7 @@ describe('OrganizationSettings', () => { input: { id: 'gid://gitlab/Organizations::Organization/1', name: 'Foo bar', + avatar: file, }, }); expect(visitUrlWithAlerts).toHaveBeenCalledWith(window.location.href, [ @@ -162,5 +172,46 @@ describe('OrganizationSettings', () => { }); }); }); + + describe('when organization has avatar', () => { + beforeEach(() => { + createComponent({ + provide: { organization: { ...defaultProvide.organization, avatar: 'avatar.jpg' } }, + }); + }); + + describe('when avatar is explicitly removed', () => { + beforeEach(async () => { + await submitForm({ avatar: null }); + await waitForPromises(); + }); + + it('sets `avatar` argument to `null`', () => { + expect(successfulResponseHandler).toHaveBeenCalledWith({ + input: { + id: 'gid://gitlab/Organizations::Organization/1', + name: 'Foo bar', + avatar: null, + }, + }); + }); + }); + + describe('when avatar is not changed', () => { + beforeEach(async () => { + await submitForm({ avatar: 'avatar.jpg' }); + await waitForPromises(); + }); + + it('does not pass `avatar` argument', () => { + expect(successfulResponseHandler).toHaveBeenCalledWith({ + input: { + id: 'gid://gitlab/Organizations::Organization/1', + name: 'Foo bar', + }, + }); + }); + }); + }); }); }); diff --git a/spec/frontend/organizations/shared/components/new_edit_form_spec.js b/spec/frontend/organizations/shared/components/new_edit_form_spec.js index 1fcfc20bf1a..4897a81fc1c 100644 --- a/spec/frontend/organizations/shared/components/new_edit_form_spec.js +++ b/spec/frontend/organizations/shared/components/new_edit_form_spec.js @@ -3,7 +3,13 @@ import { nextTick } from 'vue'; import NewEditForm from '~/organizations/shared/components/new_edit_form.vue'; import OrganizationUrlField from '~/organizations/shared/components/organization_url_field.vue'; -import { FORM_FIELD_NAME, FORM_FIELD_ID, FORM_FIELD_PATH } from '~/organizations/shared/constants'; +import AvatarUploadDropzone from '~/vue_shared/components/upload_dropzone/avatar_upload_dropzone.vue'; +import { + FORM_FIELD_NAME, + FORM_FIELD_ID, + FORM_FIELD_PATH, + FORM_FIELD_AVATAR, +} from '~/organizations/shared/constants'; import { mountExtended } from 'helpers/vue_test_utils_helper'; describe('NewEditForm', () => { @@ -32,6 +38,7 @@ describe('NewEditForm', () => { const findNameField = () => wrapper.findByLabelText('Organization name'); const findIdField = () => wrapper.findByLabelText('Organization ID'); const findUrlField = () => wrapper.findComponent(OrganizationUrlField); + const findAvatarField = () => wrapper.findComponent(AvatarUploadDropzone); const setUrlFieldValue = async (value) => { findUrlField().vm.$emit('input', value); @@ -53,6 +60,32 @@ describe('NewEditForm', () => { expect(findUrlField().exists()).toBe(true); }); + it('renders `Organization avatar` field', () => { + createComponent(); + + expect(findAvatarField().props()).toMatchObject({ + value: null, + entity: { [FORM_FIELD_NAME]: '', [FORM_FIELD_PATH]: '', [FORM_FIELD_AVATAR]: null }, + label: 'Organization avatar', + }); + }); + + describe('when `Organization avatar` field is changed', () => { + const file = new File(['foo'], 'foo.jpg', { + type: 'text/plain', + }); + + beforeEach(() => { + window.URL.revokeObjectURL = jest.fn(); + createComponent(); + findAvatarField().vm.$emit('input', file); + }); + + it('updates `value` prop', () => { + expect(findAvatarField().props('value')).toEqual(file); + }); + }); + it('requires `Organization URL` field to be a minimum of two characters', async () => { createComponent(); @@ -125,7 +158,9 @@ describe('NewEditForm', () => { }); it('emits `submit` event with form values', () => { - expect(wrapper.emitted('submit')).toEqual([[{ name: 'Foo bar', path: 'foo-bar' }]]); + expect(wrapper.emitted('submit')).toEqual([ + [{ name: 'Foo bar', path: 'foo-bar', avatar: null }], + ]); }); }); diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js index 1b7338744e8..adbf4e1d371 100644 --- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -858,6 +858,42 @@ describe('ReadyToMerge', () => { }); }); + describe('only allow merge if pipeline succeeds', () => { + beforeEach(() => { + const response = JSON.parse(JSON.stringify(readyToMergeResponse)); + response.data.project.onlyAllowMergeIfPipelineSucceeds = true; + response.data.project.mergeRequest.headPipeline = { + id: 1, + active: true, + status: '', + path: '', + }; + + readyToMergeResponseSpy = jest.fn().mockResolvedValueOnce(response); + }); + + it('hides merge immediately dropdown when subscription returns', async () => { + createComponent({ mr: { id: 1 } }); + + await waitForPromises(); + + expect(findMergeImmediatelyDropdown().exists()).toBe(false); + + mockedSubscription.next({ + data: { + mergeRequestMergeStatusUpdated: { + ...readyToMergeResponse.data.project.mergeRequest, + headPipeline: { id: 1, active: true, status: '', path: '' }, + }, + }, + }); + + await waitForPromises(); + + expect(findMergeImmediatelyDropdown().exists()).toBe(false); + }); + }); + describe('commit message', () => { it('updates commit message from subscription', async () => { createComponent({ mr: { id: 1 } }); diff --git a/spec/frontend/vue_shared/components/upload_dropzone/avatar_upload_dropzone_spec.js b/spec/frontend/vue_shared/components/upload_dropzone/avatar_upload_dropzone_spec.js new file mode 100644 index 00000000000..6313bf588a0 --- /dev/null +++ b/spec/frontend/vue_shared/components/upload_dropzone/avatar_upload_dropzone_spec.js @@ -0,0 +1,116 @@ +import { GlAvatar, GlButton, GlTruncate } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import AvatarUploadDropzone from '~/vue_shared/components/upload_dropzone/avatar_upload_dropzone.vue'; +import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue'; +import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants'; + +describe('AvatarUploadDropzone', () => { + let wrapper; + + const defaultPropsData = { + entity: { id: 1, name: 'Foo' }, + value: null, + label: 'Avatar', + }; + + const file = new File(['foo'], 'foo.jpg', { + type: 'text/plain', + }); + const file2 = new File(['bar'], 'bar.jpg', { + type: 'text/plain', + }); + const blob = 'blob:http://127.0.0.1:3000/0046cf8c-ea21-4720-91ef-2e354d570c75'; + + const createComponent = ({ propsData = {} } = {}) => { + wrapper = shallowMountExtended(AvatarUploadDropzone, { + propsData: { + ...defaultPropsData, + ...propsData, + }, + }); + }; + + const findUploadDropzone = () => wrapper.findComponent(UploadDropzone); + const findButton = () => wrapper.findComponent(GlButton); + + beforeEach(() => { + window.URL.createObjectURL = jest.fn().mockImplementation(() => blob); + window.URL.revokeObjectURL = jest.fn(); + }); + + it('renders `GlAvatar` with correct props', () => { + createComponent(); + + expect(wrapper.findComponent(GlAvatar).props()).toMatchObject({ + entityId: defaultPropsData.entity.id, + entityName: defaultPropsData.entity.name, + shape: AVATAR_SHAPE_OPTION_RECT, + size: 96, + src: null, + }); + }); + + it('renders label', () => { + createComponent(); + + expect(wrapper.findByText(defaultPropsData.label).exists()).toBe(true); + }); + + describe('when `value` prop is updated', () => { + beforeEach(() => { + createComponent(); + + // setProps is justified here because we are testing the component's + // reactive behavior which constitutes an exception + // See https://docs.gitlab.com/ee/development/fe_guide/style/vue.html#setting-component-state + wrapper.setProps({ value: file }); + }); + + it('updates `GlAvatar` `src` prop', () => { + expect(wrapper.findComponent(GlAvatar).props('src')).toBe(blob); + }); + + it('renders remove button', () => { + expect(findButton().exists()).toBe(true); + }); + + it('renders truncated file name', () => { + expect(wrapper.findComponent(GlTruncate).props('text')).toBe('foo.jpg'); + }); + + it('does not render upload dropzone', () => { + expect(findUploadDropzone().exists()).toBe(false); + }); + + describe('when `value` prop is updated a second time', () => { + beforeEach(() => { + wrapper.setProps({ value: file2 }); + }); + + it('revokes the object URL of the previous avatar', () => { + expect(window.URL.revokeObjectURL).toHaveBeenCalledWith(blob); + }); + }); + + describe('when avatar is removed', () => { + beforeEach(() => { + findButton().vm.$emit('click'); + }); + + it('emits `input` event with `null` payload', () => { + expect(wrapper.emitted('input')).toEqual([[null]]); + }); + }); + }); + + describe('when `UploadDropzone` emits `change` event', () => { + beforeEach(() => { + createComponent(); + findUploadDropzone().vm.$emit('change', file); + }); + + it('emits `input` event', () => { + expect(wrapper.emitted('input')).toEqual([[file]]); + }); + }); +}); |