summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-11-10 09:08:45 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-11-10 09:08:45 +0000
commit01c201bc6a9b99e1f3095f4139110c6fd0cf7aa9 (patch)
tree7445a1fc4797d9f093c3b1352cf3889fadc6d967 /spec
parent552db97a0dfa486b751a808eb4e9fadc8b875e9c (diff)
downloadgitlab-ce-01c201bc6a9b99e1f3095f4139110c6fd0cf7aa9.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/projects/templates_controller_spec.rb97
-rw-r--r--spec/features/projects/issues/design_management/user_uploads_designs_spec.rb2
-rw-r--r--spec/frontend/api_spec.js58
-rw-r--r--spec/frontend/design_management/pages/__snapshots__/index_spec.js.snap266
-rw-r--r--spec/frontend/design_management/pages/index_spec.js14
-rw-r--r--spec/frontend/issue_show/components/header_actions_spec.js157
-rw-r--r--spec/frontend/pipeline_new/components/pipeline_new_form_spec.js22
-rw-r--r--spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js4
-rw-r--r--spec/frontend/static_site_editor/components/edit_meta_modal_spec.js36
-rw-r--r--spec/frontend/static_site_editor/pages/home_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js12
-rw-r--r--spec/frontend/vue_shared/components/upload_dropzone/__snapshots__/upload_dropzone_spec.js.snap (renamed from spec/frontend/design_management/components/upload/__snapshots__/design_dropzone_spec.js.snap)227
-rw-r--r--spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js (renamed from spec/frontend/design_management/components/upload/design_dropzone_spec.js)45
-rw-r--r--spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb140
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