diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-07-26 12:09:02 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-07-26 12:09:02 +0300 |
commit | d79bf171e4bcbb551a8320211ee337368b4d114c (patch) | |
tree | 63f4305ee043eb6ca4b6f2113d8067b388b68ed8 /spec | |
parent | 6723a4288d29b11beec2de92fb7cfd682c9dcc50 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
27 files changed, 481 insertions, 70 deletions
diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb index 151d3c60fa2..f2be85a4d0e 100644 --- a/spec/features/issues/user_creates_issue_spec.rb +++ b/spec/features/issues/user_creates_issue_spec.rb @@ -151,7 +151,7 @@ RSpec.describe "User creates issue" do click_button 'Cancel' end - expect(page).to have_button('Attach a file') + expect(page).to have_selector('[data-testid="button-attach-file"]') expect(page).not_to have_button('Cancel') expect(page).not_to have_selector('.uploading-progress-container', visible: true) end diff --git a/spec/features/markdown/copy_as_gfm_spec.rb b/spec/features/markdown/copy_as_gfm_spec.rb index d472134a2c7..b5bf9279371 100644 --- a/spec/features/markdown/copy_as_gfm_spec.rb +++ b/spec/features/markdown/copy_as_gfm_spec.rb @@ -109,10 +109,24 @@ RSpec.describe 'Copy as GFM', :js do <<~GFM, * [ ] Unchecked task * [x] Checked task + * [~] Inapplicable task + * [~] Inapplicable task with ~~del~~ and <s>strike</s> embedded GFM - <<~GFM + <<~GFM, 1. [ ] Unchecked ordered task 1. [x] Checked ordered task + 1. [~] Inapplicable ordered task + 1. [~] Inapplicable ordered task with ~~del~~ and <s>strike</s> embedded + GFM + <<~GFM + * [ ] Unchecked loose list task + * [x] Checked loose list task + * [~] Inapplicable loose list task + + With a paragraph + * [~] Inapplicable loose list task with ~~del~~ and <s>strike</s> embedded + + With a paragraph GFM ) @@ -605,7 +619,8 @@ RSpec.describe 'Copy as GFM', :js do '###### Heading', '**Bold**', '*Italics*', - '~~Strikethrough~~', + '~~Strikethrough (del)~~', + '<s>Strikethrough</s>', '---', # table <<~GFM, diff --git a/spec/features/projects/tags/user_edits_tags_spec.rb b/spec/features/projects/tags/user_edits_tags_spec.rb index c8438b73dc3..af7ffa2c1ec 100644 --- a/spec/features/projects/tags/user_edits_tags_spec.rb +++ b/spec/features/projects/tags/user_edits_tags_spec.rb @@ -103,9 +103,9 @@ RSpec.describe 'Project > Tags', :js do end end - it 'release notes form shows "Attach a file" button', :js do + it 'release notes form shows "Attach a file or image" button', :js do page.within('.content form.release-form') do - expect(page).to have_button('Attach a file') + expect(page).to have_selector('[data-testid="button-attach-file"]') expect(page).not_to have_selector('.uploading-progress-container', visible: true) end end diff --git a/spec/features/uploads/user_uploads_file_to_note_spec.rb b/spec/features/uploads/user_uploads_file_to_note_spec.rb index 589cc9f9b02..2547e2d274c 100644 --- a/spec/features/uploads/user_uploads_file_to_note_spec.rb +++ b/spec/features/uploads/user_uploads_file_to_note_spec.rb @@ -16,8 +16,8 @@ RSpec.describe 'User uploads file to note' do end context 'before uploading' do - it 'shows "Attach a file" button', :js do - expect(page).to have_button('Attach a file') + it 'shows "Attach a file or image" button', :js do + expect(page).to have_selector('[data-testid="button-attach-file"]') expect(page).not_to have_selector('.uploading-progress-container', visible: true) end end @@ -30,7 +30,7 @@ RSpec.describe 'User uploads file to note' do click_button 'Cancel' end - expect(page).to have_button('Attach a file') + expect(page).to have_selector('[data-testid="button-attach-file"]') expect(page).not_to have_button('Cancel') expect(page).not_to have_selector('.uploading-progress-container', visible: true) end @@ -60,16 +60,15 @@ RSpec.describe 'User uploads file to note' do expect(page).to have_selector('.uploading-error-message', visible: true, text: error_text) expect(page).to have_button('Try again', visible: true) expect(page).to have_button('attach a new file', visible: true) - expect(page).not_to have_button('Attach a file') end end context 'uploading is complete' do - it 'shows "Attach a file" button on uploading complete', :js do + it 'shows "Attach a file or image" button on uploading complete', :js do dropzone_file([Rails.root.join('spec', 'fixtures', 'dk.png')]) wait_for_requests - expect(page).to have_button('Attach a file') + expect(page).to have_selector('[data-testid="button-attach-file"]') expect(page).not_to have_selector('.uploading-progress-container', visible: true) end diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb index 2da16408fbc..18cd63b7bcb 100644 --- a/spec/fixtures/markdown.md.erb +++ b/spec/fixtures/markdown.md.erb @@ -275,9 +275,11 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e - [ ] Incomplete task 1 - [x] Complete task 1 +- [~] Inapplicable task 1 - [ ] Incomplete task 2 - [ ] Incomplete sub-task 1 - [ ] Incomplete sub-task 2 + - [~] Inapplicable sub-task 1 - [x] Complete sub-task 1 - [X] Complete task 2 diff --git a/spec/frontend/environments/environments_detail_header_spec.js b/spec/frontend/environments/environments_detail_header_spec.js index 305e7385b43..4687119127d 100644 --- a/spec/frontend/environments/environments_detail_header_spec.js +++ b/spec/frontend/environments/environments_detail_header_spec.js @@ -1,5 +1,6 @@ import { GlSprintf } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import DeleteEnvironmentModal from '~/environments/components/delete_environment_modal.vue'; import EnvironmentsDetailHeader from '~/environments/components/environments_detail_header.vue'; import StopEnvironmentModal from '~/environments/components/stop_environment_modal.vue'; @@ -43,6 +44,9 @@ describe('Environments detail header component', () => { GlSprintf, TimeAgo, }, + directives: { + GlTooltip: createMockDirective(), + }, propsData: { canAdminEnvironment: false, canUpdateEnvironment: false, @@ -185,6 +189,14 @@ describe('Environments detail header component', () => { it('displays the metrics button with correct path', () => { expect(findMetricsButton().attributes('href')).toBe(metricsPath); }); + + it('uses a gl tooltip for the title', () => { + const button = findMetricsButton(); + const tooltip = getBinding(button.element, 'gl-tooltip'); + + expect(tooltip).toBeDefined(); + expect(button.attributes('title')).toBe('See metrics'); + }); }); describe('when has all admin rights', () => { diff --git a/spec/frontend/lib/dompurify_spec.js b/spec/frontend/lib/dompurify_spec.js index b585c69e911..29b927ef628 100644 --- a/spec/frontend/lib/dompurify_spec.js +++ b/spec/frontend/lib/dompurify_spec.js @@ -173,4 +173,50 @@ describe('~/lib/dompurify', () => { expect(sanitize(html)).toBe(`<a>internal link</a>`); }); }); + + describe('links with target attribute', () => { + const getSanitizedNode = (html) => { + return document.createRange().createContextualFragment(sanitize(html)).firstElementChild; + }; + + it('adds secure context', () => { + const html = `<a href="https://example.com" target="_blank">link</a>`; + const el = getSanitizedNode(html); + + expect(el.getAttribute('target')).toBe('_blank'); + expect(el.getAttribute('rel')).toBe('noopener noreferrer'); + }); + + it('adds secure context and merge existing `rel` values', () => { + const html = `<a href="https://example.com" target="_blank" rel="help External">link</a>`; + const el = getSanitizedNode(html); + + expect(el.getAttribute('target')).toBe('_blank'); + expect(el.getAttribute('rel')).toBe('help external noopener noreferrer'); + }); + + it('does not duplicate noopener/noreferrer `rel` values', () => { + const html = `<a href="https://example.com" target="_blank" rel="noreferrer noopener">link</a>`; + const el = getSanitizedNode(html); + + expect(el.getAttribute('target')).toBe('_blank'); + expect(el.getAttribute('rel')).toBe('noreferrer noopener'); + }); + + it('does not update `rel` values when target is not `_blank` ', () => { + const html = `<a href="https://example.com" target="_self" rel="help">internal</a>`; + const el = getSanitizedNode(html); + + expect(el.getAttribute('target')).toBe('_self'); + expect(el.getAttribute('rel')).toBe('help'); + }); + + it('does not update `rel` values when target attribute is not present', () => { + const html = `<a href="https://example.com">link</a>`; + const el = getSanitizedNode(html); + + expect(el.hasAttribute('target')).toBe(false); + expect(el.hasAttribute('rel')).toBe(false); + }); + }); }); diff --git a/spec/frontend/lib/utils/text_markdown_spec.js b/spec/frontend/lib/utils/text_markdown_spec.js index d1bca3c73b6..8f37c79e235 100644 --- a/spec/frontend/lib/utils/text_markdown_spec.js +++ b/spec/frontend/lib/utils/text_markdown_spec.js @@ -193,6 +193,7 @@ describe('init markdown', () => { ${'- [ ] item'} | ${'- [ ] item\n- [ ] '} ${'- [x] item'} | ${'- [x] item\n- [ ] '} ${'- [X] item'} | ${'- [X] item\n- [ ] '} + ${'- [~] item'} | ${'- [~] item\n- [ ] '} ${'- [ ] nbsp (U+00A0)'} | ${'- [ ] nbsp (U+00A0)\n- [ ] '} ${'- item\n - second'} | ${'- item\n - second\n - '} ${'- - -'} | ${'- - -'} @@ -205,6 +206,7 @@ describe('init markdown', () => { ${'1. [ ] item'} | ${'1. [ ] item\n2. [ ] '} ${'1. [x] item'} | ${'1. [x] item\n2. [ ] '} ${'1. [X] item'} | ${'1. [X] item\n2. [ ] '} + ${'1. [~] item'} | ${'1. [~] item\n2. [ ] '} ${'108. item'} | ${'108. item\n109. '} ${'108. item\n - second'} | ${'108. item\n - second\n - '} ${'108. item\n 1. second'} | ${'108. item\n 1. second\n 2. '} @@ -228,11 +230,13 @@ describe('init markdown', () => { ${'- [ ] item\n- [ ] '} | ${'- [ ] item\n'} ${'- [x] item\n- [x] '} | ${'- [x] item\n'} ${'- [X] item\n- [X] '} | ${'- [X] item\n'} + ${'- [~] item\n- [~] '} | ${'- [~] item\n'} ${'- item\n - second\n - '} | ${'- item\n - second\n'} ${'1. item\n2. '} | ${'1. item\n'} ${'1. [ ] item\n2. [ ] '} | ${'1. [ ] item\n'} ${'1. [x] item\n2. [x] '} | ${'1. [x] item\n'} ${'1. [X] item\n2. [X] '} | ${'1. [X] item\n'} + ${'1. [~] item\n2. [~] '} | ${'1. [~] item\n'} ${'108. item\n109. '} | ${'108. item\n'} ${'108. item\n - second\n - '} | ${'108. item\n - second\n'} ${'108. item\n 1. second\n 1. '} | ${'108. item\n 1. second\n'} diff --git a/spec/frontend/vue_shared/components/markdown/field_spec.js b/spec/frontend/vue_shared/components/markdown/field_spec.js index 85a135d2b89..50864a4bf25 100644 --- a/spec/frontend/vue_shared/components/markdown/field_spec.js +++ b/spec/frontend/vue_shared/components/markdown/field_spec.js @@ -76,7 +76,7 @@ describe('Markdown field component', () => { const getMarkdownButton = () => subject.find('.js-md'); const getListBulletedButton = () => subject.findAll('.js-md[title="Add a bullet list"]'); const getVideo = () => subject.find('video'); - const getAttachButton = () => subject.find('.button-attach-file'); + const getAttachButton = () => subject.findByTestId('button-attach-file'); const clickAttachButton = () => getAttachButton().trigger('click'); const findDropzone = () => subject.find('.div-dropzone'); const findMarkdownHeader = () => subject.findComponent(MarkdownFieldHeader); @@ -232,13 +232,10 @@ describe('Markdown field component', () => { }); }); - it('should render attach a file button', () => { - expect(getAttachButton().text()).toBe('Attach a file'); - }); - it('should trigger dropzone when attach button is clicked', () => { expect(dropzoneSpy).not.toHaveBeenCalled(); + getAttachButton().trigger('click'); clickAttachButton(); expect(dropzoneSpy).toHaveBeenCalled(); diff --git a/spec/frontend/vue_shared/components/markdown/header_spec.js b/spec/frontend/vue_shared/components/markdown/header_spec.js index 67222cab247..12972fff58e 100644 --- a/spec/frontend/vue_shared/components/markdown/header_spec.js +++ b/spec/frontend/vue_shared/components/markdown/header_spec.js @@ -56,6 +56,7 @@ describe('Markdown field header component', () => { 'Add a task list', 'Add a collapsible section', 'Add a table', + 'Attach a file or image', 'Go full screen', ]; const elements = findToolbarButtons(); diff --git a/spec/graphql/types/work_items/widgets/start_and_due_date_type_spec.rb b/spec/graphql/types/work_items/widgets/start_and_due_date_type_spec.rb new file mode 100644 index 00000000000..ddc26d964be --- /dev/null +++ b/spec/graphql/types/work_items/widgets/start_and_due_date_type_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::WorkItems::Widgets::StartAndDueDateType do + it 'exposes the expected fields' do + expected_fields = %i[due_date start_date type] + + expect(described_class).to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/helpers/gitlab_script_tag_helper_spec.rb b/spec/helpers/gitlab_script_tag_helper_spec.rb index 35f2c0795be..9d71e25286e 100644 --- a/spec/helpers/gitlab_script_tag_helper_spec.rb +++ b/spec/helpers/gitlab_script_tag_helper_spec.rb @@ -14,6 +14,16 @@ RSpec.describe GitlabScriptTagHelper do expect(helper.javascript_include_tag(script_url).to_s) .to eq "<script src=\"/javascripts/#{script_url}\" defer=\"defer\" nonce=\"noncevalue\"></script>" end + + it 'returns a script tag with defer=false and a nonce' do + expect(helper.javascript_include_tag(script_url, defer: nil).to_s) + .to eq "<script src=\"/javascripts/#{script_url}\" nonce=\"noncevalue\"></script>" + end + + it 'returns a script tag with a nonce even nonce is set to nil' do + expect(helper.javascript_include_tag(script_url, nonce: nil).to_s) + .to eq "<script src=\"/javascripts/#{script_url}\" defer=\"defer\" nonce=\"noncevalue\"></script>" + end end describe 'inline script tag' do diff --git a/spec/helpers/profiles_helper_spec.rb b/spec/helpers/profiles_helper_spec.rb index 399726263db..63641e65942 100644 --- a/spec/helpers/profiles_helper_spec.rb +++ b/spec/helpers/profiles_helper_spec.rb @@ -67,38 +67,6 @@ RSpec.describe ProfilesHelper do end end - describe "#user_status_set_to_busy?" do - using RSpec::Parameterized::TableSyntax - - where(:availability, :result) do - "busy" | true - "not_set" | false - "" | false - nil | false - end - - with_them do - it { expect(helper.user_status_set_to_busy?(OpenStruct.new(availability: availability))).to eq(result) } - end - end - - describe "#show_status_emoji?" do - using RSpec::Parameterized::TableSyntax - - where(:message, :emoji, :result) do - "Some message" | UserStatus::DEFAULT_EMOJI | true - "Some message" | "" | true - "" | "basketball" | true - "" | "basketball" | true - "" | UserStatus::DEFAULT_EMOJI | false - "" | UserStatus::DEFAULT_EMOJI | false - end - - with_them do - it { expect(helper.show_status_emoji?(OpenStruct.new(message: message, emoji: emoji))).to eq(result) } - end - end - describe "#ssh_key_expiration_tooltip" do using RSpec::Parameterized::TableSyntax diff --git a/spec/initializers/rails_safe_load_yaml_patch_spec.rb b/spec/initializers/rails_safe_load_yaml_patch_spec.rb new file mode 100644 index 00000000000..2637bb167b6 --- /dev/null +++ b/spec/initializers/rails_safe_load_yaml_patch_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Rails YAML safe load patch' do + let(:unsafe_load) { false } + + let(:klass) do + Class.new(ActiveRecord::Base) do + self.table_name = 'issues' + + serialize :description + end + end + + before do + allow(ActiveRecord::Base).to receive(:use_yaml_unsafe_load).and_return(unsafe_load) + end + + context 'with safe load' do + let(:instance) { klass.new(description: data) } + + context 'with default permitted classes' do + let(:data) do + { + "test" => Time.now, + ab: 1 + } + end + + it 'deserializes data' do + expect(Gitlab::ErrorTracking).not_to receive(:track_and_raise_for_dev_exception) + + instance.save! + + expect(klass.find(instance.id).description).to eq(data) + end + end + + context 'with unpermitted classes' do + let(:data) { DateTime.now } + + it 'logs an exception and loads the data' do + expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).twice + + instance.save! + + expect(klass.find(instance.id).description).to eq(data) + end + end + end + + context 'with unsafe load' do + let(:unsafe_load) { true } + let(:data) { DateTime.now } + let(:instance) { klass.new(description: data) } + + it 'loads the data' do + expect(Gitlab::ErrorTracking).not_to receive(:track_and_raise_for_dev_exception) + + instance.save! + + expect(klass.find(instance.id).description).to eq(data) + end + end +end diff --git a/spec/lib/banzai/filter/task_list_filter_spec.rb b/spec/lib/banzai/filter/task_list_filter_spec.rb index c89acd1a643..920904b0f29 100644 --- a/spec/lib/banzai/filter/task_list_filter_spec.rb +++ b/spec/lib/banzai/filter/task_list_filter_spec.rb @@ -10,4 +10,38 @@ RSpec.describe Banzai::Filter::TaskListFilter do expect(doc.xpath('.//li//task-button').count).to eq(2) end + + describe 'inapplicable list items' do + shared_examples 'a valid inapplicable task list item' do |html| + it "behaves correctly for `#{html}`" do + doc = filter("<ul><li>#{html}</li></ul>") + + expect(doc.css('li.inapplicable input[data-inapplicable]').count).to eq(1) + expect(doc.css('li.inapplicable > s').count).to eq(1) + end + end + + shared_examples 'an invalid inapplicable task list item' do |html| + it "does nothing for `#{html}`" do + doc = filter("<ul><li>#{html}</li></ul>") + + expect(doc.css('li.inapplicable input[data-inapplicable]').count).to eq(0) + end + end + + it_behaves_like 'a valid inapplicable task list item', '[~] foobar' + it_behaves_like 'a valid inapplicable task list item', '[~] foo <em>bar</em>' + it_behaves_like 'an invalid inapplicable task list item', '[ ] foobar' + it_behaves_like 'an invalid inapplicable task list item', '[x] foobar' + it_behaves_like 'an invalid inapplicable task list item', 'foo [~] bar' + + it 'does not wrap a sublist with <s>' do + html = '[~] foo <em>bar</em>\n<ol><li>sublist</li></ol>' + doc = filter("<ul><li>#{html}</li></ul>") + + expect(doc.to_html).to include('<s>foo <em>bar</em>\n</s>') + expect(doc.css('li.inapplicable input[data-inapplicable]').count).to eq(1) + expect(doc.css('li.inapplicable > s').count).to eq(1) + end + end end diff --git a/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_no_issues_no_repo_projects_spec.rb b/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_no_issues_no_repo_projects_spec.rb new file mode 100644 index 00000000000..d20eaef3650 --- /dev/null +++ b/spec/lib/gitlab/background_migration/disable_legacy_open_source_license_for_no_issues_no_repo_projects_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::DisableLegacyOpenSourceLicenseForNoIssuesNoRepoProjects, + :migration, + schema: 20220722084543 do + let(:namespaces_table) { table(:namespaces) } + let(:projects_table) { table(:projects) } + let(:project_settings_table) { table(:project_settings) } + let(:project_statistics_table) { table(:project_statistics) } + let(:issues_table) { table(:issues) } + + subject(:perform_migration) do + described_class.new(start_id: projects_table.minimum(:id), + end_id: projects_table.maximum(:id), + batch_table: :projects, + batch_column: :id, + sub_batch_size: 2, + pause_ms: 0, + connection: ActiveRecord::Base.connection) + .perform + end + + it 'sets `legacy_open_source_license_available` to false only for public projects with no issues and no repo', + :aggregate_failures do + project_with_no_issues_no_repo = create_legacy_license_public_project('project-with-no-issues-no-repo') + project_with_repo = create_legacy_license_public_project('project-with-repo', repo_size: 1) + project_with_issues = create_legacy_license_public_project('project-with-issues', with_issue: true) + project_with_issues_and_repo = + create_legacy_license_public_project('project-with-issues-and-repo', repo_size: 1, with_issue: true) + + queries = ActiveRecord::QueryRecorder.new { perform_migration } + + expect(queries.count).to eq(7) + expect(migrated_attribute(project_with_no_issues_no_repo)).to be_falsey + expect(migrated_attribute(project_with_repo)).to be_truthy + expect(migrated_attribute(project_with_issues)).to be_truthy + expect(migrated_attribute(project_with_issues_and_repo)).to be_truthy + end + + def create_legacy_license_public_project(path, repo_size: 0, with_issue: false) + namespace = namespaces_table.create!(name: "namespace-#{path}", path: "namespace-#{path}") + project_namespace = + namespaces_table.create!(name: "-project-namespace-#{path}", path: "project-namespace-#{path}", type: 'Project') + project = projects_table + .create!( + name: path, path: path, namespace_id: namespace.id, + project_namespace_id: project_namespace.id, visibility_level: 20 + ) + + project_statistics_table.create!(project_id: project.id, namespace_id: namespace.id, repository_size: repo_size) + issues_table.create!(project_id: project.id) if with_issue + project_settings_table.create!(project_id: project.id, legacy_open_source_license_available: true) + + project + end + + def migrated_attribute(project) + project_settings_table.find(project.id).legacy_open_source_license_available + end +end diff --git a/spec/lib/gitlab/form_builders/gitlab_ui_form_builder_spec.rb b/spec/lib/gitlab/form_builders/gitlab_ui_form_builder_spec.rb index 2b1fcac9257..a7b195a16b4 100644 --- a/spec/lib/gitlab/form_builders/gitlab_ui_form_builder_spec.rb +++ b/spec/lib/gitlab/form_builders/gitlab_ui_form_builder_spec.rb @@ -25,7 +25,7 @@ RSpec.describe Gitlab::FormBuilders::GitlabUiFormBuilder do it 'renders correct html' do expected_html = <<~EOS <div class="gl-form-checkbox custom-control custom-checkbox"> - <input name="user[view_diffs_file_by_file]" type="hidden" value="0" /> + <input name="user[view_diffs_file_by_file]" type="hidden" value="0" autocomplete="off" /> <input class="custom-control-input" type="checkbox" value="1" name="user[view_diffs_file_by_file]" id="user_view_diffs_file_by_file" /> <label class="custom-control-label" for="user_view_diffs_file_by_file"> <span>Show one file at a time on merge request's Changes tab</span> @@ -51,7 +51,7 @@ RSpec.describe Gitlab::FormBuilders::GitlabUiFormBuilder do it 'renders help text' do expected_html = <<~EOS <div class="gl-form-checkbox custom-control custom-checkbox"> - <input name="user[view_diffs_file_by_file]" type="hidden" value="1" /> + <input name="user[view_diffs_file_by_file]" type="hidden" value="1" autocomplete="off" /> <input class="custom-control-input checkbox-foo-bar" type="checkbox" value="3" name="user[view_diffs_file_by_file]" id="user_view_diffs_file_by_file" /> <label class="custom-control-label label-foo-bar" for="user_view_diffs_file_by_file"> <span>Show one file at a time on merge request's Changes tab</span> @@ -101,7 +101,7 @@ RSpec.describe Gitlab::FormBuilders::GitlabUiFormBuilder do it 'renders correct html' do expected_html = <<~EOS <div class="gl-form-checkbox custom-control custom-checkbox"> - <input name="user[view_diffs_file_by_file]" type="hidden" value="0" /> + <input name="user[view_diffs_file_by_file]" type="hidden" value="0" autocomplete="off" /> <input class="custom-control-input" type="checkbox" value="1" name="user[view_diffs_file_by_file]" id="user_view_diffs_file_by_file" /> <label class="custom-control-label" for="user_view_diffs_file_by_file"> <span>Show one file at a time on merge request's Changes tab</span> diff --git a/spec/migrations/20220722084543_schedule_disable_legacy_open_source_license_for_no_issues_no_repo_projects_spec.rb b/spec/migrations/20220722084543_schedule_disable_legacy_open_source_license_for_no_issues_no_repo_projects_spec.rb new file mode 100644 index 00000000000..cb0f941aea1 --- /dev/null +++ b/spec/migrations/20220722084543_schedule_disable_legacy_open_source_license_for_no_issues_no_repo_projects_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe ScheduleDisableLegacyOpenSourceLicenseForNoIssuesNoRepoProjects do + context 'when on gitlab.com' do + let(:migration) { described_class::MIGRATION } + + before do + allow(Gitlab).to receive(:com?).and_return(true) + end + + describe '#up' do + it 'schedules background jobs for each batch of projects' do + migrate! + + expect(migration).to( + have_scheduled_batched_migration( + table_name: :projects, + column_name: :id, + interval: described_class::INTERVAL, + batch_size: described_class::BATCH_SIZE, + max_batch_size: described_class::MAX_BATCH_SIZE, + sub_batch_size: described_class::SUB_BATCH_SIZE + ) + ) + end + end + + describe '#down' do + it 'deletes all batched migration records' do + migrate! + schema_migrate_down! + + expect(migration).not_to have_scheduled_batched_migration + end + end + end + + context 'when on self-managed instance' do + let(:migration) { described_class.new } + + before do + allow(Gitlab).to receive(:com?).and_return(false) + end + + describe '#up' do + it 'does not schedule background job' do + expect(migration).not_to receive(:queue_batched_background_migration) + + migration.up + end + end + + describe '#down' do + it 'does not delete background job' do + expect(migration).not_to receive(:delete_batched_background_migration) + + migration.down + end + end + end +end diff --git a/spec/models/user_status_spec.rb b/spec/models/user_status_spec.rb index 87d1fa14aca..663df9712ab 100644 --- a/spec/models/user_status_spec.rb +++ b/spec/models/user_status_spec.rb @@ -47,4 +47,30 @@ RSpec.describe UserStatus do end end end + + describe '#customized?' do + it 'is customized when message text is present' do + subject.message = 'My custom status' + + expect(subject).to be_customized + end + + it 'is not customized when message text is absent' do + subject.message = nil + + expect(subject).not_to be_customized + end + + it 'is customized without message but with custom emoji' do + subject.emoji = 'bow' + + expect(subject).to be_customized + end + + it 'is not customized without message but with default custom emoji' do + subject.emoji = 'speech_balloon' + + expect(subject).not_to be_customized + end + end end diff --git a/spec/models/work_item_spec.rb b/spec/models/work_item_spec.rb index 777ade511b0..e281a2a8fff 100644 --- a/spec/models/work_item_spec.rb +++ b/spec/models/work_item_spec.rb @@ -40,9 +40,12 @@ RSpec.describe WorkItem do subject { build(:work_item).widgets } it 'returns instances of supported widgets' do - is_expected.to include(instance_of(WorkItems::Widgets::Description), - instance_of(WorkItems::Widgets::Hierarchy), - instance_of(WorkItems::Widgets::Assignees)) + is_expected.to include( + instance_of(WorkItems::Widgets::Description), + instance_of(WorkItems::Widgets::Hierarchy), + instance_of(WorkItems::Widgets::Assignees), + instance_of(WorkItems::Widgets::StartAndDueDate) + ) end end diff --git a/spec/models/work_items/type_spec.rb b/spec/models/work_items/type_spec.rb index 057bf045f60..ec0b5536546 100644 --- a/spec/models/work_items/type_spec.rb +++ b/spec/models/work_items/type_spec.rb @@ -64,9 +64,12 @@ RSpec.describe WorkItems::Type do subject { described_class.available_widgets } it 'returns list of all possible widgets' do - is_expected.to include(::WorkItems::Widgets::Description, - ::WorkItems::Widgets::Hierarchy, - ::WorkItems::Widgets::Assignees) + is_expected.to include( + ::WorkItems::Widgets::Description, + ::WorkItems::Widgets::Hierarchy, + ::WorkItems::Widgets::Assignees, + ::WorkItems::Widgets::StartAndDueDate + ) end end diff --git a/spec/models/work_items/widgets/start_and_due_date_spec.rb b/spec/models/work_items/widgets/start_and_due_date_spec.rb new file mode 100644 index 00000000000..b023cc73e0f --- /dev/null +++ b/spec/models/work_items/widgets/start_and_due_date_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WorkItems::Widgets::StartAndDueDate do + let_it_be(:work_item) { create(:work_item, start_date: Date.today, due_date: 1.week.from_now) } + + describe '.type' do + subject { described_class.type } + + it { is_expected.to eq(:start_and_due_date) } + end + + describe '#type' do + subject { described_class.new(work_item).type } + + it { is_expected.to eq(:start_and_due_date) } + end + + describe '#start_date' do + subject { described_class.new(work_item).start_date } + + it { is_expected.to eq(work_item.start_date) } + end + + describe '#due_date' do + subject { described_class.new(work_item).due_date } + + it { is_expected.to eq(work_item.due_date) } + end +end diff --git a/spec/requests/api/graphql/work_item_spec.rb b/spec/requests/api/graphql/work_item_spec.rb index e9753affcc3..b4f4cb68350 100644 --- a/spec/requests/api/graphql/work_item_spec.rb +++ b/spec/requests/api/graphql/work_item_spec.rb @@ -8,7 +8,16 @@ RSpec.describe 'Query.work_item(id)' do let_it_be(:developer) { create(:user) } let_it_be(:guest) { create(:user) } let_it_be(:project) { create(:project, :private) } - let_it_be(:work_item) { create(:work_item, project: project, description: '- List item') } + let_it_be(:work_item) do + create( + :work_item, + project: project, + description: '- List item', + start_date: Date.today, + due_date: 1.week.from_now + ) + end + let_it_be(:child_item1) { create(:work_item, :task, project: project) } let_it_be(:child_item2) { create(:work_item, :task, confidential: true, project: project) } let_it_be(:child_link1) { create(:parent_link, work_item_parent: work_item, work_item: child_item1) } @@ -205,6 +214,34 @@ RSpec.describe 'Query.work_item(id)' do ) end end + + describe 'start and due date widget' do + let(:work_item_fields) do + <<~GRAPHQL + id + widgets { + type + ... on WorkItemWidgetStartAndDueDate { + startDate + dueDate + } + } + GRAPHQL + end + + it 'returns widget information' do + expect(work_item_data).to include( + 'id' => work_item.to_gid.to_s, + 'widgets' => include( + hash_including( + 'type' => 'START_AND_DUE_DATE', + 'startDate' => work_item.start_date.to_s, + 'dueDate' => work_item.due_date.to_s + ) + ) + ) + end + end end context 'when an Issue Global ID is provided' do diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb index 1932f78506f..8bec3be2535 100644 --- a/spec/support/matchers/markdown_matchers.rb +++ b/spec/support/matchers/markdown_matchers.rb @@ -189,8 +189,10 @@ module MarkdownMatchers match do |actual| expect(actual).to have_selector('ul.task-list', count: 2) - expect(actual).to have_selector('li.task-list-item', count: 7) + expect(actual).to have_selector('li.task-list-item', count: 9) + expect(actual).to have_selector('li.task-list-item.inapplicable > s', count: 2) expect(actual).to have_selector('input[checked]', count: 3) + expect(actual).to have_selector('input[data-inapplicable]', count: 2) end end diff --git a/spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb b/spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb index 0ef1ccdfe57..8d1502bed84 100644 --- a/spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb +++ b/spec/support/shared_examples/features/wiki/file_attachments_shared_examples.rb @@ -12,8 +12,8 @@ RSpec.shared_examples 'wiki file attachments' do end context 'before uploading' do - it 'shows "Attach a file" button' do - expect(page).to have_button('Attach a file') + it 'shows "Attach a file or image" button' do + expect(page).to have_selector('[data-testid="button-attach-file"]') expect(page).not_to have_selector('.uploading-progress-container', visible: true) end end @@ -26,7 +26,7 @@ RSpec.shared_examples 'wiki file attachments' do click_button 'Cancel' end - expect(page).to have_button('Attach a file') + expect(page).to have_selector('[data-testid="button-attach-file"]') expect(page).not_to have_button('Cancel') expect(page).not_to have_selector('.uploading-progress-container', visible: true) end @@ -41,11 +41,11 @@ RSpec.shared_examples 'wiki file attachments' do end context 'uploading is complete' do - it 'shows "Attach a file" button on uploading complete' do + it 'shows "Attach a file or image" button on uploading complete' do attach_with_dropzone wait_for_requests - expect(page).to have_button('Attach a file') + expect(page).to have_selector('[data-testid="button-attach-file"]') expect(page).not_to have_selector('.uploading-progress-container', visible: true) end diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb index 7f42c700ce4..30c85464452 100644 --- a/spec/workers/project_cache_worker_spec.rb +++ b/spec/workers/project_cache_worker_spec.rb @@ -115,7 +115,7 @@ RSpec.describe ProjectCacheWorker do .twice expect(UpdateProjectStatisticsWorker).to receive(:perform_in) - .with(lease_timeout, project.id, statistics) + .with(lease_timeout, lease_key, project.id, statistics) .and_call_original expect(Namespaces::ScheduleAggregationWorker) diff --git a/spec/workers/update_project_statistics_worker_spec.rb b/spec/workers/update_project_statistics_worker_spec.rb index 1f840e363ea..2f356376d7c 100644 --- a/spec/workers/update_project_statistics_worker_spec.rb +++ b/spec/workers/update_project_statistics_worker_spec.rb @@ -3,17 +3,35 @@ require 'spec_helper' RSpec.describe UpdateProjectStatisticsWorker do + include ExclusiveLeaseHelpers + let(:worker) { described_class.new } let(:project) { create(:project, :repository) } let(:statistics) { %w(repository_size) } + let(:lease_key) { "namespace:namespaces_root_statistics:#{project.namespace_id}" } describe '#perform' do - it 'updates the project statistics' do - expect(Projects::UpdateStatisticsService).to receive(:new) - .with(project, nil, statistics: statistics) - .and_call_original + context 'when a lease could be obtained' do + it 'updates the project statistics' do + expect(Projects::UpdateStatisticsService).to receive(:new) + .with(project, nil, statistics: statistics) + .and_call_original + + worker.perform(lease_key, project.id, statistics) + end + end + + context 'when a lease could not be obtained' do + before do + stub_exclusive_lease_taken(lease_key, timeout: ProjectCacheWorker::LEASE_TIMEOUT) + end + + it 'does not update the project statistics' do + lease_key = "namespace:namespaces_root_statistics:#{project.namespace_id}" + expect(Projects::UpdateStatisticsService).not_to receive(:new) - worker.perform(project.id, statistics) + worker.perform(lease_key, project.id, statistics) + end end end end |