diff options
Diffstat (limited to 'spec')
61 files changed, 1271 insertions, 717 deletions
diff --git a/spec/controllers/concerns/send_file_upload_spec.rb b/spec/controllers/concerns/send_file_upload_spec.rb index 379b2d6b935..a07113a6156 100644 --- a/spec/controllers/concerns/send_file_upload_spec.rb +++ b/spec/controllers/concerns/send_file_upload_spec.rb @@ -53,19 +53,38 @@ describe SendFileUpload do end context 'with attachment' do - let(:params) { { attachment: 'test.js' } } + let(:filename) { 'test.js' } + let(:params) { { attachment: filename } } it 'sends a file with content-type of text/plain' do + # Notice the filename= is omitted from the disposition; this is because + # Rails 5 will append this header in send_file expected_params = { content_type: 'text/plain', filename: 'test.js', - disposition: 'attachment' + disposition: "attachment; filename*=UTF-8''test.js" } expect(controller).to receive(:send_file).with(uploader.path, expected_params) subject end + context 'with non-ASCII encoded filename' do + let(:filename) { 'テスト.txt' } + + # Notice the filename= is omitted from the disposition; this is because + # Rails 5 will append this header in send_file + it 'sends content-disposition for non-ASCII encoded filenames' do + expected_params = { + filename: filename, + disposition: "attachment; filename*=UTF-8''%E3%83%86%E3%82%B9%E3%83%88.txt" + } + expect(controller).to receive(:send_file).with(uploader.path, expected_params) + + subject + end + end + context 'with a proxied file in object storage' do before do stub_uploads_object_storage(uploader: uploader_class) @@ -76,7 +95,7 @@ describe SendFileUpload do it 'sends a file with a custom type' do headers = double - expected_headers = %r(response-content-disposition=attachment%3Bfilename%3D%22test.js%22&response-content-type=application/ecmascript) + expected_headers = %r(response-content-disposition=attachment%3B%20filename%3D%22test.js%22%3B%20filename%2A%3DUTF-8%27%27test.js&response-content-type=application/ecmascript) expect(Gitlab::Workhorse).to receive(:send_url).with(expected_headers).and_call_original expect(headers).to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-url:/) diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb index 59463462e5a..232a5e2793b 100644 --- a/spec/controllers/omniauth_callbacks_controller_spec.rb +++ b/spec/controllers/omniauth_callbacks_controller_spec.rb @@ -45,6 +45,29 @@ describe OmniauthCallbacksController, type: :controller do end end + context 'when sign in fails' do + include RoutesHelpers + + let(:extern_uid) { 'my-uid' } + let(:provider) { :saml } + + def stub_route_as(path) + allow(@routes).to receive(:generate_extras) { [path, []] } + end + + it 'it calls through to the failure handler' do + request.env['omniauth.error'] = OneLogin::RubySaml::ValidationError.new("Fingerprint mismatch") + request.env['omniauth.error.strategy'] = OmniAuth::Strategies::SAML.new(nil) + stub_route_as('/users/auth/saml/callback') + + ForgeryProtection.with_forgery_protection do + post :failure + end + + expect(flash[:alert]).to match(/Fingerprint mismatch/) + end + end + context 'when a redirect fragment is provided' do let(:provider) { :jwt } let(:extern_uid) { 'my-uid' } diff --git a/spec/controllers/projects/artifacts_controller_spec.rb b/spec/controllers/projects/artifacts_controller_spec.rb index bd10de45b67..29df00e6bb0 100644 --- a/spec/controllers/projects/artifacts_controller_spec.rb +++ b/spec/controllers/projects/artifacts_controller_spec.rb @@ -26,8 +26,15 @@ describe Projects::ArtifactsController do end context 'when no file type is supplied' do + let(:filename) { job.artifacts_file.filename } + it 'sends the artifacts file' do - expect(controller).to receive(:send_file).with(job.artifacts_file.path, hash_including(disposition: 'attachment')).and_call_original + # Notice the filename= is omitted from the disposition; this is because + # Rails 5 will append this header in send_file + expect(controller).to receive(:send_file) + .with( + job.artifacts_file.file.path, + hash_including(disposition: %Q(attachment; filename*=UTF-8''#{filename}))).and_call_original download_artifact end @@ -46,6 +53,7 @@ describe Projects::ArtifactsController do context 'when codequality file type is supplied' do let(:file_type) { 'codequality' } + let(:filename) { job.job_artifacts_codequality.filename } context 'when file is stored locally' do before do @@ -53,7 +61,11 @@ describe Projects::ArtifactsController do end it 'sends the codequality report' do - expect(controller).to receive(:send_file).with(job.job_artifacts_codequality.file.path, hash_including(disposition: 'attachment')).and_call_original + # Notice the filename= is omitted from the disposition; this is because + # Rails 5 will append this header in send_file + expect(controller).to receive(:send_file) + .with(job.job_artifacts_codequality.file.path, + hash_including(disposition: %Q(attachment; filename*=UTF-8''#{filename}))).and_call_original download_artifact(file_type: file_type) end diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb index 4a5d2bdecb7..601a292bf54 100644 --- a/spec/controllers/projects/services_controller_spec.rb +++ b/spec/controllers/projects/services_controller_spec.rb @@ -152,6 +152,16 @@ describe Projects::ServicesController do expect(service.namespace).not_to eq('updated_namespace') end end + + context 'when activating JIRA service from a template' do + let(:template_service) { create(:jira_service, project: project, template: true) } + + it 'activate JIRA service from template' do + put :update, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: { active: true } } + + expect(flash[:notice]).to eq 'JIRA activated.' + end + end end describe "GET #edit" do diff --git a/spec/factories/project_error_tracking_settings.rb b/spec/factories/project_error_tracking_settings.rb index fbd8dfd395c..be30bd0260a 100644 --- a/spec/factories/project_error_tracking_settings.rb +++ b/spec/factories/project_error_tracking_settings.rb @@ -6,5 +6,7 @@ FactoryBot.define do api_url 'https://gitlab.com/api/0/projects/sentry-org/sentry-project' enabled true token 'access_token_123' + project_name 'Sentry Project' + organization_name 'Sentry Org' end end diff --git a/spec/features/markdown/markdown_spec.rb b/spec/features/markdown/markdown_spec.rb index 3b37ede8579..8815643ca96 100644 --- a/spec/features/markdown/markdown_spec.rb +++ b/spec/features/markdown/markdown_spec.rb @@ -13,7 +13,7 @@ require 'erb' # # Raw Markdown # -> `markdown` helper -# -> Redcarpet::Render::GitlabHTML converts Markdown to HTML +# -> CommonMark::Render::GitlabHTML converts Markdown to HTML # -> Post-process HTML # -> `gfm` helper # -> HTML::Pipeline @@ -324,31 +324,6 @@ describe 'GitLab Markdown', :aggregate_failures do end end - context 'Redcarpet documents' do - before do - allow_any_instance_of(Banzai::Filter::MarkdownFilter).to receive(:engine).and_return('Redcarpet') - @html = markdown(@feat.raw_markdown) - end - - it 'processes certain elements differently' do - aggregate_failures 'parses superscript' do - expect(doc).to have_selector('sup', count: 3) - end - - aggregate_failures 'permits style attribute in th elements' do - expect(doc.at_css('th:contains("Header")')['style']).to eq 'text-align: center' - expect(doc.at_css('th:contains("Row")')['style']).to eq 'text-align: right' - expect(doc.at_css('th:contains("Example")')['style']).to eq 'text-align: left' - end - - aggregate_failures 'permits style attribute in td elements' do - expect(doc.at_css('td:contains("Foo")')['style']).to eq 'text-align: center' - expect(doc.at_css('td:contains("Bar")')['style']).to eq 'text-align: right' - expect(doc.at_css('td:contains("Baz")')['style']).to eq 'text-align: left' - end - end - end - # Fake a `current_user` helper def current_user @feat.user diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb index f45bcabd196..b43711f6ef6 100644 --- a/spec/features/profiles/user_edit_profile_spec.rb +++ b/spec/features/profiles/user_edit_profile_spec.rb @@ -10,6 +10,7 @@ describe 'User edit profile' do def submit_settings click_button 'Update profile settings' + wait_for_requests if respond_to?(:wait_for_requests) end it 'changes user profile' do @@ -35,6 +36,17 @@ describe 'User edit profile' do expect(page).to have_content('Profile was successfully updated') end + it 'shows an error if the full name contains an emoji', :js do + simulate_input('#user_name', 'Martin 😀') + submit_settings + + page.within('.qa-full-name') do + expect(page).to have_css '.gl-field-error-outline' + expect(find('.gl-field-error')).not_to have_selector('.hidden') + expect(find('.gl-field-error')).to have_content('Using emojis in names seems fun, but please try to set a status message instead') + end + end + context 'user avatar' do before do attach_file(:user_avatar, Rails.root.join('spec', 'fixtures', 'banana_sample.gif')) @@ -61,6 +73,11 @@ describe 'User edit profile' do end context 'user status', :js do + def visit_user + visit user_path(user) + wait_for_requests + end + def select_emoji(emoji_name, is_modal = false) emoji_menu_class = is_modal ? '.js-modal-status-emoji-menu' : '.js-status-emoji-menu' toggle_button = find('.js-toggle-emoji-menu') @@ -71,18 +88,16 @@ describe 'User edit profile' do context 'profile edit form' do it 'shows the user status form' do - visit(profile_path) - expect(page).to have_content('Current status') end it 'adds emoji to user status' do emoji = 'biohazard' - visit(profile_path) select_emoji(emoji) submit_settings - visit user_path(user) + visit_user + within('.cover-status') do expect(page).to have_emoji(emoji) end @@ -90,11 +105,11 @@ describe 'User edit profile' do it 'adds message to user status' do message = 'I have something to say' - visit(profile_path) fill_in 'js-status-message-field', with: message submit_settings - visit user_path(user) + visit_user + within('.cover-status') do expect(page).to have_emoji('speech_balloon') expect(page).to have_content message @@ -104,12 +119,12 @@ describe 'User edit profile' do it 'adds message and emoji to user status' do emoji = 'tanabata_tree' message = 'Playing outside' - visit(profile_path) select_emoji(emoji) fill_in 'js-status-message-field', with: message submit_settings - visit user_path(user) + visit_user + within('.cover-status') do expect(page).to have_emoji(emoji) expect(page).to have_content message @@ -119,7 +134,8 @@ describe 'User edit profile' do it 'clears the user status' do user_status = create(:user_status, user: user, message: 'Eating bread', emoji: 'stuffed_flatbread') - visit user_path(user) + visit_user + within('.cover-status') do expect(page).to have_emoji(user_status.emoji) expect(page).to have_content user_status.message @@ -129,15 +145,13 @@ describe 'User edit profile' do click_button 'js-clear-user-status-button' submit_settings - wait_for_requests + visit_user - visit user_path(user) expect(page).not_to have_selector '.cover-status' end it 'displays a default emoji if only message is entered' do message = 'a status without emoji' - visit(profile_path) fill_in 'js-status-message-field', with: message within('.js-toggle-emoji-menu') do @@ -162,6 +176,7 @@ describe 'User edit profile' do page.within "#set-user-status-modal" do click_button 'Set status' end + wait_for_requests end before do @@ -202,7 +217,8 @@ describe 'User edit profile' do select_emoji(emoji, true) set_user_status_in_modal - visit user_path(user) + visit_user + within('.cover-status') do expect(page).to have_emoji(emoji) end @@ -225,7 +241,8 @@ describe 'User edit profile' do find('.js-status-message-field').native.send_keys(message) set_user_status_in_modal - visit user_path(user) + visit_user + within('.cover-status') do expect(page).to have_emoji('speech_balloon') expect(page).to have_content message @@ -240,7 +257,8 @@ describe 'User edit profile' do find('.js-status-message-field').native.send_keys(message) set_user_status_in_modal - visit user_path(user) + visit_user + within('.cover-status') do expect(page).to have_emoji(emoji) expect(page).to have_content message @@ -250,7 +268,9 @@ describe 'User edit profile' do it 'clears the user status with the "X" button' do user_status = create(:user_status, user: user, message: 'Eating bread', emoji: 'stuffed_flatbread') - visit user_path(user) + visit_user + wait_for_requests + within('.cover-status') do expect(page).to have_emoji(user_status.emoji) expect(page).to have_content user_status.message @@ -265,14 +285,18 @@ describe 'User edit profile' do find('.js-clear-user-status-button').click set_user_status_in_modal - visit user_path(user) + visit_user + wait_for_requests + expect(page).not_to have_selector '.cover-status' end it 'clears the user status with the "Remove status" button' do user_status = create(:user_status, user: user, message: 'Eating bread', emoji: 'stuffed_flatbread') - visit user_path(user) + visit_user + wait_for_requests + within('.cover-status') do expect(page).to have_emoji(user_status.emoji) expect(page).to have_content user_status.message @@ -288,7 +312,8 @@ describe 'User edit profile' do click_button 'Remove status' end - visit user_path(user) + visit_user + expect(page).not_to have_selector '.cover-status' end diff --git a/spec/features/projects/artifacts/user_downloads_artifacts_spec.rb b/spec/features/projects/artifacts/user_downloads_artifacts_spec.rb index 554f0b49052..5cb015e80be 100644 --- a/spec/features/projects/artifacts/user_downloads_artifacts_spec.rb +++ b/spec/features/projects/artifacts/user_downloads_artifacts_spec.rb @@ -7,7 +7,7 @@ describe "User downloads artifacts" do shared_examples "downloading" do it "downloads the zip" do - expect(page.response_headers["Content-Disposition"]).to eq(%Q{attachment; filename="#{job.artifacts_file.filename}"}) + expect(page.response_headers["Content-Disposition"]).to eq(%Q{attachment; filename*=UTF-8''#{job.artifacts_file.filename}; filename="#{job.artifacts_file.filename}"}) expect(page.response_headers['Content-Transfer-Encoding']).to eq("binary") expect(page.response_headers['Content-Type']).to eq("application/zip") expect(page.source.b).to eq(job.artifacts_file.file.read.b) diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb index e2f9e7e9cc5..3edcc7ac2cd 100644 --- a/spec/features/projects/blobs/blob_show_spec.rb +++ b/spec/features/projects/blobs/blob_show_spec.rb @@ -5,8 +5,8 @@ describe 'File blob', :js do let(:project) { create(:project, :public, :repository) } - def visit_blob(path, anchor: nil, ref: 'master', legacy_render: nil) - visit project_blob_path(project, File.join(ref, path), anchor: anchor, legacy_render: legacy_render) + def visit_blob(path, anchor: nil, ref: 'master') + visit project_blob_path(project, File.join(ref, path), anchor: anchor) wait_for_requests end @@ -171,21 +171,6 @@ describe 'File blob', :js do end end end - - context 'when rendering legacy markdown' do - before do - visit_blob('files/commonmark/file.md', legacy_render: 1) - - wait_for_requests - end - - it 'renders using RedCarpet' do - aggregate_failures do - expect(page).to have_content("sublist") - expect(page).to have_xpath("//ol//li//ul") - end - end - end end context 'Markdown file (stored in LFS)' do diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb index d5b20605860..6e6c299ee2e 100644 --- a/spec/features/projects/blobs/edit_spec.rb +++ b/spec/features/projects/blobs/edit_spec.rb @@ -81,17 +81,6 @@ describe 'Editing file blob', :js do expect(page).to have_content("sublist") expect(page).not_to have_xpath("//ol//li//ul") end - - it 'renders content with RedCarpet when legacy_render is set' do - visit project_edit_blob_path(project, tree_join(branch, readme_file_path), legacy_render: 1) - fill_editor(content: "1. one\\n - sublist\\n") - click_link 'Preview' - wait_for_requests - - # the above generates a sublist list in RedCarpet - expect(page).to have_content("sublist") - expect(page).to have_xpath("//ol//li//ul") - end end end diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 24830b2bd3e..65ce872363f 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -220,7 +220,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do artifact_request = requests.find { |req| req.url.match(%r{artifacts/download}) } - expect(artifact_request.response_headers["Content-Disposition"]).to eq(%Q{attachment; filename="#{job.artifacts_file.filename}"}) + expect(artifact_request.response_headers["Content-Disposition"]).to eq(%Q{attachment; filename*=UTF-8''#{job.artifacts_file.filename}; filename="#{job.artifacts_file.filename}"}) expect(artifact_request.response_headers['Content-Transfer-Encoding']).to eq("binary") expect(artifact_request.response_headers['Content-Type']).to eq("image/gif") expect(artifact_request.body).to eq(job.artifacts_file.file.read.b) diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb index f505023d1d0..3b469fee867 100644 --- a/spec/features/projects/wiki/markdown_preview_spec.rb +++ b/spec/features/projects/wiki/markdown_preview_spec.rb @@ -175,20 +175,6 @@ describe 'Projects > Wiki > User previews markdown changes', :js do expect(page).to have_content("sublist") expect(page).not_to have_xpath("//ol//li//ul") end - - it 'renders content with RedCarpet when legacy_render is set' do - wiki_page = create(:wiki_page, - wiki: project.wiki, - attrs: { title: 'home', content: "Empty content" }) - visit(project_wiki_edit_path(project, wiki_page, legacy_render: 1)) - - fill_in :wiki_content, with: "1. one\n - sublist\n" - click_on "Preview" - - # the above generates a sublist list in RedCarpet - expect(page).to have_content("sublist") - expect(page).to have_xpath("//ol//li//ul") - end end end diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index f7efc3f325c..bc36c6f948f 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -110,16 +110,23 @@ describe 'Project' do it 'shows project topics' do project.update_attribute(:tag_list, 'topic1') + visit path + expect(page).to have_css('.home-panel-topic-list') - expect(page).to have_content('topic1') + expect(page).to have_link('Topic1', href: explore_projects_path(tag: 'topic1')) end it 'shows up to 3 project tags' do project.update_attribute(:tag_list, 'topic1, topic2, topic3, topic4') + visit path + expect(page).to have_css('.home-panel-topic-list') - expect(page).to have_content('topic1, topic2, topic3 + 1 more') + expect(page).to have_link('Topic1', href: explore_projects_path(tag: 'topic1')) + expect(page).to have_link('Topic2', href: explore_projects_path(tag: 'topic2')) + expect(page).to have_link('Topic3', href: explore_projects_path(tag: 'topic3')) + expect(page).to have_content('+ 1 more') end end diff --git a/spec/features/snippets/show_spec.rb b/spec/features/snippets/show_spec.rb index 367a479f62a..eb974c7c7fd 100644 --- a/spec/features/snippets/show_spec.rb +++ b/spec/features/snippets/show_spec.rb @@ -80,19 +80,6 @@ describe 'Snippet', :js do end end - context 'when rendering legacy markdown' do - before do - visit snippet_path(snippet, legacy_render: 1) - - wait_for_requests - end - - it 'renders using RedCarpet' do - expect(page).to have_content("sublist") - expect(page).to have_xpath("//ol//li//ul") - end - end - context 'with cached CommonMark html' do let(:snippet) { create(:personal_snippet, :public, file_name: file_name, content: content, cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION) } @@ -100,14 +87,6 @@ describe 'Snippet', :js do expect(page).not_to have_xpath("//ol//li//ul") end end - - context 'with cached Redcarpet html' do - let(:snippet) { create(:personal_snippet, :public, file_name: file_name, content: content, cached_markdown_version: CacheMarkdownField::CACHE_REDCARPET_VERSION) } - - it 'renders correctly' do - expect(page).to have_xpath("//ol//li//ul") - end - end end context 'switching to the simple viewer' do diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb index b549f2b5c62..6fe840dccf6 100644 --- a/spec/features/task_lists_spec.rb +++ b/spec/features/task_lists_spec.rb @@ -36,19 +36,6 @@ describe 'Task Lists' do MARKDOWN end - let(:nested_tasks_markdown_redcarpet) do - <<-EOT.strip_heredoc - - [ ] Task a - - [x] Task a.1 - - [ ] Task a.2 - - [ ] Task b - - 1. [ ] Task 1 - 1. [ ] Task 1.1 - 1. [x] Task 1.2 - EOT - end - let(:nested_tasks_markdown) do <<-EOT.strip_heredoc - [ ] Task a @@ -153,61 +140,6 @@ describe 'Task Lists' do expect(page).to have_content("1 of 1 task completed") end end - - shared_examples 'shared nested tasks' do - before do - allow(Banzai::Filter::MarkdownFilter).to receive(:engine).and_return('Redcarpet') - visit_issue(project, issue) - end - it 'renders' do - expect(page).to have_selector('ul.task-list', count: 2) - expect(page).to have_selector('li.task-list-item', count: 7) - expect(page).to have_selector('ul input[checked]', count: 1) - expect(page).to have_selector('ol input[checked]', count: 1) - end - - it 'solves tasks' do - expect(page).to have_content("2 of 7 tasks completed") - - page.find('li.task-list-item', text: 'Task b').find('input').click - wait_for_requests - page.find('li.task-list-item ul li.task-list-item', text: 'Task a.2').find('input').click - wait_for_requests - page.find('li.task-list-item ol li.task-list-item', text: 'Task 1.1').find('input').click - wait_for_requests - - expect(page).to have_content("5 of 7 tasks completed") - - visit_issue(project, issue) # reload to see new system notes - - expect(page).to have_content('marked the task Task b as complete') - expect(page).to have_content('marked the task Task a.2 as complete') - expect(page).to have_content('marked the task Task 1.1 as complete') - end - end - - describe 'nested tasks', :js do - let(:cache_version) { CacheMarkdownField::CACHE_COMMONMARK_VERSION } - let!(:issue) do - create(:issue, description: nested_tasks_markdown, author: user, project: project, - cached_markdown_version: cache_version) - end - - before do - visit_issue(project, issue) - end - - context 'with Redcarpet' do - let(:cache_version) { CacheMarkdownField::CACHE_REDCARPET_VERSION } - let(:nested_tasks_markdown) { nested_tasks_markdown_redcarpet } - - it_behaves_like 'shared nested tasks' - end - - context 'with CommonMark' do - it_behaves_like 'shared nested tasks' - end - end end describe 'for Notes' do diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 682fae06434..34cb09942be 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -314,6 +314,14 @@ describe IssuesFinder do end end + context 'filtering by issue term in title' do + let(:params) { { search: 'git', in: 'title' } } + + it 'returns issues with title match for search term' do + expect(issues).to contain_exactly(issue1) + end + end + context 'filtering by issues iids' do let(:params) { { iids: issue3.iid } } diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb index e5d01c3bd03..bbeacf1707b 100644 --- a/spec/fixtures/markdown.md.erb +++ b/spec/fixtures/markdown.md.erb @@ -6,7 +6,7 @@ started. ## Markdown -GitLab uses [Redcarpet](http://git.io/ld_NVQ) to parse all Markdown into +GitLab uses [Commonmark](https://git.io/fhDag) to parse all Markdown into HTML. It has some special features. Let's try 'em out! diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index af319e5ebfe..8b82dea2524 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -188,7 +188,6 @@ describe IssuablesHelper do issuableRef: "##{issue.iid}", markdownPreviewPath: "/#{@project.full_path}/preview_markdown", markdownDocsPath: '/help/user/markdown', - markdownVersion: CacheMarkdownField::CACHE_COMMONMARK_VERSION, issuableTemplates: [], lockVersion: issue.lock_version, projectPath: @project.path, diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb index a0c0af94fa5..c3956ba08fd 100644 --- a/spec/helpers/markup_helper_spec.rb +++ b/spec/helpers/markup_helper_spec.rb @@ -212,17 +212,6 @@ describe MarkupHelper do helper.render_wiki_content(@wiki) end - it 'uses Wiki pipeline for markdown files with RedCarpet if feature disabled' do - stub_feature_flags(commonmark_for_repositories: false) - allow(@wiki).to receive(:format).and_return(:markdown) - - expect(helper).to receive(:markdown_unsafe).with('wiki content', - pipeline: :wiki, project: project, project_wiki: @wiki, page_slug: "nested/page", - issuable_state_filter_enabled: true, markdown_engine: :redcarpet) - - helper.render_wiki_content(@wiki) - end - it "uses Asciidoctor for asciidoc files" do allow(@wiki).to receive(:format).and_return(:asciidoc) @@ -273,16 +262,6 @@ describe MarkupHelper do it 'defaults to CommonMark' do expect(helper.markup('foo.md', 'x^2')).to include('x^2') end - - it 'honors markdown_engine for RedCarpet' do - expect(helper.markup('foo.md', 'x^2', { markdown_engine: :redcarpet })).to include('x<sup>2</sup>') - end - - it 'uses RedCarpet if feature disabled' do - stub_feature_flags(commonmark_for_repositories: false) - - expect(helper.markup('foo.md', 'x^2', { markdown_engine: :redcarpet })).to include('x<sup>2</sup>') - end end describe '#first_line_in_markdown' do diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 10f61731206..49895b0680b 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -372,21 +372,16 @@ describe ProjectsHelper do end context 'when project has external wiki' do - before do - allow(project).to receive(:has_external_wiki?).and_return(true) - end - it 'includes external wiki tab' do + project.create_external_wiki_service(active: true, properties: { 'external_wiki_url' => 'https://gitlab.com' }) + is_expected.to include(:external_wiki) end end context 'when project does not have external wiki' do - before do - allow(project).to receive(:has_external_wiki?).and_return(false) - end - it 'does not include external wiki tab' do + expect(project.external_wiki).to be_nil is_expected.not_to include(:external_wiki) end end @@ -540,18 +535,6 @@ describe ProjectsHelper do end end - describe '#legacy_render_context' do - it 'returns the redcarpet engine' do - params = { legacy_render: '1' } - - expect(helper.legacy_render_context(params)).to include(markdown_engine: :redcarpet) - end - - it 'returns nothing' do - expect(helper.legacy_render_context({})).to be_empty - end - end - describe '#explore_projects_tab?' do subject { helper.explore_projects_tab? } diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js index 9d55c615450..1e9470970ff 100644 --- a/spec/javascripts/api_spec.js +++ b/spec/javascripts/api_spec.js @@ -49,6 +49,22 @@ describe('Api', () => { }); }); + describe('groupMembers', () => { + it('fetches group members', done => { + const groupId = '54321'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/members`; + const expectedData = [{ id: 7 }]; + mock.onGet(expectedUrl).reply(200, expectedData); + + Api.groupMembers(groupId) + .then(({ data }) => { + expect(data).toEqual(expectedData); + }) + .then(done) + .catch(done.fail); + }); + }); + describe('groups', () => { it('fetches groups', done => { const query = 'dummy query'; diff --git a/spec/javascripts/diffs/components/tree_list_spec.js b/spec/javascripts/diffs/components/tree_list_spec.js index 08b0b4f9e45..c5ef48a81e9 100644 --- a/spec/javascripts/diffs/components/tree_list_spec.js +++ b/spec/javascripts/diffs/components/tree_list_spec.js @@ -83,17 +83,6 @@ describe('Diffs tree list component', () => { expect(vm.$el.querySelectorAll('.file-row')[1].textContent).toContain('app'); }); - it('filters tree list to blobs matching search', done => { - vm.search = 'app/index'; - - vm.$nextTick(() => { - expect(vm.$el.querySelectorAll('.file-row').length).toBe(1); - expect(vm.$el.querySelectorAll('.file-row')[0].textContent).toContain('index.js'); - - done(); - }); - }); - it('calls toggleTreeOpen when clicking folder', () => { spyOn(vm.$store, 'dispatch').and.stub(); @@ -130,14 +119,4 @@ describe('Diffs tree list component', () => { }); }); }); - - describe('clearSearch', () => { - it('resets search', () => { - vm.search = 'test'; - - vm.$el.querySelector('.tree-list-clear-icon').click(); - - expect(vm.search).toBe(''); - }); - }); }); diff --git a/spec/javascripts/diffs/store/getters_spec.js b/spec/javascripts/diffs/store/getters_spec.js index 190ca1230ca..4f69dc92ab8 100644 --- a/spec/javascripts/diffs/store/getters_spec.js +++ b/spec/javascripts/diffs/store/getters_spec.js @@ -242,7 +242,11 @@ describe('Diffs Module Getters', () => { }, }; - expect(getters.allBlobs(localState)).toEqual([ + expect( + getters.allBlobs(localState, { + flatBlobsList: getters.flatBlobsList(localState), + }), + ).toEqual([ { isHeader: true, path: '/', diff --git a/spec/javascripts/ide/components/ide_spec.js b/spec/javascripts/ide/components/ide_spec.js index 55f40be0e4e..dc5790f6562 100644 --- a/spec/javascripts/ide/components/ide_spec.js +++ b/spec/javascripts/ide/components/ide_spec.js @@ -1,5 +1,4 @@ import Vue from 'vue'; -import Mousetrap from 'mousetrap'; import store from '~/ide/stores'; import ide from '~/ide/components/ide.vue'; import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; @@ -72,73 +71,6 @@ describe('ide component', () => { }); }); - describe('file finder', () => { - beforeEach(done => { - spyOn(vm, 'toggleFileFinder'); - - vm.$store.state.fileFindVisible = true; - - vm.$nextTick(done); - }); - - it('calls toggleFileFinder on `t` key press', done => { - Mousetrap.trigger('t'); - - vm.$nextTick() - .then(() => { - expect(vm.toggleFileFinder).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); - }); - - it('calls toggleFileFinder on `command+p` key press', done => { - Mousetrap.trigger('command+p'); - - vm.$nextTick() - .then(() => { - expect(vm.toggleFileFinder).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); - }); - - it('calls toggleFileFinder on `ctrl+p` key press', done => { - Mousetrap.trigger('ctrl+p'); - - vm.$nextTick() - .then(() => { - expect(vm.toggleFileFinder).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); - }); - - it('always allows `command+p` to trigger toggleFileFinder', () => { - expect( - vm.mousetrapStopCallback(null, vm.$el.querySelector('.dropdown-input-field'), 'command+p'), - ).toBe(false); - }); - - it('always allows `ctrl+p` to trigger toggleFileFinder', () => { - expect( - vm.mousetrapStopCallback(null, vm.$el.querySelector('.dropdown-input-field'), 'ctrl+p'), - ).toBe(false); - }); - - it('onlys handles `t` when focused in input-field', () => { - expect( - vm.mousetrapStopCallback(null, vm.$el.querySelector('.dropdown-input-field'), 't'), - ).toBe(true); - }); - - it('stops callback in monaco editor', () => { - setFixtures('<div class="inputarea"></div>'); - - expect(vm.mousetrapStopCallback(null, document.querySelector('.inputarea'), 't')).toBe(true); - }); - }); - it('shows error message when set', done => { expect(vm.$el.querySelector('.flash-container')).toBe(null); diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index e3fd9604474..3eff3f655ee 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -232,6 +232,21 @@ describe('common_utils', () => { }); }); + describe('debounceByAnimationFrame', () => { + it('debounces a function to allow a maximum of one call per animation frame', done => { + const spy = jasmine.createSpy('spy'); + const debouncedSpy = commonUtils.debounceByAnimationFrame(spy); + window.requestAnimationFrame(() => { + debouncedSpy(); + debouncedSpy(); + window.requestAnimationFrame(() => { + expect(spy).toHaveBeenCalledTimes(1); + done(); + }); + }); + }); + }); + describe('getParameterByName', () => { beforeEach(() => { window.history.pushState({}, null, '?scope=all&p=2'); diff --git a/spec/javascripts/lib/utils/grammar_spec.js b/spec/javascripts/lib/utils/grammar_spec.js new file mode 100644 index 00000000000..377b2ffb48c --- /dev/null +++ b/spec/javascripts/lib/utils/grammar_spec.js @@ -0,0 +1,35 @@ +import * as grammar from '~/lib/utils/grammar'; + +describe('utils/grammar', () => { + describe('toNounSeriesText', () => { + it('with empty items returns empty string', () => { + expect(grammar.toNounSeriesText([])).toBe(''); + }); + + it('with single item returns item', () => { + const items = ['Lorem Ipsum']; + + expect(grammar.toNounSeriesText(items)).toBe(items[0]); + }); + + it('with 2 items returns item1 and item2', () => { + const items = ['Dolar', 'Sit Amit']; + + expect(grammar.toNounSeriesText(items)).toBe(`${items[0]} and ${items[1]}`); + }); + + it('with 3 items returns comma separated series', () => { + const items = ['Lorem', 'Ipsum', 'dolar']; + const expected = 'Lorem, Ipsum, and dolar'; + + expect(grammar.toNounSeriesText(items)).toBe(expected); + }); + + it('with 6 items returns comma separated series', () => { + const items = ['Lorem', 'ipsum', 'dolar', 'sit', 'amit', 'consectetur']; + const expected = 'Lorem, ipsum, dolar, sit, amit, and consectetur'; + + expect(grammar.toNounSeriesText(items)).toBe(expected); + }); + }); +}); diff --git a/spec/javascripts/lib/utils/icon_utils_spec.js b/spec/javascripts/lib/utils/icon_utils_spec.js new file mode 100644 index 00000000000..3fd3940efe8 --- /dev/null +++ b/spec/javascripts/lib/utils/icon_utils_spec.js @@ -0,0 +1,67 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import * as iconUtils from '~/lib/utils/icon_utils'; + +describe('Icon utils', () => { + describe('getSvgIconPathContent', () => { + let spriteIcons; + + beforeAll(() => { + spriteIcons = gon.sprite_icons; + gon.sprite_icons = 'mockSpriteIconsEndpoint'; + }); + + afterAll(() => { + gon.sprite_icons = spriteIcons; + }); + + let axiosMock; + let mockEndpoint; + let getIcon; + const mockName = 'mockIconName'; + const mockPath = 'mockPath'; + + beforeEach(() => { + axiosMock = new MockAdapter(axios); + mockEndpoint = axiosMock.onGet(gon.sprite_icons); + getIcon = iconUtils.getSvgIconPathContent(mockName); + }); + + afterEach(() => { + axiosMock.restore(); + }); + + it('extracts svg icon path content from sprite icons', done => { + mockEndpoint.replyOnce( + 200, + `<svg><symbol id="${mockName}"><path d="${mockPath}"/></symbol></svg>`, + ); + getIcon + .then(path => { + expect(path).toBe(mockPath); + done(); + }) + .catch(done.fail); + }); + + it('returns null if icon path content does not exist', done => { + mockEndpoint.replyOnce(200, ``); + getIcon + .then(path => { + expect(path).toBe(null); + done(); + }) + .catch(done.fail); + }); + + it('returns null if an http error occurs', done => { + mockEndpoint.replyOnce(500); + getIcon + .then(path => { + expect(path).toBe(null); + done(); + }) + .catch(done.fail); + }); + }); +}); diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index 565b87de248..97b9671c809 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -29,7 +29,7 @@ describe('Dashboard', () => { beforeEach(() => { setFixtures(` <div class="prometheus-graphs"></div> - <div class="nav-sidebar"></div> + <div class="layout-page"></div> `); DashboardComponent = Vue.extend(Dashboard); }); @@ -164,16 +164,16 @@ describe('Dashboard', () => { jasmine.clock().uninstall(); }); - it('rerenders the dashboard when the sidebar is resized', done => { + it('sets elWidth to page width when the sidebar is resized', done => { const component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData, hasMetrics: true, showPanels: false }, }); - expect(component.forceRedraw).toEqual(0); + expect(component.elWidth).toEqual(0); - const navSidebarEl = document.querySelector('.nav-sidebar'); - navSidebarEl.classList.add('nav-sidebar-collapsed'); + const pageLayoutEl = document.querySelector('.layout-page'); + pageLayoutEl.classList.add('page-with-icon-sidebar'); Vue.nextTick() .then(() => { @@ -181,7 +181,7 @@ describe('Dashboard', () => { return Vue.nextTick(); }) .then(() => { - expect(component.forceRedraw).toEqual(component.elWidth); + expect(component.elWidth).toEqual(pageLayoutEl.clientWidth); done(); }) .catch(done.fail); diff --git a/spec/javascripts/notes/components/discussion_reply_placeholder_spec.js b/spec/javascripts/notes/components/discussion_reply_placeholder_spec.js new file mode 100644 index 00000000000..07a366cf339 --- /dev/null +++ b/spec/javascripts/notes/components/discussion_reply_placeholder_spec.js @@ -0,0 +1,34 @@ +import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; + +const localVue = createLocalVue(); + +describe('ReplyPlaceholder', () => { + let wrapper; + + beforeEach(() => { + wrapper = shallowMount(ReplyPlaceholder, { + localVue, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('emits onClick even on button click', () => { + const button = wrapper.find({ ref: 'button' }); + + button.trigger('click'); + + expect(wrapper.emitted()).toEqual({ + onClick: [[]], + }); + }); + + it('should render reply button', () => { + const button = wrapper.find({ ref: 'button' }); + + expect(button.text()).toEqual('Reply...'); + }); +}); diff --git a/spec/javascripts/notes/components/noteable_discussion_spec.js b/spec/javascripts/notes/components/noteable_discussion_spec.js index c4b7eb17393..2eae22e095f 100644 --- a/spec/javascripts/notes/components/noteable_discussion_spec.js +++ b/spec/javascripts/notes/components/noteable_discussion_spec.js @@ -1,6 +1,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils'; import createStore from '~/notes/stores'; import noteableDiscussion from '~/notes/components/noteable_discussion.vue'; +import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue'; import '~/behaviors/markdown/render_gfm'; import { noteableDataMock, discussionMock, notesDataMock } from '../mock_data'; import mockDiffFile from '../../diffs/mock_data/diff_file'; @@ -57,27 +58,23 @@ describe('noteable_discussion component', () => { }); describe('actions', () => { - it('should render reply button', () => { - expect( - wrapper - .find('.js-vue-discussion-reply') - .text() - .trim(), - ).toEqual('Reply...'); - }); - it('should toggle reply form', done => { - wrapper.find('.js-vue-discussion-reply').trigger('click'); + const replyPlaceholder = wrapper.find(ReplyPlaceholder); - wrapper.vm.$nextTick(() => { - expect(wrapper.vm.isReplying).toEqual(true); + wrapper.vm + .$nextTick() + .then(() => { + expect(wrapper.vm.isReplying).toEqual(false); - // There is a watcher for `isReplying` which will init autosave in the next tick - wrapper.vm.$nextTick(() => { + replyPlaceholder.vm.$emit('onClick'); + }) + .then(() => wrapper.vm.$nextTick()) + .then(() => { + expect(wrapper.vm.isReplying).toEqual(true); expect(wrapper.vm.$refs.noteForm).not.toBeNull(); - done(); - }); - }); + }) + .then(done) + .catch(done.fail); }); it('does not render jump to discussion button', () => { diff --git a/spec/javascripts/ide/components/file_finder/index_spec.js b/spec/javascripts/vue_shared/components/file_finder/index_spec.js index 15ef8c31f91..bae4741f652 100644 --- a/spec/javascripts/ide/components/file_finder/index_spec.js +++ b/spec/javascripts/vue_shared/components/file_finder/index_spec.js @@ -1,54 +1,51 @@ import Vue from 'vue'; -import store from '~/ide/stores'; -import FindFileComponent from '~/ide/components/file_finder/index.vue'; +import Mousetrap from 'mousetrap'; +import FindFileComponent from '~/vue_shared/components/file_finder/index.vue'; import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes'; -import router from '~/ide/ide_router'; -import { file, resetStore } from '../../helpers'; -import { mountComponentWithStore } from '../../../helpers/vue_mount_component_helper'; +import { file } from 'spec/ide/helpers'; +import timeoutPromise from 'spec/helpers/set_timeout_promise_helper'; -describe('IDE File finder item spec', () => { +describe('File finder item spec', () => { const Component = Vue.extend(FindFileComponent); let vm; - beforeEach(done => { - setFixtures('<div id="app"></div>'); - - vm = mountComponentWithStore(Component, { - store, - el: '#app', - props: { - index: 0, + function createComponent(props) { + vm = new Component({ + propsData: { + files: [], + visible: true, + loading: false, + ...props, }, }); - setTimeout(done); + vm.$mount('#app'); + } + + beforeEach(() => { + setFixtures('<div id="app"></div>'); }); afterEach(() => { vm.$destroy(); - - resetStore(vm.$store); }); describe('with entries', () => { beforeEach(done => { - Vue.set(vm.$store.state.entries, 'folder', { - ...file('folder'), - path: 'folder', - type: 'folder', - }); - - Vue.set(vm.$store.state.entries, 'index.js', { - ...file('index.js'), - path: 'index.js', - type: 'blob', - url: '/index.jsurl', - }); - - Vue.set(vm.$store.state.entries, 'component.js', { - ...file('component.js'), - path: 'component.js', - type: 'blob', + createComponent({ + files: [ + { + ...file('index.js'), + path: 'index.js', + type: 'blob', + url: '/index.jsurl', + }, + { + ...file('component.js'), + path: 'component.js', + type: 'blob', + }, + ], }); setTimeout(done); @@ -56,13 +53,14 @@ describe('IDE File finder item spec', () => { it('renders list of blobs', () => { expect(vm.$el.textContent).toContain('index.js'); + expect(vm.$el.textContent).toContain('component.js'); expect(vm.$el.textContent).not.toContain('folder'); }); it('filters entries', done => { vm.searchText = 'index'; - vm.$nextTick(() => { + setTimeout(() => { expect(vm.$el.textContent).toContain('index.js'); expect(vm.$el.textContent).not.toContain('component.js'); @@ -73,8 +71,8 @@ describe('IDE File finder item spec', () => { it('shows clear button when searchText is not empty', done => { vm.searchText = 'index'; - vm.$nextTick(() => { - expect(vm.$el.querySelector('.dropdown-input-clear').classList).toContain('show'); + setTimeout(() => { + expect(vm.$el.querySelector('.dropdown-input').classList).toContain('has-value'); expect(vm.$el.querySelector('.dropdown-input-search').classList).toContain('hidden'); done(); @@ -84,11 +82,11 @@ describe('IDE File finder item spec', () => { it('clear button resets searchText', done => { vm.searchText = 'index'; - vm.$nextTick() + timeoutPromise() .then(() => { vm.$el.querySelector('.dropdown-input-clear').click(); }) - .then(vm.$nextTick) + .then(timeoutPromise) .then(() => { expect(vm.searchText).toBe(''); }) @@ -100,11 +98,11 @@ describe('IDE File finder item spec', () => { spyOn(vm.$refs.searchInput, 'focus'); vm.searchText = 'index'; - vm.$nextTick() + timeoutPromise() .then(() => { vm.$el.querySelector('.dropdown-input-clear').click(); }) - .then(vm.$nextTick) + .then(timeoutPromise) .then(() => { expect(vm.$refs.searchInput.focus).toHaveBeenCalled(); }) @@ -116,7 +114,7 @@ describe('IDE File finder item spec', () => { it('returns 1 when no filtered entries exist', done => { vm.searchText = 'testing 123'; - vm.$nextTick(() => { + setTimeout(() => { expect(vm.listShowCount).toBe(1); done(); @@ -136,7 +134,7 @@ describe('IDE File finder item spec', () => { it('returns 33 when entries dont exist', done => { vm.searchText = 'testing 123'; - vm.$nextTick(() => { + setTimeout(() => { expect(vm.listHeight).toBe(33); done(); @@ -148,7 +146,7 @@ describe('IDE File finder item spec', () => { it('returns length of filtered blobs', done => { vm.searchText = 'index'; - vm.$nextTick(() => { + setTimeout(() => { expect(vm.filteredBlobsLength).toBe(1); done(); @@ -162,7 +160,7 @@ describe('IDE File finder item spec', () => { vm.focusedIndex = 1; vm.searchText = 'test'; - vm.$nextTick(() => { + setTimeout(() => { expect(vm.focusedIndex).toBe(0); done(); @@ -170,16 +168,16 @@ describe('IDE File finder item spec', () => { }); }); - describe('fileFindVisible', () => { + describe('visible', () => { it('returns searchText when false', done => { vm.searchText = 'test'; - vm.$store.state.fileFindVisible = true; + vm.visible = true; - vm.$nextTick() + timeoutPromise() .then(() => { - vm.$store.state.fileFindVisible = false; + vm.visible = false; }) - .then(vm.$nextTick) + .then(timeoutPromise) .then(() => { expect(vm.searchText).toBe(''); }) @@ -191,20 +189,19 @@ describe('IDE File finder item spec', () => { describe('openFile', () => { beforeEach(() => { - spyOn(router, 'push'); - spyOn(vm, 'toggleFileFinder'); + spyOn(vm, '$emit'); }); it('closes file finder', () => { - vm.openFile(vm.$store.state.entries['index.js']); + vm.openFile(vm.files[0]); - expect(vm.toggleFileFinder).toHaveBeenCalled(); + expect(vm.$emit).toHaveBeenCalledWith('toggle', false); }); it('pushes to router', () => { - vm.openFile(vm.$store.state.entries['index.js']); + vm.openFile(vm.files[0]); - expect(router.push).toHaveBeenCalledWith('/project/index.jsurl'); + expect(vm.$emit).toHaveBeenCalledWith('click', vm.files[0]); }); }); @@ -217,8 +214,8 @@ describe('IDE File finder item spec', () => { vm.$refs.searchInput.dispatchEvent(event); - vm.$nextTick(() => { - expect(vm.openFile).toHaveBeenCalledWith(vm.$store.state.entries['index.js']); + setTimeout(() => { + expect(vm.openFile).toHaveBeenCalledWith(vm.files[0]); done(); }); @@ -228,12 +225,12 @@ describe('IDE File finder item spec', () => { const event = new CustomEvent('keyup'); event.keyCode = ESC_KEY_CODE; - spyOn(vm, 'toggleFileFinder'); + spyOn(vm, '$emit'); vm.$refs.searchInput.dispatchEvent(event); - vm.$nextTick(() => { - expect(vm.toggleFileFinder).toHaveBeenCalled(); + setTimeout(() => { + expect(vm.$emit).toHaveBeenCalledWith('toggle', false); done(); }); @@ -287,18 +284,85 @@ describe('IDE File finder item spec', () => { }); describe('without entries', () => { - it('renders loading text when loading', done => { - store.state.loading = true; - - vm.$nextTick(() => { - expect(vm.$el.textContent).toContain('Loading...'); - - done(); + it('renders loading text when loading', () => { + createComponent({ + loading: true, }); + + expect(vm.$el.textContent).toContain('Loading...'); }); it('renders no files text', () => { + createComponent(); + expect(vm.$el.textContent).toContain('No files found.'); }); }); + + describe('keyboard shortcuts', () => { + beforeEach(done => { + createComponent(); + + spyOn(vm, 'toggle'); + + vm.$nextTick(done); + }); + + it('calls toggle on `t` key press', done => { + Mousetrap.trigger('t'); + + vm.$nextTick() + .then(() => { + expect(vm.toggle).toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); + + it('calls toggle on `command+p` key press', done => { + Mousetrap.trigger('command+p'); + + vm.$nextTick() + .then(() => { + expect(vm.toggle).toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); + + it('calls toggle on `ctrl+p` key press', done => { + Mousetrap.trigger('ctrl+p'); + + vm.$nextTick() + .then(() => { + expect(vm.toggle).toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); + + it('always allows `command+p` to trigger toggle', () => { + expect( + vm.mousetrapStopCallback(null, vm.$el.querySelector('.dropdown-input-field'), 'command+p'), + ).toBe(false); + }); + + it('always allows `ctrl+p` to trigger toggle', () => { + expect( + vm.mousetrapStopCallback(null, vm.$el.querySelector('.dropdown-input-field'), 'ctrl+p'), + ).toBe(false); + }); + + it('onlys handles `t` when focused in input-field', () => { + expect( + vm.mousetrapStopCallback(null, vm.$el.querySelector('.dropdown-input-field'), 't'), + ).toBe(true); + }); + + it('stops callback in monaco editor', () => { + setFixtures('<div class="inputarea"></div>'); + + expect(vm.mousetrapStopCallback(null, document.querySelector('.inputarea'), 't')).toBe(true); + }); + }); }); diff --git a/spec/javascripts/ide/components/file_finder/item_spec.js b/spec/javascripts/vue_shared/components/file_finder/item_spec.js index 0f1116c6912..c1511643a9d 100644 --- a/spec/javascripts/ide/components/file_finder/item_spec.js +++ b/spec/javascripts/vue_shared/components/file_finder/item_spec.js @@ -1,9 +1,9 @@ import Vue from 'vue'; -import ItemComponent from '~/ide/components/file_finder/item.vue'; -import { file } from '../../helpers'; +import ItemComponent from '~/vue_shared/components/file_finder/item.vue'; +import { file } from 'spec/ide/helpers'; import createComponent from '../../../helpers/vue_mount_component_helper'; -describe('IDE File finder item spec', () => { +describe('File finder item spec', () => { const Component = Vue.extend(ItemComponent); let vm; let localFile; diff --git a/spec/lib/api/helpers_spec.rb b/spec/lib/api/helpers_spec.rb index e1aea82653d..08165f147bb 100644 --- a/spec/lib/api/helpers_spec.rb +++ b/spec/lib/api/helpers_spec.rb @@ -179,7 +179,7 @@ describe API::Helpers do context 'when blob name is not null' do it 'returns disposition with the blob name' do - expect(send_git_blob['Content-Disposition']).to eq 'inline; filename="foobar"' + expect(send_git_blob['Content-Disposition']).to eq %q(inline; filename="foobar"; filename*=UTF-8''foobar) end end end diff --git a/spec/lib/banzai/filter/autolink_filter_spec.rb b/spec/lib/banzai/filter/autolink_filter_spec.rb index 6217381c491..4972c4b4bd2 100644 --- a/spec/lib/banzai/filter/autolink_filter_spec.rb +++ b/spec/lib/banzai/filter/autolink_filter_spec.rb @@ -121,6 +121,13 @@ describe Banzai::Filter::AutolinkFilter do expect(doc.to_s).to eq("See #{link}") end + it 'does not autolink bad URLs after we remove trailing punctuation' do + link = 'http://]' + doc = filter("See #{link}") + + expect(doc.to_s).to eq("See #{link}") + end + it 'does not include trailing punctuation' do ['.', ', ok?', '...', '?', '!', ': is that ok?'].each do |trailing_punctuation| doc = filter("See #{link}#{trailing_punctuation}") diff --git a/spec/lib/banzai/filter/markdown_filter_spec.rb b/spec/lib/banzai/filter/markdown_filter_spec.rb index 4c4e821deab..83fcda29680 100644 --- a/spec/lib/banzai/filter/markdown_filter_spec.rb +++ b/spec/lib/banzai/filter/markdown_filter_spec.rb @@ -10,12 +10,6 @@ describe Banzai::Filter::MarkdownFilter do filter('test') end - it 'uses Redcarpet' do - expect_any_instance_of(Banzai::Filter::MarkdownEngines::Redcarpet).to receive(:render).and_return('test') - - filter('test', { markdown_engine: :redcarpet }) - end - it 'uses CommonMark' do expect_any_instance_of(Banzai::Filter::MarkdownEngines::CommonMark).to receive(:render).and_return('test') @@ -47,24 +41,6 @@ describe Banzai::Filter::MarkdownFilter do expect(result).to start_with('<pre><code lang="日">') end end - - context 'using Redcarpet' do - before do - stub_const('Banzai::Filter::MarkdownFilter::DEFAULT_ENGINE', :redcarpet) - end - - it 'adds language to lang attribute when specified' do - result = filter("```html\nsome code\n```") - - expect(result).to start_with("\n<pre><code lang=\"html\">") - end - - it 'does not add language to lang attribute when not specified' do - result = filter("```\nsome code\n```") - - expect(result).to start_with("\n<pre><code>") - end - end end describe 'source line position' do @@ -85,18 +61,6 @@ describe Banzai::Filter::MarkdownFilter do expect(result).to eq '<p>test</p>' end end - - context 'using Redcarpet' do - before do - stub_const('Banzai::Filter::MarkdownFilter::DEFAULT_ENGINE', :redcarpet) - end - - it 'does not support data-sourcepos' do - result = filter('test') - - expect(result).to eq '<p>test</p>' - end - end end describe 'footnotes in tables' do diff --git a/spec/lib/banzai/filter/spaced_link_filter_spec.rb b/spec/lib/banzai/filter/spaced_link_filter_spec.rb index 1ad7f3ff567..76d7644d76c 100644 --- a/spec/lib/banzai/filter/spaced_link_filter_spec.rb +++ b/spec/lib/banzai/filter/spaced_link_filter_spec.rb @@ -26,11 +26,6 @@ describe Banzai::Filter::SpacedLinkFilter do expect(doc.at_css('p')).to be_nil end - it 'does nothing when markdown_engine is redcarpet' do - exp = act = link - expect(filter(act, markdown_engine: :redcarpet).to_html).to eq exp - end - it 'does nothing with empty text' do link = '[](page slug)' doc = filter("See #{link}") diff --git a/spec/lib/gitlab/background_migration_spec.rb b/spec/lib/gitlab/background_migration_spec.rb index 8a83b76fd94..7d3d8a949ef 100644 --- a/spec/lib/gitlab/background_migration_spec.rb +++ b/spec/lib/gitlab/background_migration_spec.rb @@ -104,6 +104,38 @@ describe Gitlab::BackgroundMigration do end end end + + context 'when retry_dead_jobs is true', :sidekiq, :redis do + let(:retry_queue) do + [double(args: ['Object', [3]], queue: described_class.queue, delete: true)] + end + let(:dead_queue) do + [double(args: ['Object', [4]], queue: described_class.queue, delete: true)] + end + + before do + allow(Sidekiq::RetrySet).to receive(:new).and_return(retry_queue) + allow(Sidekiq::DeadSet).to receive(:new).and_return(dead_queue) + end + + it 'steals from the dead and retry queue' do + Sidekiq::Testing.disable! do + expect(described_class).to receive(:perform) + .with('Object', [1]).ordered + expect(described_class).to receive(:perform) + .with('Object', [2]).ordered + expect(described_class).to receive(:perform) + .with('Object', [3]).ordered + expect(described_class).to receive(:perform) + .with('Object', [4]).ordered + + BackgroundMigrationWorker.perform_async('Object', [2]) + BackgroundMigrationWorker.perform_in(10.minutes, 'Object', [1]) + + described_class.steal('Object', retry_dead_jobs: true) + end + end + end end describe '.perform' do diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index fe2087e8fc3..baca8f6d542 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -603,5 +603,6 @@ ResourceLabelEvent: ErrorTracking::ProjectErrorTrackingSetting: - id - api_url -- enabled - project_id +- project_name +- organization_name diff --git a/spec/migrations/fix_null_type_labels_spec.rb b/spec/migrations/fix_null_type_labels_spec.rb new file mode 100644 index 00000000000..462ae9b913f --- /dev/null +++ b/spec/migrations/fix_null_type_labels_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20190131122559_fix_null_type_labels') + +describe FixNullTypeLabels, :migration do + let(:migration) { described_class.new } + let(:projects) { table(:projects) } + let(:namespaces) { table(:namespaces) } + let(:labels) { table(:labels) } + + before do + group = namespaces.create(name: 'labels-test-project', path: 'labels-test-project', type: 'Group') + project = projects.create!(namespace_id: group.id, name: 'labels-test-group', path: 'labels-test-group') + + @template_label = labels.create(title: 'template', template: true) + @project_label = labels.create(title: 'project label', project_id: project.id, type: 'ProjectLabel') + @group_label = labels.create(title: 'group_label', group_id: group.id, type: 'GroupLabel') + @broken_label_1 = labels.create(title: 'broken 1', project_id: project.id) + @broken_label_2 = labels.create(title: 'broken 2', project_id: project.id) + end + + describe '#up' do + it 'fix labels with type missing' do + migration.up + + # Labels that requires type change + expect(@broken_label_1.reload.type).to eq('ProjectLabel') + expect(@broken_label_2.reload.type).to eq('ProjectLabel') + # Labels out of scope + expect(@template_label.reload.type).to be_nil + expect(@project_label.reload.type).to eq('ProjectLabel') + expect(@group_label.reload.type).to eq('GroupLabel') + end + end +end diff --git a/spec/models/application_record_spec.rb b/spec/models/application_record_spec.rb index 68aed387bfc..ca23f581fdc 100644 --- a/spec/models/application_record_spec.rb +++ b/spec/models/application_record_spec.rb @@ -10,4 +10,14 @@ describe ApplicationRecord do expect(User.id_in(records.last(2).map(&:id))).to eq(records.last(2)) end end + + describe '#safe_find_or_create_by' do + it 'creates the user avoiding race conditions' do + expect(Suggestion).to receive(:find_or_create_by).and_raise(ActiveRecord::RecordNotUnique) + allow(Suggestion).to receive(:find_or_create_by).and_call_original + + expect { Suggestion.safe_find_or_create_by(build(:suggestion).attributes) } + .to change { Suggestion.count }.by(1) + end + end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 8a1bbb26e57..47865e4d08f 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1844,6 +1844,26 @@ describe Ci::Build do context 'when there is no environment' do it { is_expected.to be_nil } end + + context 'when build has a start environment' do + let(:build) { create(:ci_build, :deploy_to_production, pipeline: pipeline) } + + it 'does not expand environment name' do + expect(build).not_to receive(:expanded_environment_name) + + subject + end + end + + context 'when build has a stop environment' do + let(:build) { create(:ci_build, :stop_review_app, pipeline: pipeline) } + + it 'expands environment name' do + expect(build).to receive(:expanded_environment_name) + + subject + end + end end describe '#play' do diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb index 925e2ab0955..29197ef372e 100644 --- a/spec/models/concerns/cache_markdown_field_spec.rb +++ b/spec/models/concerns/cache_markdown_field_spec.rb @@ -73,6 +73,7 @@ describe CacheMarkdownField do let(:updated_html) { '<p dir="auto"><code>Bar</code></p>' } let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION) } + let(:cache_version) { CacheMarkdownField::CACHE_COMMONMARK_VERSION } before do stub_commonmark_sourcepos_disabled @@ -97,20 +98,15 @@ describe CacheMarkdownField do end context 'a changed markdown field' do - shared_examples 'with cache version' do |cache_version| - let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) } + let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) } - before do - thing.foo = updated_markdown - thing.save - end - - it { expect(thing.foo_html).to eq(updated_html) } - it { expect(thing.cached_markdown_version).to eq(cache_version) } + before do + thing.foo = updated_markdown + thing.save end - it_behaves_like 'with cache version', CacheMarkdownField::CACHE_REDCARPET_VERSION - it_behaves_like 'with cache version', CacheMarkdownField::CACHE_COMMONMARK_VERSION + it { expect(thing.foo_html).to eq(updated_html) } + it { expect(thing.cached_markdown_version).to eq(cache_version) } end context 'when a markdown field is set repeatedly to an empty string' do @@ -143,22 +139,17 @@ describe CacheMarkdownField do end context 'a non-markdown field changed' do - shared_examples 'with cache version' do |cache_version| - let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) } - - before do - thing.bar = 'OK' - thing.save - end + let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) } - it { expect(thing.bar).to eq('OK') } - it { expect(thing.foo).to eq(markdown) } - it { expect(thing.foo_html).to eq(html) } - it { expect(thing.cached_markdown_version).to eq(cache_version) } + before do + thing.bar = 'OK' + thing.save end - it_behaves_like 'with cache version', CacheMarkdownField::CACHE_REDCARPET_VERSION - it_behaves_like 'with cache version', CacheMarkdownField::CACHE_COMMONMARK_VERSION + it { expect(thing.bar).to eq('OK') } + it { expect(thing.foo).to eq(markdown) } + it { expect(thing.foo_html).to eq(html) } + it { expect(thing.cached_markdown_version).to eq(cache_version) } end context 'version is out of date' do @@ -173,73 +164,63 @@ describe CacheMarkdownField do end describe '#cached_html_up_to_date?' do - shared_examples 'with cache version' do |cache_version| - let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) } + let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) } - subject { thing.cached_html_up_to_date?(:foo) } + subject { thing.cached_html_up_to_date?(:foo) } - it 'returns false when the version is absent' do - thing.cached_markdown_version = nil + it 'returns false when the version is absent' do + thing.cached_markdown_version = nil - is_expected.to be_falsy - end + is_expected.to be_falsy + end - it 'returns false when the version is too early' do - thing.cached_markdown_version -= 1 + it 'returns false when the version is too early' do + thing.cached_markdown_version -= 1 - is_expected.to be_falsy - end + is_expected.to be_falsy + end - it 'returns false when the version is too late' do - thing.cached_markdown_version += 1 + it 'returns false when the version is too late' do + thing.cached_markdown_version += 1 - is_expected.to be_falsy - end + is_expected.to be_falsy + end - it 'returns true when the version is just right' do - thing.cached_markdown_version = cache_version + it 'returns true when the version is just right' do + thing.cached_markdown_version = cache_version - is_expected.to be_truthy - end + is_expected.to be_truthy + end - it 'returns false if markdown has been changed but html has not' do - thing.foo = updated_html + it 'returns false if markdown has been changed but html has not' do + thing.foo = updated_html - is_expected.to be_falsy - end + is_expected.to be_falsy + end - it 'returns true if markdown has not been changed but html has' do - thing.foo_html = updated_html + it 'returns true if markdown has not been changed but html has' do + thing.foo_html = updated_html - is_expected.to be_truthy - end + is_expected.to be_truthy + end - it 'returns true if markdown and html have both been changed' do - thing.foo = updated_markdown - thing.foo_html = updated_html + it 'returns true if markdown and html have both been changed' do + thing.foo = updated_markdown + thing.foo_html = updated_html - is_expected.to be_truthy - end + is_expected.to be_truthy + end - it 'returns false if the markdown field is set but the html is not' do - thing.foo_html = nil + it 'returns false if the markdown field is set but the html is not' do + thing.foo_html = nil - is_expected.to be_falsy - end + is_expected.to be_falsy end - - it_behaves_like 'with cache version', CacheMarkdownField::CACHE_REDCARPET_VERSION - it_behaves_like 'with cache version', CacheMarkdownField::CACHE_COMMONMARK_VERSION end describe '#latest_cached_markdown_version' do subject { thing.latest_cached_markdown_version } - it 'returns redcarpet version' do - thing.cached_markdown_version = CacheMarkdownField::CACHE_COMMONMARK_VERSION_START - 1 - is_expected.to eq(CacheMarkdownField::CACHE_REDCARPET_VERSION) - end - it 'returns commonmark version' do thing.cached_markdown_version = CacheMarkdownField::CACHE_COMMONMARK_VERSION_START + 1 is_expected.to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION) @@ -251,30 +232,6 @@ describe CacheMarkdownField do end end - describe '#legacy_markdown?' do - subject { thing.legacy_markdown? } - - it 'returns true for redcarpet versions' do - thing.cached_markdown_version = CacheMarkdownField::CACHE_COMMONMARK_VERSION_START - 1 - is_expected.to be_truthy - end - - it 'returns false for commonmark versions' do - thing.cached_markdown_version = CacheMarkdownField::CACHE_COMMONMARK_VERSION_START - is_expected.to be_falsey - end - - it 'returns false if nil' do - thing.cached_markdown_version = nil - is_expected.to be_falsey - end - - it 'returns false if 0' do - thing.cached_markdown_version = 0 - is_expected.to be_falsey - end - end - describe '#refresh_markdown_cache' do before do thing.foo = updated_markdown @@ -303,39 +260,34 @@ describe CacheMarkdownField do end describe '#refresh_markdown_cache!' do - shared_examples 'with cache version' do |cache_version| - let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) } + let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) } - before do - thing.foo = updated_markdown - end + before do + thing.foo = updated_markdown + end - it 'fills all html fields' do - thing.refresh_markdown_cache! + it 'fills all html fields' do + thing.refresh_markdown_cache! - expect(thing.foo_html).to eq(updated_html) - expect(thing.foo_html_changed?).to be_truthy - expect(thing.baz_html_changed?).to be_truthy - end + expect(thing.foo_html).to eq(updated_html) + expect(thing.foo_html_changed?).to be_truthy + expect(thing.baz_html_changed?).to be_truthy + end - it 'skips saving if not persisted' do - expect(thing).to receive(:persisted?).and_return(false) - expect(thing).not_to receive(:update_columns) + it 'skips saving if not persisted' do + expect(thing).to receive(:persisted?).and_return(false) + expect(thing).not_to receive(:update_columns) - thing.refresh_markdown_cache! - end + thing.refresh_markdown_cache! + end - it 'saves the changes using #update_columns' do - expect(thing).to receive(:persisted?).and_return(true) - expect(thing).to receive(:update_columns) - .with("foo_html" => updated_html, "baz_html" => "", "cached_markdown_version" => cache_version) + it 'saves the changes using #update_columns' do + expect(thing).to receive(:persisted?).and_return(true) + expect(thing).to receive(:update_columns) + .with("foo_html" => updated_html, "baz_html" => "", "cached_markdown_version" => cache_version) - thing.refresh_markdown_cache! - end + thing.refresh_markdown_cache! end - - it_behaves_like 'with cache version', CacheMarkdownField::CACHE_REDCARPET_VERSION - it_behaves_like 'with cache version', CacheMarkdownField::CACHE_COMMONMARK_VERSION end describe '#banzai_render_context' do @@ -408,20 +360,4 @@ describe CacheMarkdownField do end end end - - describe CacheMarkdownField::MarkdownEngine do - subject { lambda { |version| CacheMarkdownField::MarkdownEngine.from_version(version) } } - - it 'returns :common_mark as a default' do - expect(subject.call(nil)).to eq :common_mark - end - - it 'returns :common_mark' do - expect(subject.call(CacheMarkdownField::CACHE_COMMONMARK_VERSION)).to eq :common_mark - end - - it 'returns :redcarpet' do - expect(subject.call(CacheMarkdownField::CACHE_REDCARPET_VERSION)).to eq :redcarpet - end - end end diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 5753c646106..41159348e04 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -139,6 +139,78 @@ describe Issuable do it 'returns issues with a matching description for a query shorter than 3 chars' do expect(issuable_class.full_search(searchable_issue2.description.downcase)).to eq([searchable_issue2]) end + + context 'when matching columns is "title"' do + it 'returns issues with a matching title' do + expect(issuable_class.full_search(searchable_issue.title, matched_columns: 'title')) + .to eq([searchable_issue]) + end + + it 'returns no issues with a matching description' do + expect(issuable_class.full_search(searchable_issue.description, matched_columns: 'title')) + .to be_empty + end + end + + context 'when matching columns is "description"' do + it 'returns no issues with a matching title' do + expect(issuable_class.full_search(searchable_issue.title, matched_columns: 'description')) + .to be_empty + end + + it 'returns issues with a matching description' do + expect(issuable_class.full_search(searchable_issue.description, matched_columns: 'description')) + .to eq([searchable_issue]) + end + end + + context 'when matching columns is "title,description"' do + it 'returns issues with a matching title' do + expect(issuable_class.full_search(searchable_issue.title, matched_columns: 'title,description')) + .to eq([searchable_issue]) + end + + it 'returns issues with a matching description' do + expect(issuable_class.full_search(searchable_issue.description, matched_columns: 'title,description')) + .to eq([searchable_issue]) + end + end + + context 'when matching columns is nil"' do + it 'returns issues with a matching title' do + expect(issuable_class.full_search(searchable_issue.title, matched_columns: nil)) + .to eq([searchable_issue]) + end + + it 'returns issues with a matching description' do + expect(issuable_class.full_search(searchable_issue.description, matched_columns: nil)) + .to eq([searchable_issue]) + end + end + + context 'when matching columns is "invalid"' do + it 'returns issues with a matching title' do + expect(issuable_class.full_search(searchable_issue.title, matched_columns: 'invalid')) + .to eq([searchable_issue]) + end + + it 'returns issues with a matching description' do + expect(issuable_class.full_search(searchable_issue.description, matched_columns: 'invalid')) + .to eq([searchable_issue]) + end + end + + context 'when matching columns is "title,invalid"' do + it 'returns issues with a matching title' do + expect(issuable_class.full_search(searchable_issue.title, matched_columns: 'title,invalid')) + .to eq([searchable_issue]) + end + + it 'returns no issues with a matching description' do + expect(issuable_class.full_search(searchable_issue.description, matched_columns: 'title,invalid')) + .to be_empty + end + end end describe '.to_ability_name' do diff --git a/spec/models/error_tracking/project_error_tracking_setting_spec.rb b/spec/models/error_tracking/project_error_tracking_setting_spec.rb index 2f8ab21d4b2..d30228b863c 100644 --- a/spec/models/error_tracking/project_error_tracking_setting_spec.rb +++ b/spec/models/error_tracking/project_error_tracking_setting_spec.rb @@ -15,9 +15,11 @@ describe ErrorTracking::ProjectErrorTrackingSetting do describe 'Validations' do context 'when api_url is over 255 chars' do - it 'fails validation' do + before do subject.api_url = 'https://' + 'a' * 250 + end + it 'fails validation' do expect(subject).not_to be_valid expect(subject.errors.messages[:api_url]).to include('is too long (maximum is 255 characters)') end @@ -31,6 +33,34 @@ describe ErrorTracking::ProjectErrorTrackingSetting do end end + context 'presence validations' do + using RSpec::Parameterized::TableSyntax + + valid_api_url = 'http://example.com/api/0/projects/org-slug/proj-slug/' + valid_token = 'token' + + where(:enabled, :token, :api_url, :valid?) do + true | nil | nil | false + true | nil | valid_api_url | false + true | valid_token | nil | false + true | valid_token | valid_api_url | true + false | nil | nil | true + false | nil | valid_api_url | true + false | valid_token | nil | true + false | valid_token | valid_api_url | true + end + + with_them do + before do + subject.enabled = enabled + subject.token = token + subject.api_url = api_url + end + + it { expect(subject.valid?).to eq(valid?) } + end + end + context 'URL path' do it 'fails validation with wrong path' do subject.api_url = 'http://gitlab.com/project1/something' @@ -45,6 +75,16 @@ describe ErrorTracking::ProjectErrorTrackingSetting do expect(subject).to be_valid end end + + context 'non ascii chars in api_url' do + before do + subject.api_url = 'http://gitlab.com/api/0/projects/project1/something€' + end + + it 'fails validation' do + expect(subject).not_to be_valid + end + end end describe '#sentry_external_url' do @@ -106,4 +146,138 @@ describe ErrorTracking::ProjectErrorTrackingSetting do end end end + + describe '#list_sentry_projects' do + let(:projects) { [:list, :of, :projects] } + let(:sentry_client) { spy(:sentry_client) } + + it 'calls sentry client' do + expect(subject).to receive(:sentry_client).and_return(sentry_client) + expect(sentry_client).to receive(:list_projects).and_return(projects) + + result = subject.list_sentry_projects + + expect(result).to eq(projects: projects) + end + end + + context 'slugs' do + shared_examples_for 'slug from api_url' do |method, slug| + context 'when api_url is correct' do + before do + subject.api_url = 'http://gitlab.com/api/0/projects/org-slug/project-slug/' + end + + it 'returns slug' do + expect(subject.public_send(method)).to eq(slug) + end + end + + context 'when api_url is blank' do + before do + subject.api_url = nil + end + + it 'returns nil' do + expect(subject.public_send(method)).to be_nil + end + end + end + + it_behaves_like 'slug from api_url', :project_slug, 'project-slug' + it_behaves_like 'slug from api_url', :organization_slug, 'org-slug' + end + + context 'names from api_url' do + shared_examples_for 'name from api_url' do |name, titleized_slug| + context 'name is present in DB' do + it 'returns name from DB' do + subject[name] = 'Sentry name' + subject.api_url = 'http://gitlab.com/api/0/projects/org-slug/project-slug' + + expect(subject.public_send(name)).to eq('Sentry name') + end + end + + context 'name is null in DB' do + it 'titleizes and returns slug from api_url' do + subject[name] = nil + subject.api_url = 'http://gitlab.com/api/0/projects/org-slug/project-slug' + + expect(subject.public_send(name)).to eq(titleized_slug) + end + + it 'returns nil when api_url is incorrect' do + subject[name] = nil + subject.api_url = 'http://gitlab.com/api/0/projects/' + + expect(subject.public_send(name)).to be_nil + end + + it 'returns nil when api_url is blank' do + subject[name] = nil + subject.api_url = nil + + expect(subject.public_send(name)).to be_nil + end + end + end + + it_behaves_like 'name from api_url', :organization_name, 'Org Slug' + it_behaves_like 'name from api_url', :project_name, 'Project Slug' + end + + describe '.build_api_url_from' do + it 'correctly builds api_url with slugs' do + api_url = described_class.build_api_url_from( + api_host: 'http://sentry.com/', + organization_slug: 'org-slug', + project_slug: 'proj-slug' + ) + + expect(api_url).to eq('http://sentry.com/api/0/projects/org-slug/proj-slug/') + end + + it 'correctly builds api_url without slugs' do + api_url = described_class.build_api_url_from( + api_host: 'http://sentry.com/', + organization_slug: nil, + project_slug: nil + ) + + expect(api_url).to eq('http://sentry.com/api/0/projects/') + end + + it 'does not raise exception with invalid url' do + api_url = described_class.build_api_url_from( + api_host: ':::', + organization_slug: 'org-slug', + project_slug: 'proj-slug' + ) + + expect(api_url).to eq(':::') + end + end + + describe '#api_host' do + context 'when api_url exists' do + before do + subject.api_url = 'https://example.com/api/0/projects/org-slug/proj-slug/' + end + + it 'extracts the api_host from api_url' do + expect(subject.api_host).to eq('https://example.com/') + end + end + + context 'when api_url is nil' do + before do + subject.api_url = nil + end + + it 'returns nil' do + expect(subject.api_url).to eq(nil) + end + end + end end diff --git a/spec/models/ssh_host_key_spec.rb b/spec/models/ssh_host_key_spec.rb index 75db43b3d56..4c677569561 100644 --- a/spec/models/ssh_host_key_spec.rb +++ b/spec/models/ssh_host_key_spec.rb @@ -50,6 +50,35 @@ describe SshHostKey do subject(:ssh_host_key) { described_class.new(project: project, url: 'ssh://example.com:2222', compare_host_keys: compare_host_keys) } + describe '.primary_key' do + it 'returns a symbol' do + expect(described_class.primary_key).to eq(:id) + end + end + + describe '.find_by' do + let(:project) { create(:project) } + let(:url) { 'ssh://invalid.invalid:2222' } + + let(:finding_id) { [project.id, url].join(':') } + + it 'accepts a string key' do + result = described_class.find_by('id' => finding_id) + + expect(result).to be_a(described_class) + expect(result.project).to eq(project) + expect(result.url.to_s).to eq(url) + end + + it 'accepts a symbol key' do + result = described_class.find_by(id: finding_id) + + expect(result).to be_a(described_class) + expect(result.project).to eq(project) + expect(result.url.to_s).to eq(url) + end + end + describe '#fingerprints', :use_clean_rails_memory_store_caching do it 'returns an array of indexed fingerprints when the cache is filled' do stub_reactive_cache(ssh_host_key, known_hosts: known_hosts) diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index 9b32dc78274..1ad536258ba 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -191,7 +191,7 @@ describe API::Files do get api(url, current_user), params: params - expect(headers['Content-Disposition']).to eq('inline; filename="popen.rb"') + expect(headers['Content-Disposition']).to eq(%q(inline; filename="popen.rb"; filename*=UTF-8''popen.rb)) end context 'when mandatory params are not given' do diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index e0f1e303e96..04908378a24 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -208,6 +208,18 @@ describe API::Issues do expect_paginated_array_response(issue.id) end + it 'returns issues matching given search string for title and scoped in title' do + get api("/issues", user), params: { search: issue.title, in: 'title' } + + expect_paginated_array_response(issue.id) + end + + it 'returns an empty array if no issue matches given search string for title and scoped in description' do + get api("/issues", user), params: { search: issue.title, in: 'description' } + + expect_paginated_array_response([]) + end + it 'returns issues matching given search string for description' do get api("/issues", user), params: { search: issue.description } diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb index 97aa71bf231..3defe8bbf51 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/jobs_spec.rb @@ -403,7 +403,7 @@ describe API::Jobs do shared_examples 'downloads artifact' do let(:download_headers) do { 'Content-Transfer-Encoding' => 'binary', - 'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' } + 'Content-Disposition' => %q(attachment; filename="ci_build_artifacts.zip"; filename*=UTF-8''ci_build_artifacts.zip) } end it 'returns specific job artifacts' do @@ -555,7 +555,7 @@ describe API::Jobs do let(:download_headers) do { 'Content-Transfer-Encoding' => 'binary', 'Content-Disposition' => - "attachment; filename=#{job.artifacts_file.filename}" } + %Q(attachment; filename="#{job.artifacts_file.filename}"; filename*=UTF-8''#{job.artifacts_file.filename}) } end it { expect(response).to have_http_status(:ok) } diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 4d42bc39ac3..51343287a13 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -260,6 +260,18 @@ describe API::MergeRequests do expect_response_ordered_exactly(merge_request) end + it 'returns merge requests matching given search string for title and scoped in title' do + get api("/merge_requests", user), params: { search: merge_request.title, in: 'title' } + + expect_response_ordered_exactly(merge_request) + end + + it 'returns an empty array if no merge reques matches given search string for description and scoped in title' do + get api("/merge_requests", user), params: { search: merge_request.description, in: 'title' } + + expect_response_contain_exactly + end + it 'returns merge requests for project matching given search string for description' do get api("/merge_requests", user), params: { project_id: project.id, search: merge_request.description } diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 70686158b7d..cfa7a1a31a3 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -49,6 +49,27 @@ describe API::Projects do namespace: user4.namespace) end + shared_context 'with language detection' do + let(:ruby) { create(:programming_language, name: 'Ruby') } + let(:javascript) { create(:programming_language, name: 'JavaScript') } + let(:html) { create(:programming_language, name: 'HTML') } + + let(:mock_repo_languages) do + { + project => { ruby => 0.5, html => 0.5 }, + project3 => { html => 0.7, javascript => 0.3 } + } + end + + before do + mock_repo_languages.each do |proj, lang_shares| + lang_shares.each do |lang, share| + create(:repository_language, project: proj, programming_language: lang, share: share) + end + end + end + end + describe 'GET /projects' do shared_examples_for 'projects response' do it 'returns an array of projects' do @@ -344,6 +365,19 @@ describe API::Projects do end end + context 'and using the programming language filter' do + include_context 'with language detection' + + it 'filters case-insensitively by programming language' do + get api('/projects', user), params: { with_programming_language: 'javascript' } + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.map { |p| p['id'] }).to contain_exactly(project3.id) + end + end + context 'and using sorting' do it 'returns the correct order when sorted by id' do get api('/projects', user), params: { order_by: 'id', sort: 'desc' } @@ -755,6 +789,19 @@ describe API::Projects do expect(json_response).to be_an Array expect(json_response.map { |project| project['id'] }).to contain_exactly(private_project1.id) end + + context 'and using the programming language filter' do + include_context 'with language detection' + + it 'filters case-insensitively by programming language' do + get api('/projects', user), params: { with_programming_language: 'ruby' } + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.map { |p| p['id'] }).to contain_exactly(project.id) + end + end end describe 'POST /projects/user/:id' do diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index ed0108c846a..d7ddd97e8c8 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -1584,7 +1584,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do context 'when artifacts are stored locally' do let(:download_headers) do { 'Content-Transfer-Encoding' => 'binary', - 'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' } + 'Content-Disposition' => %q(attachment; filename="ci_build_artifacts.zip"; filename*=UTF-8''ci_build_artifacts.zip) } end before do diff --git a/spec/requests/user_activity_spec.rb b/spec/requests/user_activity_spec.rb new file mode 100644 index 00000000000..15666e00b9f --- /dev/null +++ b/spec/requests/user_activity_spec.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Update of user activity' do + let(:user) { create(:user, last_activity_on: nil) } + + before do + group = create(:group, name: 'group') + project = create(:project, :public, namespace: group, name: 'project') + + create(:issue, project: project, iid: 10) + create(:merge_request, source_project: project, iid: 15) + + project.add_maintainer(user) + end + + paths_to_visit = [ + '/group', + '/group/project', + '/groups/group/-/issues', + '/groups/group/-/boards', + '/dashboard/projects', + '/dashboard/snippets', + '/dashboard/groups', + '/dashboard/todos', + '/group/project/issues', + '/group/project/issues/10', + '/group/project/merge_requests', + '/group/project/merge_requests/15' + ] + + context 'without an authenticated user' do + it 'does not set the last activity cookie' do + get "/group/project" + + expect(response.cookies['user_last_activity_on']).to be_nil + end + end + + context 'with an authenticated user' do + before do + login_as(user) + end + + context 'with a POST request' do + it 'does not set the last activity cookie' do + post "/group/project/archive" + + expect(response.cookies['user_last_activity_on']).to be_nil + end + end + + paths_to_visit.each do |path| + context "on GET to #{path}" do + it 'updates the last activity date' do + expect(Users::ActivityService).to receive(:new).and_call_original + + get path + + expect(user.last_activity_on).to eq(Date.today) + end + + context 'when calling it twice' do + it 'updates last_activity_on just once' do + expect(Users::ActivityService).to receive(:new).once.and_call_original + + 2.times do + get path + end + end + end + + context 'when last_activity_on is nil' do + before do + user.update_attribute(:last_activity_on, nil) + end + + it 'updates the last activity date' do + expect(user.last_activity_on).to be_nil + + get path + + expect(user.last_activity_on).to eq(Date.today) + end + end + + context 'when last_activity_on is stale' do + before do + user.update_attribute(:last_activity_on, 2.days.ago.to_date) + end + + it 'updates the last activity date' do + get path + + expect(user.last_activity_on).to eq(Date.today) + end + end + + context 'when last_activity_on is up to date' do + before do + user.update_attribute(:last_activity_on, Date.today) + end + + it 'does not try to update it' do + expect(Users::ActivityService).not_to receive(:new) + + get path + end + end + end + end + end +end diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb index 4e64b0c9414..b46aa65818d 100644 --- a/spec/services/merge_requests/create_service_spec.rb +++ b/spec/services/merge_requests/create_service_spec.rb @@ -197,6 +197,24 @@ describe MergeRequests::CreateService do expect(merge_request.actual_head_pipeline).to be_merge_request end + context 'when there are no commits between source branch and target branch' do + let(:opts) do + { + title: 'Awesome merge_request', + description: 'please fix', + source_branch: 'not-merged-branch', + target_branch: 'master' + } + end + + it 'does not create a merge request pipeline' do + expect(merge_request).to be_persisted + + merge_request.reload + expect(merge_request.merge_request_pipelines.count).to eq(0) + end + end + context "when branch pipeline was created before a merge request pipline has been created" do before do create(:ci_pipeline, project: merge_request.source_project, diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 1169ed5f9f2..9e9dc5a576c 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -150,11 +150,15 @@ describe MergeRequests::RefreshService do } end - it 'create merge request pipeline' do + it 'create merge request pipeline with commits' do expect { subject } .to change { @merge_request.merge_request_pipelines.count }.by(1) .and change { @fork_merge_request.merge_request_pipelines.count }.by(1) - .and change { @another_merge_request.merge_request_pipelines.count }.by(1) + .and change { @another_merge_request.merge_request_pipelines.count }.by(0) + + expect(@merge_request.has_commits?).to be_truthy + expect(@fork_merge_request.has_commits?).to be_truthy + expect(@another_merge_request.has_commits?).to be_falsy end context "when branch pipeline was created before a merge request pipline has been created" do diff --git a/spec/services/preview_markdown_service_spec.rb b/spec/services/preview_markdown_service_spec.rb index 458cb8f1f31..85515d548a7 100644 --- a/spec/services/preview_markdown_service_spec.rb +++ b/spec/services/preview_markdown_service_spec.rb @@ -114,23 +114,4 @@ describe PreviewMarkdownService do expect(result[:commands]).to eq 'Tags this commit to v1.2.3 with "Stable release".' end end - - it 'sets correct markdown engine' do - service = described_class.new(project, user, { markdown_version: CacheMarkdownField::CACHE_REDCARPET_VERSION }) - result = service.execute - - expect(result[:markdown_engine]).to eq :redcarpet - - service = described_class.new(project, user, { markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION }) - result = service.execute - - expect(result[:markdown_engine]).to eq :common_mark - end - - it 'honors the legacy_render parameter' do - service = described_class.new(project, user, { legacy_render: '1' }) - result = service.execute - - expect(result[:markdown_engine]).to eq :redcarpet - end end diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index f71e2b4bc24..54ce33dd103 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -16,7 +16,11 @@ describe Projects::CreateService, '#execute' do Label.create(title: "bug", template: true) project = create_project(user, opts) - expect(project.labels).not_to be_empty + created_label = project.reload.labels.last + + expect(created_label.type).to eq('ProjectLabel') + expect(created_label.project_id).to eq(project.id) + expect(created_label.title).to eq('bug') end context 'user namespace' do diff --git a/spec/services/projects/update_pages_configuration_service_spec.rb b/spec/services/projects/update_pages_configuration_service_spec.rb index e4d4e6ff3dd..7f5ef3129d7 100644 --- a/spec/services/projects/update_pages_configuration_service_spec.rb +++ b/spec/services/projects/update_pages_configuration_service_spec.rb @@ -2,23 +2,41 @@ require 'spec_helper' describe Projects::UpdatePagesConfigurationService do let(:project) { create(:project) } - subject { described_class.new(project) } + let(:service) { described_class.new(project) } describe "#update" do let(:file) { Tempfile.new('pages-test') } + subject { service.execute } + after do file.close file.unlink end - it 'updates the .update file' do - # Access this reference to ensure scoping works - Projects::Settings # rubocop:disable Lint/Void - expect(subject).to receive(:pages_config_file).and_return(file.path) - expect(subject).to receive(:reload_daemon).and_call_original + before do + allow(service).to receive(:pages_config_file).and_return(file.path) + end + + context 'when configuration changes' do + it 'updates the .update file' do + expect(service).to receive(:reload_daemon).and_call_original + + expect(subject).to include(status: :success, reload: true) + end + end + + context 'when configuration does not change' do + before do + # we set the configuration + service.execute + end + + it 'does not update the .update file' do + expect(service).not_to receive(:reload_daemon) - expect(subject.execute).to eq({ status: :success }) + expect(subject).to include(status: :success, reload: false) + end end end end diff --git a/spec/services/task_list_toggle_service_spec.rb b/spec/services/task_list_toggle_service_spec.rb index 750ac4c40ba..cc64dd25085 100644 --- a/spec/services/task_list_toggle_service_spec.rb +++ b/spec/services/task_list_toggle_service_spec.rb @@ -3,7 +3,6 @@ require 'spec_helper' describe TaskListToggleService do - let(:sourcepos) { true } let(:markdown) do <<-EOT.strip_heredoc * [ ] Task 1 @@ -40,87 +39,47 @@ describe TaskListToggleService do EOT end - shared_examples 'task lists' do - it 'checks Task 1' do - toggler = described_class.new(markdown, markdown_html, - index: 1, toggle_as_checked: true, - line_source: '* [ ] Task 1', line_number: 1, - sourcepos: sourcepos) + it 'checks Task 1' do + toggler = described_class.new(markdown, markdown_html, + toggle_as_checked: true, + line_source: '* [ ] Task 1', line_number: 1) - expect(toggler.execute).to be_truthy - expect(toggler.updated_markdown.lines[0]).to eq "* [x] Task 1\n" - expect(toggler.updated_markdown_html).to include('disabled checked> Task 1') - end - - it 'unchecks Item 1' do - toggler = described_class.new(markdown, markdown_html, - index: 3, toggle_as_checked: false, - line_source: '1. [X] Item 1', line_number: 6, - sourcepos: sourcepos) - - expect(toggler.execute).to be_truthy - expect(toggler.updated_markdown.lines[5]).to eq "1. [ ] Item 1\n" - expect(toggler.updated_markdown_html).to include('disabled> Item 1') - end - - it 'returns false if line_source does not match the text' do - toggler = described_class.new(markdown, markdown_html, - index: 2, toggle_as_checked: false, - line_source: '* [x] Task Added', line_number: 2, - sourcepos: sourcepos) - - expect(toggler.execute).to be_falsey - end + expect(toggler.execute).to be_truthy + expect(toggler.updated_markdown.lines[0]).to eq "* [x] Task 1\n" + expect(toggler.updated_markdown_html).to include('disabled checked> Task 1') + end - it 'returns false if markdown is nil' do - toggler = described_class.new(nil, markdown_html, - index: 2, toggle_as_checked: false, - line_source: '* [x] Task Added', line_number: 2, - sourcepos: sourcepos) + it 'unchecks Item 1' do + toggler = described_class.new(markdown, markdown_html, + toggle_as_checked: false, + line_source: '1. [X] Item 1', line_number: 6) - expect(toggler.execute).to be_falsey - end + expect(toggler.execute).to be_truthy + expect(toggler.updated_markdown.lines[5]).to eq "1. [ ] Item 1\n" + expect(toggler.updated_markdown_html).to include('disabled> Item 1') + end - it 'returns false if markdown_html is nil' do - toggler = described_class.new(markdown, nil, - index: 2, toggle_as_checked: false, - line_source: '* [x] Task Added', line_number: 2, - sourcepos: sourcepos) + it 'returns false if line_source does not match the text' do + toggler = described_class.new(markdown, markdown_html, + toggle_as_checked: false, + line_source: '* [x] Task Added', line_number: 2) - expect(toggler.execute).to be_falsey - end + expect(toggler.execute).to be_falsey end - context 'when using sourcepos' do - it_behaves_like 'task lists' + it 'returns false if markdown is nil' do + toggler = described_class.new(nil, markdown_html, + toggle_as_checked: false, + line_source: '* [x] Task Added', line_number: 2) + + expect(toggler.execute).to be_falsey end - context 'when using checkbox indexing' do - let(:sourcepos) { false } - let(:markdown_html) do - <<-EOT.strip_heredoc - <ul class="task-list" dir="auto"> - <li class="task-list-item"> - <input type="checkbox" class="task-list-item-checkbox" disabled> Task 1 - </li> - <li class="task-list-item"> - <input type="checkbox" class="task-list-item-checkbox" disabled checked> Task 2 - </li> - </ul> - <p dir="auto">A paragraph</p> - <ol class="task-list" dir="auto"> - <li class="task-list-item"> - <input type="checkbox" class="task-list-item-checkbox" disabled checked> Item 1 - <ul class="task-list"> - <li class="task-list-item"> - <input type="checkbox" class="task-list-item-checkbox" disabled> Sub-item 1 - </li> - </ul> - </li> - </ol> - EOT - end + it 'returns false if markdown_html is nil' do + toggler = described_class.new(markdown, nil, + toggle_as_checked: false, + line_source: '* [x] Task Added', line_number: 2) - it_behaves_like 'task lists' + expect(toggler.execute).to be_falsey end end diff --git a/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb b/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb index a3d31e26498..982e0317f7f 100644 --- a/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb +++ b/spec/support/shared_examples/controllers/repository_lfs_file_load_examples.rb @@ -28,7 +28,13 @@ shared_examples 'repository lfs file load' do end it 'serves the file' do - expect(controller).to receive(:send_file).with("#{LfsObjectUploader.root}/91/ef/f75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", filename: filename, disposition: 'attachment') + # Notice the filename= is omitted from the disposition; this is because + # Rails 5 will append this header in send_file + expect(controller).to receive(:send_file) + .with( + "#{LfsObjectUploader.root}/91/ef/f75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", + filename: filename, + disposition: %Q(attachment; filename*=UTF-8''#{filename})) subject @@ -56,7 +62,7 @@ shared_examples 'repository lfs file load' do file_uri = URI.parse(response.location) params = CGI.parse(file_uri.query) - expect(params["response-content-disposition"].first).to eq "attachment;filename=\"#{filename}\"" + expect(params["response-content-disposition"].first).to eq(%q(attachment; filename="lfs_object.iso"; filename*=UTF-8''lfs_object.iso)) end end end diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index 3b8f7f5fe7d..a8fae4a88a3 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -71,21 +71,29 @@ describe 'gitlab:app namespace rake task' do end.to raise_error(SystemExit) end - it 'invokes restoration on match' do - allow(YAML).to receive(:load_file) - .and_return({ gitlab_version: gitlab_version }) - - expect(Rake::Task['gitlab:db:drop_tables']).to receive(:invoke) - expect(Rake::Task['gitlab:backup:db:restore']).to receive(:invoke) - expect(Rake::Task['gitlab:backup:repo:restore']).to receive(:invoke) - expect(Rake::Task['gitlab:backup:builds:restore']).to receive(:invoke) - expect(Rake::Task['gitlab:backup:uploads:restore']).to receive(:invoke) - expect(Rake::Task['gitlab:backup:artifacts:restore']).to receive(:invoke) - expect(Rake::Task['gitlab:backup:pages:restore']).to receive(:invoke) - expect(Rake::Task['gitlab:backup:lfs:restore']).to receive(:invoke) - expect(Rake::Task['gitlab:backup:registry:restore']).to receive(:invoke) - expect(Rake::Task['gitlab:shell:setup']).to receive(:invoke) - expect { run_rake_task('gitlab:backup:restore') }.to output.to_stdout + context 'restore with matching gitlab version' do + before do + allow(YAML).to receive(:load_file) + .and_return({ gitlab_version: gitlab_version }) + expect(Rake::Task['gitlab:db:drop_tables']).to receive(:invoke) + expect(Rake::Task['gitlab:backup:db:restore']).to receive(:invoke) + expect(Rake::Task['gitlab:backup:repo:restore']).to receive(:invoke) + expect(Rake::Task['gitlab:backup:builds:restore']).to receive(:invoke) + expect(Rake::Task['gitlab:backup:uploads:restore']).to receive(:invoke) + expect(Rake::Task['gitlab:backup:artifacts:restore']).to receive(:invoke) + expect(Rake::Task['gitlab:backup:pages:restore']).to receive(:invoke) + expect(Rake::Task['gitlab:backup:lfs:restore']).to receive(:invoke) + expect(Rake::Task['gitlab:backup:registry:restore']).to receive(:invoke) + expect(Rake::Task['gitlab:shell:setup']).to receive(:invoke) + end + + it 'invokes restoration on match' do + expect { run_rake_task('gitlab:backup:restore') }.to output.to_stdout + end + + it 'prints timestamps on messages' do + expect { run_rake_task('gitlab:backup:restore') }.to output(/.*\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\s[-+]\d{4}\s--\s.*/).to_stdout + end end end diff --git a/spec/workers/mail_scheduler/notification_service_worker_spec.rb b/spec/workers/mail_scheduler/notification_service_worker_spec.rb index 1033557ee88..5cfba01850c 100644 --- a/spec/workers/mail_scheduler/notification_service_worker_spec.rb +++ b/spec/workers/mail_scheduler/notification_service_worker_spec.rb @@ -9,6 +9,10 @@ describe MailScheduler::NotificationServiceWorker do ActiveJob::Arguments.serialize(args) end + def deserialize(args) + ActiveJob::Arguments.deserialize(args) + end + describe '#perform' do it 'deserializes arguments from global IDs' do expect(worker.notification_service).to receive(method).with(key) @@ -42,13 +46,48 @@ describe MailScheduler::NotificationServiceWorker do end end - describe '.perform_async' do + describe '.perform_async', :sidekiq do + around do |example| + Sidekiq::Testing.fake! { example.run } + end + it 'serializes arguments as global IDs when scheduling' do - Sidekiq::Testing.fake! do - described_class.perform_async(method, key) + described_class.perform_async(method, key) + + expect(described_class.jobs.count).to eq(1) + expect(described_class.jobs.first).to include('args' => [method, *serialize(key)]) + end + + context 'with ActiveController::Parameters' do + let(:parameters) { ActionController::Parameters.new(hash) } + + let(:hash) do + { + "nested" => { + "hash" => true + } + } + end + + context 'when permitted' do + before do + parameters.permit! + end + + it 'serializes as a serializable Hash' do + described_class.perform_async(method, parameters) - expect(described_class.jobs.count).to eq(1) - expect(described_class.jobs.first).to include('args' => [method, *serialize(key)]) + expect(described_class.jobs.count).to eq(1) + expect(deserialize(described_class.jobs.first['args'])) + .to eq([method, hash]) + end + end + + context 'when not permitted' do + it 'fails to serialize' do + expect { described_class.perform_async(method, parameters) } + .to raise_error(ActionController::UnfilteredParameters) + end end end end |