Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-08-11 06:09:21 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-08-11 06:09:21 +0300
commit8754d20bbb9e573d48e80d7f6aed1ded40a40263 (patch)
tree217d3e06689c37e8626da9a2c79e40ab997d8e2b /spec
parent4f1e40017d9eadb0abeeb89d9690a8e5f0694fd9 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/factories/users/project_user_callouts.rb10
-rw-r--r--spec/frontend/__helpers__/vue_test_utils_helper.js19
-rw-r--r--spec/frontend/__helpers__/vue_test_utils_helper_spec.js57
-rw-r--r--spec/frontend/runner/components/runner_bulk_delete_spec.js5
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/models/project_spec.rb1
-rw-r--r--spec/models/user_spec.rb79
-rw-r--r--spec/models/users/project_callout_spec.rb23
-rw-r--r--spec/requests/users/project_callouts_spec.rb58
-rw-r--r--spec/scripts/lib/glfm/update_example_snapshots_spec.rb12
-rw-r--r--spec/services/users/dismiss_project_callout_service_spec.rb25
11 files changed, 278 insertions, 12 deletions
diff --git a/spec/factories/users/project_user_callouts.rb b/spec/factories/users/project_user_callouts.rb
new file mode 100644
index 00000000000..50e85315bb9
--- /dev/null
+++ b/spec/factories/users/project_user_callouts.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :project_callout, class: 'Users::ProjectCallout' do
+ feature_name { :awaiting_members_banner }
+
+ user
+ project
+ end
+end
diff --git a/spec/frontend/__helpers__/vue_test_utils_helper.js b/spec/frontend/__helpers__/vue_test_utils_helper.js
index 2aae91f8a39..75bd5df8cbf 100644
--- a/spec/frontend/__helpers__/vue_test_utils_helper.js
+++ b/spec/frontend/__helpers__/vue_test_utils_helper.js
@@ -7,6 +7,20 @@ const vNodeContainsText = (vnode, text) =>
(vnode.children && vnode.children.filter((child) => vNodeContainsText(child, text)).length);
/**
+ * Create a VTU wrapper from an element.
+ *
+ * If a Vue instance manages the element, the wrapper is created
+ * with that Vue instance.
+ *
+ * @param {HTMLElement} element
+ * @param {Object} options
+ * @returns VTU wrapper
+ */
+const createWrapperFromElement = (element, options) =>
+ // eslint-disable-next-line no-underscore-dangle
+ createWrapper(element.__vue__ || element, options || {});
+
+/**
* Determines whether a `shallowMount` Wrapper contains text
* within one of it's slots. This will also work on Wrappers
* acquired with `find()`, but only if it's parent Wrapper
@@ -85,8 +99,7 @@ export const extendedWrapper = (wrapper) => {
if (!elements.length) {
return new ErrorWrapper(query);
}
-
- return createWrapper(elements[0], this.options || {});
+ return createWrapperFromElement(elements[0], this.options);
},
},
};
@@ -104,7 +117,7 @@ export const extendedWrapper = (wrapper) => {
);
const wrappers = elements.map((element) => {
- const elementWrapper = createWrapper(element, this.options || {});
+ const elementWrapper = createWrapperFromElement(element, this.options);
elementWrapper.selector = text;
return elementWrapper;
diff --git a/spec/frontend/__helpers__/vue_test_utils_helper_spec.js b/spec/frontend/__helpers__/vue_test_utils_helper_spec.js
index 3bb228f94b8..ae180c3b49d 100644
--- a/spec/frontend/__helpers__/vue_test_utils_helper_spec.js
+++ b/spec/frontend/__helpers__/vue_test_utils_helper_spec.js
@@ -6,6 +6,7 @@ import {
WrapperArray as VTUWrapperArray,
ErrorWrapper as VTUErrorWrapper,
} from '@vue/test-utils';
+import Vue from 'vue';
import {
extendedWrapper,
shallowMountExtended,
@@ -139,9 +140,12 @@ describe('Vue test utils helpers', () => {
const text = 'foo bar';
const options = { selector: 'div' };
const mockDiv = document.createElement('div');
+ const mockVm = new Vue({ render: (h) => h('div') }).$mount();
let wrapper;
beforeEach(() => {
+ jest.spyOn(vtu, 'createWrapper');
+
wrapper = extendedWrapper(
shallowMount({
template: `<div>foo bar</div>`,
@@ -164,7 +168,6 @@ describe('Vue test utils helpers', () => {
describe('when element is found', () => {
beforeEach(() => {
jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => [mockDiv]);
- jest.spyOn(vtu, 'createWrapper');
});
it('returns a VTU wrapper', () => {
@@ -172,14 +175,27 @@ describe('Vue test utils helpers', () => {
expect(vtu.createWrapper).toHaveBeenCalledWith(mockDiv, wrapper.options);
expect(result).toBeInstanceOf(VTUWrapper);
+ expect(result.vm).toBeUndefined();
});
});
+ describe('when a Vue instance element is found', () => {
+ beforeEach(() => {
+ jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => [mockVm.$el]);
+ });
+
+ it('returns a VTU wrapper', () => {
+ const result = wrapper[findMethod](text, options);
+
+ expect(vtu.createWrapper).toHaveBeenCalledWith(mockVm, wrapper.options);
+ expect(result).toBeInstanceOf(VTUWrapper);
+ expect(result.vm).toBeInstanceOf(Vue);
+ });
+ });
describe('when multiple elements are found', () => {
beforeEach(() => {
const mockSpan = document.createElement('span');
jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => [mockDiv, mockSpan]);
- jest.spyOn(vtu, 'createWrapper');
});
it('returns the first element as a VTU wrapper', () => {
@@ -187,6 +203,24 @@ describe('Vue test utils helpers', () => {
expect(vtu.createWrapper).toHaveBeenCalledWith(mockDiv, wrapper.options);
expect(result).toBeInstanceOf(VTUWrapper);
+ expect(result.vm).toBeUndefined();
+ });
+ });
+
+ describe('when multiple Vue instances are found', () => {
+ beforeEach(() => {
+ const mockVm2 = new Vue({ render: (h) => h('span') }).$mount();
+ jest
+ .spyOn(testingLibrary, expectedQuery)
+ .mockImplementation(() => [mockVm.$el, mockVm2.$el]);
+ });
+
+ it('returns the first element as a VTU wrapper', () => {
+ const result = wrapper[findMethod](text, options);
+
+ expect(vtu.createWrapper).toHaveBeenCalledWith(mockVm, wrapper.options);
+ expect(result).toBeInstanceOf(VTUWrapper);
+ expect(result.vm).toBeInstanceOf(Vue);
});
});
@@ -211,12 +245,17 @@ describe('Vue test utils helpers', () => {
${'findAllByAltText'} | ${'queryAllByAltText'}
`('$findMethod', ({ findMethod, expectedQuery }) => {
const text = 'foo bar';
- const options = { selector: 'div' };
+ const options = { selector: 'li' };
const mockElements = [
document.createElement('li'),
document.createElement('li'),
document.createElement('li'),
];
+ const mockVms = [
+ new Vue({ render: (h) => h('li') }).$mount(),
+ new Vue({ render: (h) => h('li') }).$mount(),
+ new Vue({ render: (h) => h('li') }).$mount(),
+ ];
let wrapper;
beforeEach(() => {
@@ -245,9 +284,13 @@ describe('Vue test utils helpers', () => {
);
});
- describe('when elements are found', () => {
+ describe.each`
+ case | mockResult | isVueInstance
+ ${'HTMLElements'} | ${mockElements} | ${false}
+ ${'Vue instance elements'} | ${mockVms} | ${true}
+ `('when $case are found', ({ mockResult, isVueInstance }) => {
beforeEach(() => {
- jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => mockElements);
+ jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => mockResult);
});
it('returns a VTU wrapper array', () => {
@@ -257,7 +300,9 @@ describe('Vue test utils helpers', () => {
expect(
result.wrappers.every(
(resultWrapper) =>
- resultWrapper instanceof VTUWrapper && resultWrapper.options === wrapper.options,
+ resultWrapper instanceof VTUWrapper &&
+ resultWrapper.vm instanceof Vue === isVueInstance &&
+ resultWrapper.options === wrapper.options,
),
).toBe(true);
expect(result.length).toBe(3);
diff --git a/spec/frontend/runner/components/runner_bulk_delete_spec.js b/spec/frontend/runner/components/runner_bulk_delete_spec.js
index f5b56396cf1..cc679a52b34 100644
--- a/spec/frontend/runner/components/runner_bulk_delete_spec.js
+++ b/spec/frontend/runner/components/runner_bulk_delete_spec.js
@@ -2,6 +2,7 @@ import Vue from 'vue';
import { GlSprintf } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { s__ } from '~/locale';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import RunnerBulkDelete from '~/runner/components/runner_bulk_delete.vue';
import createMockApollo from 'helpers/mock_apollo_helper';
@@ -17,8 +18,8 @@ describe('RunnerBulkDelete', () => {
let mockState;
let mockCheckedRunnerIds;
- const findClearBtn = () => wrapper.findByTestId('clear-btn');
- const findDeleteBtn = () => wrapper.findByTestId('delete-btn');
+ const findClearBtn = () => wrapper.findByText(s__('Runners|Clear selection'));
+ const findDeleteBtn = () => wrapper.findByText(s__('Runners|Delete selected'));
const createComponent = () => {
const { cacheConfig, localMutations } = mockState;
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index a324fd70424..4ffde842819 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -627,6 +627,7 @@ project:
- security_trainings
- vulnerability_reads
- build_artifacts_size_refresh
+- project_callouts
award_emoji:
- awardable
- user
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index afb321c0777..2920278679f 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -147,6 +147,7 @@ RSpec.describe Project, factory_default: :keep do
it { is_expected.to have_many(:build_trace_chunks).through(:builds).dependent(:restrict_with_error) }
it { is_expected.to have_many(:secure_files).class_name('Ci::SecureFile').dependent(:restrict_with_error) }
it { is_expected.to have_one(:build_artifacts_size_refresh).class_name('Projects::BuildArtifactsSizeRefresh') }
+ it { is_expected.to have_many(:project_callouts).class_name('Users::ProjectCallout').with_foreign_key(:project_id) }
# GitLab Pages
it { is_expected.to have_many(:pages_domains) }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 12029b4151d..01b6b36db77 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -137,6 +137,7 @@ RSpec.describe User do
it { is_expected.to have_many(:callouts).class_name('Users::Callout') }
it { is_expected.to have_many(:group_callouts).class_name('Users::GroupCallout') }
it { is_expected.to have_many(:namespace_callouts).class_name('Users::NamespaceCallout') }
+ it { is_expected.to have_many(:project_callouts).class_name('Users::ProjectCallout') }
describe '#user_detail' do
it 'does not persist `user_detail` by default' do
@@ -6671,6 +6672,40 @@ RSpec.describe User do
end
end
+ describe '#dismissed_callout_for_project?' do
+ let_it_be(:user, refind: true) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:feature_name) { Users::ProjectCallout.feature_names.each_key.first }
+
+ context 'when no callout dismissal record exists' do
+ it 'returns false when no ignore_dismissal_earlier_than provided' do
+ expect(user.dismissed_callout_for_project?(feature_name: feature_name, project: project)).to eq false
+ end
+ end
+
+ context 'when dismissed callout exists' do
+ before_all do
+ create(:project_callout,
+ user: user,
+ project_id: project.id,
+ feature_name: feature_name,
+ dismissed_at: 4.months.ago)
+ end
+
+ it 'returns true when no ignore_dismissal_earlier_than provided' do
+ expect(user.dismissed_callout_for_project?(feature_name: feature_name, project: project)).to eq true
+ end
+
+ it 'returns true when ignore_dismissal_earlier_than is earlier than dismissed_at' do
+ expect(user.dismissed_callout_for_project?(feature_name: feature_name, project: project, ignore_dismissal_earlier_than: 6.months.ago)).to eq true
+ end
+
+ it 'returns false when ignore_dismissal_earlier_than is later than dismissed_at' do
+ expect(user.dismissed_callout_for_project?(feature_name: feature_name, project: project, ignore_dismissal_earlier_than: 3.months.ago)).to eq false
+ end
+ end
+ end
+
describe '#find_or_initialize_group_callout' do
let_it_be(:user, refind: true) { create(:user) }
let_it_be(:group) { create(:group) }
@@ -6715,6 +6750,50 @@ RSpec.describe User do
end
end
+ describe '#find_or_initialize_project_callout' do
+ let_it_be(:user, refind: true) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:feature_name) { Users::ProjectCallout.feature_names.each_key.first }
+
+ subject(:callout_with_source) do
+ user.find_or_initialize_project_callout(feature_name, project.id)
+ end
+
+ context 'when callout exists' do
+ let!(:callout) do
+ create(:project_callout, user: user, feature_name: feature_name, project_id: project.id)
+ end
+
+ it 'returns existing callout' do
+ expect(callout_with_source).to eq(callout)
+ end
+ end
+
+ context 'when callout does not exist' do
+ context 'when feature name is valid' do
+ it 'initializes a new callout' do
+ expect(callout_with_source).to be_a_new(Users::ProjectCallout)
+ end
+
+ it 'is valid' do
+ expect(callout_with_source).to be_valid
+ end
+ end
+
+ context 'when feature name is not valid' do
+ let(:feature_name) { 'notvalid' }
+
+ it 'initializes a new callout' do
+ expect(callout_with_source).to be_a_new(Users::ProjectCallout)
+ end
+
+ it 'is not valid' do
+ expect(callout_with_source).not_to be_valid
+ end
+ end
+ end
+ end
+
describe '#hook_attrs' do
let(:user) { create(:user) }
let(:user_attributes) do
diff --git a/spec/models/users/project_callout_spec.rb b/spec/models/users/project_callout_spec.rb
new file mode 100644
index 00000000000..214568b4de3
--- /dev/null
+++ b/spec/models/users/project_callout_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Users::ProjectCallout do
+ let_it_be(:user) { create_default(:user) }
+ let_it_be(:project) { create_default(:project) }
+ let_it_be(:callout) { create(:project_callout) }
+
+ it_behaves_like 'having unique enum values'
+
+ describe 'relationships' do
+ it { is_expected.to belong_to(:project) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:project) }
+ it { is_expected.to validate_presence_of(:feature_name) }
+ it {
+ is_expected.to validate_uniqueness_of(:feature_name).scoped_to(:user_id, :project_id).ignoring_case_sensitivity
+ }
+ end
+end
diff --git a/spec/requests/users/project_callouts_spec.rb b/spec/requests/users/project_callouts_spec.rb
new file mode 100644
index 00000000000..98c00fef052
--- /dev/null
+++ b/spec/requests/users/project_callouts_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Project callouts' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+
+ before do
+ sign_in(user)
+ end
+
+ describe 'POST /-/users/project_callouts' do
+ let(:params) { { feature_name: feature_name, project_id: project.id } }
+
+ subject { post project_callouts_path, params: params, headers: { 'ACCEPT' => 'application/json' } }
+
+ context 'with valid feature name and project' do
+ let(:feature_name) { Users::ProjectCallout.feature_names.each_key.first }
+
+ context 'when callout entry does not exist' do
+ it 'creates a callout entry with dismissed state' do
+ expect { subject }.to change { Users::ProjectCallout.count }.by(1)
+ end
+
+ it 'returns success' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'when callout entry already exists' do
+ let!(:callout) do
+ create(:project_callout,
+ feature_name: Users::ProjectCallout.feature_names.each_key.first,
+ user: user,
+ project: project)
+ end
+
+ it 'returns success', :aggregate_failures do
+ expect { subject }.not_to change { Users::ProjectCallout.count }
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+
+ context 'with invalid feature name' do
+ let(:feature_name) { 'bogus_feature_name' }
+
+ it 'returns bad request' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
+end
diff --git a/spec/scripts/lib/glfm/update_example_snapshots_spec.rb b/spec/scripts/lib/glfm/update_example_snapshots_spec.rb
index 149a384d31e..fe815aa6f1e 100644
--- a/spec/scripts/lib/glfm/update_example_snapshots_spec.rb
+++ b/spec/scripts/lib/glfm/update_example_snapshots_spec.rb
@@ -65,13 +65,19 @@ RSpec.describe Glfm::UpdateExampleSnapshots, '#process' do
## Strong
+ This example doesn't have an extension after the `example` keyword, so its
+ `source_specification` will be `commonmark`.
+
```````````````````````````````` example
__bold__
.
<p><strong>bold</strong></p>
````````````````````````````````
- ```````````````````````````````` example strong
+ This example has an extension after the `example` keyword, so its
+ `source_specification` will be `github`.
+
+ ```````````````````````````````` example some_extension_name
__bold with more text__
.
<p><strong>bold with more text</strong></p>
@@ -132,6 +138,10 @@ RSpec.describe Glfm::UpdateExampleSnapshots, '#process' do
## Strong but with HTML
+ This example has the `gitlab` keyword after the `example` keyword, so its
+ `source_specification` will be `gitlab`.
+
+
```````````````````````````````` example gitlab strong
<strong>
bold
diff --git a/spec/services/users/dismiss_project_callout_service_spec.rb b/spec/services/users/dismiss_project_callout_service_spec.rb
new file mode 100644
index 00000000000..73e50a4c37d
--- /dev/null
+++ b/spec/services/users/dismiss_project_callout_service_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Users::DismissProjectCalloutService do
+ describe '#execute' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+
+ let(:params) { { feature_name: feature_name, project_id: project.id } }
+ let(:feature_name) { Users::ProjectCallout.feature_names.each_key.first }
+
+ subject(:execute) do
+ described_class.new(
+ container: nil, current_user: user, params: params
+ ).execute
+ end
+
+ it_behaves_like 'dismissing user callout', Users::ProjectCallout
+
+ it 'sets the project_id' do
+ expect(execute.project_id).to eq(project.id)
+ end
+ end
+end