diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-24 00:10:28 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-24 00:10:28 +0300 |
commit | 4e7abe540dbd1d170bfb2b3594e645cbfb48cac3 (patch) | |
tree | f3de940e069b4d927acfdf54247c9900113a4c79 /spec | |
parent | f6b95a66bc12adeb4fac7277d1eb345d9e7819fd (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
21 files changed, 447 insertions, 366 deletions
diff --git a/spec/controllers/concerns/sorting_preference_spec.rb b/spec/controllers/concerns/sorting_preference_spec.rb index 82a920215ca..6880d83142d 100644 --- a/spec/controllers/concerns/sorting_preference_spec.rb +++ b/spec/controllers/concerns/sorting_preference_spec.rb @@ -26,11 +26,14 @@ RSpec.describe SortingPreference do describe '#set_sort_order' do let(:group) { build(:group) } + let(:controller_name) { 'issues' } + let(:action_name) { 'issues' } let(:issue_weights_available) { true } before do allow(controller).to receive(:default_sort_order).and_return('updated_desc') - allow(controller).to receive(:action_name).and_return('issues') + allow(controller).to receive(:controller_name).and_return(controller_name) + allow(controller).to receive(:action_name).and_return(action_name) allow(controller).to receive(:can_sort_by_issue_weight?).and_return(issue_weights_available) user.user_preference.update!(issues_sort: sorting_field) end @@ -62,6 +65,42 @@ RSpec.describe SortingPreference do end end end + + context 'when user preference contains merged date sorting' do + let(:sorting_field) { 'merged_at_desc' } + let(:can_sort_by_merged_date?) { false } + + before do + allow(controller) + .to receive(:can_sort_by_merged_date?) + .with(can_sort_by_merged_date?) + .and_return(can_sort_by_merged_date?) + end + + it 'sets default sort order' do + is_expected.to eq('updated_desc') + end + + shared_examples 'user can sort by merged date' do + it 'sets sort order from user_preference' do + is_expected.to eq('merged_at_desc') + end + end + + context 'when controller_name is merge_requests' do + let(:controller_name) { 'merge_requests' } + let(:can_sort_by_merged_date?) { true } + + it_behaves_like 'user can sort by merged date' + end + + context 'when action_name is merge_requests' do + let(:action_name) { 'merge_requests' } + let(:can_sort_by_merged_date?) { true } + + it_behaves_like 'user can sort by merged date' + end + end end describe '#set_sort_order_from_user_preference' do diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb index 1f913e10f43..96006974c13 100644 --- a/spec/controllers/projects/notes_controller_spec.rb +++ b/spec/controllers/projects/notes_controller_spec.rb @@ -37,6 +37,8 @@ RSpec.describe Projects::NotesController, type: :controller, feature_category: : project.add_developer(user) end + specify { expect(get(:index, params: request_params)).to have_request_urgency(:medium) } + it 'passes last_fetched_at from headers to NotesFinder and MergeIntoNotesService' do last_fetched_at = Time.zone.at(3.hours.ago.to_i) # remove nanoseconds @@ -244,6 +246,8 @@ RSpec.describe Projects::NotesController, type: :controller, feature_category: : sign_in(user) end + specify { expect(create!).to have_request_urgency(:low) } + describe 'making the creation request' do before do create! @@ -732,19 +736,21 @@ RSpec.describe Projects::NotesController, type: :controller, feature_category: : end describe 'PUT update' do - context "should update the note with a valid issue" do - let(:request_params) do - { - namespace_id: project.namespace, - project_id: project, - id: note, - format: :json, - note: { - note: "New comment" - } + let(:request_params) do + { + namespace_id: project.namespace, + project_id: project, + id: note, + format: :json, + note: { + note: "New comment" } - end + } + end + + specify { expect(put(:update, params: request_params)).to have_request_urgency(:low) } + context "should update the note with a valid issue" do before do sign_in(note.author) project.add_developer(note.author) @@ -790,6 +796,8 @@ RSpec.describe Projects::NotesController, type: :controller, feature_category: : } end + specify { expect(delete(:destroy, params: request_params)).to have_request_urgency(:low) } + context 'user is the author of a note' do before do sign_in(note.author) @@ -831,6 +839,8 @@ RSpec.describe Projects::NotesController, type: :controller, feature_category: : let(:emoji_name) { 'thumbsup' } + it { is_expected.to have_request_urgency(:low) } + it "toggles the award emoji" do expect do subject @@ -866,6 +876,8 @@ RSpec.describe Projects::NotesController, type: :controller, feature_category: : sign_in user end + specify { expect(post(:resolve, params: request_params)).to have_request_urgency(:low) } + context "when the user is not authorized to resolve the note" do it "returns status 404" do post :resolve, params: request_params @@ -929,6 +941,8 @@ RSpec.describe Projects::NotesController, type: :controller, feature_category: : note.resolve!(user) end + specify { expect(delete(:unresolve, params: request_params)).to have_request_urgency(:low) } + context "when the user is not authorized to resolve the note" do it "returns status 404" do delete :unresolve, params: request_params @@ -998,6 +1012,8 @@ RSpec.describe Projects::NotesController, type: :controller, feature_category: : expect(json_response.count).to eq(1) expect(json_response.first).to include({ "line_text" => "Test" }) end + + specify { expect(get(:outdated_line_change, params: request_params)).to have_request_urgency(:low) } end # Convert a time to an integer number of microseconds diff --git a/spec/features/commit_spec.rb b/spec/features/commit_spec.rb index a9672569a4a..a3208ca6d37 100644 --- a/spec/features/commit_spec.rb +++ b/spec/features/commit_spec.rb @@ -6,7 +6,7 @@ RSpec.describe 'Commit', feature_category: :source_code_management do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } - describe "single commit view" do + shared_examples "single commit view" do let(:commit) do project.repository.commits(nil, limit: 100).find do |commit| commit.diffs.size > 1 @@ -69,4 +69,15 @@ RSpec.describe 'Commit', feature_category: :source_code_management do end end end + + it_behaves_like "single commit view" + + context "when super sidebar is enabled" do + before do + user.update!(use_new_navigation: true) + stub_feature_flags(super_sidebar_nav: true) + end + + it_behaves_like "single commit view" + end end diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb index 3171ae89fe6..371c40b40a5 100644 --- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb +++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb @@ -23,7 +23,7 @@ RSpec.describe 'Merge requests > User lists merge requests', feature_category: : milestone: create(:milestone, project: project, due_date: '2013-12-11'), created_at: 1.minute.ago, updated_at: 1.minute.ago) - @fix.metrics.update!(merged_at: 10.seconds.ago, latest_closed_at: 10.seconds.ago) + @fix.metrics.update!(merged_at: 10.seconds.ago, latest_closed_at: 20.seconds.ago) @markdown = create(:merge_request, title: 'markdown', @@ -33,7 +33,8 @@ RSpec.describe 'Merge requests > User lists merge requests', feature_category: : reviewers: [user, user2, user3, user4], milestone: create(:milestone, project: project, due_date: '2013-12-12'), created_at: 2.minutes.ago, - updated_at: 2.minutes.ago) + updated_at: 2.minutes.ago, + state: 'merged') @markdown.metrics.update!(merged_at: 10.minutes.ago, latest_closed_at: 10.seconds.ago) @merge_test = create(:merge_request, @@ -49,7 +50,8 @@ RSpec.describe 'Merge requests > User lists merge requests', feature_category: : source_project: project, source_branch: 'feautre', created_at: 2.minutes.ago, - updated_at: 1.minute.ago) + updated_at: 1.minute.ago, + state: 'merged') @feature.metrics.update!(merged_at: 10.seconds.ago, latest_closed_at: 10.minutes.ago) end @@ -79,10 +81,9 @@ RSpec.describe 'Merge requests > User lists merge requests', feature_category: : expect(page).to have_current_path(project_merge_requests_path(project), ignore_query: true) expect(page).to have_content 'merge-test' - expect(page).to have_content 'feature' expect(page).not_to have_content 'fix' expect(page).not_to have_content 'markdown' - expect(count_merge_requests).to eq(2) + expect(count_merge_requests).to eq(1) end it 'filters on a specific assignee' do @@ -90,8 +91,7 @@ RSpec.describe 'Merge requests > User lists merge requests', feature_category: : expect(page).not_to have_content 'merge-test' expect(page).to have_content 'fix' - expect(page).to have_content 'markdown' - expect(count_merge_requests).to eq(2) + expect(count_merge_requests).to eq(1) end it 'sorts by newest' do @@ -99,35 +99,35 @@ RSpec.describe 'Merge requests > User lists merge requests', feature_category: : expect(first_merge_request).to include('fix') expect(last_merge_request).to include('merge-test') - expect(count_merge_requests).to eq(4) + expect(count_merge_requests).to eq(2) end it 'sorts by last updated' do visit_merge_requests(project, sort: sort_value_recently_updated) expect(first_merge_request).to include('merge-test') - expect(count_merge_requests).to eq(4) + expect(count_merge_requests).to eq(2) end it 'sorts by milestone due date' do visit_merge_requests(project, sort: sort_value_milestone) expect(first_merge_request).to include('fix') - expect(count_merge_requests).to eq(4) + expect(count_merge_requests).to eq(2) end - it 'sorts by merged at' do + it 'ignores sorting by merged at' do visit_merge_requests(project, sort: sort_value_merged_date) - expect(first_merge_request).to include('markdown') - expect(count_merge_requests).to eq(4) + expect(first_merge_request).to include('fix') + expect(count_merge_requests).to eq(2) end it 'sorts by closed at' do visit_merge_requests(project, sort: sort_value_closed_date) - expect(first_merge_request).to include('feature') - expect(count_merge_requests).to eq(4) + expect(first_merge_request).to include('fix') + expect(count_merge_requests).to eq(2) end it 'filters on one label and sorts by milestone due date' do @@ -141,6 +141,15 @@ RSpec.describe 'Merge requests > User lists merge requests', feature_category: : expect(count_merge_requests).to eq(1) end + context 'when viewing merged merge requests' do + it 'sorts by merged at' do + visit_merge_requests(project, state: 'merged', sort: sort_value_merged_date) + + expect(first_merge_request).to include('markdown') + expect(count_merge_requests).to eq(2) + end + end + context 'while filtering on two labels' do let(:label) { create(:label, project: project) } let(:label2) { create(:label, project: project) } diff --git a/spec/frontend/issuable/components/issue_milestone_spec.js b/spec/frontend/issuable/components/issue_milestone_spec.js index eac53c5f761..232d6177862 100644 --- a/spec/frontend/issuable/components/issue_milestone_spec.js +++ b/spec/frontend/issuable/components/issue_milestone_spec.js @@ -1,160 +1,61 @@ -import { GlIcon } from '@gitlab/ui'; +import { GlIcon, GlTooltip } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import Vue, { nextTick } from 'vue'; - import { mockMilestone } from 'jest/boards/mock_data'; import IssueMilestone from '~/issuable/components/issue_milestone.vue'; -const createComponent = (milestone = mockMilestone) => { - const Component = Vue.extend(IssueMilestone); - - return shallowMount(Component, { - propsData: { - milestone, - }, - }); -}; - -describe('IssueMilestoneComponent', () => { +describe('IssueMilestone component', () => { let wrapper; - let vm; - beforeEach(async () => { - wrapper = createComponent(); + const findTooltip = () => wrapper.findComponent(GlTooltip); - ({ vm } = wrapper); + const createComponent = (milestone = mockMilestone) => + shallowMount(IssueMilestone, { propsData: { milestone } }); - await nextTick(); + beforeEach(() => { + wrapper = createComponent(); }); - afterEach(() => { - wrapper.destroy(); + it('renders milestone icon', () => { + expect(wrapper.findComponent(GlIcon).props('name')).toBe('clock'); }); - describe('computed', () => { - describe('isMilestoneStarted', () => { - it('should return `false` when milestoneStart prop is not defined', async () => { - wrapper.setProps({ - milestone: { ...mockMilestone, start_date: '' }, - }); - await nextTick(); - - expect(wrapper.vm.isMilestoneStarted).toBe(false); - }); - - it('should return `true` when milestone start date is past current date', async () => { - await wrapper.setProps({ - milestone: { ...mockMilestone, start_date: '1990-07-22' }, - }); - await nextTick(); + it('renders milestone title', () => { + expect(wrapper.find('.milestone-title').text()).toBe(mockMilestone.title); + }); - expect(wrapper.vm.isMilestoneStarted).toBe(true); - }); + describe('tooltip', () => { + it('renders `Milestone`', () => { + expect(findTooltip().text()).toContain('Milestone'); }); - describe('isMilestonePastDue', () => { - it('should return `false` when milestoneDue prop is not defined', async () => { - wrapper.setProps({ - milestone: { ...mockMilestone, due_date: '' }, - }); - await nextTick(); - - expect(wrapper.vm.isMilestonePastDue).toBe(false); - }); - - it('should return `true` when milestone due is past current date', () => { - wrapper.setProps({ - milestone: { ...mockMilestone, due_date: '1990-07-22' }, - }); - - expect(wrapper.vm.isMilestonePastDue).toBe(true); - }); + it('renders milestone title', () => { + expect(findTooltip().text()).toContain(mockMilestone.title); }); - describe('milestoneDatesAbsolute', () => { - it('returns string containing absolute milestone due date', () => { - expect(vm.milestoneDatesAbsolute).toBe('(December 31, 2019)'); - }); + describe('humanized dates', () => { + it('renders `Expired` when there is a due date in the past', () => { + wrapper = createComponent({ ...mockMilestone, due_date: '2019-12-31', start_date: '' }); - it('returns string containing absolute milestone start date when due date is not present', async () => { - wrapper.setProps({ - milestone: { ...mockMilestone, due_date: '' }, - }); - await nextTick(); - - expect(wrapper.vm.milestoneDatesAbsolute).toBe('(January 1, 2018)'); + expect(findTooltip().text()).toContain('Expired 6 months ago(December 31, 2019)'); }); - it('returns empty string when both milestone start and due dates are not present', async () => { - wrapper.setProps({ - milestone: { ...mockMilestone, start_date: '', due_date: '' }, - }); - await nextTick(); + it('renders `remaining` when there is a due date in the future', () => { + wrapper = createComponent({ ...mockMilestone, due_date: '2020-12-31', start_date: '' }); - expect(wrapper.vm.milestoneDatesAbsolute).toBe(''); + expect(findTooltip().text()).toContain('5 months remaining(December 31, 2020)'); }); - }); - describe('milestoneDatesHuman', () => { - it('returns string containing milestone due date when date is yet to be due', async () => { - wrapper.setProps({ - milestone: { ...mockMilestone, due_date: `${new Date().getFullYear() + 10}-01-01` }, - }); - await nextTick(); + it('renders `Started` when there is a start date in the past', () => { + wrapper = createComponent({ ...mockMilestone, due_date: '', start_date: '2019-12-31' }); - expect(wrapper.vm.milestoneDatesHuman).toContain('years remaining'); + expect(findTooltip().text()).toContain('Started 6 months ago(December 31, 2019)'); }); - it('returns string containing milestone start date when date has already started and due date is not present', async () => { - wrapper.setProps({ - milestone: { ...mockMilestone, start_date: '1990-07-22', due_date: '' }, - }); - await nextTick(); + it('renders `Starts` when there is a start date in the future', () => { + wrapper = createComponent({ ...mockMilestone, due_date: '', start_date: '2020-12-31' }); - expect(wrapper.vm.milestoneDatesHuman).toContain('Started'); + expect(findTooltip().text()).toContain('Starts in 5 months(December 31, 2020)'); }); - - it('returns string containing milestone start date when date is yet to start and due date is not present', async () => { - wrapper.setProps({ - milestone: { - ...mockMilestone, - start_date: `${new Date().getFullYear() + 10}-01-01`, - due_date: '', - }, - }); - await nextTick(); - - expect(wrapper.vm.milestoneDatesHuman).toContain('Starts'); - }); - - it('returns empty string when milestone start and due dates are not present', async () => { - wrapper.setProps({ - milestone: { ...mockMilestone, start_date: '', due_date: '' }, - }); - await nextTick(); - - expect(wrapper.vm.milestoneDatesHuman).toBe(''); - }); - }); - }); - - describe('template', () => { - it('renders component root element with class `issue-milestone-details`', () => { - expect(vm.$el.classList.contains('issue-milestone-details')).toBe(true); - }); - - it('renders milestone icon', () => { - expect(wrapper.findComponent(GlIcon).props('name')).toBe('clock'); - }); - - it('renders milestone title', () => { - expect(vm.$el.querySelector('.milestone-title').innerText.trim()).toBe(mockMilestone.title); - }); - - it('renders milestone tooltip', () => { - expect(vm.$el.querySelector('.js-item-milestone').innerText.trim()).toContain( - mockMilestone.title, - ); }); }); }); diff --git a/spec/frontend/lib/utils/text_markdown_spec.js b/spec/frontend/lib/utils/text_markdown_spec.js index 2180ea7e6c2..7ca9715430d 100644 --- a/spec/frontend/lib/utils/text_markdown_spec.js +++ b/spec/frontend/lib/utils/text_markdown_spec.js @@ -10,6 +10,7 @@ import { } from '~/lib/utils/text_markdown'; import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import '~/lib/utils/jquery_at_who'; +import { ENTER_KEY } from '~/lib/utils/keys'; import axios from '~/lib/utils/axios_utils'; import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; @@ -208,7 +209,7 @@ describe('init markdown', () => { let enterEvent; beforeEach(() => { - enterEvent = new KeyboardEvent('keydown', { key: 'Enter', cancelable: true }); + enterEvent = new KeyboardEvent('keydown', { key: ENTER_KEY, cancelable: true }); textArea.addEventListener('keydown', keypressNoteText); textArea.addEventListener('compositionstart', compositionStartNoteText); textArea.addEventListener('compositionend', compositionEndNoteText); @@ -492,6 +493,53 @@ describe('init markdown', () => { }); }); + describe('adding a hard break using Shift+Enter', () => { + let enterEvent; + + beforeEach(() => { + enterEvent = new KeyboardEvent('keydown', { key: ENTER_KEY, shiftKey: true }); + textArea.addEventListener('keydown', keypressNoteText); + textArea.addEventListener('compositionstart', compositionStartNoteText); + textArea.addEventListener('compositionend', compositionEndNoteText); + }); + + it.each` + selectionStart | selectionEnd | expected | expectedSelectionStart + ${0} | ${0} | ${'\\\n0123456789'} | ${2} + ${3} | ${3} | ${'012\\\n3456789'} | ${5} + ${3} | ${6} | ${'012\\\n6789'} | ${5} + `( + 'adds a hard break', + ({ selectionStart, selectionEnd, expected, expectedSelectionStart }) => { + const text = '0123456789'; + textArea.value = text; + textArea.setSelectionRange(selectionStart, selectionEnd); + + textArea.dispatchEvent(enterEvent); + + expect(textArea.value).toEqual(expected); + expect(textArea.selectionStart).toEqual(expectedSelectionStart); + expect(textArea.selectionEnd).toEqual(expectedSelectionStart); + }, + ); + + it.each` + keyEvent + ${new KeyboardEvent('keydown', { key: ENTER_KEY, shiftKey: false })} + ${new KeyboardEvent('keydown', { key: ENTER_KEY, shiftKey: true, metaKey: true })} + ${new KeyboardEvent('keydown', { key: ENTER_KEY, shiftKey: true, altKey: true })} + ${new KeyboardEvent('keydown', { key: ENTER_KEY, shiftKey: true, ctrlKey: true })} + `('does not add when shift is pressed with other keys', ({ keyEvent }) => { + const text = '0123456789'; + textArea.value = text; + textArea.setSelectionRange(0, 0); + + textArea.dispatchEvent(keyEvent); + + expect(textArea.value).toEqual(text); + }); + }); + describe('with selection', () => { let text = 'initial selected value'; let selected = 'selected'; diff --git a/spec/frontend/profile/account/components/update_username_spec.js b/spec/frontend/profile/account/components/update_username_spec.js index 028c30cb291..b19db73459d 100644 --- a/spec/frontend/profile/account/components/update_username_spec.js +++ b/spec/frontend/profile/account/components/update_username_spec.js @@ -1,6 +1,7 @@ import { GlModal } from '@gitlab/ui'; import MockAdapter from 'axios-mock-adapter'; -import { nextTick } from 'vue'; +import Vue, { nextTick } from 'vue'; +import waitForPromises from 'helpers/wait_for_promises'; import { TEST_HOST } from 'helpers/test_constants'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { createAlert } from '~/flash'; @@ -43,6 +44,7 @@ describe('UpdateUsername component', () => { afterEach(() => { wrapper.destroy(); axiosMock.restore(); + Vue.config.errorHandler = null; }); const findElements = () => { @@ -58,6 +60,13 @@ describe('UpdateUsername component', () => { }; }; + const clickModalWithErrorResponse = () => { + Vue.config.errorHandler = jest.fn(); // silence thrown error + const { modal } = findElements(); + modal.vm.$emit('primary'); + return waitForPromises(); + }; + it('has a disabled button if the username was not changed', async () => { const { openModalBtn } = findElements(); @@ -98,14 +107,15 @@ describe('UpdateUsername component', () => { axiosMock.onPut(actionUrl).replyOnce(() => [HTTP_STATUS_OK, { message: 'Username changed' }]); jest.spyOn(axios, 'put'); - await wrapper.vm.onConfirm(); - await nextTick(); + const { modal } = findElements(); + modal.vm.$emit('primary'); + await waitForPromises(); expect(axios.put).toHaveBeenCalledWith(actionUrl, { user: { username: newUsername } }); }); it('sets the username after a successful update', async () => { - const { input, openModalBtn } = findElements(); + const { input, openModalBtn, modal } = findElements(); axiosMock.onPut(actionUrl).replyOnce(() => { expect(input.attributes('disabled')).toBe('disabled'); @@ -115,8 +125,8 @@ describe('UpdateUsername component', () => { return [HTTP_STATUS_OK, { message: 'Username changed' }]; }); - await wrapper.vm.onConfirm(); - await nextTick(); + modal.vm.$emit('primary'); + await waitForPromises(); expect(input.attributes('disabled')).toBe(undefined); expect(openModalBtn.props('disabled')).toBe(true); @@ -134,7 +144,8 @@ describe('UpdateUsername component', () => { return [HTTP_STATUS_BAD_REQUEST, { message: 'Invalid username' }]; }); - await expect(wrapper.vm.onConfirm()).rejects.toThrow(); + await clickModalWithErrorResponse(); + expect(input.attributes('disabled')).toBe(undefined); expect(openModalBtn.props('disabled')).toBe(false); expect(openModalBtn.props('loading')).toBe(false); @@ -145,7 +156,7 @@ describe('UpdateUsername component', () => { return [HTTP_STATUS_BAD_REQUEST, { message: 'Invalid username' }]; }); - await expect(wrapper.vm.onConfirm()).rejects.toThrow(); + await clickModalWithErrorResponse(); expect(createAlert).toHaveBeenCalledWith({ message: 'Invalid username', @@ -157,7 +168,7 @@ describe('UpdateUsername component', () => { return [HTTP_STATUS_BAD_REQUEST]; }); - await expect(wrapper.vm.onConfirm()).rejects.toThrow(); + await clickModalWithErrorResponse(); expect(createAlert).toHaveBeenCalledWith({ message: 'An error occurred while updating your username, please try again.', diff --git a/spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js b/spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js index 8d0fd390e35..8bea84f4429 100644 --- a/spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js +++ b/spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js @@ -71,8 +71,10 @@ describe('Branch rule', () => { }); it('renders a detail button with the correct href', () => { + const encodedBranchName = encodeURIComponent(branchRulePropsMock.name); + expect(findDetailsButton().attributes('href')).toBe( - `${branchRuleProvideMock.branchRulesPath}?branch=${branchRulePropsMock.name}`, + `${branchRuleProvideMock.branchRulesPath}?branch=${encodedBranchName}`, ); }); }); diff --git a/spec/frontend/projects/settings/repository/branch_rules/mock_data.js b/spec/frontend/projects/settings/repository/branch_rules/mock_data.js index de7f6c8b88d..d169397241d 100644 --- a/spec/frontend/projects/settings/repository/branch_rules/mock_data.js +++ b/spec/frontend/projects/settings/repository/branch_rules/mock_data.js @@ -74,7 +74,7 @@ export const branchRuleProvideMock = { }; export const branchRulePropsMock = { - name: 'main', + name: 'branch-with-$speci@l-#-chars', isDefault: true, matchingBranchesCount: 1, branchProtection: { diff --git a/spec/frontend/sidebar/components/participants/participants_spec.js b/spec/frontend/sidebar/components/participants/participants_spec.js index f7a626a189c..72d83ebeca4 100644 --- a/spec/frontend/sidebar/components/participants/participants_spec.js +++ b/spec/frontend/sidebar/components/participants/participants_spec.js @@ -1,203 +1,114 @@ -import { GlLoadingIcon } from '@gitlab/ui'; +import { GlButton, GlLoadingIcon } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import { nextTick } from 'vue'; import Participants from '~/sidebar/components/participants/participants.vue'; -const PARTICIPANT = { - id: 1, - state: 'active', - username: 'marcene', - name: 'Allie Will', - web_url: 'foo.com', - avatar_url: 'gravatar.com/avatar/xxx', -}; - -const PARTICIPANT_LIST = [PARTICIPANT, { ...PARTICIPANT, id: 2 }, { ...PARTICIPANT, id: 3 }]; - -describe('Participants', () => { +describe('Participants component', () => { let wrapper; - const getMoreParticipantsButton = () => wrapper.find('[data-testid="more-participants"]'); - const getCollapsedParticipantsCount = () => wrapper.find('[data-testid="collapsed-count"]'); + const participant = { + id: 1, + state: 'active', + username: 'marcene', + name: 'Allie Will', + web_url: 'foo.com', + avatar_url: 'gravatar.com/avatar/xxx', + }; - const mountComponent = (propsData) => - shallowMount(Participants, { - propsData, - }); + const participants = [participant, { ...participant, id: 2 }, { ...participant, id: 3 }]; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findMoreParticipantsButton = () => wrapper.findComponent(GlButton); + const findCollapsedIcon = () => wrapper.find('.sidebar-collapsed-icon'); + const findParticipantsAuthor = () => wrapper.findAll('.participants-author'); + + const mountComponent = (propsData) => shallowMount(Participants, { propsData }); describe('collapsed sidebar state', () => { it('shows loading spinner when loading', () => { - wrapper = mountComponent({ - loading: true, - }); + wrapper = mountComponent({ loading: true }); - expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); + expect(findLoadingIcon().exists()).toBe(true); }); - it('does not show loading spinner not loading', () => { - wrapper = mountComponent({ - loading: false, - }); + it('does not show loading spinner when not loading', () => { + wrapper = mountComponent({ loading: false }); - expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false); + expect(findLoadingIcon().exists()).toBe(false); }); it('shows participant count when given', () => { - wrapper = mountComponent({ - loading: false, - participants: PARTICIPANT_LIST, - }); + wrapper = mountComponent({ participants }); - expect(getCollapsedParticipantsCount().text()).toBe(`${PARTICIPANT_LIST.length}`); + expect(findCollapsedIcon().text()).toBe(participants.length.toString()); }); it('shows full participant count when there are hidden participants', () => { - wrapper = mountComponent({ - loading: false, - participants: PARTICIPANT_LIST, - numberOfLessParticipants: 1, - }); + wrapper = mountComponent({ participants, numberOfLessParticipants: 1 }); - expect(getCollapsedParticipantsCount().text()).toBe(`${PARTICIPANT_LIST.length}`); + expect(findCollapsedIcon().text()).toBe(participants.length.toString()); }); }); describe('expanded sidebar state', () => { it('shows loading spinner when loading', () => { - wrapper = mountComponent({ - loading: true, - }); + wrapper = mountComponent({ loading: true }); - expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); + expect(findLoadingIcon().exists()).toBe(true); }); - it('when only showing visible participants, shows an avatar only for each participant under the limit', async () => { + it('when only showing visible participants, shows an avatar only for each participant under the limit', () => { const numberOfLessParticipants = 2; - wrapper = mountComponent({ - loading: false, - participants: PARTICIPANT_LIST, - numberOfLessParticipants, - }); - - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ - isShowingMoreParticipants: false, - }); - - await nextTick(); - expect(wrapper.findAll('.participants-author')).toHaveLength(numberOfLessParticipants); + wrapper = mountComponent({ participants, numberOfLessParticipants }); + + expect(findParticipantsAuthor()).toHaveLength(numberOfLessParticipants); }); it('when only showing all participants, each has an avatar', async () => { - wrapper = mountComponent({ - loading: false, - participants: PARTICIPANT_LIST, - numberOfLessParticipants: 2, - }); - - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ - isShowingMoreParticipants: true, - }); - - await nextTick(); - expect(wrapper.findAll('.participants-author')).toHaveLength(PARTICIPANT_LIST.length); + wrapper = mountComponent({ participants, numberOfLessParticipants: 2 }); + + await findMoreParticipantsButton().vm.$emit('click'); + + expect(findParticipantsAuthor()).toHaveLength(participants.length); }); it('does not have more participants link when they can all be shown', () => { const numberOfLessParticipants = 100; - wrapper = mountComponent({ - loading: false, - participants: PARTICIPANT_LIST, - numberOfLessParticipants, - }); - - expect(PARTICIPANT_LIST.length).toBeLessThan(numberOfLessParticipants); - expect(getMoreParticipantsButton().exists()).toBe(false); - }); + wrapper = mountComponent({ participants, numberOfLessParticipants }); - it('when too many participants, has more participants link to show more', async () => { - wrapper = mountComponent({ - loading: false, - participants: PARTICIPANT_LIST, - numberOfLessParticipants: 2, - }); - - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ - isShowingMoreParticipants: false, - }); - - await nextTick(); - expect(getMoreParticipantsButton().text()).toBe('+ 1 more'); + expect(participants.length).toBeLessThan(numberOfLessParticipants); + expect(findMoreParticipantsButton().exists()).toBe(false); }); - it('when too many participants and already showing them, has more participants link to show less', async () => { - wrapper = mountComponent({ - loading: false, - participants: PARTICIPANT_LIST, - numberOfLessParticipants: 2, - }); - - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ - isShowingMoreParticipants: true, - }); - - await nextTick(); - expect(getMoreParticipantsButton().text()).toBe('- show less'); - }); + it('when too many participants, has more participants link to show more', () => { + wrapper = mountComponent({ participants, numberOfLessParticipants: 2 }); - it('clicking more participants link emits event', () => { - wrapper = mountComponent({ - loading: false, - participants: PARTICIPANT_LIST, - numberOfLessParticipants: 2, - }); + expect(findMoreParticipantsButton().text()).toBe('+ 1 more'); + }); - expect(wrapper.vm.isShowingMoreParticipants).toBe(false); + it('when too many participants and already showing them, has more participants link to show less', async () => { + wrapper = mountComponent({ participants, numberOfLessParticipants: 2 }); - getMoreParticipantsButton().vm.$emit('click'); + await findMoreParticipantsButton().vm.$emit('click'); - expect(wrapper.vm.isShowingMoreParticipants).toBe(true); + expect(findMoreParticipantsButton().text()).toBe('- show less'); }); - it('clicking on participants icon emits `toggleSidebar` event', async () => { - wrapper = mountComponent({ - loading: false, - participants: PARTICIPANT_LIST, - numberOfLessParticipants: 2, - }); - - const spy = jest.spyOn(wrapper.vm, '$emit'); + it('clicking on participants icon emits `toggleSidebar` event', () => { + wrapper = mountComponent({ participants, numberOfLessParticipants: 2 }); - wrapper.find('.sidebar-collapsed-icon').trigger('click'); + findCollapsedIcon().trigger('click'); - await nextTick(); - expect(spy).toHaveBeenCalledWith('toggleSidebar'); - spy.mockRestore(); + expect(wrapper.emitted('toggleSidebar')).toEqual([[]]); }); }); describe('when not showing participants label', () => { beforeEach(() => { - wrapper = mountComponent({ - participants: PARTICIPANT_LIST, - showParticipantLabel: false, - }); + wrapper = mountComponent({ participants, showParticipantLabel: false }); }); it('does not show sidebar collapsed icon', () => { - expect(wrapper.find('.sidebar-collapsed-icon').exists()).toBe(false); + expect(findCollapsedIcon().exists()).toBe(false); }); it('does not show participants label title', () => { diff --git a/spec/frontend/super_sidebar/components/sidebar_portal_spec.js b/spec/frontend/super_sidebar/components/sidebar_portal_spec.js new file mode 100644 index 00000000000..3ef1cb7e692 --- /dev/null +++ b/spec/frontend/super_sidebar/components/sidebar_portal_spec.js @@ -0,0 +1,68 @@ +import { nextTick } from 'vue'; +import { mount } from '@vue/test-utils'; +import SidebarPortal from '~/super_sidebar/components/sidebar_portal.vue'; +import SidebarPortalTarget from '~/super_sidebar/components/sidebar_portal_target.vue'; + +describe('SidebarPortal', () => { + let targetWrapper; + + const Target = { + components: { SidebarPortalTarget }, + props: ['show'], + template: '<sidebar-portal-target v-if="show" />', + }; + + const Source = { + components: { SidebarPortal }, + template: '<sidebar-portal><br data-testid="test"></sidebar-portal>', + }; + + const mountSource = () => { + mount(Source); + }; + + const mountTarget = ({ show = true } = {}) => { + targetWrapper = mount(Target, { + propsData: { show }, + attachTo: document.body, + }); + }; + + const findTestContent = () => targetWrapper.find('[data-testid="test"]'); + + it('renders content into the target', async () => { + mountTarget(); + await nextTick(); + + mountSource(); + await nextTick(); + + expect(findTestContent().exists()).toBe(true); + }); + + it('waits for target to be available before rendering', async () => { + mountSource(); + await nextTick(); + + mountTarget(); + await nextTick(); + + expect(findTestContent().exists()).toBe(true); + }); + + it('supports conditional rendering of target', async () => { + mountTarget({ show: false }); + await nextTick(); + + mountSource(); + await nextTick(); + + expect(findTestContent().exists()).toBe(false); + + await targetWrapper.setProps({ show: true }); + expect(findTestContent().exists()).toBe(true); + + await targetWrapper.setProps({ show: false }); + expect(findTestContent().exists()).toBe(false); + }); +}); diff --git a/spec/frontend/super_sidebar/components/super_sidebar_spec.js b/spec/frontend/super_sidebar/components/super_sidebar_spec.js index 45fc30c08f0..57c84bc87a6 100644 --- a/spec/frontend/super_sidebar/components/super_sidebar_spec.js +++ b/spec/frontend/super_sidebar/components/super_sidebar_spec.js @@ -2,6 +2,7 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import SuperSidebar from '~/super_sidebar/components/super_sidebar.vue'; import HelpCenter from '~/super_sidebar/components/help_center.vue'; import UserBar from '~/super_sidebar/components/user_bar.vue'; +import SidebarPortalTarget from '~/super_sidebar/components/sidebar_portal_target.vue'; import { sidebarData } from '../mock_data'; describe('SuperSidebar component', () => { @@ -9,6 +10,7 @@ describe('SuperSidebar component', () => { const findUserBar = () => wrapper.findComponent(UserBar); const findHelpCenter = () => wrapper.findComponent(HelpCenter); + const findSidebarPortalTarget = () => wrapper.findComponent(SidebarPortalTarget); const createWrapper = (props = {}) => { wrapper = shallowMountExtended(SuperSidebar, { @@ -31,5 +33,9 @@ describe('SuperSidebar component', () => { it('renders HelpCenter with sidebarData', () => { expect(findHelpCenter().props('sidebarData')).toBe(sidebarData); }); + + it('renders SidebarPortalTarget', () => { + expect(findSidebarPortalTarget().exists()).toBe(true); + }); }); }); diff --git a/spec/helpers/jira_connect_helper_spec.rb b/spec/helpers/jira_connect_helper_spec.rb index 31aeff85c70..4f56bb7467f 100644 --- a/spec/helpers/jira_connect_helper_spec.rb +++ b/spec/helpers/jira_connect_helper_spec.rb @@ -9,8 +9,7 @@ RSpec.describe JiraConnectHelper, feature_category: :integrations do let(:user) { create(:user) } let(:client_id) { '123' } - let(:enable_public_keys_storage_config) { false } - let(:enable_public_keys_storage_setting) { false } + let(:enable_public_keys_storage) { false } before do stub_application_setting(jira_connect_application_key: client_id) @@ -22,9 +21,7 @@ RSpec.describe JiraConnectHelper, feature_category: :integrations do before do allow(view).to receive(:current_user).and_return(nil) allow(Gitlab.config.gitlab).to receive(:url).and_return('http://test.host') - allow(Gitlab.config.jira_connect).to receive(:enable_public_keys_storage) - .and_return(enable_public_keys_storage_config) - stub_application_setting(jira_connect_public_key_storage_enabled: enable_public_keys_storage_setting) + stub_application_setting(jira_connect_public_key_storage_enabled: enable_public_keys_storage) end it 'includes Jira Connect app attributes' do @@ -108,16 +105,8 @@ RSpec.describe JiraConnectHelper, feature_category: :integrations do expect(subject[:public_key_storage_enabled]).to eq(false) end - context 'when public_key_storage is enabled via config' do - let(:enable_public_keys_storage_config) { true } - - it 'assignes public_key_storage_enabled to true' do - expect(subject[:public_key_storage_enabled]).to eq(true) - end - end - - context 'when public_key_storage is enabled via setting' do - let(:enable_public_keys_storage_setting) { true } + context 'when public_key_storage is enabled' do + let(:enable_public_keys_storage) { true } it 'assignes public_key_storage_enabled to true' do expect(subject[:public_key_storage_enabled]).to eq(true) diff --git a/spec/helpers/sorting_helper_spec.rb b/spec/helpers/sorting_helper_spec.rb index d561b08efac..d625b46e286 100644 --- a/spec/helpers/sorting_helper_spec.rb +++ b/spec/helpers/sorting_helper_spec.rb @@ -10,6 +10,60 @@ RSpec.describe SortingHelper do allow(self).to receive(:request).and_return(double(path: 'http://test.com', query_parameters: { label_name: option })) end + describe '#issuable_sort_options' do + let(:viewing_issues) { false } + let(:viewing_merge_requests) { false } + let(:params) { {} } + + subject(:options) { helper.issuable_sort_options(viewing_issues, viewing_merge_requests) } + + before do + allow(helper).to receive(:params).and_return(params) + end + + shared_examples 'with merged date option' do + it 'adds merged date option' do + expect(options).to include( + a_hash_including( + value: 'merged_at', + text: 'Merged date' + ) + ) + end + end + + shared_examples 'without merged date option' do + it 'does not set merged date option' do + expect(options).not_to include( + a_hash_including( + value: 'merged_at', + text: 'Merged date' + ) + ) + end + end + + it_behaves_like 'without merged date option' + + context 'when viewing_merge_requests is true' do + let(:viewing_merge_requests) { true } + + it_behaves_like 'without merged date option' + + context 'when state param is all' do + let(:params) { { state: 'all' } } + + it_behaves_like 'with merged date option' + end + + context 'when state param is merged' do + let(:params) { { state: 'merged' } } + + it_behaves_like 'with merged date option' + end + end + end + describe '#admin_users_sort_options' do it 'returns correct link attributes in array' do options = admin_users_sort_options(filter: 'filter', search_query: 'search') diff --git a/spec/lib/gitlab/database/gitlab_schema_spec.rb b/spec/lib/gitlab/database/gitlab_schema_spec.rb index 28a087d5401..b187b29c270 100644 --- a/spec/lib/gitlab/database/gitlab_schema_spec.rb +++ b/spec/lib/gitlab/database/gitlab_schema_spec.rb @@ -16,19 +16,21 @@ RSpec.shared_examples 'validate schema data' do |tables_and_views| end end -RSpec.describe Gitlab::Database::GitlabSchema do +RSpec.describe Gitlab::Database::GitlabSchema, feature_category: :database do shared_examples 'maps table name to table schema' do using RSpec::Parameterized::TableSyntax where(:name, :classification) do - 'ci_builds' | :gitlab_ci - 'my_schema.ci_builds' | :gitlab_ci - 'information_schema.columns' | :gitlab_internal - 'audit_events_part_5fc467ac26' | :gitlab_main - '_test_gitlab_main_table' | :gitlab_main - '_test_gitlab_ci_table' | :gitlab_ci - '_test_my_table' | :gitlab_shared - 'pg_attribute' | :gitlab_internal + 'ci_builds' | :gitlab_ci + 'my_schema.ci_builds' | :gitlab_ci + 'my_schema.ci_runner_machine_builds_100' | :gitlab_ci + 'my_schema._test_gitlab_main_table' | :gitlab_main + 'information_schema.columns' | :gitlab_internal + 'audit_events_part_5fc467ac26' | :gitlab_main + '_test_gitlab_main_table' | :gitlab_main + '_test_gitlab_ci_table' | :gitlab_ci + '_test_my_table' | :gitlab_shared + 'pg_attribute' | :gitlab_internal end with_them do diff --git a/spec/lib/gitlab/database/partitioning/partition_manager_spec.rb b/spec/lib/gitlab/database/partitioning/partition_manager_spec.rb index c7e4eaf8f46..ac54c307108 100644 --- a/spec/lib/gitlab/database/partitioning/partition_manager_spec.rb +++ b/spec/lib/gitlab/database/partitioning/partition_manager_spec.rb @@ -238,23 +238,20 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionManager do expect(pending_drop.drop_after).to eq(Time.current + described_class::RETAIN_DETACHED_PARTITIONS_FOR) end - # Postgres 11 does not support foreign keys to partitioned tables - if ApplicationRecord.database.version.to_f >= 12 - context 'when the model is the target of a foreign key' do - before do - connection.execute(<<~SQL) + context 'when the model is the target of a foreign key' do + before do + connection.execute(<<~SQL) create unique index idx_for_fk ON #{partitioned_table_name}(created_at); create table _test_gitlab_main_referencing_table ( id bigserial primary key not null, referencing_created_at timestamptz references #{partitioned_table_name}(created_at) ); - SQL - end + SQL + end - it 'does not detach partitions with a referenced foreign key' do - expect { subject }.not_to change { find_partitions(my_model.table_name).size } - end + it 'does not detach partitions with a referenced foreign key' do + expect { subject }.not_to change { find_partitions(my_model.table_name).size } end end end diff --git a/spec/lib/gitlab/database/postgres_foreign_key_spec.rb b/spec/lib/gitlab/database/postgres_foreign_key_spec.rb index 75f94bf2654..c128c56c708 100644 --- a/spec/lib/gitlab/database/postgres_foreign_key_spec.rb +++ b/spec/lib/gitlab/database/postgres_foreign_key_spec.rb @@ -203,10 +203,8 @@ RSpec.describe Gitlab::Database::PostgresForeignKey, type: :model, feature_categ end end - context 'when supporting foreign keys to inherited tables in postgres 12' do + context 'when supporting foreign keys to inherited tables' do before do - skip('not supported before postgres 12') if ApplicationRecord.database.version.to_f < 12 - ApplicationRecord.connection.execute(<<~SQL) create table #{schema_table_name('parent')} ( id bigserial primary key not null diff --git a/spec/requests/api/avatar_spec.rb b/spec/requests/api/avatar_spec.rb index fcef5b6ca78..0a77b6e228e 100644 --- a/spec/requests/api/avatar_spec.rb +++ b/spec/requests/api/avatar_spec.rb @@ -19,6 +19,7 @@ RSpec.describe API::Avatar, feature_category: :user_profile do expect(response).to have_gitlab_http_status(:ok) expect(json_response['avatar_url']).to eql("#{::Settings.gitlab.base_url}#{user.avatar.local_url}") + is_expected.to have_request_urgency(:medium) end end diff --git a/spec/requests/jira_connect/public_keys_controller_spec.rb b/spec/requests/jira_connect/public_keys_controller_spec.rb index 7f0262eaf65..62a81d43e65 100644 --- a/spec/requests/jira_connect/public_keys_controller_spec.rb +++ b/spec/requests/jira_connect/public_keys_controller_spec.rb @@ -5,11 +5,10 @@ require 'spec_helper' RSpec.describe JiraConnect::PublicKeysController, feature_category: :integrations do describe 'GET /-/jira_connect/public_keys/:uuid' do let(:uuid) { non_existing_record_id } - let(:public_key_storage_enabled_config) { true } + let(:public_key_storage_enabled) { true } before do - allow(Gitlab.config.jira_connect).to receive(:enable_public_keys_storage) - .and_return(public_key_storage_enabled_config) + stub_application_setting(jira_connect_public_key_storage_enabled: public_key_storage_enabled) end it 'renders 404' do @@ -30,26 +29,14 @@ RSpec.describe JiraConnect::PublicKeysController, feature_category: :integration expect(response.body).to eq(public_key.key) end - context 'when public key storage config disabled' do - let(:public_key_storage_enabled_config) { false } + context 'when public key storage setting disabled' do + let(:public_key_storage_enabled) { false } it 'renders 404' do get jira_connect_public_key_path(id: uuid) expect(response).to have_gitlab_http_status(:not_found) end - - context 'when public key storage setting is enabled' do - before do - stub_application_setting(jira_connect_public_key_storage_enabled: true) - end - - it 'renders 404' do - get jira_connect_public_key_path(id: uuid) - - expect(response).to have_gitlab_http_status(:ok) - end - end end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3f0900886eb..f76ba3528e9 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -178,6 +178,8 @@ RSpec.configure do |config| config.include RenderedHelpers config.include RSpec::Benchmark::Matchers, type: :benchmark config.include DetailedErrorHelpers + config.include RequestUrgencyMatcher, type: :controller + config.include RequestUrgencyMatcher, type: :request config.include_context 'when rendered has no HTML escapes', type: :view diff --git a/spec/support/matchers/request_urgency_matcher.rb b/spec/support/matchers/request_urgency_matcher.rb new file mode 100644 index 00000000000..d3c5093719e --- /dev/null +++ b/spec/support/matchers/request_urgency_matcher.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module RequestUrgencyMatcher + RSpec::Matchers.define :have_request_urgency do |request_urgency| + match do |_actual| + if controller_instance = request.env["action_controller.instance"] + controller_instance.urgency.name == request_urgency + elsif endpoint = request.env['api.endpoint'] + urgency = endpoint.options[:for].try(:urgency_for_app, endpoint) + urgency.name == request_urgency + else + raise 'neither a controller nor a request spec' + end + end + + failure_message do |_actual| + if controller_instance = request.env["action_controller.instance"] + "request urgency #{controller_instance.urgency.name} is set, \ + but expected to be #{request_urgency}".squish + elsif endpoint = request.env['api.endpoint'] + urgency = endpoint.options[:for].try(:urgency_for_app, endpoint) + "request urgency #{urgency.name} is set, \ + but expected to be #{request_urgency}".squish + end + end + end +end |