diff options
| author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-11-10 09:08:45 +0000 |
|---|---|---|
| committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-11-10 09:08:45 +0000 |
| commit | 01c201bc6a9b99e1f3095f4139110c6fd0cf7aa9 (patch) | |
| tree | 7445a1fc4797d9f093c3b1352cf3889fadc6d967 /spec | |
| parent | 552db97a0dfa486b751a808eb4e9fadc8b875e9c (diff) | |
| download | gitlab-ce-01c201bc6a9b99e1f3095f4139110c6fd0cf7aa9.tar.gz | |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
14 files changed, 590 insertions, 492 deletions
diff --git a/spec/controllers/projects/templates_controller_spec.rb b/spec/controllers/projects/templates_controller_spec.rb index 40632e0dea7..01593f4133c 100644 --- a/spec/controllers/projects/templates_controller_spec.rb +++ b/spec/controllers/projects/templates_controller_spec.rb @@ -5,35 +5,84 @@ require 'spec_helper' RSpec.describe Projects::TemplatesController do let(:project) { create(:project, :repository, :private) } let(:user) { create(:user) } - let(:file_path_1) { '.gitlab/issue_templates/issue_template.md' } - let(:file_path_2) { '.gitlab/merge_request_templates/merge_request_template.md' } - let!(:file_1) { project.repository.create_file(user, file_path_1, 'issue content', message: 'message', branch_name: 'master') } - let!(:file_2) { project.repository.create_file(user, file_path_2, 'merge request content', message: 'message', branch_name: 'master') } + let(:issue_template_path_1) { '.gitlab/issue_templates/issue_template_1.md' } + let(:issue_template_path_2) { '.gitlab/issue_templates/issue_template_2.md' } + let(:merge_request_template_path_1) { '.gitlab/merge_request_templates/merge_request_template_1.md' } + let(:merge_request_template_path_2) { '.gitlab/merge_request_templates/merge_request_template_2.md' } + let!(:issue_template_file_1) { project.repository.create_file(user, issue_template_path_1, 'issue content 1', message: 'message 1', branch_name: 'master') } + let!(:issue_template_file_2) { project.repository.create_file(user, issue_template_path_2, 'issue content 2', message: 'message 2', branch_name: 'master') } + let!(:merge_request_template_file_1) { project.repository.create_file(user, merge_request_template_path_1, 'merge request content 1', message: 'message 1', branch_name: 'master') } + let!(:merge_request_template_file_2) { project.repository.create_file(user, merge_request_template_path_2, 'merge request content 2', message: 'message 2', branch_name: 'master') } + let(:expected_issue_template_1) { { 'key' => 'issue_template_1', 'name' => 'issue_template_1', 'content' => 'issue content 1' } } + let(:expected_issue_template_2) { { 'key' => 'issue_template_2', 'name' => 'issue_template_2', 'content' => 'issue content 2' } } + let(:expected_merge_request_template_1) { { 'key' => 'merge_request_template_1', 'name' => 'merge_request_template_1', 'content' => 'merge request content 1' } } + let(:expected_merge_request_template_2) { { 'key' => 'merge_request_template_2', 'name' => 'merge_request_template_2', 'content' => 'merge request content 2' } } + + describe '#index' do + before do + project.add_developer(user) + sign_in(user) + end + + shared_examples 'templates request' do + it 'returns the templates' do + get(:index, params: { namespace_id: project.namespace, template_type: template_type, project_id: project }, format: :json) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to match(expected_templates) + end + + it 'fails for user with no access' do + other_user = create(:user) + sign_in(other_user) + + get(:index, params: { namespace_id: project.namespace, template_type: template_type, project_id: project }, format: :json) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when querying for issue templates' do + it_behaves_like 'templates request' do + let(:template_type) { 'issue' } + let(:expected_templates) { [expected_issue_template_1, expected_issue_template_2] } + end + end + + context 'when querying for merge_request templates' do + it_behaves_like 'templates request' do + let(:template_type) { 'merge_request' } + let(:expected_templates) { [expected_merge_request_template_1, expected_merge_request_template_2] } + end + end + end describe '#show' do shared_examples 'renders issue templates as json' do + let(:expected_issue_template) { expected_issue_template_2 } + it do - get(:show, params: { namespace_id: project.namespace, template_type: 'issue', key: 'issue_template', project_id: project }, format: :json) + get(:show, params: { namespace_id: project.namespace, template_type: 'issue', key: 'issue_template_2', project_id: project }, format: :json) expect(response).to have_gitlab_http_status(:ok) - expect(json_response['name']).to eq('issue_template') - expect(json_response['content']).to eq('issue content') + expect(json_response).to match(expected_issue_template) end end shared_examples 'renders merge request templates as json' do + let(:expected_merge_request_template) { expected_merge_request_template_2 } + it do - get(:show, params: { namespace_id: project.namespace, template_type: 'merge_request', key: 'merge_request_template', project_id: project }, format: :json) + get(:show, params: { namespace_id: project.namespace, template_type: 'merge_request', key: 'merge_request_template_2', project_id: project }, format: :json) expect(response).to have_gitlab_http_status(:ok) - expect(json_response['name']).to eq('merge_request_template') - expect(json_response['content']).to eq('merge request content') + expect(json_response).to match(expected_merge_request_template) end end shared_examples 'renders 404 when requesting an issue template' do it do - get(:show, params: { namespace_id: project.namespace, template_type: 'issue', key: 'issue_template', project_id: project }, format: :json) + get(:show, params: { namespace_id: project.namespace, template_type: 'issue', key: 'issue_template_1', project_id: project }, format: :json) expect(response).to have_gitlab_http_status(:not_found) end @@ -41,21 +90,23 @@ RSpec.describe Projects::TemplatesController do shared_examples 'renders 404 when requesting a merge request template' do it do - get(:show, params: { namespace_id: project.namespace, template_type: 'merge_request', key: 'merge_request_template', project_id: project }, format: :json) + get(:show, params: { namespace_id: project.namespace, template_type: 'merge_request', key: 'merge_request_template_1', project_id: project }, format: :json) expect(response).to have_gitlab_http_status(:not_found) end end - shared_examples 'renders 404 when params are invalid' do + shared_examples 'raises error when template type is invalid' do it 'does not route when the template type is invalid' do expect do - get(:show, params: { namespace_id: project.namespace, template_type: 'invalid_type', key: 'issue_template', project_id: project }, format: :json) + get(:show, params: { namespace_id: project.namespace, template_type: 'invalid_type', key: 'issue_template_1', project_id: project }, format: :json) end.to raise_error(ActionController::UrlGenerationError) end + end + shared_examples 'renders 404 when params are invalid' do it 'renders 404 when the format type is invalid' do - get(:show, params: { namespace_id: project.namespace, template_type: 'issue', key: 'issue_template', project_id: project }, format: :html) + get(:show, params: { namespace_id: project.namespace, template_type: 'issue', key: 'issue_template_1', project_id: project }, format: :html) expect(response).to have_gitlab_http_status(:not_found) end @@ -74,7 +125,6 @@ RSpec.describe Projects::TemplatesController do include_examples 'renders 404 when requesting an issue template' include_examples 'renders 404 when requesting a merge request template' - include_examples 'renders 404 when params are invalid' end context 'when user is a member of the project' do @@ -85,7 +135,11 @@ RSpec.describe Projects::TemplatesController do include_examples 'renders issue templates as json' include_examples 'renders merge request templates as json' - include_examples 'renders 404 when params are invalid' + + context 'when params are invalid' do + include_examples 'raises error when template type is invalid' + include_examples 'renders 404 when params are invalid' + end end context 'when user is a guest of the project' do @@ -96,7 +150,6 @@ RSpec.describe Projects::TemplatesController do include_examples 'renders issue templates as json' include_examples 'renders 404 when requesting a merge request template' - include_examples 'renders 404 when params are invalid' end end @@ -111,8 +164,8 @@ RSpec.describe Projects::TemplatesController do get(:names, params: { namespace_id: project.namespace, template_type: template_type, project_id: project }, format: :json) expect(response).to have_gitlab_http_status(:ok) - expect(json_response.size).to eq(1) - expect(json_response[0]['name']).to eq(expected_template_name) + expect(json_response.size).to eq(2) + expect(json_response).to match(expected_template_names) end it 'fails for user with no access' do @@ -128,14 +181,14 @@ RSpec.describe Projects::TemplatesController do context 'when querying for issue templates' do it_behaves_like 'template names request' do let(:template_type) { 'issue' } - let(:expected_template_name) { 'issue_template' } + let(:expected_template_names) { [{ 'name' => 'issue_template_1' }, { 'name' => 'issue_template_2' }] } end end context 'when querying for merge_request templates' do it_behaves_like 'template names request' do let(:template_type) { 'merge_request' } - let(:expected_template_name) { 'merge_request_template' } + let(:expected_template_names) { [{ 'name' => 'merge_request_template_1' }, { 'name' => 'merge_request_template_2' }] } end end end diff --git a/spec/features/projects/issues/design_management/user_uploads_designs_spec.rb b/spec/features/projects/issues/design_management/user_uploads_designs_spec.rb index de1fcc9d787..b5d5527bbfe 100644 --- a/spec/features/projects/issues/design_management/user_uploads_designs_spec.rb +++ b/spec/features/projects/issues/design_management/user_uploads_designs_spec.rb @@ -51,7 +51,7 @@ RSpec.describe 'User uploads new design', :js do end def upload_design(fixture, count:) - attach_file(:design_file, fixture, match: :first, make_visible: true) + attach_file(:upload_file, fixture, match: :first, make_visible: true) wait_for('designs uploaded') do issue.reload.designs.count == count diff --git a/spec/frontend/api_spec.js b/spec/frontend/api_spec.js index 26ec399bc5f..724d33922a1 100644 --- a/spec/frontend/api_spec.js +++ b/spec/frontend/api_spec.js @@ -553,14 +553,15 @@ describe('Api', () => { }); describe('issueTemplate', () => { + const namespace = 'some namespace'; + const project = 'some project'; + const templateKey = ' template #%?.key '; + const templateType = 'template type'; + const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/templates/${templateType}/${encodeURIComponent( + templateKey, + )}`; + it('fetches an issue template', done => { - const namespace = 'some namespace'; - const project = 'some project'; - const templateKey = ' template #%?.key '; - const templateType = 'template type'; - const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/templates/${templateType}/${encodeURIComponent( - templateKey, - )}`; mock.onGet(expectedUrl).reply(httpStatus.OK, 'test'); Api.issueTemplate(namespace, project, templateKey, templateType, (error, response) => { @@ -568,6 +569,49 @@ describe('Api', () => { done(); }); }); + + describe('when an error occurs while fetching an issue template', () => { + it('rejects the Promise', () => { + mock.onGet(expectedUrl).replyOnce(httpStatus.INTERNAL_SERVER_ERROR); + + Api.issueTemplate(namespace, project, templateKey, templateType, () => { + expect(mock.history.get).toHaveLength(1); + }); + }); + }); + }); + + describe('issueTemplates', () => { + const namespace = 'some namespace'; + const project = 'some project'; + const templateType = 'template type'; + const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/templates/${templateType}`; + + it('fetches all templates by type', done => { + const expectedData = [ + { key: 'Template1', name: 'Template 1', content: 'This is template 1!' }, + ]; + mock.onGet(expectedUrl).reply(httpStatus.OK, expectedData); + + Api.issueTemplates(namespace, project, templateType, (error, response) => { + expect(response.length).toBe(1); + const { key, name, content } = response[0]; + expect(key).toBe('Template1'); + expect(name).toBe('Template 1'); + expect(content).toBe('This is template 1!'); + done(); + }); + }); + + describe('when an error occurs while fetching issue templates', () => { + it('rejects the Promise', () => { + mock.onGet(expectedUrl).replyOnce(httpStatus.INTERNAL_SERVER_ERROR); + + Api.issueTemplates(namespace, project, templateType, () => { + expect(mock.history.get).toHaveLength(1); + }); + }); + }); }); describe('projectTemplates', () => { diff --git a/spec/frontend/design_management/pages/__snapshots__/index_spec.js.snap b/spec/frontend/design_management/pages/__snapshots__/index_spec.js.snap index ecf30ef3195..abd455ae750 100644 --- a/spec/frontend/design_management/pages/__snapshots__/index_spec.js.snap +++ b/spec/frontend/design_management/pages/__snapshots__/index_spec.js.snap @@ -1,240 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Design management index page designs does not render toolbar when there is no permission 1`] = ` -<div - class="gl-mt-5" - data-testid="designs-root" -> - <!----> - - <div - class="gl-mt-6" - > - <ol - class="list-unstyled row" - > - <li - class="gl-flex-direction-column col-md-6 col-lg-3 gl-mb-3" - data-testid="design-dropzone-wrapper" - > - <design-dropzone-stub - class="design-list-item design-list-item-new" - data-qa-selector="design_dropzone_content" - hasdesigns="true" - /> - </li> - <li - class="col-md-6 col-lg-3 gl-mb-3 gl-bg-transparent gl-shadow-none js-design-tile" - > - <design-dropzone-stub - hasdesigns="true" - > - <design-stub - class="gl-bg-white" - event="NONE" - filename="design-1-name" - id="design-1" - image="design-1-image" - notescount="0" - /> - </design-dropzone-stub> - - <!----> - </li> - <li - class="col-md-6 col-lg-3 gl-mb-3 gl-bg-transparent gl-shadow-none js-design-tile" - > - <design-dropzone-stub - hasdesigns="true" - > - <design-stub - class="gl-bg-white" - event="NONE" - filename="design-2-name" - id="design-2" - image="design-2-image" - notescount="1" - /> - </design-dropzone-stub> - - <!----> - </li> - <li - class="col-md-6 col-lg-3 gl-mb-3 gl-bg-transparent gl-shadow-none js-design-tile" - > - <design-dropzone-stub - hasdesigns="true" - > - <design-stub - class="gl-bg-white" - event="NONE" - filename="design-3-name" - id="design-3" - image="design-3-image" - notescount="0" - /> - </design-dropzone-stub> - - <!----> - </li> - </ol> - </div> - - <router-view-stub - name="default" - /> -</div> -`; - -exports[`Design management index page designs renders designs list and header with upload button 1`] = ` -<div - class="gl-mt-5" - data-testid="designs-root" -> - <header - class="row-content-block gl-border-t-0 gl-p-3 gl-display-flex" - > - <div - class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-w-full" - > - <div> - <span - class="gl-font-weight-bold gl-mr-3" - > - Designs - </span> - - <design-version-dropdown-stub /> - </div> - - <div - class="qa-selector-toolbar gl-display-flex gl-align-items-center" - > - <gl-button-stub - buttontextclasses="" - category="primary" - class="gl-mr-4 js-select-all" - icon="" - size="small" - variant="link" - > - Select all - - </gl-button-stub> - - <div> - <delete-button-stub - buttoncategory="secondary" - buttonclass="gl-mr-3" - buttonsize="small" - buttonvariant="warning" - data-qa-selector="archive_button" - > - - Archive selected - - </delete-button-stub> - </div> - - <upload-button-stub /> - </div> - </div> - </header> - - <div - class="gl-mt-6" - > - <ol - class="list-unstyled row" - > - <li - class="gl-flex-direction-column col-md-6 col-lg-3 gl-mb-3" - data-testid="design-dropzone-wrapper" - > - <design-dropzone-stub - class="design-list-item design-list-item-new" - data-qa-selector="design_dropzone_content" - hasdesigns="true" - /> - </li> - <li - class="col-md-6 col-lg-3 gl-mb-3 gl-bg-transparent gl-shadow-none js-design-tile" - > - <design-dropzone-stub - hasdesigns="true" - > - <design-stub - class="gl-bg-white" - event="NONE" - filename="design-1-name" - id="design-1" - image="design-1-image" - notescount="0" - /> - </design-dropzone-stub> - - <input - class="design-checkbox" - data-qa-design="design-1-name" - data-qa-selector="design_checkbox" - type="checkbox" - /> - </li> - <li - class="col-md-6 col-lg-3 gl-mb-3 gl-bg-transparent gl-shadow-none js-design-tile" - > - <design-dropzone-stub - hasdesigns="true" - > - <design-stub - class="gl-bg-white" - event="NONE" - filename="design-2-name" - id="design-2" - image="design-2-image" - notescount="1" - /> - </design-dropzone-stub> - - <input - class="design-checkbox" - data-qa-design="design-2-name" - data-qa-selector="design_checkbox" - type="checkbox" - /> - </li> - <li - class="col-md-6 col-lg-3 gl-mb-3 gl-bg-transparent gl-shadow-none js-design-tile" - > - <design-dropzone-stub - hasdesigns="true" - > - <design-stub - class="gl-bg-white" - event="NONE" - filename="design-3-name" - id="design-3" - image="design-3-image" - notescount="0" - /> - </design-dropzone-stub> - - <input - class="design-checkbox" - data-qa-design="design-3-name" - data-qa-selector="design_checkbox" - type="checkbox" - /> - </li> - </ol> - </div> - - <router-view-stub - name="default" - /> -</div> -`; - exports[`Design management index page designs renders error 1`] = ` <div class="gl-mt-5" @@ -288,34 +53,3 @@ exports[`Design management index page designs renders loading icon 1`] = ` /> </div> `; - -exports[`Design management index page when has no designs renders design dropzone 1`] = ` -<div - class="gl-mt-5" - data-testid="designs-root" -> - <!----> - - <div - class="gl-mt-6" - > - <ol - class="list-unstyled row" - > - <li - class="col-12" - data-testid="design-dropzone-wrapper" - > - <design-dropzone-stub - class="" - data-qa-selector="design_dropzone_content" - /> - </li> - </ol> - </div> - - <router-view-stub - name="default" - /> -</div> -`; diff --git a/spec/frontend/design_management/pages/index_spec.js b/spec/frontend/design_management/pages/index_spec.js index 63de96a1b4e..05238efd761 100644 --- a/spec/frontend/design_management/pages/index_spec.js +++ b/spec/frontend/design_management/pages/index_spec.js @@ -10,7 +10,7 @@ import permissionsQuery from 'shared_queries/design_management/design_permission import Index from '~/design_management/pages/index.vue'; import uploadDesignQuery from '~/design_management/graphql/mutations/upload_design.mutation.graphql'; import DesignDestroyer from '~/design_management/components/design_destroyer.vue'; -import DesignDropzone from '~/design_management/components/upload/design_dropzone.vue'; +import DesignDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue'; import DeleteButton from '~/design_management/components/delete_button.vue'; import Design from '~/design_management/components/list/item.vue'; import { DESIGNS_ROUTE_NAME } from '~/design_management/router/constants'; @@ -105,6 +105,8 @@ describe('Design management index page', () => { const findDesignsWrapper = () => wrapper.find('[data-testid="designs-root"]'); const findDesigns = () => wrapper.findAll(Design); const draggableAttributes = () => wrapper.find(VueDraggable).vm.$attrs; + const findDesignUploadButton = () => wrapper.find('[data-testid="design-upload-button"]'); + const findDesignToolbarWrapper = () => wrapper.find('[data-testid="design-toolbar-wrapper"]'); async function moveDesigns(localWrapper) { await jest.runOnlyPendingTimers(); @@ -214,13 +216,17 @@ describe('Design management index page', () => { it('renders designs list and header with upload button', () => { createComponent({ allVersions: [mockVersion] }); - expect(wrapper.element).toMatchSnapshot(); + expect(findDesignsWrapper().exists()).toBe(true); + expect(findDesigns().length).toBe(3); + expect(findDesignToolbarWrapper().exists()).toBe(true); + expect(findDesignUploadButton().exists()).toBe(true); }); it('does not render toolbar when there is no permission', () => { createComponent({ designs: mockDesigns, allVersions: [mockVersion], createDesign: false }); - expect(wrapper.element).toMatchSnapshot(); + expect(findDesignToolbarWrapper().exists()).toBe(false); + expect(findDesignUploadButton().exists()).toBe(false); }); it('has correct classes applied to design dropzone', () => { @@ -247,7 +253,7 @@ describe('Design management index page', () => { it('renders design dropzone', () => wrapper.vm.$nextTick().then(() => { - expect(wrapper.element).toMatchSnapshot(); + expect(findDropzone().exists()).toBe(true); })); it('has correct classes applied to design dropzone', () => { diff --git a/spec/frontend/issue_show/components/header_actions_spec.js b/spec/frontend/issue_show/components/header_actions_spec.js index 7f4a9c8cdc3..ec9f8ea1dc8 100644 --- a/spec/frontend/issue_show/components/header_actions_spec.js +++ b/spec/frontend/issue_show/components/header_actions_spec.js @@ -1,6 +1,7 @@ import { GlButton, GlDropdown, GlDropdownItem, GlLink, GlModal } from '@gitlab/ui'; import { createLocalVue, shallowMount } from '@vue/test-utils'; import Vuex from 'vuex'; +import { IssuableType } from '~/issuable_show/constants'; import HeaderActions from '~/issue_show/components/header_actions.vue'; import { IssuableStatus, IssueStateEvent } from '~/issue_show/constants'; import createStore from '~/notes/stores'; @@ -20,6 +21,7 @@ describe('HeaderActions component', () => { canUpdateIssue: true, iid: '32', isIssueAuthor: true, + issueType: IssuableType.Issue, newIssuePath: 'gitlab-org/gitlab-test/-/issues/new', projectPath: 'gitlab-org/gitlab-test', reportAbusePath: @@ -74,93 +76,100 @@ describe('HeaderActions component', () => { wrapper.destroy(); }); - describe('close/reopen button', () => { - describe.each` - description | issueState | buttonText | newIssueState - ${'when the issue is open'} | ${IssuableStatus.Open} | ${'Close issue'} | ${IssueStateEvent.Close} - ${'when the issue is closed'} | ${IssuableStatus.Closed} | ${'Reopen issue'} | ${IssueStateEvent.Reopen} - `('$description', ({ issueState, buttonText, newIssueState }) => { - beforeEach(() => { - dispatchEventSpy = jest.spyOn(document, 'dispatchEvent'); - - wrapper = mountComponent({ issueState }); - }); + describe.each` + issueType + ${IssuableType.Issue} + ${IssuableType.Incident} + `('when issue type is $issueType', ({ issueType }) => { + describe('close/reopen button', () => { + describe.each` + description | issueState | buttonText | newIssueState + ${`when the ${issueType} is open`} | ${IssuableStatus.Open} | ${`Close ${issueType}`} | ${IssueStateEvent.Close} + ${`when the ${issueType} is closed`} | ${IssuableStatus.Closed} | ${`Reopen ${issueType}`} | ${IssueStateEvent.Reopen} + `('$description', ({ issueState, buttonText, newIssueState }) => { + beforeEach(() => { + dispatchEventSpy = jest.spyOn(document, 'dispatchEvent'); - it(`has text "${buttonText}"`, () => { - expect(findToggleIssueStateButton().text()).toBe(buttonText); - }); + wrapper = mountComponent({ props: { issueType }, issueState }); + }); - it('calls apollo mutation', () => { - findToggleIssueStateButton().vm.$emit('click'); + it(`has text "${buttonText}"`, () => { + expect(findToggleIssueStateButton().text()).toBe(buttonText); + }); - expect(mutate).toHaveBeenCalledWith( - expect.objectContaining({ - variables: { - input: { - iid: defaultProps.iid.toString(), - projectPath: defaultProps.projectPath, - stateEvent: newIssueState, + it('calls apollo mutation', () => { + findToggleIssueStateButton().vm.$emit('click'); + + expect(mutate).toHaveBeenCalledWith( + expect.objectContaining({ + variables: { + input: { + iid: defaultProps.iid.toString(), + projectPath: defaultProps.projectPath, + stateEvent: newIssueState, + }, }, - }, - }), - ); - }); + }), + ); + }); - it('dispatches a custom event to update the issue page', async () => { - findToggleIssueStateButton().vm.$emit('click'); + it('dispatches a custom event to update the issue page', async () => { + findToggleIssueStateButton().vm.$emit('click'); - await wrapper.vm.$nextTick(); + await wrapper.vm.$nextTick(); - expect(dispatchEventSpy).toHaveBeenCalledTimes(1); + expect(dispatchEventSpy).toHaveBeenCalledTimes(1); + }); }); }); - }); - describe.each` - description | isCloseIssueItemVisible | findDropdownItems - ${'mobile dropdown'} | ${true} | ${findMobileDropdownItems} - ${'desktop dropdown'} | ${false} | ${findDesktopDropdownItems} - `('$description', ({ isCloseIssueItemVisible, findDropdownItems }) => { describe.each` - description | itemText | isItemVisible | canUpdateIssue | canCreateIssue | isIssueAuthor | canReportSpam - ${'when user can update issue'} | ${'Close issue'} | ${isCloseIssueItemVisible} | ${true} | ${true} | ${true} | ${true} - ${'when user cannot update issue'} | ${'Close issue'} | ${false} | ${false} | ${true} | ${true} | ${true} - ${'when user can create issue'} | ${'New issue'} | ${true} | ${true} | ${true} | ${true} | ${true} - ${'when user cannot create issue'} | ${'New issue'} | ${false} | ${true} | ${false} | ${true} | ${true} - ${'when user can report abuse'} | ${'Report abuse'} | ${true} | ${true} | ${true} | ${false} | ${true} - ${'when user cannot report abuse'} | ${'Report abuse'} | ${false} | ${true} | ${true} | ${true} | ${true} - ${'when user can submit as spam'} | ${'Submit as spam'} | ${true} | ${true} | ${true} | ${true} | ${true} - ${'when user cannot submit as spam'} | ${'Submit as spam'} | ${false} | ${true} | ${true} | ${true} | ${false} - `( - '$description', - ({ - itemText, - isItemVisible, - canUpdateIssue, - canCreateIssue, - isIssueAuthor, - canReportSpam, - }) => { - beforeEach(() => { - wrapper = mountComponent({ - props: { - canUpdateIssue, - canCreateIssue, - isIssueAuthor, - canReportSpam, - }, + description | isCloseIssueItemVisible | findDropdownItems + ${'mobile dropdown'} | ${true} | ${findMobileDropdownItems} + ${'desktop dropdown'} | ${false} | ${findDesktopDropdownItems} + `('$description', ({ isCloseIssueItemVisible, findDropdownItems }) => { + describe.each` + description | itemText | isItemVisible | canUpdateIssue | canCreateIssue | isIssueAuthor | canReportSpam + ${`when user can update ${issueType}`} | ${`Close ${issueType}`} | ${isCloseIssueItemVisible} | ${true} | ${true} | ${true} | ${true} + ${`when user cannot update ${issueType}`} | ${`Close ${issueType}`} | ${false} | ${false} | ${true} | ${true} | ${true} + ${`when user can create ${issueType}`} | ${`New ${issueType}`} | ${true} | ${true} | ${true} | ${true} | ${true} + ${`when user cannot create ${issueType}`} | ${`New ${issueType}`} | ${false} | ${true} | ${false} | ${true} | ${true} + ${'when user can report abuse'} | ${'Report abuse'} | ${true} | ${true} | ${true} | ${false} | ${true} + ${'when user cannot report abuse'} | ${'Report abuse'} | ${false} | ${true} | ${true} | ${true} | ${true} + ${'when user can submit as spam'} | ${'Submit as spam'} | ${true} | ${true} | ${true} | ${true} | ${true} + ${'when user cannot submit as spam'} | ${'Submit as spam'} | ${false} | ${true} | ${true} | ${true} | ${false} + `( + '$description', + ({ + itemText, + isItemVisible, + canUpdateIssue, + canCreateIssue, + isIssueAuthor, + canReportSpam, + }) => { + beforeEach(() => { + wrapper = mountComponent({ + props: { + canUpdateIssue, + canCreateIssue, + isIssueAuthor, + issueType, + canReportSpam, + }, + }); }); - }); - it(`${isItemVisible ? 'shows' : 'hides'} "${itemText}" item`, () => { - expect( - findDropdownItems() - .filter(item => item.text() === itemText) - .exists(), - ).toBe(isItemVisible); - }); - }, - ); + it(`${isItemVisible ? 'shows' : 'hides'} "${itemText}" item`, () => { + expect( + findDropdownItems() + .filter(item => item.text() === itemText) + .exists(), + ).toBe(isItemVisible); + }); + }, + ); + }); }); describe('modal', () => { diff --git a/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js b/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js index 040c0fbecc5..197f646a22e 100644 --- a/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js +++ b/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js @@ -1,5 +1,5 @@ import { mount, shallowMount } from '@vue/test-utils'; -import { GlDropdown, GlDropdownItem, GlForm, GlSprintf } from '@gitlab/ui'; +import { GlDropdown, GlDropdownItem, GlForm, GlSprintf, GlLoadingIcon } from '@gitlab/ui'; import MockAdapter from 'axios-mock-adapter'; import waitForPromises from 'helpers/wait_for_promises'; import httpStatusCodes from '~/lib/utils/http_status'; @@ -35,6 +35,7 @@ describe('Pipeline New Form', () => { const findWarningAlert = () => wrapper.find('[data-testid="run-pipeline-warning-alert"]'); const findWarningAlertSummary = () => findWarningAlert().find(GlSprintf); const findWarnings = () => wrapper.findAll('[data-testid="run-pipeline-warning"]'); + const findLoadingIcon = () => wrapper.find(GlLoadingIcon); const getExpectedPostParams = () => JSON.parse(mock.history.post[0].data); const createComponent = (term = '', props = {}, method = shallowMount) => { @@ -207,6 +208,25 @@ describe('Pipeline New Form', () => { window.gon = origGon; }); + describe('loading state', () => { + it('loading icon is shown when content is requested and hidden when received', async () => { + createComponent('', mockParams, mount); + + mock.onGet(configVariablesPath).reply(httpStatusCodes.OK, { + [mockYmlKey]: { + value: mockYmlValue, + description: mockYmlDesc, + }, + }); + + expect(findLoadingIcon().exists()).toBe(true); + + await waitForPromises(); + + expect(findLoadingIcon().exists()).toBe(false); + }); + }); + describe('when yml defines a variable with description', () => { beforeEach(async () => { createComponent('', mockParams, mount); diff --git a/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js b/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js index 708b6e5bb96..7c8ebc27974 100644 --- a/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js +++ b/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js @@ -33,7 +33,9 @@ describe('pipeline graph component', () => { }); it('renders an empty section', () => { - expect(wrapper.text()).toContain('No content to show'); + expect(wrapper.text()).toContain( + 'The visualization will appear in this tab when the CI/CD configuration file is populated with valid syntax.', + ); expect(findAllStagePills()).toHaveLength(0); expect(findAllJobPills()).toHaveLength(0); }); diff --git a/spec/frontend/static_site_editor/components/edit_meta_modal_spec.js b/spec/frontend/static_site_editor/components/edit_meta_modal_spec.js index 70ffb480e4c..c7d0abee05c 100644 --- a/spec/frontend/static_site_editor/components/edit_meta_modal_spec.js +++ b/spec/frontend/static_site_editor/components/edit_meta_modal_spec.js @@ -1,34 +1,49 @@ import { shallowMount } from '@vue/test-utils'; import { GlModal } from '@gitlab/ui'; import { useLocalStorageSpy } from 'helpers/local_storage_helper'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; import EditMetaModal from '~/static_site_editor/components/edit_meta_modal.vue'; import EditMetaControls from '~/static_site_editor/components/edit_meta_controls.vue'; import { MR_META_LOCAL_STORAGE_KEY } from '~/static_site_editor/constants'; -import { sourcePath, mergeRequestMeta, mergeRequestTemplates } from '../mock_data'; +import { + sourcePath, + mergeRequestMeta, + mergeRequestTemplates, + project as namespaceProject, +} from '../mock_data'; describe('~/static_site_editor/components/edit_meta_modal.vue', () => { useLocalStorageSpy(); let wrapper; - let resetCachedEditable; - let mockEditMetaControlsInstance; + let mockAxios; const { title, description } = mergeRequestMeta; + const [namespace, project] = namespaceProject.split('/'); const buildWrapper = (propsData = {}, data = {}) => { wrapper = shallowMount(EditMetaModal, { propsData: { sourcePath, + namespace, + project, ...propsData, }, data: () => data, }); }; - const buildMocks = () => { - resetCachedEditable = jest.fn(); - mockEditMetaControlsInstance = { resetCachedEditable }; - wrapper.vm.$refs.editMetaControls = mockEditMetaControlsInstance; + const buildMockAxios = () => { + mockAxios = new MockAdapter(axios); + const templatesMergeRequestsPath = `templates/merge_request`; + mockAxios + .onGet(`${namespace}/${project}/${templatesMergeRequestsPath}`) + .reply(200, mergeRequestTemplates); + }; + + const buildMockRefs = () => { + wrapper.vm.$refs.editMetaControls = { resetCachedEditable: jest.fn() }; }; const findGlModal = () => wrapper.find(GlModal); @@ -37,16 +52,17 @@ describe('~/static_site_editor/components/edit_meta_modal.vue', () => { beforeEach(() => { localStorage.setItem(MR_META_LOCAL_STORAGE_KEY); - }); - beforeEach(() => { + buildMockAxios(); buildWrapper(); - buildMocks(); + buildMockRefs(); return wrapper.vm.$nextTick(); }); afterEach(() => { + mockAxios.restore(); + wrapper.destroy(); wrapper = null; }); diff --git a/spec/frontend/static_site_editor/pages/home_spec.js b/spec/frontend/static_site_editor/pages/home_spec.js index f5daa70714e..d0b72ad0cf0 100644 --- a/spec/frontend/static_site_editor/pages/home_spec.js +++ b/spec/frontend/static_site_editor/pages/home_spec.js @@ -12,7 +12,7 @@ import { SUCCESS_ROUTE } from '~/static_site_editor/router/constants'; import { TRACKING_ACTION_INITIALIZE_EDITOR } from '~/static_site_editor/constants'; import { - projectId as project, + project, returnUrl, sourceContentYAML as content, sourceContentTitle as title, diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js index b19e74b5b11..c0a000690f8 100644 --- a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js +++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js @@ -29,6 +29,10 @@ describe('Suggestion Diff component', () => { }); }; + beforeEach(() => { + window.gon.current_user_id = 1; + }); + afterEach(() => { wrapper.destroy(); }); @@ -71,6 +75,14 @@ describe('Suggestion Diff component', () => { expect(addToBatchBtn.html().includes('Add suggestion to batch')).toBe(true); }); + it('does not render apply suggestion button with anonymous user', () => { + window.gon.current_user_id = null; + + createComponent(); + + expect(findApplyButton().exists()).toBe(false); + }); + describe('when apply suggestion is clicked', () => { beforeEach(() => { createComponent(); diff --git a/spec/frontend/design_management/components/upload/__snapshots__/design_dropzone_spec.js.snap b/spec/frontend/vue_shared/components/upload_dropzone/__snapshots__/upload_dropzone_spec.js.snap index 1ca5360fa59..d2fe3cd76cb 100644 --- a/spec/frontend/design_management/components/upload/__snapshots__/design_dropzone_spec.js.snap +++ b/spec/frontend/vue_shared/components/upload_dropzone/__snapshots__/upload_dropzone_spec.js.snap @@ -1,11 +1,90 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Design management dropzone component when dragging renders correct template when drag event contains files 1`] = ` +exports[`Upload dropzone component correctly overrides description and drop messages 1`] = ` <div class="gl-w-full gl-relative" > <button - class="card design-dropzone-card design-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3" + class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3" + > + <div + class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column" + data-testid="dropzone-area" + > + <gl-icon-stub + class="gl-mb-2" + name="upload" + size="24" + /> + + <p + class="gl-mb-0" + > + <span> + Test %{linkStart}description%{linkEnd} message. + </span> + </p> + </div> + </button> + + <input + accept="image/jpg,image/jpeg" + class="hide" + multiple="multiple" + name="upload_file" + type="file" + /> + + <transition-stub + name="upload-dropzone-fade" + > + <div + class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white" + style="display: none;" + > + <div + class="mw-50 gl-text-center" + > + <h3 + class="" + > + + Oh no! + + </h3> + + <span> + You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico. + </span> + </div> + + <div + class="mw-50 gl-text-center" + style="display: none;" + > + <h3 + class="" + > + + Incoming! + + </h3> + + <span> + Test drop-to-start message. + </span> + </div> + </div> + </transition-stub> +</div> +`; + +exports[`Upload dropzone component when dragging renders correct template when drag event contains files 1`] = ` +<div + class="gl-w-full gl-relative" +> + <button + class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3" > <div class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column" @@ -21,7 +100,7 @@ exports[`Design management dropzone component when dragging renders correct temp class="gl-mb-0" > <gl-sprintf-stub - message="Drop or %{linkStart}upload%{linkEnd} designs to attach" + message="Drop or %{linkStart}upload%{linkEnd} files to attach" /> </p> </div> @@ -31,15 +110,15 @@ exports[`Design management dropzone component when dragging renders correct temp accept="image/*" class="hide" multiple="multiple" - name="design_file" + name="upload_file" type="file" /> <transition-stub - name="design-dropzone-fade" + name="upload-dropzone-fade" > <div - class="card design-dropzone-border design-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white" + class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white" style="" > <div @@ -49,7 +128,9 @@ exports[`Design management dropzone component when dragging renders correct temp <h3 class="" > - Oh no! + + Oh no! + </h3> <span> @@ -64,11 +145,13 @@ exports[`Design management dropzone component when dragging renders correct temp <h3 class="" > - Incoming! + + Incoming! + </h3> <span> - Drop your designs to start your upload. + Drop your files to start your upload. </span> </div> </div> @@ -76,12 +159,12 @@ exports[`Design management dropzone component when dragging renders correct temp </div> `; -exports[`Design management dropzone component when dragging renders correct template when drag event contains files and text 1`] = ` +exports[`Upload dropzone component when dragging renders correct template when drag event contains files and text 1`] = ` <div class="gl-w-full gl-relative" > <button - class="card design-dropzone-card design-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3" + class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3" > <div class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column" @@ -97,7 +180,7 @@ exports[`Design management dropzone component when dragging renders correct temp class="gl-mb-0" > <gl-sprintf-stub - message="Drop or %{linkStart}upload%{linkEnd} designs to attach" + message="Drop or %{linkStart}upload%{linkEnd} files to attach" /> </p> </div> @@ -107,15 +190,15 @@ exports[`Design management dropzone component when dragging renders correct temp accept="image/*" class="hide" multiple="multiple" - name="design_file" + name="upload_file" type="file" /> <transition-stub - name="design-dropzone-fade" + name="upload-dropzone-fade" > <div - class="card design-dropzone-border design-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white" + class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white" style="" > <div @@ -125,7 +208,9 @@ exports[`Design management dropzone component when dragging renders correct temp <h3 class="" > - Oh no! + + Oh no! + </h3> <span> @@ -140,11 +225,13 @@ exports[`Design management dropzone component when dragging renders correct temp <h3 class="" > - Incoming! + + Incoming! + </h3> <span> - Drop your designs to start your upload. + Drop your files to start your upload. </span> </div> </div> @@ -152,12 +239,12 @@ exports[`Design management dropzone component when dragging renders correct temp </div> `; -exports[`Design management dropzone component when dragging renders correct template when drag event contains text 1`] = ` +exports[`Upload dropzone component when dragging renders correct template when drag event contains text 1`] = ` <div class="gl-w-full gl-relative" > <button - class="card design-dropzone-card design-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3" + class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3" > <div class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column" @@ -173,7 +260,7 @@ exports[`Design management dropzone component when dragging renders correct temp class="gl-mb-0" > <gl-sprintf-stub - message="Drop or %{linkStart}upload%{linkEnd} designs to attach" + message="Drop or %{linkStart}upload%{linkEnd} files to attach" /> </p> </div> @@ -183,15 +270,15 @@ exports[`Design management dropzone component when dragging renders correct temp accept="image/*" class="hide" multiple="multiple" - name="design_file" + name="upload_file" type="file" /> <transition-stub - name="design-dropzone-fade" + name="upload-dropzone-fade" > <div - class="card design-dropzone-border design-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white" + class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white" style="" > <div @@ -200,7 +287,9 @@ exports[`Design management dropzone component when dragging renders correct temp <h3 class="" > - Oh no! + + Oh no! + </h3> <span> @@ -215,11 +304,13 @@ exports[`Design management dropzone component when dragging renders correct temp <h3 class="" > - Incoming! + + Incoming! + </h3> <span> - Drop your designs to start your upload. + Drop your files to start your upload. </span> </div> </div> @@ -227,12 +318,12 @@ exports[`Design management dropzone component when dragging renders correct temp </div> `; -exports[`Design management dropzone component when dragging renders correct template when drag event is empty 1`] = ` +exports[`Upload dropzone component when dragging renders correct template when drag event is empty 1`] = ` <div class="gl-w-full gl-relative" > <button - class="card design-dropzone-card design-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3" + class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3" > <div class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column" @@ -248,7 +339,7 @@ exports[`Design management dropzone component when dragging renders correct temp class="gl-mb-0" > <gl-sprintf-stub - message="Drop or %{linkStart}upload%{linkEnd} designs to attach" + message="Drop or %{linkStart}upload%{linkEnd} files to attach" /> </p> </div> @@ -258,15 +349,15 @@ exports[`Design management dropzone component when dragging renders correct temp accept="image/*" class="hide" multiple="multiple" - name="design_file" + name="upload_file" type="file" /> <transition-stub - name="design-dropzone-fade" + name="upload-dropzone-fade" > <div - class="card design-dropzone-border design-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white" + class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white" style="" > <div @@ -275,7 +366,9 @@ exports[`Design management dropzone component when dragging renders correct temp <h3 class="" > - Oh no! + + Oh no! + </h3> <span> @@ -290,11 +383,13 @@ exports[`Design management dropzone component when dragging renders correct temp <h3 class="" > - Incoming! + + Incoming! + </h3> <span> - Drop your designs to start your upload. + Drop your files to start your upload. </span> </div> </div> @@ -302,12 +397,12 @@ exports[`Design management dropzone component when dragging renders correct temp </div> `; -exports[`Design management dropzone component when dragging renders correct template when dragging stops 1`] = ` +exports[`Upload dropzone component when dragging renders correct template when dragging stops 1`] = ` <div class="gl-w-full gl-relative" > <button - class="card design-dropzone-card design-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3" + class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3" > <div class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column" @@ -323,7 +418,7 @@ exports[`Design management dropzone component when dragging renders correct temp class="gl-mb-0" > <gl-sprintf-stub - message="Drop or %{linkStart}upload%{linkEnd} designs to attach" + message="Drop or %{linkStart}upload%{linkEnd} files to attach" /> </p> </div> @@ -333,15 +428,15 @@ exports[`Design management dropzone component when dragging renders correct temp accept="image/*" class="hide" multiple="multiple" - name="design_file" + name="upload_file" type="file" /> <transition-stub - name="design-dropzone-fade" + name="upload-dropzone-fade" > <div - class="card design-dropzone-border design-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white" + class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white" style="display: none;" > <div @@ -350,7 +445,9 @@ exports[`Design management dropzone component when dragging renders correct temp <h3 class="" > - Oh no! + + Oh no! + </h3> <span> @@ -365,11 +462,13 @@ exports[`Design management dropzone component when dragging renders correct temp <h3 class="" > - Incoming! + + Incoming! + </h3> <span> - Drop your designs to start your upload. + Drop your files to start your upload. </span> </div> </div> @@ -377,12 +476,12 @@ exports[`Design management dropzone component when dragging renders correct temp </div> `; -exports[`Design management dropzone component when no slot provided renders default dropzone card 1`] = ` +exports[`Upload dropzone component when no slot provided renders default dropzone card 1`] = ` <div class="gl-w-full gl-relative" > <button - class="card design-dropzone-card design-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3" + class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3" > <div class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column" @@ -398,7 +497,7 @@ exports[`Design management dropzone component when no slot provided renders defa class="gl-mb-0" > <gl-sprintf-stub - message="Drop or %{linkStart}upload%{linkEnd} designs to attach" + message="Drop or %{linkStart}upload%{linkEnd} files to attach" /> </p> </div> @@ -408,15 +507,15 @@ exports[`Design management dropzone component when no slot provided renders defa accept="image/*" class="hide" multiple="multiple" - name="design_file" + name="upload_file" type="file" /> <transition-stub - name="design-dropzone-fade" + name="upload-dropzone-fade" > <div - class="card design-dropzone-border design-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white" + class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white" style="display: none;" > <div @@ -425,7 +524,9 @@ exports[`Design management dropzone component when no slot provided renders defa <h3 class="" > - Oh no! + + Oh no! + </h3> <span> @@ -440,11 +541,13 @@ exports[`Design management dropzone component when no slot provided renders defa <h3 class="" > - Incoming! + + Incoming! + </h3> <span> - Drop your designs to start your upload. + Drop your files to start your upload. </span> </div> </div> @@ -452,7 +555,7 @@ exports[`Design management dropzone component when no slot provided renders defa </div> `; -exports[`Design management dropzone component when slot provided renders dropzone with slot content 1`] = ` +exports[`Upload dropzone component when slot provided renders dropzone with slot content 1`] = ` <div class="gl-w-full gl-relative" > @@ -461,10 +564,10 @@ exports[`Design management dropzone component when slot provided renders dropzon </div> <transition-stub - name="design-dropzone-fade" + name="upload-dropzone-fade" > <div - class="card design-dropzone-border design-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white" + class="card upload-dropzone-border upload-dropzone-overlay gl-w-full gl-h-full gl-absolute gl-display-flex gl-align-items-center gl-justify-content-center gl-p-3 gl-bg-white" style="display: none;" > <div @@ -473,7 +576,9 @@ exports[`Design management dropzone component when slot provided renders dropzon <h3 class="" > - Oh no! + + Oh no! + </h3> <span> @@ -488,11 +593,13 @@ exports[`Design management dropzone component when slot provided renders dropzon <h3 class="" > - Incoming! + + Incoming! + </h3> <span> - Drop your designs to start your upload. + Drop your files to start your upload. </span> </div> </div> diff --git a/spec/frontend/design_management/components/upload/design_dropzone_spec.js b/spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js index 3c6adf56bbc..11982eb513d 100644 --- a/spec/frontend/design_management/components/upload/design_dropzone_spec.js +++ b/spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js @@ -1,26 +1,25 @@ import { shallowMount } from '@vue/test-utils'; import { GlIcon } from '@gitlab/ui'; -import DesignDropzone from '~/design_management/components/upload/design_dropzone.vue'; -import createFlash from '~/flash'; +import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue'; jest.mock('~/flash'); -describe('Design management dropzone component', () => { +describe('Upload dropzone component', () => { let wrapper; const mockDragEvent = ({ types = ['Files'], files = [] }) => { return { dataTransfer: { types, files } }; }; - const findDropzoneCard = () => wrapper.find('.design-dropzone-card'); + const findDropzoneCard = () => wrapper.find('.upload-dropzone-card'); const findDropzoneArea = () => wrapper.find('[data-testid="dropzone-area"]'); const findIcon = () => wrapper.find(GlIcon); function createComponent({ slots = {}, data = {}, props = {} } = {}) { - wrapper = shallowMount(DesignDropzone, { + wrapper = shallowMount(UploadDropzone, { slots, propsData: { - hasDesigns: true, + displayAsCard: true, ...props, }, data() { @@ -126,28 +125,50 @@ describe('Design management dropzone component', () => { expect(wrapper.emitted().change[0]).toEqual([[mockFile]]); }); - it('calls createFlash when files are invalid', () => { + it('emits error event when files are invalid', () => { createComponent({ data: mockData }); + const mockEvent = mockDragEvent({ files: [{ type: 'audio/midi' }] }); + + wrapper.vm.ondrop(mockEvent); + expect(wrapper.emitted()).toHaveProperty('error'); + }); + + it('allows validation function to be overwritten', () => { + createComponent({ data: mockData, props: { isFileValid: () => true } }); const mockEvent = mockDragEvent({ files: [{ type: 'audio/midi' }] }); wrapper.vm.ondrop(mockEvent); - expect(createFlash).toHaveBeenCalledTimes(1); + expect(wrapper.emitted()).not.toHaveProperty('error'); }); }); }); - it('applies correct classes when there are no designs or no design saving loader', () => { - createComponent({ props: { hasDesigns: false } }); + it('applies correct classes when displaying as a standalone item', () => { + createComponent({ props: { displayAsCard: false } }); expect(findDropzoneArea().classes()).not.toContain('gl-flex-direction-column'); expect(findIcon().classes()).toEqual(['gl-mr-3', 'gl-text-gray-500']); expect(findIcon().props('size')).toBe(16); }); - it('applies correct classes when there are designs or design saving loader', () => { - createComponent({ props: { hasDesigns: true } }); + it('applies correct classes when displaying in card mode', () => { + createComponent({ props: { displayAsCard: true } }); expect(findDropzoneArea().classes()).toContain('gl-flex-direction-column'); expect(findIcon().classes()).toEqual(['gl-mb-2']); expect(findIcon().props('size')).toBe(24); }); + + it('correctly overrides description and drop messages', () => { + createComponent({ + props: { + dropToStartMessage: 'Test drop-to-start message.', + validFileMimetypes: ['image/jpg', 'image/jpeg'], + }, + slots: { + 'upload-text': '<span>Test %{linkStart}description%{linkEnd} message.</span>', + }, + }); + + expect(wrapper.element).toMatchSnapshot(); + }); }); diff --git a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb index a3799fea48a..81764d252c2 100644 --- a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb @@ -277,29 +277,23 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s end end - context 'aggregated metrics' do + context 'aggregated_metrics_data' do let(:known_events) do [ { name: 'event1_slot', redis_slot: "slot", category: 'category1', aggregation: "weekly" }, { name: 'event2_slot', redis_slot: "slot", category: 'category2', aggregation: "weekly" }, - { name: 'event3', category: 'category2', aggregation: "weekly" } - ].map(&:with_indifferent_access) - end - - let(:aggregated_metrics) do - [ - { name: 'gmau_1', events: %w[event1_slot event2_slot], operator: "ANY" }, - { name: 'gmau_2', events: %w[event3], operator: "ANY" } + { name: 'event3_slot', redis_slot: "slot", category: 'category3', aggregation: "weekly" }, + { name: 'event5_slot', redis_slot: "slot", category: 'category4', aggregation: "weekly" }, + { name: 'event4', category: 'category2', aggregation: "weekly" } ].map(&:with_indifferent_access) end before do allow(described_class).to receive(:known_events).and_return(known_events) - allow(described_class).to receive(:aggregated_metrics).and_return(aggregated_metrics) end shared_examples 'aggregated_metrics_data' do - context 'no combination is tracked' do + context 'no aggregated metrics is defined' do it 'returns empty hash' do allow(described_class).to receive(:aggregated_metrics).and_return([]) @@ -307,14 +301,51 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s end end - context 'there are some combinations defined' do - it 'returns the number of unique events for all known events' do - results = { - 'gmau_1' => 2, - 'gmau_2' => 3 - } + context 'there are aggregated metrics defined' do + before do + allow(described_class).to receive(:aggregated_metrics).and_return(aggregated_metrics) + end + + context 'with ALL operator' do + let(:aggregated_metrics) do + [ + { name: 'gmau_1', events: %w[event1_slot event2_slot], operator: "ALL" }, + { name: 'gmau_2', events: %w[event1_slot event2_slot event3_slot], operator: "ALL" }, + { name: 'gmau_3', events: %w[event1_slot event2_slot event3_slot event5_slot], operator: "ALL" }, + { name: 'gmau_4', events: %w[event4], operator: "ALL" } + ].map(&:with_indifferent_access) + end - expect(aggregated_metrics_data).to eq(results) + it 'returns the number of unique events for all known events' do + results = { + 'gmau_1' => 3, + 'gmau_2' => 2, + 'gmau_3' => 1, + 'gmau_4' => 3 + } + + expect(aggregated_metrics_data).to eq(results) + end + end + + context 'with ANY operator' do + let(:aggregated_metrics) do + [ + { name: 'gmau_1', events: %w[event3_slot event5_slot], operator: "ANY" }, + { name: 'gmau_2', events: %w[event1_slot event2_slot event3_slot event5_slot], operator: "ANY" }, + { name: 'gmau_3', events: %w[event4], operator: "ANY" } + ].map(&:with_indifferent_access) + end + + it 'returns the number of unique events for all known events' do + results = { + 'gmau_1' => 2, + 'gmau_2' => 3, + 'gmau_3' => 3 + } + + expect(aggregated_metrics_data).to eq(results) + end end end end @@ -324,16 +355,22 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s before do described_class.track_event(entity1, 'event1_slot', 2.days.ago) + described_class.track_event(entity2, 'event1_slot', 2.days.ago) + described_class.track_event(entity3, 'event1_slot', 2.days.ago) described_class.track_event(entity1, 'event2_slot', 2.days.ago) + described_class.track_event(entity2, 'event2_slot', 3.days.ago) described_class.track_event(entity3, 'event2_slot', 3.days.ago) + described_class.track_event(entity1, 'event3_slot', 3.days.ago) + described_class.track_event(entity2, 'event3_slot', 3.days.ago) + described_class.track_event(entity2, 'event5_slot', 3.days.ago) # events out of time scope described_class.track_event(entity3, 'event2_slot', 8.days.ago) # events in different slots - described_class.track_event(entity1, 'event3', 2.days.ago) - described_class.track_event(entity2, 'event3', 2.days.ago) - described_class.track_event(entity4, 'event3', 2.days.ago) + described_class.track_event(entity1, 'event4', 2.days.ago) + described_class.track_event(entity2, 'event4', 2.days.ago) + described_class.track_event(entity4, 'event4', 2.days.ago) end it_behaves_like 'aggregated_metrics_data' @@ -342,21 +379,58 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s describe '.aggregated_metrics_monthly_data' do subject(:aggregated_metrics_data) { described_class.aggregated_metrics_monthly_data } - before do - described_class.track_event(entity1, 'event1_slot', 2.days.ago) - described_class.track_event(entity1, 'event2_slot', 10.days.ago) - described_class.track_event(entity3, 'event2_slot', 4.weeks.ago.advance(days: 1)) + it_behaves_like 'aggregated_metrics_data' do + before do + described_class.track_event(entity1, 'event1_slot', 2.days.ago) + described_class.track_event(entity2, 'event1_slot', 2.days.ago) + described_class.track_event(entity3, 'event1_slot', 2.days.ago) + described_class.track_event(entity1, 'event2_slot', 2.days.ago) + described_class.track_event(entity2, 'event2_slot', 3.days.ago) + described_class.track_event(entity3, 'event2_slot', 3.days.ago) + described_class.track_event(entity1, 'event3_slot', 3.days.ago) + described_class.track_event(entity2, 'event3_slot', 10.days.ago) + described_class.track_event(entity2, 'event5_slot', 4.weeks.ago.advance(days: 1)) + + # events out of time scope + described_class.track_event(entity1, 'event5_slot', 4.weeks.ago.advance(days: -1)) + + # events in different slots + described_class.track_event(entity1, 'event4', 2.days.ago) + described_class.track_event(entity2, 'event4', 2.days.ago) + described_class.track_event(entity4, 'event4', 2.days.ago) + end + end - # events out of time scope - described_class.track_event(entity3, 'event2_slot', 4.weeks.ago.advance(days: -1)) + context 'Redis calls' do + let(:aggregated_metrics) do + [ + { name: 'gmau_3', events: %w[event1_slot event2_slot event3_slot event5_slot], operator: "ALL" } + ].map(&:with_indifferent_access) + end - # events in different slots - described_class.track_event(entity1, 'event3', 2.days.ago) - described_class.track_event(entity2, 'event3', 2.days.ago) - described_class.track_event(entity4, 'event3', 2.days.ago) - end + let(:known_events) do + [ + { name: 'event1_slot', redis_slot: "slot", category: 'category1', aggregation: "weekly" }, + { name: 'event2_slot', redis_slot: "slot", category: 'category2', aggregation: "weekly" }, + { name: 'event3_slot', redis_slot: "slot", category: 'category3', aggregation: "weekly" }, + { name: 'event5_slot', redis_slot: "slot", category: 'category4', aggregation: "weekly" } + ].map(&:with_indifferent_access) + end - it_behaves_like 'aggregated_metrics_data' + it 'caches intermediate operations' do + allow(described_class).to receive(:known_events).and_return(known_events) + allow(described_class).to receive(:aggregated_metrics).and_return(aggregated_metrics) + + 4.downto(1) do |subset_size| + known_events.combination(subset_size).each do |events| + keys = described_class.send(:weekly_redis_keys, events: events, start_date: 4.weeks.ago.to_date, end_date: Date.current) + expect(Gitlab::Redis::HLL).to receive(:count).with(keys: keys).once.and_return(0) + end + end + + subject + end + end end end end |
