diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-23 21:11:07 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-23 21:11:07 +0300 |
commit | 5e9fe672fa0eda6322bd392e8502d1886804bd07 (patch) | |
tree | e2dbccd4f9b92ead855d87ff4fcffaff4fe9fb72 /spec | |
parent | a7f478c9b1806a67ec9d991c3f54c242bb596f60 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
10 files changed, 501 insertions, 101 deletions
diff --git a/spec/factories/packages.rb b/spec/factories/packages.rb index cd9c8a8bfbb..d4993cc765a 100644 --- a/spec/factories/packages.rb +++ b/spec/factories/packages.rb @@ -112,7 +112,7 @@ FactoryBot.define do factory :npm_package do sequence(:name) { |n| "@#{project.root_namespace.path}/package-#{n}"} - version { '1.0.0' } + sequence(:version) { |n| "1.0.#{n}" } package_type { :npm } after :create do |package| diff --git a/spec/finders/packages/npm/package_finder_spec.rb b/spec/finders/packages/npm/package_finder_spec.rb index a995f3b96c4..99174e2dd3a 100644 --- a/spec/finders/packages/npm/package_finder_spec.rb +++ b/spec/finders/packages/npm/package_finder_spec.rb @@ -68,6 +68,20 @@ RSpec.describe ::Packages::Npm::PackageFinder do it { is_expected.to be_empty } end + + context 'with npm_presenter_queries_tuning disabled' do + before do + stub_feature_flags(npm_presenter_queries_tuning: false) + end + + it_behaves_like 'finding packages by name' + + context 'set to nil' do + let(:project) { nil } + + it { is_expected.to be_empty } + end + end end context 'with a namespace' do @@ -80,6 +94,20 @@ RSpec.describe ::Packages::Npm::PackageFinder do it { is_expected.to be_empty } end + + context 'with npm_presenter_queries_tuning disabled' do + before do + stub_feature_flags(npm_presenter_queries_tuning: false) + end + + it_behaves_like 'accepting a namespace for', 'finding packages by name' + + context 'set to nil' do + let_it_be(:namespace) { nil } + + it { is_expected.to be_empty } + end + end end end @@ -109,6 +137,24 @@ RSpec.describe ::Packages::Npm::PackageFinder do it_behaves_like 'accepting a namespace for', 'finding packages by version' end + + context 'with npm_presenter_queries_tuning disabled' do + before do + stub_feature_flags(npm_presenter_queries_tuning: false) + end + + context 'with a project' do + let(:finder) { described_class.new(package_name, project: project) } + + it_behaves_like 'finding packages by version' + end + + context 'with a namespace' do + let(:finder) { described_class.new(package_name, namespace: namespace) } + + it_behaves_like 'accepting a namespace for', 'finding packages by version' + end + end end describe '#last' do @@ -118,31 +164,43 @@ RSpec.describe ::Packages::Npm::PackageFinder do it { is_expected.to eq(package) } end - context 'with a project' do - let(:finder) { described_class.new(package_name, project: project) } + shared_examples 'handling project or namespace parameter' do + context 'with a project' do + let(:finder) { described_class.new(package_name, project: project) } - it_behaves_like 'finding package by last' - end + it_behaves_like 'finding package by last' + end - context 'with a namespace' do - let(:finder) { described_class.new(package_name, namespace: namespace) } + context 'with a namespace' do + let(:finder) { described_class.new(package_name, namespace: namespace) } - it_behaves_like 'accepting a namespace for', 'finding package by last' + it_behaves_like 'accepting a namespace for', 'finding package by last' - context 'with duplicate packages' do - let_it_be(:namespace) { create(:group) } - let_it_be(:subgroup1) { create(:group, parent: namespace) } - let_it_be(:subgroup2) { create(:group, parent: namespace) } - let_it_be(:project2) { create(:project, namespace: subgroup2) } - let_it_be(:package2) { create(:npm_package, name: package.name, project: project2) } + context 'with duplicate packages' do + let_it_be(:namespace) { create(:group) } + let_it_be(:subgroup1) { create(:group, parent: namespace) } + let_it_be(:subgroup2) { create(:group, parent: namespace) } + let_it_be(:project2) { create(:project, namespace: subgroup2) } + let_it_be(:package2) { create(:npm_package, name: package.name, project: project2) } - before do - project.update!(namespace: subgroup1) + before do + project.update!(namespace: subgroup1) + end + + # the most recent one is returned + it { is_expected.to eq(package2) } end + end + end - # the most recent one is returned - it { is_expected.to eq(package2) } + it_behaves_like 'handling project or namespace parameter' + + context 'with npm_presenter_queries_tuning disabled' do + before do + stub_feature_flags(npm_presenter_queries_tuning: false) end + + it_behaves_like 'handling project or namespace parameter' end end end diff --git a/spec/frontend/content_editor/services/markdown_serializer_spec.js b/spec/frontend/content_editor/services/markdown_serializer_spec.js index 24b3779057c..868473faa14 100644 --- a/spec/frontend/content_editor/services/markdown_serializer_spec.js +++ b/spec/frontend/content_editor/services/markdown_serializer_spec.js @@ -115,6 +115,64 @@ describe('markdownSerializer', () => { expect(serialize(paragraph('hello', hardBreak(), 'world'))).toBe('hello\\\nworld'); }); + it('correctly serializes a link', () => { + expect(serialize(paragraph(link({ href: 'https://example.com' }, 'example url')))).toBe( + '[example url](https://example.com)', + ); + }); + + it('correctly serializes a link with a title', () => { + expect( + serialize( + paragraph(link({ href: 'https://example.com', title: 'click this link' }, 'example url')), + ), + ).toBe('[example url](https://example.com "click this link")'); + }); + + it('correctly serializes a link with a canonicalSrc', () => { + expect( + serialize( + paragraph( + link( + { + href: '/uploads/abcde/file.zip', + canonicalSrc: 'file.zip', + title: 'click here to download', + }, + 'download file', + ), + ), + ), + ).toBe('[download file](file.zip "click here to download")'); + }); + + it('correctly serializes an image', () => { + expect(serialize(paragraph(image({ src: 'img.jpg', alt: 'foo bar' })))).toBe( + '![foo bar](img.jpg)', + ); + }); + + it('correctly serializes an image with a title', () => { + expect(serialize(paragraph(image({ src: 'img.jpg', title: 'baz', alt: 'foo bar' })))).toBe( + '![foo bar](img.jpg "baz")', + ); + }); + + it('correctly serializes an image with a canonicalSrc', () => { + expect( + serialize( + paragraph( + image({ + src: '/uploads/abcde/file.png', + alt: 'this is an image', + canonicalSrc: 'file.png', + title: 'foo bar baz', + }), + ), + ), + ).toBe('![this is an image](file.png "foo bar baz")'); + }); + it('correctly serializes a table with inline content', () => { expect( serialize( diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js index c8ac7ffc9d9..6f186ba3227 100644 --- a/spec/frontend/lib/utils/url_utility_spec.js +++ b/spec/frontend/lib/utils/url_utility_spec.js @@ -645,29 +645,6 @@ describe('URL utility', () => { }); }); - describe('urlParamsToObject', () => { - it('parses path for label with trailing +', () => { - // eslint-disable-next-line import/no-deprecated - expect(urlUtils.urlParamsToObject('label_name[]=label%2B', {})).toEqual({ - label_name: ['label+'], - }); - }); - - it('parses path for milestone with trailing +', () => { - // eslint-disable-next-line import/no-deprecated - expect(urlUtils.urlParamsToObject('milestone_title=A%2B', {})).toEqual({ - milestone_title: 'A+', - }); - }); - - it('parses path for search terms with spaces', () => { - // eslint-disable-next-line import/no-deprecated - expect(urlUtils.urlParamsToObject('search=two+words', {})).toEqual({ - search: 'two words', - }); - }); - }); - describe('queryToObject', () => { it.each` case | query | options | result diff --git a/spec/presenters/packages/npm/package_presenter_spec.rb b/spec/presenters/packages/npm/package_presenter_spec.rb index e524edaadc6..a8ecc809a77 100644 --- a/spec/presenters/packages/npm/package_presenter_spec.rb +++ b/spec/presenters/packages/npm/package_presenter_spec.rb @@ -5,62 +5,131 @@ require 'spec_helper' RSpec.describe ::Packages::Npm::PackagePresenter do let_it_be(:project) { create(:project) } let_it_be(:package_name) { "@#{project.root_namespace.path}/test" } + let_it_be(:package1) { create(:npm_package, version: '2.0.4', project: project, name: package_name) } + let_it_be(:package2) { create(:npm_package, version: '2.0.6', project: project, name: package_name) } + let_it_be(:latest_package) { create(:npm_package, version: '2.0.11', project: project, name: package_name) } - let!(:package1) { create(:npm_package, version: '1.0.4', project: project, name: package_name) } - let!(:package2) { create(:npm_package, version: '1.0.6', project: project, name: package_name) } - let!(:latest_package) { create(:npm_package, version: '1.0.11', project: project, name: package_name) } let(:packages) { project.packages.npm.with_name(package_name).last_of_each_version } let(:presenter) { described_class.new(package_name, packages) } describe '#versions' do subject { presenter.versions } - context 'for packages without dependencies' do - it { is_expected.to be_a(Hash) } - it { expect(subject[package1.version].with_indifferent_access).to match_schema('public_api/v4/packages/npm_package_version') } - it { expect(subject[package2.version].with_indifferent_access).to match_schema('public_api/v4/packages/npm_package_version') } + shared_examples 'returning packages versions' do |expect_n_plus_one: false| + context 'for packages without dependencies' do + it { is_expected.to be_a(Hash) } + it { expect(subject[package1.version].with_indifferent_access).to match_schema('public_api/v4/packages/npm_package_version') } + it { expect(subject[package2.version].with_indifferent_access).to match_schema('public_api/v4/packages/npm_package_version') } - described_class::NPM_VALID_DEPENDENCY_TYPES.each do |dependency_type| - it { expect(subject.dig(package1.version, dependency_type)).to be nil } - it { expect(subject.dig(package2.version, dependency_type)).to be nil } + ::Packages::DependencyLink.dependency_types.keys.each do |dependency_type| + it { expect(subject.dig(package1.version, dependency_type)).to be nil } + it { expect(subject.dig(package2.version, dependency_type)).to be nil } + end + + it 'avoids N+1 database queries' do + check_n_plus_one(:versions, expect_it: expect_n_plus_one) do + create_list(:npm_package, 5, project: project, name: package_name) + end + end end - end - context 'for packages with dependencies' do - described_class::NPM_VALID_DEPENDENCY_TYPES.each do |dependency_type| - let!("package_dependency_link_for_#{dependency_type}") { create(:packages_dependency_link, package: package1, dependency_type: dependency_type) } + context 'for packages with dependencies' do + ::Packages::DependencyLink.dependency_types.keys.each do |dependency_type| + let_it_be("package_dependency_link_for_#{dependency_type}") { create(:packages_dependency_link, package: package1, dependency_type: dependency_type) } + end + + it { is_expected.to be_a(Hash) } + it { expect(subject[package1.version].with_indifferent_access).to match_schema('public_api/v4/packages/npm_package_version') } + it { expect(subject[package2.version].with_indifferent_access).to match_schema('public_api/v4/packages/npm_package_version') } + ::Packages::DependencyLink.dependency_types.keys.each do |dependency_type| + it { expect(subject.dig(package1.version, dependency_type.to_s)).to be_any } + end + + it 'avoids N+1 database queries' do + check_n_plus_one(:versions, expect_it: expect_n_plus_one) do + create_list(:npm_package, 5, project: project, name: package_name).each do |npm_package| + ::Packages::DependencyLink.dependency_types.keys.each do |dependency_type| + create(:packages_dependency_link, package: npm_package, dependency_type: dependency_type) + end + end + end + end end + end + + it_behaves_like 'returning packages versions' - it { is_expected.to be_a(Hash) } - it { expect(subject[package1.version].with_indifferent_access).to match_schema('public_api/v4/packages/npm_package_version') } - it { expect(subject[package2.version].with_indifferent_access).to match_schema('public_api/v4/packages/npm_package_version') } - described_class::NPM_VALID_DEPENDENCY_TYPES.each do |dependency_type| - it { expect(subject.dig(package1.version, dependency_type.to_s)).to be_any } + context 'with npm_presenter_queries_tuning disabled' do + before do + stub_feature_flags(npm_presenter_queries_tuning: false) end + + it_behaves_like 'returning packages versions', expect_n_plus_one: true end end describe '#dist_tags' do subject { presenter.dist_tags } - context 'for packages without tags' do - it { is_expected.to be_a(Hash) } - it { expect(subject["latest"]).to eq(latest_package.version) } + shared_examples 'returning packages tags' do + context 'for packages without tags' do + it { is_expected.to be_a(Hash) } + it { expect(subject["latest"]).to eq(latest_package.version) } + + it 'avoids N+1 database queries' do + check_n_plus_one(:dist_tags) do + create_list(:npm_package, 5, project: project, name: package_name) + end + end + end + + context 'for packages with tags' do + let_it_be(:package_tag1) { create(:packages_tag, package: package1, name: 'release_a') } + let_it_be(:package_tag2) { create(:packages_tag, package: package1, name: 'test_release') } + let_it_be(:package_tag3) { create(:packages_tag, package: package2, name: 'release_b') } + let_it_be(:package_tag4) { create(:packages_tag, package: latest_package, name: 'release_c') } + let_it_be(:package_tag5) { create(:packages_tag, package: latest_package, name: 'latest') } + + it { is_expected.to be_a(Hash) } + it { expect(subject[package_tag1.name]).to eq(package1.version) } + it { expect(subject[package_tag2.name]).to eq(package1.version) } + it { expect(subject[package_tag3.name]).to eq(package2.version) } + it { expect(subject[package_tag4.name]).to eq(latest_package.version) } + it { expect(subject[package_tag5.name]).to eq(latest_package.version) } + + it 'avoids N+1 database queries' do + check_n_plus_one(:dist_tags) do + create_list(:npm_package, 5, project: project, name: package_name).each_with_index do |npm_package, index| + create(:packages_tag, package: npm_package, name: "tag_#{index}") + end + end + end + end end - context 'for packages with tags' do - let!(:package_tag1) { create(:packages_tag, package: package1, name: 'release_a') } - let!(:package_tag2) { create(:packages_tag, package: package1, name: 'test_release') } - let!(:package_tag3) { create(:packages_tag, package: package2, name: 'release_b') } - let!(:package_tag4) { create(:packages_tag, package: latest_package, name: 'release_c') } - let!(:package_tag5) { create(:packages_tag, package: latest_package, name: 'latest') } - - it { is_expected.to be_a(Hash) } - it { expect(subject[package_tag1.name]).to eq(package1.version) } - it { expect(subject[package_tag2.name]).to eq(package1.version) } - it { expect(subject[package_tag3.name]).to eq(package2.version) } - it { expect(subject[package_tag4.name]).to eq(latest_package.version) } - it { expect(subject[package_tag5.name]).to eq(latest_package.version) } + it_behaves_like 'returning packages tags' + + context 'with npm_presenter_queries_tuning disabled' do + before do + stub_feature_flags(npm_presenter_queries_tuning: false) + end + + it_behaves_like 'returning packages tags' + end + end + + def check_n_plus_one(field, expect_it: false) + pkgs = project.packages.npm.with_name(package_name).last_of_each_version.preload_files + control = ActiveRecord::QueryRecorder.new { described_class.new(package_name, pkgs).public_send(field) } + + yield + + pkgs = project.packages.npm.with_name(package_name).last_of_each_version.preload_files + + if expect_it + expect { described_class.new(package_name, pkgs).public_send(field) }.to exceed_query_limit(control) + else + expect { described_class.new(package_name, pkgs).public_send(field) }.not_to exceed_query_limit(control) end end end diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb index 125db58ed69..4f13a72e40c 100644 --- a/spec/requests/api/issues/issues_spec.rb +++ b/spec/requests/api/issues/issues_spec.rb @@ -9,15 +9,17 @@ RSpec.describe API::Issues do create(:project, :public, :repository, creator_id: user.id, namespace: user.namespace, merge_requests_access_level: ProjectFeature::PRIVATE) end - let(:user2) { create(:user) } - let(:non_member) { create(:user) } + let_it_be(:user2) { create(:user) } + let_it_be(:non_member) { create(:user) } let_it_be(:guest) { create(:user) } let_it_be(:author) { create(:author) } let_it_be(:assignee) { create(:assignee) } - let(:admin) { create(:user, :admin) } - let(:issue_title) { 'foo' } - let(:issue_description) { 'closed' } - let!(:closed_issue) do + let_it_be(:admin) { create(:user, :admin) } + + let_it_be(:milestone) { create(:milestone, title: '1.0.0', project: project) } + let_it_be(:empty_milestone) { create(:milestone, title: '2.0.0', project: project) } + + let_it_be(:closed_issue) do create :closed_issue, author: user, assignees: [user], @@ -29,7 +31,7 @@ RSpec.describe API::Issues do closed_at: 1.hour.ago end - let!(:confidential_issue) do + let_it_be(:confidential_issue) do create :issue, :confidential, project: project, @@ -39,7 +41,7 @@ RSpec.describe API::Issues do updated_at: 2.hours.ago end - let!(:issue) do + let_it_be(:issue) do create :issue, author: user, assignees: [user], @@ -47,21 +49,16 @@ RSpec.describe API::Issues do milestone: milestone, created_at: generate(:past_time), updated_at: 1.hour.ago, - title: issue_title, - description: issue_description + title: 'foo', + description: 'bar' end let_it_be(:label) do create(:label, title: 'label', color: '#FFAABB', project: project) end - let!(:label_link) { create(:label_link, label: label, target: issue) } - let(:milestone) { create(:milestone, title: '1.0.0', project: project) } - let_it_be(:empty_milestone) do - create(:milestone, title: '2.0.0', project: project) - end - - let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) } + let_it_be(:label_link) { create(:label_link, label: label, target: issue) } + let_it_be(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) } let(:no_milestone_title) { 'None' } let(:any_milestone_title) { 'Any' } @@ -711,8 +708,8 @@ RSpec.describe API::Issues do milestone: milestone, created_at: closed_issue.created_at, updated_at: 1.hour.ago, - title: issue_title, - description: issue_description + title: 'foo', + description: 'bar' end it 'page breaks first page correctly' do @@ -1001,13 +998,15 @@ RSpec.describe API::Issues do end describe 'DELETE /projects/:id/issues/:issue_iid' do + let(:issue_for_deletion) { create(:issue, author: user, assignees: [user], project: project) } + it 'rejects a non member from deleting an issue' do - delete api("/projects/#{project.id}/issues/#{issue.iid}", non_member) + delete api("/projects/#{project.id}/issues/#{issue_for_deletion.iid}", non_member) expect(response).to have_gitlab_http_status(:forbidden) end it 'rejects a developer from deleting an issue' do - delete api("/projects/#{project.id}/issues/#{issue.iid}", author) + delete api("/projects/#{project.id}/issues/#{issue_for_deletion.iid}", author) expect(response).to have_gitlab_http_status(:forbidden) end @@ -1016,13 +1015,13 @@ RSpec.describe API::Issues do let(:project) { create(:project, namespace: owner.namespace) } it 'deletes the issue if an admin requests it' do - delete api("/projects/#{project.id}/issues/#{issue.iid}", owner) + delete api("/projects/#{project.id}/issues/#{issue_for_deletion.iid}", owner) expect(response).to have_gitlab_http_status(:no_content) end it_behaves_like '412 response' do - let(:request) { api("/projects/#{project.id}/issues/#{issue.iid}", owner) } + let(:request) { api("/projects/#{project.id}/issues/#{issue_for_deletion.iid}", owner) } end end @@ -1035,7 +1034,7 @@ RSpec.describe API::Issues do end it 'returns 404 when using the issue ID instead of IID' do - delete api("/projects/#{project.id}/issues/#{issue.id}", user) + delete api("/projects/#{project.id}/issues/#{issue_for_deletion.id}", user) expect(response).to have_gitlab_http_status(:not_found) end diff --git a/spec/rubocop/cop/performance/active_record_subtransactions_spec.rb b/spec/rubocop/cop/performance/active_record_subtransactions_spec.rb new file mode 100644 index 00000000000..0da2e30062a --- /dev/null +++ b/spec/rubocop/cop/performance/active_record_subtransactions_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require_relative '../../../../rubocop/cop/performance/active_record_subtransactions' + +RSpec.describe RuboCop::Cop::Performance::ActiveRecordSubtransactions do + subject(:cop) { described_class.new } + + let(:message) { described_class::MSG } + + context 'when calling #transaction with only requires_new: true' do + it 'registers an offense' do + expect_offense(<<~RUBY) + ApplicationRecord.transaction(requires_new: true) do + ^^^^^^^^^^^^^^^^^^ #{message} + Project.create!(name: 'MyProject') + end + RUBY + end + end + + context 'when passing multiple arguments to #transaction, including requires_new: true' do + it 'registers an offense' do + expect_offense(<<~RUBY) + ApplicationRecord.transaction(isolation: :read_committed, requires_new: true) do + ^^^^^^^^^^^^^^^^^^ #{message} + Project.create!(name: 'MyProject') + end + RUBY + end + end + + context 'when calling #transaction with requires_new: false' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + ApplicationRecord.transaction(requires_new: false) do + Project.create!(name: 'MyProject') + end + RUBY + end + end + + context 'when calling #transaction with other options' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + ApplicationRecord.transaction(isolation: :read_committed) do + Project.create!(name: 'MyProject') + end + RUBY + end + end + + context 'when calling #transaction with no arguments' do + it 'does not register an offense' do + expect_no_offenses(<<~RUBY) + ApplicationRecord.transaction do + Project.create!(name: 'MyProject') + end + RUBY + end + end +end diff --git a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb index 0390e60747f..2af7b616659 100644 --- a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb @@ -21,11 +21,24 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project| expect(response).to match_response_schema('public_api/v4/packages/npm_package') expect(json_response['name']).to eq(package.name) expect(json_response['versions'][package.version]).to match_schema('public_api/v4/packages/npm_package_version') - ::Packages::Npm::PackagePresenter::NPM_VALID_DEPENDENCY_TYPES.each do |dependency_type| + ::Packages::DependencyLink.dependency_types.keys.each do |dependency_type| expect(json_response.dig('versions', package.version, dependency_type.to_s)).to be_any end expect(json_response['dist-tags']).to match_schema('public_api/v4/packages/npm_package_tags') end + + it 'avoids N+1 database queries' do + control = ActiveRecord::QueryRecorder.new { get(url, headers: headers) } + + create_list(:npm_package, 5, project: project, name: package_name).each do |npm_package| + ::Packages::DependencyLink.dependency_types.keys.each do |dependency_type| + create(:packages_dependency_link, package: package, dependency_type: dependency_type) + end + end + + # query count can slightly change between the examples so we're using a custom threshold + expect { get(url, headers: headers) }.not_to exceed_query_limit(control).with_threshold(4) + end end shared_examples 'reject metadata request' do |status:| diff --git a/spec/views/groups/group_members/index.html.haml_spec.rb b/spec/views/groups/group_members/index.html.haml_spec.rb new file mode 100644 index 00000000000..8e190c24495 --- /dev/null +++ b/spec/views/groups/group_members/index.html.haml_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'groups/group_members/index', :aggregate_failures do + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group) } + + before do + allow(view).to receive(:group_members_app_data).and_return({}) + allow(view).to receive(:current_user).and_return(user) + assign(:group, group) + assign(:group_member, build(:group_member, group: group)) + end + + context 'when user can invite members for the group' do + before do + group.add_owner(user) + end + + context 'when modal is enabled' do + it 'renders as expected' do + render + + expect(rendered).to have_content('Group members') + expect(rendered).to have_content('You can invite a new member') + + expect(rendered).to have_selector('.js-invite-group-trigger') + expect(rendered).to have_selector('.js-invite-members-trigger') + expect(response).to render_template(partial: 'groups/_invite_members_modal') + + expect(rendered).not_to have_selector('#invite-member-tab') + expect(rendered).not_to have_selector('#invite-group-tab') + expect(response).not_to render_template(partial: 'shared/members/_invite_group') + end + end + + context 'when modal is not enabled' do + before do + stub_feature_flags(invite_members_group_modal: false) + end + + it 'renders as expected' do + render + + expect(rendered).to have_content('Group members') + expect(rendered).to have_content('You can invite a new member') + + expect(rendered).to have_selector('#invite-member-tab') + expect(rendered).to have_selector('#invite-group-tab') + expect(response).to render_template(partial: 'shared/members/_invite_group') + + expect(rendered).not_to have_selector('.js-invite-group-trigger') + expect(rendered).not_to have_selector('.js-invite-members-trigger') + expect(response).not_to render_template(partial: 'groups/_invite_members_modal') + end + end + end + + context 'when user can not invite members for the group' do + it 'renders as expected', :aggregate_failures do + render + + expect(rendered).not_to have_content('Group members') + expect(rendered).not_to have_content('You can invite a new member') + end + end +end diff --git a/spec/views/projects/project_members/index.html.haml_spec.rb b/spec/views/projects/project_members/index.html.haml_spec.rb new file mode 100644 index 00000000000..ba255a474e2 --- /dev/null +++ b/spec/views/projects/project_members/index.html.haml_spec.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'projects/project_members/index', :aggregate_failures do + let_it_be(:user) { create(:user) } + let_it_be(:source) { create(:project, :empty_repo) } + let_it_be(:project) { ProjectPresenter.new(source, current_user: user) } + + before do + allow(view).to receive(:project_members_app_data_json).and_return({}) + allow(view).to receive(:current_user).and_return(user) + assign(:project, project) + assign(:project_member, build(:project_member, project: source)) + end + + context 'when user can invite members for the project' do + before do + project.add_maintainer(user) + end + + context 'when modal is enabled' do + it 'renders as expected' do + render + + expect(rendered).to have_content('Project members') + expect(rendered).to have_content('You can invite a new member') + expect(rendered).to have_link('Import a project') + expect(rendered).to have_selector('.js-invite-group-trigger') + expect(rendered).to have_selector('.js-invite-members-trigger') + expect(rendered).not_to have_content('Members can be added by project') + expect(response).to render_template(partial: 'projects/_invite_members_modal') + end + + context 'when project is not allowed to share with group' do + before do + project.namespace.share_with_group_lock = true + end + + it 'renders as expected' do + render + + expect(rendered).not_to have_selector('.js-invite-group-trigger') + end + end + end + + context 'when modal is not enabled' do + before do + stub_feature_flags(invite_members_group_modal: false) + end + + it 'renders as expected' do + render + + expect(rendered).to have_content('Project members') + expect(rendered).to have_content('You can invite a new member') + expect(rendered).not_to have_selector('.js-invite-group-trigger') + expect(rendered).not_to have_selector('.js-invite-members-trigger') + expect(rendered).not_to have_content('Members can be added by project') + expect(response).not_to render_template(partial: 'projects/_invite_members_modal') + expect(response).to render_template(partial: 'shared/members/_invite_member') + end + + context 'when project can not be shared' do + before do + project.namespace.share_with_group_lock = true + end + + it 'renders as expected' do + render + + expect(rendered).to have_content('Project members') + expect(rendered).to have_content('You can invite a new member') + expect(response).not_to render_template(partial: 'projects/_invite_members_modal') + end + end + end + end + + context 'when user can not invite members or group for the project' do + context 'when project can be shared' do + it 'renders as expected', :aggregate_failures do + render + + expect(rendered).to have_content('Project members') + expect(rendered).not_to have_content('You can invite a new member') + expect(rendered).not_to have_link('Import a project') + expect(rendered).not_to have_selector('.js-invite-group-trigger') + expect(rendered).not_to have_selector('.js-invite-members-trigger') + expect(rendered).to have_content('Members can be added by project') + expect(response).not_to render_template(partial: 'projects/_invite_members_modal') + end + end + end +end |